【问题标题】:How can I find endpoints of binary skeleton image in OpenCV?如何在 OpenCV 中找到二进制骨架图像的端点?
【发布时间】:2014-12-19 15:58:24
【问题描述】:

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

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

效率很重要,因为我要从视频源中实时分析其中的许多内容,并且需要同时做很多其他事情。

(请注意,很抱歉上面的屏幕截图有调整大小的人工制品,但我正在使用它是一个 8 连接骨架。)

【问题讨论】:

  • 您的示例图像不是二进制 :)
  • 你关心优化;在制作时尝试获取骨架的边界框。这可能会在以后节省很多不必要的检查。

标签: python c++ opencv image-processing


【解决方案1】:

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

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

因此,我会尝试这样做,假设您的图像存储在名为imcv::Mat 结构中,它是单通道(灰度)图像,并且类型为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);
        }
    }
}

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

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

为了完整起见,这里还有一个 Python 实现。我正在使用numpy 的一些函数来简化自己的操作。假设你的图片存放在img,也是灰度图,并导入OpenCV库和numpy(即import cv2import 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))

要显示端点的坐标,你可以这样做:

print "".join(["(" + str(r) + "," + str(c) + ")\n" for (r,c) in skel_coords])

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

【讨论】:

  • 我猜你可以将它作为一个单独的过滤器(x 和 y 方向分别)来实现一个小的加速,我猜
  • @Micka 这是个好主意!如果我有时间我会编辑我的帖子。谢谢!
  • 好吧,这比我想象的要简单得多。有时最简单的方法是最好的。感谢您的详尽回答。
【解决方案2】:

有点晚了,但这仍然可能对人们有用!

有一种方法可以做与@rayryeng 建议的完全相同的事情,但使用openCV 的内置函数!这使它更小,并且可能更快(尤其是使用 Python,如果你像我一样使用它)它与 this one 的解决方案相同。

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

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

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

那么,在应用这个内核之后,任何值为 11 的像素都是我们想要的!

这是我使用的:

def skeleton_endpoints(skel):
    # Make our 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)

    # 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

编辑:此技术不适用于某些骨架,例如缺少“楼梯”模式

000
010
110

查看 cmets 了解更多信息。

【讨论】:

  • 这非常好,我想我最终在这个项目上得到了什么。谢谢!
  • 这也是我正在考虑做的,但最终没有实现它。谢谢你这样做。
  • 在很多情况下,一个像素可以是一个端点并且有多个非零邻居(想想楼梯的例子)。
  • 所以这实际上取决于您使用的骨架化定义。如果您有一条对角线(从左下到右上)作为输入,并且有 (x,y) 索引,则序列以 (0,0),(1,0),(1,1),.. 开头. 将有 (0,0) 端点有两个邻居。这就是为什么一些“端点”实现会处理 2^8 种不同情况(在 8 连接模式中)。
  • @Tobbey:这是一个 4 连接骨架,您只需要测试 4 个邻居。这个答案在假设骨架是 8 连接的情况下测试 8 个邻居。在 8 连接骨架中,您无法进行配置,因为可以移除点 (1,0) 而不会影响骨架的连接。
【解决方案3】:

这是我的 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)

【讨论】:

    猜你喜欢
    • 2017-08-19
    • 2017-06-02
    • 1970-01-01
    • 2015-10-14
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 2013-05-20
    • 1970-01-01
    相关资源
    最近更新 更多