如何从 cv2.findContours 结果中重新创建带有孔的原始图像?

Posted

技术标签:

【中文标题】如何从 cv2.findContours 结果中重新创建带有孔的原始图像?【英文标题】:How can I recreate original image from cv2.findContours result with holes? 【发布时间】:2022-01-09 10:45:31 【问题描述】:

我正在尝试使用 OpenCV 检测二进制图像中的轮廓,然后绘制生成的轮廓多边形以重新创建输入图像。但是,返回 OpenCV 轮廓多边形的表示方式并不容易。

首先,让我们设置数据:

import cv2
import numpy as np


def a_small_hole_with_diagonal_border() -> np.ndarray:
    bitmask = np.zeros((10, 10), dtype=np.uint8) + 255
    indices = [(2, 2), (2, 3), (2, 4), (2, 5), (2, 6),
               (3, 4), (3, 5), (3, 6),
               (4, 4), (4, 5), (4, 6)]
    row_indices, col_indices = zip(*indices)
    bitmask[row_indices, col_indices] = 0
    bitmask[5:, 7:] = 0
    return bitmask


bitmask = a_small_hole_with_diagonal_border()
padded_bitmask = np.zeros((bitmask.shape[0] + 2, bitmask.shape[1] + 2), dtype=bitmask.dtype)
padded_bitmask[1:-1, 1:-1] = bitmask

这张图是这样的(这些只是matplotlib的截图):

现在我正在运行 findContours 并绘制结果轮廓:

contours, hierarchy = cv2.findContours(image=padded_bitmask,
                                       mode=cv2.RETR_TREE,
                                       method=cv2.CHAIN_APPROX_SIMPLE)

def get_bitmask(*polygons: np.ndarray, width: int, height: int) -> np.ndarray:
    image = np.zeros((width, height, 3), dtype=np.uint8)
    for polygon in polygons:
        polygon = polygon.reshape((-1, 1, 2)).astype(np.int32)
        cv2.fillPoly(image, pts=[polygon], color=(0, 0, 255))

    return image[..., -1]

get_bitmask(contours[0], 10, 10)
get_bitmask(contours[1], 10, 10)

我得到以下输出:

外围区域的轮廓正是我想要的, 但是对于中间的洞,我想以某种方式勾勒坐标 不代表轮廓的最内边界,而是代表孔的最外边界。 这就是我的意思:

(我手动使用 gimp 将第二个轮廓覆盖在原始图像上)

我想要的是描述孔的这些像素的轮廓(请原谅我的绘画技巧不佳):

所以本质上,如果我在第一个轮廓上绘制第二个轮廓(我正在为此解析树层次结构),我想重新创建输入图像。 我怎样才能做到这一点? 如果我只是按照here 的建议绘制孔的多边形,我会得到错误的结果:

img = np.zeros((10, 10, 3), dtype=np.uint8)
polygon = contours[1].reshape((-1, 1, 2)).astype(np.int32)
cv2.fillPoly(img, polygon, color=(255, 0, 0),)

cv2.CHAIN_APPROX_SIMPLEcv2.CHAIN_APPROX_NONE

我尝试在图像及其逆图像上运行 findContours,然后合并结果,但必须有一种更简单的方法,因为我无法让它在所有情况下都能正常工作,而只需获得正确的表示即可容易很多。

【问题讨论】:

【参考方案1】:

这是一个解决方案,但可能会有更好的解决方案。 反转整个图像很棘手,但您可以反转每个孔的遮罩,然后再次运行 findContours。 然后用该输出替换孔的轮廓。 这是一个从 blob 边界计算孔边界的函数。 对来自问题的数据运行它,如果您放入第二个轮廓,您将获得描述您绘制的数据的多边形。

def _get_hole_contour_from_outer_boundary_contour(boundary_contour: np.ndarray,
                                                  padded_bitmask: np.ndarray) -> np.ndarray:
    # We have the outer boundary of the toplevel blob, but we need the outer boundary of the hole
    # which is the pixels that are enclosed by the boundary_contour
    col, row, width, height = cv2.boundingRect(boundary_contour)
    cutout = padded_bitmask[row:row + height, col:col + width]

    # we invert the pixels so that the black hole now becomes a white blob
    # this way we get the representation we want when we extract contours again
    inverse_cutout = np.abs(255 - cutout)
    # We zero out the first and last row and column, as these are part of the
    # bounding box and not part of what is inside the hole
    # This way we don't have to worry about any other holes potentially overlapping
    # into the bounding box
    inverse_cutout[:, 0] = 0
    inverse_cutout[:, -1] = 0
    inverse_cutout[0, :] = 0
    inverse_cutout[-1, :] = 0

    # Now we can use RETR_EXTERNAL, which only gives us the outer boundaries and does not care about holes
    contours_hole, _ = cv2.findContours(image=inverse_cutout,
                                        mode=cv2.RETR_EXTERNAL,
                                        method=cv2.CHAIN_APPROX_SIMPLE,
                                        offset=(col, row))

    # Since we cut out the area exactly around the hole, the one with the biggest bounding box is
    # always the one we want
    return sorted(contours_hole, key=lambda contour: _area_of_bounding_react(contour=contour), reverse=True)[0]


def _area_of_bounding_react(contour: np.ndarray) -> int:
    col, row, width, height = cv2.boundingRect(contour)
    return (col + width) * (row + height)

【讨论】:

以上是关于如何从 cv2.findContours 结果中重新创建带有孔的原始图像?的主要内容,如果未能解决你的问题,请参考以下文章

调用 cv2.findContours 解包的值太多

【python】opencv库中cv2.findContours()和cv2.drawContours()函数

使用cv2.findContours函数传入参数的数据类型问题

Opencv-python 找到图像轮廓并绘制,cv2.findContours()函数,求轮廓外接矩形,cv2.boundingrect()

python调用cv2.findContours时报错:ValueError: not enough values to unpack (expected 3, got 2)

python怎么识别图片中每个线的基本形状