我已经编写了一些Python代码来进行一些图像处理工作,但是运行需要花费大量的时间。我花了最后几个小时试图优化它,但我想我已经达到了我的能力。
查看分析器的输出,下面的函数占我代码总时间的很大一部分。有什么办法可以加速吗?
def make_ellipse(x, x0, y, y0, theta, a, b):
c = np.cos(theta)
s = np.sin(theta)
a2 = a**2
b2 = b**2
xnew = x - x0
ynew = y - y0
ellipse = (xnew * c + ynew * s)**2/a2 + (xnew * s - ynew * c)**2/b2 <= 1
return ellipse
为了给出上下文,调用它是x
和y
作为np.meshgrid
的输出,具有相当大的网格大小,所有其他参数作为简单的整数值。
尽管该功能似乎占用了大量时间,但可能还有其他代码可以加速的方法。我已将其余代码放在this gist。
任何想法都会感激不尽。我尝试过使用numba和autojit
主要功能,但这没什么用。
答案 0 :(得分:3)
让我们尝试将make_ellipse与其调用者一起优化。
首先,请注意a
和b
与多次调用相同。由于make_ellipse每次都会对它们进行平方,所以只需要让调用者这样做。
其次,请注意np.cos(np.arctan(theta))
是1 / np.sqrt(1 + theta**2)
,这似乎在我的系统上稍快一些。可以使用类似的技巧来计算正弦,从theta或cos(theta)(或反之亦然)。
第三,不太具体,考虑将一些最终的椭圆公式评估短路。例如,只要(xnew * c + ynew * s)**2/a2
大于1,椭圆值必须为False。如果经常发生这种情况,您可以“屏蔽”这些位置的椭圆(昂贵)计算的后半部分。我没有详细计划过,但请看numpy.ma以获取一些可能的线索。
答案 1 :(得分:2)
对于所有情况都不会加快速度,但如果您的省略号不占用整个图像,则应将椭圆内的点搜索限制为其边界矩形。我对数学很懒,所以我googled it并重新使用了@JohnZwinck反复无常的技巧来提出这个函数:
def ellipse_bounding_box(x0, y0, theta, a, b):
x_tan_t = -b * np.tan(theta) / a
if np.isinf(x_tan_t) :
x_cos_t = 0
x_sin_t = np.sign(x_tan_t)
else :
x_cos_t = 1 / np.sqrt(1 + x_tan_t*x_tan_t)
x_sin_t = x_tan_t * x_cos_t
x = x0 + a*x_cos_t*np.cos(theta) - b*x_sin_t*np.sin(theta)
y_tan_t = b / np.tan(theta) / a
if np.isinf(y_tan_t):
y_cos_t = 0
y_sin_t = np.sign(y_tan_t)
else:
y_cos_t = 1 / np.sqrt(1 + y_tan_t*y_tan_t)
y_sin_t = y_tan_t * y_cos_t
y = y0 + b*y_sin_t*np.cos(theta) + a*y_cos_t*np.sin(theta)
return np.sort([-x, x]), np.sort([-y, y])
您现在可以将原始功能修改为以下内容:
def make_ellipse(x, x0, y, y0, theta, a, b):
c = np.cos(theta)
s = np.sin(theta)
a2 = a**2
b2 = b**2
x_box, y_box = ellipse_bounding_box(x0, y0, theta, a, b)
indices = ((x >= x_box[0]) & (x <= x_box[1]) &
(y >= y_box[0]) & (y <= y_box[1]))
xnew = x[indices] - x0
ynew = y[indices] - y0
ellipse = np.zeros_like(x, dtype=np.bool)
ellipse[indices] = ((xnew * c + ynew * s)**2/a2 +
(xnew * s - ynew * c)**2/b2 <= 1)
return ellipse
答案 2 :(得分:2)
由于除x和y之外的所有内容都是整数,因此可以尝试最小化数组计算的数量。我想大部分时间都花在这个陈述上:
ellipse = (xnew * c + ynew * s)**2/a2 + (xnew * s - ynew * c)**2/b2 <= 1
这样的简单重写应该减少数组操作的次数:
a = float(a)
b = float(b)
ellipse = (xnew * (c/a) + ynew * (s/a))**2 + (xnew * (s/b) - ynew * (c/b))**2 <= 1
12个阵列操作现在是10个(加上4个标量操作)。我不确定numba的jit是否会试过这个。它可能只是首先进行所有广播,然后点击结果操作。在这种情况下,重新排序如此常见的操作会立即完成应该有所帮助。
继续,您可以再次将其重写为
ellipse = ((xnew + ynew * (s/c)) * (c/a))**2 + ((xnew * (s/c) - ynew) * (c/b))**2 <= 1
或者
t = numpy.tan(theta)
ellipse = ((xnew + ynew * t) * (b/a))**2 + (xnew * t - ynew)**2 <= (b/c)**2
用标量替换另一个数组操作,并消除其他标量操作以获得9个数组操作和2个标量操作。
与往常一样,请注意输入范围以避免舍入错误。
不幸的是,如果两个加数中的任何一个大于比较的右手边,那么就没有办法做好运行总和并提前保释。这将是一个明显的加速,但你需要cython(或c / c ++)来编码。
答案 3 :(得分:0)
使用Cython可以大大加快速度。有关如何执行此操作的非常好的文档。