簡體   English   中英

如何在 OpenCV 中找到二進制骨架圖像的端點?

[英]How can I find endpoints of binary skeleton image in OpenCV?

我有一個二進制像素的骨架,例如:

示例二值骨架圖像

我想找到這個骨架端點的坐標(在這種情況下有四個),如果適用,使用 Open CV。

效率很重要,因為我正在從視頻源中實時分析其中的一些,並且需要同時做很多其他事情。

(請注意,很抱歉上面的屏幕截圖有調整大小的人工制品,但我正在使用它是一個 8 連接的骨架。)

鑒於您的個人資料中的問題和答案標簽,我將假設您需要 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,所以希望我寫的東西會起作用。 如果它不能編譯,你當然可以將我所做的翻譯成正確的語法。 祝你好運!

有點晚了,但這仍然可能對人們有用!

有一種方法可以做與@rayryeng 建議的完全相同的事情,但是使用 openCV 的內置函數! 這使得它更小,並且可能更快(特別是使用 Python,如果你使用它,就像我一樣)它與這個解決方案相同。

基本上,我們試圖找到的是具有一個非零鄰居的非零像素。 所以我們所做的是使用 openCV 的內置 filter2D 函數來將骨架圖像與我們制作的自定義內核進行卷積。 我剛剛了解了卷積和內核,這個頁面對於解釋這些東西的含義非常有幫助。

那么,什么樣的內核會起作用呢? 怎么樣

[[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

有關更多信息,請參閱評論。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM