我正在使用以下代码旋转图像并将其粘贴到屏幕大小(4K)的画布上,但每次旋转和粘贴图像都需要100毫秒以上的时间.我正在使用的程序需要做很多这方面的工作,因此这将有助于加快速度,我还假设,鉴于这是一种相当标准的操作,这段代码是非常可优化的.如果您能提供任何关于如何优化它的指导,我将不胜感激.

值得一提的是,各种旋转的图像通常非常接近,有时重叠,这就是为什么我要做掩蔽,但这是我认为可能效率低下的地方之一.

import cv2
import numpy as np

canvas = np.zeros((2160, 3840, 3), dtype=np.uint8)

img_path = PATH_TO_IMAGE
image = cv2.imread(img_path)

offset_from_center = 10
rotation_angle = 45

width = image.shape[1]
pivot_point = (width/2, offset_from_center)

rotation_mat = cv2.getRotationMatrix2D(pivot_point, -rotation_angle, 1.)

canvas_height = canvas.shape[0]
canvas_width = canvas.shape[1]

rotation_mat[0, 2] += canvas_width/2 - pivot_point[0]
rotation_mat[1, 2] += canvas_height/2 - pivot_point[1]

rotated_image = cv2.warpAffine(image,
                               rotation_mat,
                               (canvas_width, canvas_height))

alpha = np.sum(rotated_image, axis=-1) > 0

alpha = alpha.astype(float)

alpha = np.dstack((alpha, alpha, alpha))

rotated_image = rotated_image.astype(float)
canvas = canvas.astype(float)

foreground = cv2.multiply(alpha, rotated_image)
canvas = cv2.multiply(1.0 - alpha, canvas)

canvas = cv2.add(foreground, canvas)
canvas = canvas.astype(np.uint8)

推荐答案

一个相当简单的轮廓显示,np.sum(rotated_image, axis=-1)特别慢.接下来的其他操作也有点慢,尤其是数组乘法和dstack.


基于Numpy的优化

首先要知道的是,np.sum将在进行缩减之前自动将数组类型转换为wider type.实际上是一个64位的整数.这意味着一个8 time bigger array to reduce in memory,这是一个显著的减速.也就是说,最大的问题是Numpy is not optimized for reducing very small dimension:它使用一种效率相当低的方法在每个数组行上迭代,并且迭代比添加3个整数要昂贵得多.解决方案是手动添加以显著加快速度.下面是一个示例:

tmp = rotated_image.astype(np.uint16)
alpha = (tmp[:,:,0] + tmp[:,:,1] + tmp[:,:,2]) > 0

请注意,这是次优的,使用Cython or Numba可以进一步加快速度(大幅度).另一种 Select 是在transposed image上操作.

然后,当您执行alpha.astype(float)时,将使用64位浮点,这需要大量内存,因此任何操作都很慢.这里显然不需要64位浮点.可以改用32位浮点.事实上,PC GPU几乎总是使用32位浮点来计算图像,因为64位操作要昂贵得多(它们需要大量能量和更多的晶体管).

np.dstack是不需要的,因为Numpy可以利用广播(与alpha[:,:,None]),所以到avoid big temporary arrays像这样.不幸的是,Numpy广播在实践中放慢了速度...

cv2.multiply可以被np.multiply取代,np.multiply有一个特殊的parameter 102 for faster in-place operations(在我的机器上快30%).正如@ChristophRackwitz所指出的,cv2.multiply还有一个等效的dst参数.这两个功能在我的机器上运行得一样快.

下面是一个更快的实现,总结了所有这一切:

tmp = rotated_image.astype(np.uint16)
alpha = (tmp[:,:,0] + tmp[:,:,1] + tmp[:,:,2]) > 0

alpha = alpha.astype(np.float32)

alpha = np.dstack((alpha, alpha, alpha))

rotated_image = rotated_image.astype(np.float32)
canvas = canvas.astype(np.float32)

foreground = np.multiply(alpha, rotated_image)
np.subtract(1.0, alpha, out=alpha)
np.multiply(alpha, canvas, out=canvas)

np.add(foreground, canvas, out=canvas)
canvas = canvas.astype(np.uint8)

因此,优化的解决方案速度快了4倍.它还远没有达到速度,但这将有点小到了极限.


用Nuba加速代码

创建many big temporary arrays个并在小规模上运行远远不够高效.这可以通过在基本循环中计算像素来解决,该循环由(JIT)编译器(如Numba或Cython)优化,以生成fast native code.以下是一个实现:

import numba as nb

@nb.njit('(uint8[:,:,::1], uint8[:,:,::1])', parallel=True)
def compute(img, canvas):
    for i in nb.prange(img.shape[0]):
        for j in range(img.shape[1]):
            ir = np.float32(img[i, j, 0])
            ig = np.float32(img[i, j, 1])
            ib = np.float32(img[i, j, 2])
            cr = np.float32(canvas[i, j, 0])
            cg = np.float32(canvas[i, j, 1])
            cb = np.float32(canvas[i, j, 2])
            alpha = np.float32((ir + ig + ib) > 0)
            inv_alpha = np.float32(1.0) - alpha
            cr = inv_alpha * cr + alpha * ir
            cg = inv_alpha * cg + alpha * ig
            cb = inv_alpha * cb + alpha * ib
            canvas[i, j, 0] = np.uint8(cr)
            canvas[i, j, 1] = np.uint8(cg)
            canvas[i, j, 2] = np.uint8(cb)

compute(rotated_image, canvas)

以下是我的6核机器的性能结果(重复7次):

Before:            0.427  s     1x
Optimized Numpy:   0.113  s    ~4x
Numba:             0.0023 s  ~186x

如我们所见,NUBA实现比优化Numpy实现快得多.如果这还不够,您可以在GPU上计算,这对于此类任务来说更有效.它应该更快,使用更少的能量,因为GPU有专用的单元来进行alpha混合(以及更宽的SIMD单元).一种解决方案是使用CUDA,它只适用于Nvidia GPU.另一种解决方案是为此使用OpenCL甚至OpenGL.这应该比使用Numba更复杂.

Python相关问答推荐

如何让Flask 中的请求标签发挥作用

Telethon加入私有频道

如何在给定的条件下使numpy数组的计算速度最快?

avxspan与pandas period_range

我想一列Panadas的Rashrame,这是一个URL,我保存为CSV,可以直接点击

部分视图的DataFrame

移动条情节旁边的半小提琴情节在海运

如何在UserSerializer中添加显式字段?

为什么numpy. vectorize调用vectorized函数的次数比vector中的元素要多?

基于行条件计算(pandas)

找到相对于列表索引的当前最大值列表""

如何在海上配对图中使某些标记周围的黑色边框

30个非DATETIME天内的累计金额

GPT python SDK引入了大量开销/错误超时

裁剪数字.nd数组引发-ValueError:无法将空图像写入JPEG

我如何处理超类和子类的情况

在Pandas 中以十六进制显示/打印列?

按最大属性值Django对对象进行排序

如何将ManyToManyfield用于Self类

使用百分位数对数据帧单元格的背景进行着色