【问题标题】:how to fill the hollow lines opencv如何填充空心线opencv
【发布时间】:2021-07-29 17:56:54
【问题描述】:

我有这样的图像:

在我应用了一些处理之后,例如cv2.Canny(),现在是这样的:

如您所见,黑色线条变得空心。 我尝试过腐蚀和膨胀,但如果我多次这样做,两个入口将被关闭(意味着成为连接线或闭合轮廓)。

我怎样才能使这些线条像下图一样实心,同时保持 2 个入口不受影响?


更新 1

我已经用几张照片测试了以下答案,但代码似乎已定制为只能处理这张特定的图片。由于 SOF 的限制,我无法上传大于 2MB 的照片,所以我将它们上传到我的 Microsoft OneDrive 文件夹中,以方便您测试。

https://1drv.ms/u/s!Asflam6BEzhjgbIhgkL4rt1NLSjsZg?e=OXXKBK

更新 2

我选择了@fmw42 的帖子作为答案,因为他的回答是最详细的。它没有回答我的问题,但指出了处理迷宫的正确方法,这是我的最终目标。我喜欢他回答问题的方式,首先告诉你每个步骤应该做什么,以便你清楚地知道如何完成任务,然后从头到尾提供完整的代码示例。很有帮助。

由于SOF的限制,我只能选择一个答案。如果允许多个答案,我也会选择 Shamshirsaz.Navid 的答案。他的回答不仅指出了解决问题的正确方向,而且用可视化的解释对我来说真的很管用~!我想对于所有试图理解为什么需要每一行代码的人来说,它同样适用。他还在 cmets 中跟进了我的问题,这使得 SOF 有点互动:)

Ann Zen 的答案中的阈值轨迹栏也是人们快速找出最佳值的非常有用的提示。

【问题讨论】:

    标签: python opencv image-processing opencv-python opencv-contour


    【解决方案1】:

    这是在 Python/OpenCV 中处理和纠正迷宫的一种方法。

    • 读取输入
    • 转换为灰色
    • 阈值
    • 使用形态接近去除最细的(无关的)黑线
    • 反转阈值
    • 获取外部轮廓
    • 保留大于输入宽度和高度 1/4 的轮廓
    • 将这些轮廓绘制为黑色背景上的白线
    • 从白色轮廓线图像中获取凸包
    • 将凸包绘制为黑色背景上的白线
    • 使用 GoodFeaturesToTrack 从白色船体线图像中获取 4 个角
    • 按相对于质心的角度对 4 个角进行排序,以便它们按顺时针顺序排列:左上角、右上角、右下角、左下角
    • 将这些点设置为输入的共轭控制点数组
    • 使用输入维度的 1/2 来定义输出的共轭控制点数组
    • 计算透视变换矩阵
    • 使用透视矩阵扭曲输入图像
    • 保存结果

    输入:

    import cv2
    import numpy as np
    import math
    
    # load image
    img = cv2.imread('maze.jpg')
    hh, ww = img.shape[:2]
    
    # convert to gray
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # threshold
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
    
    # use morphology to remove the thin lines
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (5,1))
    thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    
    # invert so that lines are white so that we can get contours for them
    thresh_inv = 255 - thresh
    
    # get external contours
    contours = cv2.findContours(thresh_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    
    # keep contours whose bounding boxes are greater than 1/4 in each dimension
    # draw them as white on black background
    contour = np.zeros((hh,ww), dtype=np.uint8)
    for cntr in contours:
        x,y,w,h = cv2.boundingRect(cntr)
        if w > ww/4 and h > hh/4:
            cv2.drawContours(contour, [cntr], 0, 255, 1)
            
    # get convex hull from  contour image white pixels
    points = np.column_stack(np.where(contour.transpose() > 0))
    hull_pts = cv2.convexHull(points)
    
    # draw hull on copy of input and on black background
    hull = img.copy()
    cv2.drawContours(hull, [hull_pts], 0, (0,255,0), 2)
    hull2 = np.zeros((hh,ww), dtype=np.uint8)
    cv2.drawContours(hull2, [hull_pts], 0, 255, 2)
    
    # get 4 corners from white hull points on black background
    num = 4
    quality = 0.001
    mindist = max(ww,hh) // 4
    corners = cv2.goodFeaturesToTrack(hull2, num, quality, mindist)
    corners = np.int0(corners)
    for corner in corners:
        px,py = corner.ravel()
        cv2.circle(hull, (px,py), 5, (0,0,255), -1)
    
    # get angles to each corner relative to centroid and store with x,y values in list
    # angles are clockwise between -180 and +180 with zero along positive X axis (to right)
    corner_info = []
    center = np.mean(corners, axis=0)
    centx = center.ravel()[0]
    centy = center.ravel()[1]
    for corner in corners:
        px,py = corner.ravel()
        dx = px - centx
        dy = py - centy
        angle = (180/math.pi) * math.atan2(dy,dx)
        corner_info.append([px,py,angle])
    
    # function to define sort key as element 2 (i.e. angle)
    def takeThird(elem):
        return elem[2]
    
    # sort corner_info on angle so result will be TL, TR, BR, BL order
    corner_info.sort(key=takeThird)
    
    # make conjugate control points
    # get input points from corners
    corner_list = []
    for x, y, angle in corner_info:
        corner_list.append([x,y])
    print(corner_list)
    
    # define input points from (sorted) corner_list
    input = np.float32(corner_list)
    
    # define output points from dimensions of image, say half of input image
    width = ww // 2
    height = hh // 2
    output = np.float32([[0,0], [width-1,0], [width-1,height-1], [0,height-1]])
    
    # compute perspective matrix
    matrix = cv2.getPerspectiveTransform(input,output)
    
    # do perspective transformation setting area outside input to black
    result = cv2.warpPerspective(img, matrix, (width,height), cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0,0,0))
    
    # save output
    cv2.imwrite('maze_thresh.jpg', thresh)
    cv2.imwrite('maze_contour.jpg', contour)
    cv2.imwrite('maze_hull.jpg', hull)
    cv2.imwrite('maze_rectified.jpg', result)
    
    # Display various images to see the steps
    cv2.imshow('thresh', thresh)
    cv2.imshow('contour', contour)
    cv2.imshow('hull', hull)
    cv2.imshow('result', result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    形态学后的阈值图像:

    黑色背景上的过滤轮廓:

    输入图像上的凸包和 4 个角:

    透视扭曲的结果:

    【讨论】:

    • 天啊,看看四个角点~!它们的位置非常准确!另外,我喜欢你回答问题的方法:不仅展示了解决方案,还提供了一步一步的思考过程。对于像我这样的图像处理初学者来说,这是一种非常友好的方法。谢谢~!
    • +1 用于cv2.THRESH_OTSU,它将找到最佳阈值。这使我无法找到正确的值。
    • 嗨@fmw42,我有2个问题。 1. 你是如何确定结构元素中使用的内核形状的? 2. 代码似乎是定制的,只能处理这一张特定的图片。我更新了我的问题,增加了 4 张图片来测试。你能看看吗?谢谢。
    • @fmw42 你的回答很棒:)
    • 嗨@fmw42,我为图像处理创建了一个新问题1drv.ms/u/s!Asflam6BEzhjgbIh8_tQqdv-V4RBgA?e=3nYbg6,以防您有兴趣。谢谢
    【解决方案2】:

    你可以尝试一个简单的阈值来检测迷宫的线条,因为它们是黑色的:

    import cv2
    
    img = cv2.imread("maze.jpg")
    gray = cv2.cvtColor(img, cv2.BGR2GRAY)
    _, thresh = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)
    cv2.imshow("Image", thresh)
    cv2.waitKey(0)
    

    输出:

    您可以使用轨迹栏自行调整阈值:

    import cv2
    
    cv2.namedWindow("threshold")
    cv2.createTrackbar("", "threshold", 0, 255, id)
    
    img = cv2.imread("maze.jpg")
    
    while True:
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        t = cv2.getTrackbarPos("", "threshold")
        _, thresh = cv2.threshold(gray, t, 255, cv2.THRESH_BINARY)
        cv2.imshow("Image", thresh)
        if cv2.waitKey(1) & 0xFF == ord("q"): # If you press the q key
            break
    

    【讨论】:

    • 感谢@AnnZen,我喜欢使用轨迹栏的想法,它确实是找到最佳阈值的好工具。
    【解决方案3】:

    Canny 是一个边缘检测器。它检测颜色变化的线条。输入图像中的一条线有两个这样的过渡,每侧一个。因此,您会在图像中的一条线的每一侧看到两条平行线。 This answer of mine 解释了边和线的区别。

    因此,您不应该使用边缘检测器来检测图像中的线条。

    如果一个简单的阈值不能正确地对该图像进行二值化,请尝试使用局部阈值(OpenCV 中的“自适应阈值”)。对此类图像有效的另一件事是应用顶帽过滤器(对于此图像,它将是closing(img) - img),其中结构元素被调整为您想要查找的线条的宽度。这将产生一个易于阈值的图像,并将保留所有比结构元素更细的线条。

    【讨论】:

    • 感谢克里斯,我确实理解边缘和线条之间的差异,这不是问题所在。我使用Canny() 的原因是因为图像的左角有点翘曲,因此当我进行图像处理时,该部分被遗漏了。通过应用 Canny() 它解决了这个问题,但带来了另一个问题。所以是的,我已经尝试过threshold() 它运作良好:)
    • @Franva:使用局部阈值(OpenCV 中的“自适应阈值”)来避免纸张未均匀照明的问题。
    • 感谢 Chris,我试过了,它不会像 threshold() 那样删除背景。但是很高兴知道这项技术。谢谢:)
    【解决方案4】:

    检查一下:

    import cv2
    import numpy as np
    
    im=cv2.imread("test2.jpg",1)
    
    #convert 2 gray
    mask=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    
    #convert 2 black and white
    mask=cv2.threshold(mask,127,255,cv2.THRESH_BINARY)[1]
    
    #remove thin lines and texts and then remake main lines
    mask=cv2.dilate(mask,np.ones((5, 5), 'uint8'))
    mask=cv2.erode(mask,np.ones((4, 4), 'uint8'))
    
    #smooth lines
    mask=cv2.medianBlur(mask,3)
    
    #write output mask
    cv2.imwrite("mask2.jpg",mask)
    

    从现在开始,一切都可以完成。您可以删除多余的斑点,可以根据蒙版从原始图像中提取线条,等等。


    中位数:

    这个项目的中位数变化不大。并且可以安全地移除。但我更喜欢它,因为它会使线条的末端稍微变圆。您必须放大很多才能看到像素。但这种技术通常用于去除椒盐噪声。


    腐蚀内核:

    在内核的情况下,数字越大,线条越粗。好吧,这并不总是好的。因为它会导致路径线粘在箭头上,然后很难将路径与箭头分开。


    更新:

    迷宫的一部分是否被清除并不重要。重要的是,您可以通过此蒙版围绕此形状绘制一个矩形,并为此图像创建一个新蒙版。

    在新蒙版中围绕这些路径创建一个白色矩形。使用 FloodFill 或任何其他技术完全美白面膜内部。现在你有了一个新的蒙版,可以从原始图像中取出整个形状。现在在下一步中您可以更正透视图。

    【讨论】:

    • 它看起来很不错。谢谢 :) 为什么膨胀和腐蚀的内核不同?还有为什么你需要使用cv2.medianBlur()?我试图删除它,它显示了相同的图像。
    • @Franva Hi :) 我将更新帖子并直观地显示差异:)
    • @Franva 我认为如果您获得相同的内核大小,则不会有问题。如果您需要非常干净的线条,您甚至可以将光栅线转换为矢量。当然,我不知道什么技术可以适合这个:)
    • 谢谢大佬,用可视化的解释真的很适合我~!我想它对所有试图理解为什么需要每一行代码的人同样有效。
    • 嗨@shamshirsaz.Navid,我为图像处理创建了一个新问题1drv.ms/u/s!Asflam6BEzhjgbIh8_tQqdv-V4RBgA?e=3nYbg6,以防您有兴趣。谢谢
    猜你喜欢
    • 2018-12-09
    • 2020-10-13
    • 1970-01-01
    • 2014-10-27
    • 1970-01-01
    • 2019-01-24
    • 2014-11-18
    • 2019-03-01
    • 1970-01-01
    相关资源
    最近更新 更多