【问题标题】:How to remove external hollow contours from the image without affecting the internal contours如何在不影响内部轮廓的情况下从图像中去除外部空心轮廓
【发布时间】:2020-02-15 21:28:56
【问题描述】:

这是 problem 在不同方法下的延续。

我想摆脱盒子,以及它线之外的所有东西,并保持盒子内的任何东西,原样。或者至少只有盒子。

盒子:

做了一些魔术后我希望它看起来如何:

这是它被裁剪的地方:

如果您在黑色背景上查看第一张图片,您会注意到框框外仍有一条白色带。可以通过获取边界框的统计信息并在其中添加填充来将其裁剪得更小或更大。

这是天真的方法,也是我的第一个方法。这里的问题是:

  • 它不能过多地考虑不同的线宽而不影响里面的字符。比如说,你输入 -9 作为填充。它在大约 9px 线宽的盒子上工作得很好,但超出了 ~9px 的任何东西都会留在里面。这会导致一些剩余像素,最终影响我的应用程序的准确性。
  • 另一方面,任何线宽明显低于 9 的加框字段也可能会破坏其中的字符。

我的第二次尝试是带面具的removing the contour。然而,结果并没有像预期的那样。

提取轮廓的代码很长,所以假设stats包含从contours, _ = cv2.findContours返回的contours

# loop in each contour in stats
for i in range(len(stats)):
    # get the stats of the bounding rectangle 
    x, y, w, h = cv2.boundingRect(stats[i])

    # draw the field contour into the mask
    cv2.drawContours(mask, [stats[i]], -1, 0, -1)

    # remove the contour from the original image
    section = cv2.bitwise_and(section, section, mask=mask)

    # crop the boxed field
    field = crop_by_origin(x, y, w, h, padding=p)

这是我得到的:

我不明白为什么它不起作用?也许是因为在网站上他的例子是在黑色背景上?也许它不适用于“透明”轮廓?这甚至可能吗?

如何解决这个问题?这里还有其他可能的解决方案吗?

更新

我用@nathancy 的回答尝试了另一张图片,结果如下:

结果:

我尝试过使用水平线内核,但没有达到预期效果,有没有办法让它更具动态性?

【问题讨论】:

    标签: python image opencv image-processing computer-vision


    【解决方案1】:

    这个问题可以分解成两个独立的步骤。首先,我们要隔离矩形,这可以使用轮廓近似+过滤来完成。然后我们使用从removing horizontal lines in image 中的先前答案借用的实现删除水平和垂直线。这是一个总体方法:

    • 将图像转换为灰度和高斯模糊
    • Otsu 获取二值图像的阈值
    • 寻找轮廓并使用轮廓近似来寻找矩形框

      • 如果轮廓通过此过滤器和最小阈值区域,则使用 Numpy 切片将 ROI 复制粘贴到空白的白色蒙版上
    • 移除水平线

    • 删除垂直线
    • 用输入图像按位和掩码得到结果

    Otsu 的阈值 -> 检测到的框 -> 绘制到蒙版上的框 -> 倒置

    删除水平线->删除垂直线->按位-并得到结果

    import cv2
    import numpy as np
    
    image = cv2.imread('1.png')
    mask = np.ones(image.shape, dtype=np.uint8) * 255
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (7,7), 0)
    thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    
    cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        area = cv2.contourArea(c)
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.05 * peri, True)
        if len(approx) == 4 and area > 500:
            x,y,w,h = cv2.boundingRect(approx)
            mask[y:y+h, x:x+w] = image[y:y+h, x:x+w]
    
    # Remove horizontal lines
    mask = cv2.cvtColor(255 - mask, cv2.COLOR_BGR2GRAY)
    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,2))
    detect_horizontal = cv2.morphologyEx(mask, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
    cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(mask, [c], -1, (0,0,0), 6)
    
    # Remove vertical lines
    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,45))
    detect_vertical = cv2.morphologyEx(mask, cv2.MORPH_OPEN, vertical_kernel, iterations=1)
    cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(mask, [c], -1, (0,0,0), 6)
    
    # Bitwise mask with input image
    result = cv2.bitwise_and(image, image, mask=mask)
    result[mask==0] = (255,255,255)
    
    cv2.imshow('mask', mask)
    cv2.imshow('thresh', thresh)
    cv2.imshow('result', result)
    cv2.waitKey()
    

    【讨论】:

    • 我很好奇,为什么你在预处理图像时总是使用threshold?我是否在不使用它时错过了图像处理中的一些重要步骤,即使图像之前被阈值化,它是否能提供更好的结果,或者只是你的习惯?
    • 在多张图像上进行了测试,考虑到边框粗细不同,并且某些字符(尤其是 1)也被部分或全部擦除,因此无法可靠地工作。有没有办法喜欢,抓住盒子里的内容减去边框,或者得到它的边界框?因为我有一些过滤器,我想如果剩下一些最小的噪音就可以了。
    • Thresholding 给出一个二值图像,这意味着黑色的像素值为 0,白色的像素值为 255,它允许分割图像的所需部分。另一种获得二值图像的方法是使用 Canny 边缘检测。在这种情况下,阈值不是必要的,但我认为它是管道中的一个很好的步骤。您可以在 cv2.drawcontours 中降低厚度参数,9 相当多。每个矩形的边界框是用来绘制蒙版的,所以边界框是在中间步骤中获得的
    • 还有另一种方法,一旦你得到掩码(在掩码图像上绘制的框),创建一个水平内核,然后 dilate/morph_close 将数字合并到一个轮廓中。从那里,找到轮廓并使用轮廓区域进行过滤。如果数字没有接触到盒子,这将保证工作,但如果它们接触,这种方法会遇到问题。我仍然建议修改绘制轮廓的厚度并使用当前的垂直/水平线去除方法。第三种方法是使用轮廓层次结构,但同样会遇到触摸框的问题
    • 是的,您可以修改水平/垂直内核大小以或多或少地删除。检查更新的代码,适用于您的新图像
    猜你喜欢
    • 2020-06-14
    • 2012-11-15
    • 2016-04-27
    • 2019-01-25
    • 2020-07-22
    • 2022-01-23
    • 2020-05-08
    • 2020-03-14
    • 2016-09-25
    相关资源
    最近更新 更多