Альфа-смешивание с использованием OpenCV для видео

Я хочу смешать видео поверх другого, используя альфа-видео. Это мой код. Он отлично работает, но проблема в том, что этот код совсем не эффективен, и это из-за /255 частей. Он медленный и имеет отстающие проблемы.

Есть ли стандартный и эффективный способ сделать это? Я хочу, чтобы результаты были в режиме реального времени. Спасибо

import cv2
import numpy as np

def main():
    foreground = cv2.VideoCapture('circle.mp4')
    background = cv2.VideoCapture('video.MP4')
    alpha = cv2.VideoCapture('circle_alpha.mp4')

    while foreground.isOpened():
        fr_foreground = foreground.read()[1]/255
        fr_background = background.read()[1]/255     
        fr_alpha = alpha.read()[1]/255

        cv2.imshow('My Image',cmb(fr_foreground,fr_background,fr_alpha))

        if cv2.waitKey(1) == ord('q'): break

    cv2.destroyAllWindows

def cmb(fg,bg,a):
    return fg * a + bg * (1-a)

if __name__ == '__main__':
    main()

person sd70    schedule 08.05.2018    source источник
comment
Попробуйте отдельно измерить время чтения и время обработки, чтобы убедиться, что нужно улучшить.   -  person Mark Setchell    schedule 08.05.2018
comment
Я предполагаю, что вы используете Python 3.x, так как /255 не будет работать так же, как в 2.x...   -  person Dan Mašek    schedule 09.05.2018


Ответы (3)


Давайте сначала устраним несколько очевидных проблем - foreground.isOpened() вернет true даже после того, как вы дойдете до конца видео, так что ваша программа в конечном итоге рухнет в этот момент. Решение двоякое. Прежде всего, протестируйте все 3 экземпляра VideoCapture сразу после их создания, используя что-то вроде:

if not foreground.isOpened() or not background.isOpened() or not alpha.isOpened():
    print "Unable to open input videos."
    return

Это позволит убедиться, что все они открылись правильно. Следующая часть — это правильная обработка достижения конца видео. Это означает либо проверку первого из двух возвращаемых значений read(), который является логическим флагом, представляющим успех, либо проверку того, является ли кадр None.

while True:
    r_fg, fr_foreground = foreground.read()
    r_bg, fr_background = background.read()
    r_a, fr_alpha = alpha.read()
    if not r_fg or not r_bg or not r_a:
        break # End of video

Кроме того, кажется, что вы на самом деле не вызываете cv2.destroyAllWindows() - () отсутствует. Не то, чтобы это действительно имело значение.


Чтобы помочь исследовать и оптимизировать это, я добавил некоторые подробные тайминги, используя модуль timeit и пару удобных функций.

from timeit import default_timer as timer

def update_times(times, total_times):
    for i in range(len(times) - 1):
        total_times[i] += (times[i+1]-times[i]) * 1000

def print_times(total_times, n):
    print "Iterations: %d" % n
    for i in range(len(total_times)):
        print "Step %d: %0.4f ms" % (i, total_times[i] / n)
    print "Total: %0.4f ms" % (np.sum(total_times) / n)

и изменил функцию main() для измерения времени, затрачиваемого на каждый логический шаг — чтение, масштабирование, смешивание, показ, ожиданиеKey. Для этого я разбил деление на отдельные операторы. Я также сделал небольшую модификацию, которая заставляет это работать и в Python 2.x (/255 интерпретируется как целочисленное деление и дает неверные результаты).

times = [0.0] * 6
total_times = [0.0] * (len(times) - 1)
n = 0
while True:
    times[0] = timer()
    r_fg, fr_foreground = foreground.read()
    r_bg, fr_background = background.read()
    r_a, fr_alpha = alpha.read()
    if not r_fg or not r_bg or not r_a:
        break # End of video
    times[1] = timer()
    fr_foreground = fr_foreground / 255.0
    fr_background = fr_background / 255.0
    fr_alpha = fr_alpha / 255.0
    times[2] = timer()
    result = cmb(fr_foreground,fr_background,fr_alpha)
    times[3] = timer()
    cv2.imshow('My Image', result)
    times[4] = timer()
    if cv2.waitKey(1) == ord('q'): break
    times[5] = timer()
    update_times(times, total_times)
    n += 1

print_times(total_times, n)

Когда я запускаю это с видео 1280x800 mp4 в качестве входных данных, я замечаю, что оно действительно довольно вялое и использует только 15% ЦП на моей 6-ядерной машине. Время работы секций следующее:

Iterations: 1190
Step 0: 11.4385 ms
Step 1: 37.1320 ms
Step 2: 39.4083 ms
Step 3: 2.5488 ms
Step 4: 10.7083 ms
Total: 101.2358 ms

Это говорит о том, что самыми большими узкими местами являются как этап масштабирования, так и этап смешивания. Низкая загрузка ЦП также неоптимальна, но давайте сначала сосредоточимся на легко висящих фруктах.


Давайте посмотрим на типы данных массивов numpy, которые мы используем. read() дает нам массивы с dtype из np.uint8 -- 8-битных целых чисел без знака. Однако деление с плавающей запятой (как написано) даст массив с dtype из np.float64 -- 64-битных значений с плавающей запятой. На самом деле нам не нужен такой уровень точности для нашего алгоритма, поэтому нам лучше использовать только 32-битные числа с плавающей запятой — это будет означать, что если какая-либо из операций будет векторизована, мы потенциально можем выполнить в два раза больше вычислений в одном и том же алгоритме. количество времени.

Здесь есть два варианта. Мы могли бы просто привести делитель к np.float32, что приведет к тому, что numpy выдаст нам результат с тем же dtype:

fr_foreground = fr_foreground / np.float32(255.0)
fr_background = fr_background / np.float32(255.0)
fr_alpha = fr_alpha / np.float32(255.0)

Что дает нам следующие тайминги:

Iterations: 1786
Step 0: 9.2550 ms
Step 1: 19.0144 ms
Step 2: 21.2120 ms
Step 3: 1.4662 ms
Step 4: 10.8889 ms
Total: 61.8365 ms

Или мы могли бы сначала привести массив к np.float32, а затем выполнить масштабирование на месте.

fr_foreground = np.float32(fr_foreground)
fr_background = np.float32(fr_background)
fr_alpha = np.float32(fr_alpha)

fr_foreground /= 255.0
fr_background /= 255.0
fr_alpha /= 255.0

Что дает следующие тайминги (разделение шага 1 на преобразование (1) и масштабирование (2) — остальные сдвигаются на 1):

Iterations: 1786
Step 0: 9.0589 ms
Step 1: 13.9614 ms
Step 2: 4.5960 ms
Step 3: 20.9279 ms
Step 4: 1.4631 ms
Step 5: 10.4396 ms
Total: 60.4469 ms

Оба примерно эквивалентны, работают примерно на 60% от исходного времени. Я буду придерживаться второго варианта, так как он пригодится на последующих этапах. Посмотрим, что еще мы можем улучшить.


Из предыдущих таймингов мы видим, что масштабирование больше не является узким местом, но идея все еще приходит на ум — деление, как правило, медленнее, чем умножение, так что, если мы умножим на обратное?

fr_foreground *= 1/255.0
fr_background *= 1/255.0
fr_alpha *= 1/255.0

Действительно, это дает нам миллисекунду — ничего впечатляющего, но это было легко, так что можно и с этим согласиться:

Iterations: 1786
Step 0: 9.1843 ms
Step 1: 14.2349 ms
Step 2: 3.5752 ms
Step 3: 21.0545 ms
Step 4: 1.4692 ms
Step 5: 10.6917 ms
Total: 60.2097 ms

Теперь функция смешивания является самым большим узким местом, за которым следует приведение типов всех трех массивов. Если мы посмотрим, что делает операция смешивания:

foreground * alpha + background * (1.0 - alpha)

мы можем заметить, что для того, чтобы математика работала, единственное значение, которое должно находиться в диапазоне (0,0, 1,0), — это alpha.

Что, если мы масштабируем только альфа-изображение? Кроме того, поскольку умножение на число с плавающей запятой приведет к преобразованию в число с плавающей запятой, что, если мы также пропустим преобразование типа? Это означало бы, что cmb() должен был бы вернуть массив np.uint8

def cmb(fg,bg,a):
    return np.uint8(fg * a + bg * (1-a))

и у нас было бы

    #fr_foreground = np.float32(fr_foreground)
    #fr_background = np.float32(fr_background)
    fr_alpha = np.float32(fr_alpha)

    #fr_foreground *= 1/255.0
    #fr_background *= 1/255.0
    fr_alpha *= 1/255.0

Время для этого

Step 0: 7.7023 ms
Step 1: 4.6758 ms
Step 2: 1.1061 ms
Step 3: 27.3188 ms
Step 4: 0.4783 ms
Step 5: 9.0027 ms
Total: 50.2840 ms

Очевидно, что шаги 1 и 2 намного быстрее, так как мы делаем только 1/3 работы. imshow также ускоряется, так как не нужно преобразовывать числа с плавающей запятой. Необъяснимым образом, чтение также стало быстрее (думаю, мы избегаем некоторых скрытых перераспределений, поскольку fr_foreground и fr_background всегда содержат нетронутый фрейм). Мы платим цену за дополнительное приведение в cmb(), но в целом это кажется победой — мы на 50% от исходного времени.


Чтобы продолжить, давайте избавимся от функции cmb(), переместим ее функциональность в main() и разделим ее, чтобы измерить стоимость каждой из операций. Давайте также попробуем повторно использовать результат alpha.read() (поскольку мы недавно видели улучшение производительности read()):

times = [0.0] * 11
total_times = [0.0] * (len(times) - 1)
n = 0
while True:
    times[0] = timer()
    r_fg, fr_foreground = foreground.read()
    r_bg, fr_background = background.read()
    r_a, fr_alpha_raw = alpha.read()
    if not r_fg or not r_bg or not r_a:
        break # End of video

    times[1] = timer()
    fr_alpha = np.float32(fr_alpha_raw)
    times[2] = timer()
    fr_alpha *= 1/255.0
    times[3] = timer()
    fr_alpha_inv = 1.0 - fr_alpha
    times[4] = timer()
    fr_fg_weighed = fr_foreground * fr_alpha
    times[5] = timer()
    fr_bg_weighed = fr_background * fr_alpha_inv
    times[6] = timer()
    sum = fr_fg_weighed + fr_bg_weighed
    times[7] = timer()
    result = np.uint8(sum)
    times[8] = timer()
    cv2.imshow('My Image', result)
    times[9] = timer()
    if cv2.waitKey(1) == ord('q'): break
    times[10] = timer()
    update_times(times, total_times)
    n += 1

Новые тайминги:

Iterations: 1786
Step 0: 6.8733 ms
Step 1: 5.2742 ms
Step 2: 1.1430 ms
Step 3: 4.5800 ms
Step 4: 7.0372 ms
Step 5: 7.0675 ms
Step 6: 5.3082 ms
Step 7: 2.6912 ms
Step 8: 0.4658 ms
Step 9: 9.6966 ms
Total: 50.1372 ms

На самом деле мы ничего не выиграли, но чтение стало заметно быстрее.


Это приводит к другой идее — что, если мы попытаемся свести к минимуму выделение памяти и повторно использовать массивы в последующих итерациях?

Мы можем предварительно выделить необходимые массивы в первой итерации (используя numpy.zeros_like), после того как мы прочитаем первый набор фреймов:

if n == 0: # Pre-allocate
    fr_alpha = np.zeros_like(fr_alpha_raw, np.float32)
    fr_alpha_inv = np.zeros_like(fr_alpha_raw, np.float32)
    fr_fg_weighed = np.zeros_like(fr_alpha_raw, np.float32)
    fr_bg_weighed = np.zeros_like(fr_alpha_raw, np.float32)
    sum = np.zeros_like(fr_alpha_raw, np.float32)
    result = np.zeros_like(fr_alpha_raw, np.uint8)

Теперь мы можем использовать

Мы также можем объединить шаги 1 и 2 вместе, используя один файл numpy.multiply.

times = [0.0] * 10
total_times = [0.0] * (len(times) - 1)
n = 0
while True:
    times[0] = timer()
    r_fg, fr_foreground = foreground.read()
    r_bg, fr_background = background.read()
    r_a, fr_alpha_raw = alpha.read()
    if not r_fg or not r_bg or not r_a:
        break # End of video

    if n == 0: # Pre-allocate
        fr_alpha = np.zeros_like(fr_alpha_raw, np.float32)
        fr_alpha_inv = np.zeros_like(fr_alpha_raw, np.float32)
        fr_fg_weighed = np.zeros_like(fr_alpha_raw, np.float32)
        fr_bg_weighed = np.zeros_like(fr_alpha_raw, np.float32)
        sum = np.zeros_like(fr_alpha_raw, np.float32)
        result = np.zeros_like(fr_alpha_raw, np.uint8)

    times[1] = timer()
    np.multiply(fr_alpha_raw, np.float32(1/255.0), fr_alpha)
    times[2] = timer()
    np.subtract(1.0, fr_alpha, fr_alpha_inv)
    times[3] = timer()
    np.multiply(fr_foreground, fr_alpha, fr_fg_weighed)
    times[4] = timer()
    np.multiply(fr_background, fr_alpha_inv, fr_bg_weighed)
    times[5] = timer()
    np.add(fr_fg_weighed, fr_bg_weighed, sum)
    times[6] = timer()
    np.copyto(result, sum, 'unsafe')
    times[7] = timer()
    cv2.imshow('My Image', result)
    times[8] = timer()
    if cv2.waitKey(1) == ord('q'): break
    times[9] = timer()
    update_times(times, total_times)
    n += 1

Это дает нам следующие тайминги:

Iterations: 1786
Step 0: 7.0515 ms
Step 1: 3.8839 ms
Step 2: 1.9080 ms
Step 3: 4.5198 ms
Step 4: 4.3871 ms
Step 5: 2.7576 ms
Step 6: 1.9273 ms
Step 7: 0.4382 ms
Step 8: 7.2340 ms
Total: 34.1074 ms

Значительное улучшение всех шагов, которые мы изменили. Мы сократили примерно 35% времени, необходимого для исходной реализации.


Незначительное обновление:

На основе ответа Silencer Я также измерил cv2.convertScaleAbs. На самом деле он работает немного быстрее:

Step 6: 1.2318 ms

Это натолкнуло меня на другую идею: мы могли бы воспользоваться преимуществами cv2.add< /a>, который позволяет нам указать целевой тип данных, а также выполняет преобразование насыщения. Это позволит нам объединить шаги 5 и 6 вместе.

cv2.add(fr_fg_weighed, fr_bg_weighed, result, dtype=cv2.CV_8UC3)

который выходит в

Step 5: 3.3621 ms

Опять небольшой выигрыш (раньше было около 3,9 мс).

Исходя из этого, cv2.subtract и cv2.multiply являются дополнительными кандидатами. Нам нужно использовать кортеж из 4 элементов для определения скаляра (сложность привязки Python), и нам нужно явно определить тип выходных данных для умножения.

    cv2.subtract((1.0, 1.0, 1.0, 0.0), fr_alpha, fr_alpha_inv)
    cv2.multiply(fr_foreground, fr_alpha, fr_fg_weighed, dtype=cv2.CV_32FC3)
    cv2.multiply(fr_background, fr_alpha_inv, fr_bg_weighed, dtype=cv2.CV_32FC3)

Тайминги:

Step 2: 2.1897 ms
Step 3: 2.8981 ms
Step 4: 2.9066 ms

Кажется, это все, что мы можем сделать без некоторого распараллеливания. Мы уже пользуемся тем, что может предоставить OpenCV с точки зрения отдельных операций, поэтому нам следует сосредоточиться на конвейерной обработке нашей реализации.

Чтобы помочь мне понять, как разделить код между различными этапами конвейера (потоками), я сделал диаграмму, которая показывает все операции, наше лучшее время для них, а также взаимозависимости для вычислений:

введите здесь описание изображения

WIP см. комментарии для получения дополнительной информации, пока я пишу это.

person Dan Mašek    schedule 09.05.2018
comment
Распараллеливание будет завтра, уже немного поздновато. - person Dan Mašek; 09.05.2018
comment
Большое спасибо @DanMašek. при воспроизведении FPS было 5, а теперь 16. Однако должно быть как минимум 24, но я думаю, что распараллеливание может увеличить скорость этого кода. Я буду ждать вашего ответа о распараллеливании - person sd70; 09.05.2018
comment
@DanMašek где 2 часть??!! - person Jeru Luke; 10.05.2018
comment
@JeruLuke Спасибо за интерес (и я прочитал ваш профиль, спасибо). На подготовку и написание уходит довольно много времени, а у меня была кое-какая настоящая работа. Технически, я написал этот комментарий в какое-то ужасное время утром, так что это было еще завтра... до 2 часов назад :D | Во всяком случае, я только что дошел до точки, где у меня 10 мс на кадр, причем waitKey является узким местом (в Windows есть sleep внутри), поэтому для поддержания визуализации пропускается около 30% обработанных кадров. Сейчас работаю над написанием связного текста об этом, и я буду держать вас в курсе. :) | pastebin.com/EzCy6bQK - person Dan Mašek; 11.05.2018
comment
Было бы приятно услышать, окупается ли программное смешивание в нескольких потоках для видео 4K? именно здесь мой подход начинает страдать из-за огромной передачи 58 МБ для двух слоев. - person mainactual; 11.05.2018
comment
Я наблюдаю, как развивается ваш pastebin - я поражен тем, что было бы эффективно отправлять большие объемы данных через очередь и что какая-то общая память не требуется - конечно, я понятия не имею, как Python реализует очереди внизу... Я думаю, это могут быть просто указатели. - person Mark Setchell; 11.05.2018

Если просто смешать, отрендерить и забыть об этом, имеет смысл делать это на GPU. Среди многих других, VTK (Visualization ToolKit) ( https://www.vtk.org) может сделать это для ты вместо imshow. VTK уже известен из модуля OpenCV 3D Visualizer (https://docs.opencv.org/3.2.0/d1/d19/group__viz.html), поэтому не следует добавлять большую зависимость.

После этого вся вычислительная часть (исключая чтение видеокадров) сводится к cv2.mixChannels и передаче пиксельных данных на два рендерера, и на моем компьютере это занимает около 5 мс на итерацию для видео 1280x720.

import sys
import cv2
import numpy as np
import vtk
from vtk.util import numpy_support
import time

class Renderer:
    # VTK renderer with two layers
    def __init__( self ):
        self.layer1 = vtk.vtkRenderer()
        self.layer1.SetLayer(0)
        self.layer2 = vtk.vtkRenderer()
        self.layer2.SetLayer(1)
        self.renWin = vtk.vtkRenderWindow()
        self.renWin.SetNumberOfLayers( 2 )
        self.renWin.AddRenderer(self.layer1)
        self.renWin.AddRenderer(self.layer2)
        self.iren = vtk.vtkRenderWindowInteractor()
        self.iren.SetRenderWindow(self.renWin)
        self.iren.Initialize()      
    def Render( self ):
        self.iren.Render()

# set background image to a given renderer (resets the camera)
# from https://www.vtk.org/Wiki/VTK/Examples/Cxx/Images/BackgroundImage
def SetBackground( ren, image ):    
    bits = numpy_support.numpy_to_vtk( image.ravel() )
    bits.SetNumberOfComponents( image.shape[2] )
    bits.SetNumberOfTuples( bits.GetNumberOfTuples()/bits.GetNumberOfComponents() )

    img = vtk.vtkImageData()
    img.GetPointData().SetScalars( bits );
    img.SetExtent( 0, image.shape[1]-1, 0, image.shape[0]-1, 0,0 );
    origin = img.GetOrigin()
    spacing = img.GetSpacing()
    extent = img.GetExtent()

    actor = vtk.vtkImageActor()
    actor.SetInputData( img )

    ren.RemoveAllViewProps()
    ren.AddActor( actor )
    camera = vtk.vtkCamera()
    camera.ParallelProjectionOn()
    xc = origin[0] + 0.5*(extent[0] + extent[1])*spacing[0]
    yc = origin[1] + 0.5*(extent[2] + extent[3])*spacing[1]
    yd = (extent[3] - extent[2] + 1)*spacing[1]
    d = camera.GetDistance()
    camera.SetParallelScale(0.5*yd)
    camera.SetFocalPoint(xc,yc,0.0)
    camera.SetPosition(xc,yc,-d)
    camera.SetViewUp(0,-1,0)
    ren.SetActiveCamera( camera )
    return img

# update the scalar data without bounds check
def UpdateImageData( vtkimage, image ):
    bits = numpy_support.numpy_to_vtk( image.ravel() )
    bits.SetNumberOfComponents( image.shape[2] )
    bits.SetNumberOfTuples( bits.GetNumberOfTuples()/bits.GetNumberOfComponents() )
    vtkimage.GetPointData().SetScalars( bits );

r = Renderer()
r.renWin.SetSize(1280,720)
cap = cv2.VideoCapture('video.mp4')
image = cv2.imread('hello.png',1)
alpha = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY )
ret, alpha = cv2.threshold( alpha, 127, 127, cv2.THRESH_BINARY )
alpha = np.reshape( alpha, (alpha.shape[0],alpha.shape[1], 1 ) )

src1=[]
src2=[]
overlay=[]
c=0
while ( 1 ):
    # read the data
    ret, mat = cap.read()
    if ( not ret ):
        break
    #TODO ret, image = cap2.read() #(rgb)
    #TODO ret, alpha = cap3.read() #(mono)

    # alpha blend
    t=time.time()
    if ( overlay==[] ):
        overlay = np.zeros( [image.shape[0],image.shape[1],4], np.uint8 ) 
    cv2.mixChannels( [image, alpha], [overlay], [0,0,1,1,2,2,3,3] )
    if ( src1==[] ):
        src1 = SetBackground( r.layer1, mat )
    else:
        UpdateImageData( src1, mat )
    if ( src2==[] ):
        src2 = SetBackground( r.layer2, overlay )
    else:
        UpdateImageData( src2, overlay )
    r.Render()
    # blending done
    t = time.time()-t;

    if ( c % 10 == 0 ):
        print 1000*t
    c = c+1;
person mainactual    schedule 09.05.2018
comment
Хорошо, хотя было бы более осмысленно, если бы вы использовали 3 входных видео, как это делает OP - даже всего 3 копии одного и того же файла дадут значимый результат (смешиваясь друг с другом), а также дадут вам значимые тайминги. - person Dan Mašek; 10.05.2018
comment
@DanMašek, но я делаю mixChannels на каждой итерации, как если бы альфа-изображение и оверлейное изображение менялись на каждой итерации. Время только для альфа-смешивания, потому что производительность моего жесткого диска не очень интересна, не так ли? Кроме того, мне пришлось использовать MJPG-avi; три из них заняли бы все мое время кадра, чтобы начать :) - person mainactual; 10.05.2018
comment
спасибо @mainactual Я пока не работал с VTK и должен прочитать об этом, чтобы понять ваш код. еще раз спасибо - person sd70; 10.05.2018
comment
@ sd70 попробуйте :) Или, если ваша любимая UI-библиотека поддерживает формат RGBA и z-порядок (как, например, Qt QGraphicsScene), она также сделает это после mixChannels шага. - person mainactual; 10.05.2018
comment
@mainactual Справедливо, хотя бит жесткого диска исчезает после того, как вы запустите его один раз, и данные кэшируются ОС (по крайней мере, это то, что я наблюдаю здесь). - person Dan Mašek; 11.05.2018

Я использую OpenCV 4.00-pre и Python 3.6.

  1. Нет необходимости делать три xxx/255 операции. Просто для альфы нормально.
  2. Позаботьтесь о преобразовании типов, предпочитайте cv2.convertScaleAbs(xxx), а не np.uint8(xxx) или np.copyto(xxx,yyy, "unsafe").
  3. Предварительное выделение памяти должно быть лучше.

Я использую № 2, то есть cv2.convertScaleAbs, чтобы избежать underflow/overflow, диапазон [0,255]. Например:

>>> x = np.array([[-1,256]])
>>> y = np.uint8(x)
>>> z = cv2.convertScaleAbs(x)
>>> x
array([[ -1, 256]])
>>> y
array([[255,   0]], dtype=uint8)
>>> z
array([[  1, 255]], dtype=uint8)

##! 2018/05/09 13:54:34

import cv2
import numpy as np
import time

def cmb(fg,bg,a):
    return fg * a + bg * (1-a)

def test2():
    cap = cv2.VideoCapture(0)
    ret, prev_frame = cap.read()
    """
    foreground = cv2.VideoCapture('circle.mp4')
    background = cv2.VideoCapture('video.MP4')
    alphavideo = cv2.VideoCapture('circle_alpha.mp4')
    """
    while cap.isOpened():
        ts = time.time()
        ret, fg = cap.read()
        alpha = fg.copy()
        bg = prev_frame
        """
        ret, fg = foreground.read()
        ret, bg = background.read()
        ret, alpha = alphavideo.read()
        """

        alpha = np.multiply(alpha, 1.0/255)
        blended = cv2.convertScaleAbs(cmb(fg, bg, alpha))
        te = time.time()
        dt = te-ts
        fps = 1/dt
        print("{:.3}ms, {:.3} fps".format(1000*dt, fps))
        cv2.imshow('Blended', blended)

        if cv2.waitKey(1) == ord('q'):
            break

    cv2.destroyAllWindows()

if __name__ == "__main__":
    test2()

Некоторые выводы, как это:

39.0ms, 25.6 fps
37.0ms, 27.0 fps
38.0ms, 26.3 fps
37.0ms, 27.0 fps
38.0ms, 26.3 fps
37.0ms, 27.0 fps
38.0ms, 26.3 fps
37.0ms, 27.0 fps
37.0ms, 27.0 fps
37.0ms, 27.0 fps
37.0ms, 27.0 fps
38.0ms, 26.3 fps
37.0ms, 27.0 fps
37.0ms, 27.0 fps
37.0ms, 27.0 fps
37.0ms, 27.0 fps
...
person Kinght 金    schedule 09.05.2018
comment
Спасибо, @silencer, я переписываю свой код, как вы сказали, но FPS 6, и это медленно. это мой новый код: code.py - person sd70; 09.05.2018
comment
Чем обоснован пункт №2? (Я не догадался проверить это) - person Dan Mašek; 10.05.2018
comment
Я использую #2, то есть cv2.convertScaleAbs, чтобы избежать underflow/overflow, диапазон в [0, 255] . - person Kinght 金; 10.05.2018
comment
@Silencer Справедливое замечание, хотя я не думаю, что здесь есть опасность. Тем не менее, кажется, что он работает немного лучше и заставил меня попробовать еще несколько вещей, поэтому +1. - person Dan Mašek; 11.05.2018