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

Tim*_* MB 5 c++ python opencv image-processing

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

二进制骨架图像示例

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

效率非常重要,因为我正在通过视频源实时分析其中的一些,并且需要同时执行许多其他操作.

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

ray*_*ica 10

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

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

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

// 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

for (int i = 0; i < coords.size() / 2; i++)
    cout << "(" << coords.at(2*i) << "," coords.at(2*i+1) << ")\n";
Run Code Online (Sandbox Code Playgroud)

要完成,这里也是一个Python实现.我正在使用一些numpy功能来让自己更容易.假设你的图像存储在img,这也是一个灰度图像,并导入OpenCV库和numpy(即import cv2,import numpy as np),这是等效代码:

# Find row and column locations that are non-zero
(rows,cols) = np.nonzero(img)

# Initialize empty list of co-ordinates
skel_coords = []

# For each non-zero pixel...
for (r,c) in zip(rows,cols):

    # Extract an 8-connected neighbourhood
    (col_neigh,row_neigh) = np.meshgrid(np.array([c-1,c,c+1]), np.array([r-1,r,r+1]))

    # Cast to int to index into image
    col_neigh = col_neigh.astype('int')
    row_neigh = row_neigh.astype('int')

    # Convert into a single 1D array and check for non-zero locations
    pix_neighbourhood = img[row_neigh,col_neigh].ravel() != 0

    # If the number of non-zero locations equals 2, add this to 
    # our list of co-ordinates
    if np.sum(pix_neighbourhood) == 2:
        skel_coords.append((r,c))
Run Code Online (Sandbox Code Playgroud)

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

print "".join(["(" + str(r) + "," + str(c) + ")\n" for (r,c) in skel_coords])
Run Code Online (Sandbox Code Playgroud)

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


Nic*_*ews 6

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

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

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

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

[[1, 1,1],
 [1,10,1],
 [1, 1,1]]? 
Run Code Online (Sandbox Code Playgroud)

然后,应用此内核后,任何值为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
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

  • 在很多情况下,一个像素可以是一个端点并且有 1 个以上的非零邻居(想想楼梯的例子)。 (2认同)
  • @Tobbey:这是一个 4 连接的骨架,您只需要测试 4 个邻居。该答案在骨架为 8 连接的假设下测试 8 个邻居。在 8 连接骨架中,您无法进行配置,因为可以删除点 (1,0),而不会影响骨架的连接性。 (2认同)