所以我在raspbian(raspberry pi 2模型B)上使用OpenCV。我正在进行视觉/图像处理,而rasppi就是我给的(如果可以的话,我会使用电脑)。
我需要运行一个骨架功能。我找到了以下实现:
import cv2
import numpy as np
img = cv2.imread('img.png',0)
size = np.size(img)
skeleton = np.zeros(img.shape,np.uint8)
ret,img = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
finished = False
while(not finished):
eroded = cv2.erode(img,kernel)
temp = cv2.dilate(eroded,kernel)
temp = cv2.subtract(img,temp)
skeleton = cv2.bitwise_or(skeleton,temp)
img = eroded.copy()
zeros = size - cv2.countNonZero(img)
if zeros==size:
finished = True
cv2.imshow("skeleton",skeleton)
cv2.waitKey(0)
cv2.destroyAllWindows()
当它运行时,它非常非常慢(不出所料)(我在此之前进行FFT和带通滤波操作,然后运行骨架操作)。其他代码很慢,但会完成操作。
图像很大 - 我可以裁剪一些,但我认为这还不够。我试图找到这个的优化版本,但到目前为止还没有提出任何建议。任何想法或解决方案?
答案 0 :(得分:8)
在这个答案中,我将专注于改进您的实现,而不是算法。虽然这不会给我们带来太大的影响,但我认为它仍然有用。
让我们从一些样板开始 - 必要的导入,一些测试图像和一些功能让我们轻松比较:
from timeit import default_timer as timer
import numpy as np
import cv2
# Create a decent size test image...
img = cv2.imread('cage.png',0)
img = cv2.resize(img, (2048, 2048))
cv2.normalize(img, img, 0, 255, cv2.NORM_MINMAX)
def time_fn(fn, img, iters=1):
start = timer()
result = None
for i in range(iters):
result = fn(img)
end = timer()
return (result,((end - start) / iters) * 1000)
def run_test(fn, img, i):
res, t = time_fn(fn, img, 4)
cv2.imwrite("skeleton_%d.png" % i, res[0])
print "Variant %d" % i
print "Input size = (%d, %d)" % img.shape[:2]
print "Ran %d iterations to find skeleton." % res[1]
print "Avg. find_skeleton time = %0.4f s." % (t/1000)
让我们将您的实现转换为函数,并删除一些不必要的位。出于好奇,让我们跟踪骨架化所需的迭代次数。
def find_skeleton1(img):
skeleton = np.zeros(img.shape,np.uint8)
_,thresh = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
iters = 0
while(True):
eroded = cv2.erode(thresh, kernel)
temp = cv2.dilate(eroded, kernel)
temp = cv2.subtract(thresh, temp)
skeleton = cv2.bitwise_or(skeleton, temp)
thresh = eroded.copy()
iters += 1
if cv2.countNonZero(thresh) == 0:
return (skeleton,iters)
让我们看看它如何设定基线。
>>> run_test(find_skeleton1, img, 1)
Variant 1
Input size = (2048, 2048)
Ran 338 iterations to find skeleton.
Avg. find_skeleton time = 2.7969 s.
我们可以做的第一个改进是尽量减少新数组对象的分配数量,并尽可能地重用。我们可以创建一些临时数组(如skeleton
),并在循环中使用OpenCV函数的dst
参数忽略返回值。由于我们提供了正确形状和数据类型的目标,因此可以重用现有数组。
def find_skeleton2(img):
skeleton = np.zeros(img.shape,np.uint8)
eroded = np.zeros(img.shape,np.uint8)
temp = np.zeros(img.shape,np.uint8)
_,thresh = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
iters = 0
while(True):
cv2.erode(thresh, kernel, eroded)
cv2.dilate(eroded, kernel, temp)
cv2.subtract(thresh, temp, temp)
cv2.bitwise_or(skeleton, temp, skeleton)
thresh = eroded.copy()
iters += 1
if cv2.countNonZero(thresh) == 0:
return (skeleton,iters)
让我们试一试,检查结果是否相同:
>>> print np.array_equal(find_skeleton1(img)[0], find_skeleton2(img)[0])
True
>>> run_test(find_skeleton2, img, 2)
Variant 2
Input size = (2048, 2048)
Ran 338 iterations to find skeleton.
Avg. find_skeleton time = 1.4356 s.
下一步是摆脱不必要的副本 - 这是一个非常明显的副本:thresh = eroded.copy()
。请注意,在下面的迭代中,我们会立即覆盖eroded
的内容。因此,只要它是正确的形状和数据类型,我们并不关心它包含什么。它们是,所以这意味着我们可以只交换两个对象,而不是执行副本。
def find_skeleton3(img):
skeleton = np.zeros(img.shape,np.uint8)
eroded = np.zeros(img.shape,np.uint8)
temp = np.zeros(img.shape,np.uint8)
_,thresh = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
iters = 0
while(True):
cv2.erode(thresh, kernel, eroded)
cv2.dilate(eroded, kernel, temp)
cv2.subtract(thresh, temp, temp)
cv2.bitwise_or(skeleton, temp, skeleton)
thresh, eroded = eroded, thresh # Swap instead of copy
iters += 1
if cv2.countNonZero(thresh) == 0:
return (skeleton,iters)
再次,让我们验证结果匹配并做一些时间。
>>> print np.array_equal(find_skeleton1(img)[0], find_skeleton3(img)[0])
True
>>> run_test(find_skeleton3, img, 3)
Variant 3
Input size = (2048, 2048)
Ran 338 iterations to find skeleton.
Avg. find_skeleton time = 0.9839 s.
很少有简单的改变将时间降低到原来的约35%。当然,它仍然会处理整个图像的数百次迭代。下一步是研究如何减少工作量 - 在后一次迭代中,工作图像的重要区域是黑色的,并且不会对骨架做出任何贡献。
NB:在i7-4930K上进行的测量。我没有覆盆子,随意添加你的时间,所以我们看到它有什么样的效果。