gpt4 book ai didi

python - 使用OpenCV进行视频Alpha混合

转载 作者:行者123 更新时间:2023-12-02 14:33:45 25 4
gpt4 key购买 nike

我想使用Alpha视频将视频混合在另一个视频之上。这是我的代码。它可以完美工作,但是问题在于此代码根本无效,这是由于/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()

最佳答案

首先让我们解决一些明显的问题-即使到达视频末尾,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()的两个返回值中的第一个(表示成功的 bool(boolean) 标志),要么测试帧是否为 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()函数以测量每个逻辑步骤所花费的时间-读取,缩放,混合,显示,waitKey。为此,我将部门划分为单独的语句。我还做了一点点修改,使其也可以在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视频作为输入运行此程序时,我注意到它确实相当缓慢,并且在我的6核计算机上仅使用15%的CPU。这些部分的时间安排如下:
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

这表明最大的瓶颈是缩放步骤和混合步骤。低CPU使用率也不是最佳选择,但让我们首先关注低挂水果。

让我们看一下我们使用的numpy数组的数据类型。 read()为我们提供了带有 dtypenp.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

现在,混合功能是最大的瓶颈,其次是所有3个数组的类型转换。如果我们查看混合操作的作用:
foreground * alpha + background * (1.0 - alpha)

我们可以观察到,要使数学起作用,唯一需要在(0.0,1.0)范围内的值是 alpha

如果仅缩放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_foregroundfr_background始终包含原始帧)。我们确实用 cmb()支付了额外的 Actor 表的费用,但是总体来说这似乎是一个胜利-我们的时间是原始时间的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

我们并没有真正获得任何 yield ,但是读取速度明显加快。

这引出了另一个想法-如果我们尝试最小化分配并在后续迭代中重用数组,该怎么办?

在读取了第一组帧之后,我们可以在第一次迭代中使用 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)

现在,我们可以使用
  • numpy.add 用于添加
  • 减法 numpy.subtract 减法
  • numpy.multiply 用于乘法
  • numpy.copyto 用于类型转换

  • 我们还可以使用单个 numpy.multiply将步骤1和2合并在一起。
    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%。

    较小更新:

    基于 Silenceranswer,我也测量了 cv2.convertScaleAbs 。实际上,它运行得更快:
    Step 6: 1.2318 ms

    那给了我另一个想法-我们可以利用 cv2.add 的优势,让我们指定目标数据类型并执行饱和转换。这将使我们可以将步骤5和6结合在一起。
    cv2.add(fr_fg_weighed, fr_bg_weighed, result, dtype=cv2.CV_8UC3)

    出现在
    Step 5: 3.3621 ms

    再次获得一点胜利(之前我们大约是3.9ms)。

    在此之后, cv2.subtract cv2.multiply 是进一步的候选者。我们需要使用4元素元组来定义标量(Python绑定(bind)的复杂性),并且需要显式定义用于乘法的输出数据类型。
        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提供的任何优势,因此我们应该集中精力对实施进行流水线设计。

    为了帮助我弄清楚如何在不同的饼图阶段(线程)之间划分代码,我制作了一张图表,显示了所有操作,我们为它们准备的最佳时间以及计算的相互依赖性:

    enter image description here

    WIP 在撰写本文时,请参阅注释以获取其他信息。

    关于python - 使用OpenCV进行视频Alpha混合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50241077/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com