我在Python中从头开始编写computer vision library以使用rpi
相机。目前,我已实施转换为greyscale
和其他一些基本img
操作,这些操作在model B
rpi3
上运行速度相对较快。
但是,使用sobel
运算符(wikipedia description)的边缘检测功能比其他功能要慢得多,尽管它确实有效。这是:
def sobel(img):
xKernel = np.array([[-1,0,1],[-2,0,2],[-1,0,1]])
yKernel = np.array([[-1,-2,-1],[0,0,0],[1,2,1]])
sobelled = np.zeros((img.shape[0]-2, img.shape[1]-2, 3), dtype="uint8")
for y in range(1, img.shape[0]-1):
for x in range(1, img.shape[1]-1):
gx = np.sum(np.multiply(img[y-1:y+2, x-1:x+2], xKernel))
gy = np.sum(np.multiply(img[y-1:y+2, x-1:x+2], yKernel))
g = abs(gx) + abs(gy) #math.sqrt(gx ** 2 + gy ** 2) (Slower)
g = g if g > 0 and g < 255 else (0 if g < 0 else 255)
sobelled[y-1][x-2] = g
return sobelled
并使用此猫的greyscale
图像运行它:
我得到了这个回复,这似乎是正确的:
图书馆的应用,特别是这个功能,是一个国际象棋游戏机器人,其中边缘检测将有助于识别棋子的位置。问题是运行需要>15
秒,这是一个重大问题,因为它会增加机器人进行大量移动所需的时间。
我的问题是:如何加快速度?
到目前为止,我尝试了几件事:
取代squaring
然后adding
,然后square rooting
gx
和gy
值来获得总梯度,我只是{{1 } sum
值。这样可以提高速度。
使用absolute
相机中较低的resolution
图像。这显然是一种简单的方法,可以使这些操作运行得更快,但它不是那么可行,因为它在rpi
的最小可用分辨率上仍然非常慢,这可以从相机的最大值{{{1}大幅减少。 1}}。
编写嵌套的for循环来代替480x360
执行3280x2464
。由于matrix convolutions
返回一个新数组,因此我感到很惊讶,我认为用np.sum(np.multiply(...))
执行此操作应该会更快。我认为这可能是因为np.multiply
主要是用loops
写的,或者新数组实际上没有存储,所以不需要很长时间,但我和#39;我不太确定。
我们非常感谢任何帮助 - 我认为最重要的是改进点numpy
,即C
乘法和求和。
答案 0 :(得分:4)
即使您正在构建自己的库,但您确实应该绝对使用库进行卷积,它们将在后端执行C或Fortran中的结果操作,这将更快,更快。
但如果您愿意,可以自己动手,使用线性可分离滤镜。这是个主意:
图像:
1 2 3 4 5
2 3 4 5 1
3 4 5 1 2
Sobel x
内核:
-1 0 1
-2 0 2
-1 0 1
结果:
8, 3, -7
在卷积的第一个位置,你将计算9个值。首先,为什么?你永远不会添加中间列,不要打扰它。但这不是线性可分离滤波器的重点。这个想法很简单。将内核放在第一个位置时,您将第三列乘以[1, 2, 1]
。但是接下来两步,你会将第三列乘以[-1, -2, -1]
。多么浪费!你已经计算过了,你现在只需要否定它。这就是使用线性可分离滤波器的想法。请注意,您可以将过滤器拆分为两个向量的矩阵外积:
[1]
[2] * [-1, 0, 1]
[1]
在此处获取外部产品会产生相同的矩阵。所以这里的想法是将操作分成两部分。首先将整个图像与行向量相乘,然后将列向量相乘。采取行向量
-1 0 1
整个图片,我们最终
2 2 2
2 2 -3
2 -3 -3
然后传递列向量以进行乘法和求和,我们再次得到
8, 3, -7
另一个可能有用或可能没有帮助的漂亮技巧(取决于你在记忆和效率之间的权衡):
请注意,在单行乘法中,您忽略中间值,只从左侧值中减去右侧。这意味着你正在做的就是减去这两个图像:
3 4 5 1 2 3
4 5 1 - 2 3 4
5 1 2 3 4 5
如果从图像中删除前两列,则会得到左边的矩阵,如果切掉最后两列,则得到正确的矩阵。所以你可以简单地计算卷积的第一部分
result_h = img[:,2:] - img[:,:-2]
然后你可以循环搜索sobel运算符的剩余列。或者,你甚至可以继续前进并做同样的事情。这次是纵向情况,你只需要添加第一行和第三行以及第二行的两倍;或者,使用numpy添加:
result_v = result_h[:-2] + result_h[2:] + 2*result_h[1:-1]
你已经完成了!我可以在不久的将来在这里添加一些时间。对于一些信封计算的背面(即1000x1000图像上的仓促Jupyter笔记本时序):
新方法(图像总和):每循环8.18 ms±399μs(平均值±标准偏差,7次运行,每次100次循环)
旧方法(双循环):每循环7.32 s±207 ms(平均值±标准偏差,7次运行,每次循环1次)
是的,你读的是正确的:1000倍加速。
以下是一些比较两者的代码:
import numpy as np
def sobel_x_orig(img):
xKernel = np.array([[-1,0,1],[-2,0,2],[-1,0,1]])
sobelled = np.zeros((img.shape[0]-2, img.shape[1]-2))
for y in range(1, img.shape[0]-1):
for x in range(1, img.shape[1]-1):
sobelled[y-1, x-1] = np.sum(np.multiply(img[y-1:y+2, x-1:x+2], xKernel))
return sobelled
def sobel_x_new(img):
result_h = img[:,2:] - img[:,:-2]
result_v = result_h[:-2] + result_h[2:] + 2*result_h[1:-1]
return result_v
img = np.random.rand(1000, 1000)
sobel_new = sobel_x_new(img)
sobel_orig = sobel_x_orig(img)
assert (np.abs(sobel_new-sobel_orig) < 1e-12).all()
当然,1e-12
是一种严重的容忍度,但这是每个元素所以应该没问题。但我也有float
图片,你当然会对uint8
图片有更大的差异。
请注意,您可以为任何线性可分离过滤器执行此操作!这包括高斯滤波器。另请注意,通常,这需要大量操作。在C或Fortran等等中,它通常只是作为单行/列向量的两个卷积来实现,因为最后它需要实际遍历每个矩阵的每个元素;无论你是只是添加它们还是将它们相乘,所以在C中添加图像值的方式并不比你刚刚进行卷积时更快。但循环遍历numpy
数组非常慢,因此这种方法在Python中更快。
答案 1 :(得分:1)
要加上什么价值:
Sobel x kernel:
-1 0 1
-2 0 2
-1 0 1
您不需要单独的内核。 1/3的运算始终为零。只是不计算它们。 其余的可以简化:
sum = -inputvalue[y-1][x-1] - 2 * inputvalue[y][x-1] - inputvalue[y+1][x-1]
+ inputvalue[y-1][x+1] + 2 * inputvalue[y][x+1] + inputvalue[y+1][x+1]
该点的2个乘法3个减法和3个加法以及NO循环,相比之下,朴素的方法是遍历内核的9个乘法和9个加法。 这样可以显着减少计算时间。
我对上面的numpy示例中提到的1000倍加速感到惊讶。 但是这里的这种方法帮助我大大提高了速度。 :)
答案 2 :(得分:0)
我遇到了同样的问题,并且能够使用 Numba 库中的 @jit 将我的代码加速大约 600 倍(请参阅链接:https://numba.pydata.org/numba-doc/latest/user/5minguide.html)。 在我的函数上方添加 @jit(nopython=True) 就足以完成这项工作。