如何在OpenCV中找到二进制骨架图像的端点?

时间:2014-10-23 20:55:22

标签: python c++ opencv image-processing

我有一个骨架作为二进制像素,例如:

Example binary skeleton image

我想找到这个骨架端点的坐标(在这种情况下有四个),如果适用的话,使用Open CV。

效率非常重要,因为我可以通过视频源实时分析其中的大量内容,并且需要同时执行大量其他操作。

(注意,请注意上面的屏幕截图已经调整了人工制品的大小,但它是我正在使用的8连接骨架。)

3 个答案:

答案 0 :(得分:9)

鉴于您的个人资料中的问题和答案的标记,我将假设您需要C ++实现。对对象进行骨架化时,对象的厚度应为1像素。因此,我可以建议的一件事是找到图像中非零的像素,然后在这个像素周围的8连接邻域中搜索并计算那些非零的像素。如果计数仅为2,那么这是骨架端点的候选者。请注意,我也将忽略边界,因此我们不会超出界限。如果计数为1,则它是一个嘈杂的孤立像素,所以我们应该忽略它。如果它是3或更多,那么这意味着您正在检查骨架内某个点的骨架部分,或者您正处于多条线连接在一起的点,因此这也不应该是端点。

老实说,除了检查此标准的所有骨架像素之外,我真的想不出任何算法....所以复杂度为O(mn),其中mn是图像的行和列。对于图像中的每个像素,8像素邻域检查需要恒定的时间,这对于您检查的所有骨架像素都是相同的。但是,这肯定是次线性的,因为大多数像素在图像中都是0,因此8像素邻域检查不会在大多数情况下发生。

因此,我会尝试这样做,假设您的图像存储在名为cv::Mat的{​​{1}}结构中,它是单通道(灰度)图像,并且类型为{ {1}}。我还将存储骨架端点在im类型中的坐标。每当我们检测到骨架点时,我们将一次向矢量添加两个整数 - 我们检测结束骨架点的行和列。

uchar

如果您想在完成时显示坐标,只需检查此向量中的每对元素:

std::vector

要完成,这里也是一个Python实现。我正在使用// Declare variable to count neighbourhood pixels int count; // To store a pixel intensity uchar pix; // To store the ending co-ordinates std::vector<int> coords; // For each pixel in our image... for (int i = 1; i < im.rows-1; i++) { for (int j = 1; j < im.cols-1; j++) { // See what the pixel is at this location pix = im.at<uchar>(i,j); // If not a skeleton point, skip if (pix == 0) continue; // Reset counter count = 0; // For each pixel in the neighbourhood // centered at this skeleton location... for (int y = -1; y <= 1; y++) { for (int x = -1; x <= 1; x++) { // Get the pixel in the neighbourhood pix = im.at<uchar>(i+y,j+x); // Count if non-zero if (pix != 0) count++; } } // If count is exactly 2, add co-ordinates to vector if (count == 2) { coords.push_back(i); coords.push_back(j); } } } 的一些函数来让自己更容易。假设您的图像存储在for (int i = 0; i < coords.size() / 2; i++) cout << "(" << coords.at(2*i) << "," coords.at(2*i+1) << ")\n"; 中,这也是一个灰度图像,并导入OpenCV库和numpy(即imgnumpy),这就相当于代码:

import cv2

要显示终点的坐标,您可以这样做:

import numpy as np

次要说明:此代码未经测试。我没有在这台机器上安装C ++ OpenCV,所以希望我写的内容可行。如果它没有编译,你当然可以将我所做的翻译成正确的语法。祝你好运!

答案 1 :(得分:5)

有点晚了,但这对人们来说仍然有用!

有一种方法可以完全像@rayryeng所说的那样,但是使用openCV的内置函数!这使它变得更小,并且可能更快(特别是使用Python,如果你使用它,就像我一样!)它与this one是相同的解决方案。

基本上,我们试图找到的是非零的像素,其中一个非零邻居。所以我们所做的就是使用openCV内置的filter2D函数将骨架图像与我们制作的自定义内核进行卷积。我刚刚学习了卷积和内核,this page对于解释这些内容的含义非常有帮助。

那么,什么内核可以工作? <怎么样

[[1, 1,1],
 [1,10,1],
 [1, 1,1]]? 

然后,应用此内核后,任何值为11的像素都是我们想要的那个!

以下是我使用的内容:

def skeleton_endpoints(skel):
    # make out input nice, possibly necessary
    skel = skel.copy()
    skel[skel!=0] = 1
    skel = np.uint8(skel)

    # apply the convolution
    kernel = np.uint8([[1,  1, 1],
                       [1, 10, 1],
                       [1,  1, 1]])
    src_depth = -1
    filtered = cv2.filter2D(skel,src_depth,kernel)

    # now look through to find the value of 11
    # this returns a mask of the endpoints, but if you just want the coordinates, you could simply return np.where(filtered==11)
    out = np.zeros_like(skel)
    out[np.where(filtered==11)] = 1
    return out

希望这有帮助!

答案 2 :(得分:0)

这是我的 Python 实现:

import cv2
import numpy as np


path = 'sample_image.png'
img = cv2.imread(path, 0)

# Find positions of non-zero pixels
(rows, cols) = np.nonzero(img)

# Initialize empty list of coordinates
endpoint_coords = []

# Loop through all non-zero pixels
for (r, c) in zip(rows, cols):
    top = max(0, r - 1)
    right = min(img.shape[1] - 1, c + 1)
    bottom = min(img.shape[0] - 1, r + 1)
    left = max(0, c - 1)

    sub_img = img[top: bottom + 1, left: right + 1]
    if np.sum(sub_img) == 255*2:
        endpoint_coords.append((r,c))

print(endpoint_coords)