【问题标题】:Detecting start and end point of line in image (numpy array)检测图像中线的起点和终点(numpy数组)
【发布时间】:2019-05-14 13:19:10
【问题描述】:

我有一张像下面这样的图片:

我想要的是获取每个段的起点和终点的坐标。实际上我的想法是考虑这样一个事实,即每个极值点应该只有一个点属于其邻域中的线段,而所有其他点应该至少有 2 个。不幸的是,这条线的粗细不等于一个像素,所以这个推理不成立。

【问题讨论】:

  • 我想到的解决方案的第一个“步骤”是首先将这些行标识为 2 行 - 类似于 Number of islands question。在您确定了 2 行之后,您拥有的所有坐标都在这些行内。该坐标中的 4 个是 line1 的 start1、end1 和 line2 的 start2、end2。这会大大减少您的“搜索”池。
  • 嗨,马克,我也看到了你的 DM。谢谢你的回答,但我仍然希望得到更多的答案

标签: python numpy opencv


【解决方案1】:

这是一个相当简单的方法:

  • 加载图像并丢弃多余的 Alpha 通道
  • 骨架化
  • 过滤器寻找具有中心像素集且只有一个像素集的 3x3 邻域

#!/usr/bin/env python3

import numpy as np
from PIL import Image
from scipy.ndimage import generic_filter
from skimage.morphology import medial_axis

# Line ends filter
def lineEnds(P):
    """Central pixel and just one other must be set to be a line end"""
    return 255 * ((P[4]==255) and np.sum(P)==510)

# Open image and make into Numpy array
im = Image.open('lines.png').convert('L')
im = np.array(im)

# Skeletonize
skel = (medial_axis(im)*255).astype(np.uint8)

# Find line ends
result = generic_filter(skel, lineEnds, (3, 3))

# Save result
Image.fromarray(result).save('result.png')


请注意,您可以在命令行中使用 ImageMagick 获得完全相同的结果,而且工作量要小得多,如下所示:

convert lines.png -alpha off -morphology HMT LineEnds result.png

或者,如果您希望它们是数字而不是图像:

convert result.png txt: | grep "gray(255)"

样本输出

134,78: (65535)  #FFFFFF  gray(255)    <--- line end at coordinates 134,78
106,106: (65535)  #FFFFFF  gray(255)   <--- line end at coordinates 106,106
116,139: (65535)  #FFFFFF  gray(255)   <--- line end at coordinates 116,139
196,140: (65535)  #FFFFFF  gray(255)   <--- line end at coordinates 196,140

另一种方法是使用 scipy.ndimage.morphology.binary_hit_or_miss 并将您的 "Hits" 设置为下图中的白色像素,并将您的 "Misses" 设置为黑色像素:

图来自Anthony Thyssen的优秀素材here


与上述类似,您可以将上述 "Hits""Misses" 内核与 OpenCV 一起使用,如所述here:

morphologyEx(input_image, output_image, MORPH_HITMISS, kernel);

我怀疑这是最快的方法。


关键字:Python、图像、图像处理、行端、行端、形态学、Hit or Miss、HMT、ImageMagick、过滤器。

【讨论】:

    【解决方案2】:

    你提到的方法应该可以的,你只需要在之前做一个形态学操作,将线条的宽度缩小到一个像素。您可以为此使用 scikit-image:

    from skimage.morphology import medial_axis
    import cv2
    
    # read the lines image
    img = cv2.imread('/tmp/tPVCc.png', 0)
    
    # get the skeleton
    skel = medial_axis(img)
    
    # skel is a boolean matrix, multiply by 255 to get a black and white image
    cv2.imwrite('/tmp/res.png', skel*255)
    

    请参阅this page 了解 skimage 中的骨架化方法。

    【讨论】:

    • 感谢您的回答!实际上我并没有使用 medial_axis,而是骨架化,但它似乎工作得更好!
    【解决方案3】:

    我会用分水岭式算法来解决这个问题。我在下面描述了方法,但是它是为处理单(多段)线而创建的,因此您需要将图像拆分为单独的线的图像。

    玩具示例:

    0000000
    0111110
    0111110
    0110000
    0110000
    0000000
    

    其中0 表示黑色,1 表示白色。

    现在我实施的解决方案:

    import numpy as np
    img = np.array([[0,0,0,0,0,0,0],
    [0,255,255,255,255,255,0],
    [0,255,255,255,255,255,0],
    [0,255,255,0,0,0,0],
    [0,0,0,0,0,0,0]],dtype='uint8')
    
    def flood(arr,value):
        flooded = arr.copy()
        for y in range(1,arr.shape[0]-1):
            for x in range(1,arr.shape[1]-1):
                if arr[y][x]==255:
                    if arr[y-1][x]==value:
                        flooded[y][x] = value
                    elif arr[y+1][x]==value:
                        flooded[y][x] = value
                    elif arr[y][x-1]==value:
                        flooded[y][x] = value
                    elif arr[y][x+1]==value:
                        flooded[y][x] = value
        return flooded
    
    ends = np.zeros(img.shape,dtype='uint64')
    
    for y in range(1,img.shape[0]-1):
        for x in range(1,img.shape[1]-1):
            if img[y][x]==255:
                temp = img.copy()
                temp[y][x] = 127
                count = 0
                while 255 in temp:
                    temp = flood(temp,127)
                    count += 1
                ends[y][x] = count
    
    print(ends)
    

    输出:

    [[0 0 0 0 0 0 0]
     [0 5 4 4 5 6 0]
     [0 5 4 3 4 5 0]
     [0 6 5 0 0 0 0]
     [0 0 0 0 0 0 0]]
    

    现在结束由上述数组中最大值的位置表示(在这种情况下为6)。

    说明:我正在检查所有可能的白色像素。对于每个这样的像素,我都在“泛滥”图像 - 我放置特殊值(127 - 不同于 0 并且不同于 255)然后传播它 - 在每一步中,所有 255 都是邻居(在 von诺伊曼的特殊价值感)本身就成为特殊价值。我正在计算删除所有255 所需的步骤。因为如果你从末端开始(恒速)泛滥,它会比你在任何其他位置有源需要更多的时间,那么泛滥的最大时间是你的生产线的末端。

    我必须承认我没有对此进行深入测试,所以我不能保证在特殊情况下正确工作,例如在自相交线的情况下。我也知道我的解决方案的粗糙度,特别是在检测邻居和传播特殊值方面,所以请随时改进它。我假设所有边框像素都是黑色的(没有线条触及图像的“框架”)。

    【讨论】:

      猜你喜欢
      • 2012-07-25
      • 1970-01-01
      • 1970-01-01
      • 2018-09-27
      • 1970-01-01
      • 2014-12-21
      • 1970-01-01
      • 2014-07-31
      • 1970-01-01
      相关资源
      最近更新 更多