一个相当简单的轮廓显示,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更复杂.