消除末端未连接的线

Eliminate lines with an unconnected end

我有以下骨架:

我想从此图像中删除不属于循环的线。

我把这想象成一个过程,在这个过程中找到线的末端(用红点标记),然后这些线被吞噬直到有一个分支点(用蓝点标记)。

我还没有在 OpenCV 或 Scikit-Image 中找到针对此的操作。

这种变换有名称吗?有没有一种在 Python 中有效实施的方法?

我还上传了图片 here 以防上面的图片加载不正确。

在 Python 中,我还没有找到使用现有库执行此操作的好方法(尽管我希望有人能够为我指出一个),也没有找到它的名称。

所以我决定将此称为熔断变换,因为该算法的作用类似于像熔断器一样烧掉线条,直到它们分裂。

为了提高效率,我已经将下面的 Fuse Transform 实现为 Cython 函数。

该算法需要单个 O(N) 时间来扫描矩阵大小以识别种子单元(那些位于融合开始的单元)和然后 O(N) 时间在保险丝的总长度中以消除有问题的线路。

融合变换算法

%%cython -a --cplus
import numpy as np
import cv2
import skimage.morphology as skm
import cython
from libcpp.queue cimport queue
cimport numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True) 
#Richard's Fuse Transform
#
cpdef void FuseTransform(unsigned char [:, :] image):
    # set the variable extension types
    cdef int c, x, y, nx, ny, width, height, neighbours
    cdef queue[int] q

    # grab the image dimensions
    height = image.shape[0]
    width  = image.shape[1]

    cdef int dx[8]
    cdef int dy[8]

    #Offsets to neighbouring cells
    dx[:] = [-1,-1,0,1,1,1,0,-1]
    dy[:] = [0,-1,-1,-1,0,1,1,1]

    #Find seed cells: those with only one neighbour
    for y in range(1, height-1):
        for x in range(1, width-1):
            if image[y,x]==0: #Seed cells cannot be blank cells
                continue
            neighbours = 0
            for n in range(0,8):   #Looks at all neighbours
                nx = x+dx[n]
                ny = y+dy[n]
                if image[ny,nx]>0: #This neighbour has a value
                    neighbours += 1
            if neighbours==1:      #Was there only one neighbour?
                q.push(y*width+x)  #If so, this is a seed cell

    #Starting with the seed cells, gobble up the lines
    while not q.empty():
        c = q.front()
        q.pop()
        y = c//width         #Convert flat index into 2D x-y index
        x = c%width
        image[y,x] = 0       #Gobble up this part of the fuse
        neighbour  = -1      #No neighbours yet
        for n in range(0,8): #Look at all neighbours
            nx = x+dx[n]     #Find coordinates of neighbour cells
            ny = y+dy[n]
            #If the neighbour would be off the side of the matrix, ignore it
            if nx<0 or ny<0 or nx==width or ny==height:
                continue
            if image[ny,nx]>0:      #Is the neighbouring cell active?
                if neighbour!=-1:   #If we've already found an active neighbour
                    neighbour=-1    #Then pretend we found no neighbours
                    break           #And stop looking. This is the end of the fuse.
                else:               #Otherwise, make a note of the neighbour's index.
                    neighbour = ny*width+nx
        if neighbour!=-1:           #If there was only one neighbour
            q.push(neighbour)       #Continue burning the fuse

#Read in image
img         = cv2.imread('part.jpg')
ShowImage('Original',img,'bgr')

#Convert image to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#Apply Otsu's method to eliminate pixels of intermediate colour
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_OTSU)

#Apply the Fuse Transform
skh_dilated = skelhuman.copy()
FuseTransform(skh_dilated)

输入

结果

在下面的算法中,我首先将图像像素归一化为具有值 0 和 1。然后,我通过应用 3x3 非归一化框过滤器检查非零像素的 8 个连接的邻居。如果我们(按像素)乘以输入图像的滤波器输出,我们得到所有非零像素,这一次,它们的值告诉我们它们有多少个 8 连接的邻居加 1。因此,这里的中心像素将自己算作它的邻居。

红色是中心像素。黄色是它的 8-connected 社区。

我们应该剔除小于 3 的结果像素值。

代码会让事情更清楚。它可能不是很有效。我没有尝试深入研究 的代码。也许他正在高效地做类似的事情。

import cv2
import numpy as np

im = cv2.imread('USqDW.png', 0)

# set max pixel value to 1
s = np.uint8(im > 0)

count = 0
i = 0
while count != np.sum(s):
    # non-zero pixel count
    count = np.sum(s)
    # examine 3x3 neighborhood of each pixel
    filt = cv2.boxFilter(s, -1, (3, 3), normalize=False)
    # if the center pixel of 3x3 neighborhood is zero, we are not interested in it
    s = s*filt
    # now we have pixels where the center pixel of 3x3 neighborhood is non-zero
    # if a pixels' 8-connectivity is less than 2 we can remove it
    # threshold is 3 here because the boxfilter also counted the center pixel
    s[s < 3] = 0
    # set max pixel value to 1
    s[s > 0] = 1
    i = i + 1

修剪后: