使用python优化opencv的骨架功能

时间:2017-03-16 22:13:52

标签: python opencv image-processing

所以我在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和带通滤波操作,然后运行骨架操作)。其他代码很慢,但会完成操作。

图像很大 - 我可以裁剪一些,但我认为这还不够。我试图找到这个的优化版本,但到目前为止还没有提出任何建议。任何想法或解决方案?

1 个答案:

答案 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)

变式1(原件)

让我们将您的实现转换为函数,并删除一些不必要的位。出于好奇,让我们跟踪骨架化所需的迭代次数。

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.

变式2

我们可以做的第一个改进是尽量减少新数组对象的分配数量,并尽可能地重用。我们可以创建一些临时数组(如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.

变体3

下一步是摆脱不必要的副本 - 这是一个非常明显的副本: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上进行的测量。我没有覆盆子,随意添加你的时间,所以我们看到它有什么样的效果。