【问题标题】:cv2.findContours does not find white rectangles on a black backgroundcv2.findContours 在黑色背景上找不到白色矩形
【发布时间】:2021-02-14 11:52:55
【问题描述】:

我是一位经验丰富的程序员,但对 Python 和 OpenCV 的经验有限。我正在裁剪电影帧,尝试使用电影的可见穿孔和边缘作为参考。附加的是单个帧(原始 1920 x 1080)和从该帧派生的掩码,在下面的代码中传递给 findcountours。 FindContours 找到 3 个轮廓,但只有最大的(右帧边界)是正确的,其他两个(驱动器穿孔)未正确检测到。你们哪位好心人能告诉我我做错了什么,并指出我的方向吗?

我正在使用 Python 3.8 和相关版本的 cv2。

这是sample frame 和mask 使用下面的代码派生的。

谢谢

克里斯

for file in args.files:

idx += 1

if os.path.isfile(file):

    # load the input image (whose path was supplied via command line
    # argument) and display the image to our screen
    image = cv2.imread(file)

    if image is None:
        print('Could not open or find the image: ', args["image"])
        fileinput.close()
        exit(0)

    image_clone = image.copy()
    image_height = image.shape[0]
    image_width = image.shape[1]



    """
    Threshold image
    """

    hsv = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2HSV)
    #
    # # define range of white color in HSV
    # # change it according to your need !
    lower_white = np.array([0, 0, 235], dtype=np.uint8)
    upper_white = np.array([0, 0, 255], dtype=np.uint8)
    #
    # Threshold the HSV image to get only white colors
    mask = cv2.inRange(hsv, lower_white, upper_white)

    mask = cv2.copyMakeBorder(mask, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=[0, 0, 0])

    # I have seen some spurious pixels so I filter
    
    se1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, se1, iterations = 5)

    # Make sure I get rid of noise
    mask = cv2.GaussianBlur(mask, (5, 5), 0)

    cny = cv2.Canny(mask.copy(), 255/3, 255)

    mask = cv2.bitwise_xor(mask, cny)

    cv2.imwrite(os.path.splitext(file)[0] + '_mask_' + str(idx) + '.tif', mask)

    dsize = (int(mask.shape[1] * (50 / 100)), int(mask.shape[0] * (50 / 100)))
    # # # # # #
    cv2.imshow("Mask Image", cv2.resize(mask.copy(), dsize))
    cv2.waitKey(0)
    cv2.destroyWindow("Mask Image")

    contours, _ = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    contours = sorted(contours, key=cv2.contourArea, reverse=True)
    #
    new_image = np.zeros((mask.shape[0], mask.shape[1]), np.uint8) * 0
    #
    cv2.drawContours(new_image, [max(contours, key=cv2.contourArea)], -1, 255, thickness=-1)

    dsize = (int(new_image.shape[1] * (50 / 100)), int(new_image.shape[0] * (50 / 100)))
    # # # # # # #
    cv2.imshow("Mask Image", cv2.resize(new_image.copy(), dsize))
    cv2.waitKey(0)
    cv2.destroyWindow("Mask Image")

【问题讨论】:

    标签: python opencv


    【解决方案1】:

    问题在于函数drawContours 的参数。第二个参数包含应该绘制的contours,但您似乎正在过滤较小的轮廓并仅存储/绘制最大的轮廓,在这种情况下,它是框架右侧的轮廓。不要过滤您的contours。尝试像这样计算它们:

    # Find the contours on the mask image:
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    
    # Draw the contours on the mask image:
    cv2.drawContours(maskCopy, contours, -1, (0, 255, 0), 3)
    

    注意第三个参数是-1,这意味着,绘制所有找到的轮廓。这就是你得到的:

    此外,您可能希望将这些轮廓转换为 bounding rectangles,以便轻松裁剪您感兴趣的区域。这就是将轮廓转换为4-vertex polygon 的方法:

    contoursPoly = [None] * len(contours)
    boundRect = []
    
    # Contour to Bounding Rects:
    for i, c in enumerate(contours):
       # Approximate each contour to a bonding rectangle:
       contoursPoly[i] = cv2.approxPolyDP(c, 3, True)
       # Append each bounding rect to the list:
       boundRect.append(cv2.boundingRect(contours_poly[i]))
    
    # Draw the Bounding Rects:
    rectColor = (0, 255, 0)
    for i in range(len(boundRect)):    
        cv2.rectangle(maskCopy, (int(boundRect[i][0]), int(boundRect[i][1])),
                  (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3])), rectColor, 5)
    

    现在注意轮廓是如何近似为矩形的:

    现在,您使用一些 morphology 来获得一个干净的二进制掩码。这实际上相当不错,但是形态学操作会稍微扭曲遮罩的斑点,从而引入位置偏移。查看覆盖在原始图像中的边界矩形:

    看到位置偏移了吗?您可以尝试应用额外的 morphology 来恢复 blob 的原始位置。

    第一种选择:

    不要使用形态学,只考虑外轮廓。外轮廓是没有子(内)轮廓的父轮廓。这将过滤掉您可能遇到的任何噪音:

    # BGR to HSV conversion:
    hsv = cv2.cvtColor(inputImage.copy(), cv2.COLOR_BGR2HSV)
    
    # HSV Range definition:
    lower_white = np.array([0, 0, 235], dtype=np.uint8)
    upper_white = np.array([0, 0, 255], dtype=np.uint8)
    
    # Get the HSV mask:
    mask = cv2.inRange(hsv, lower_white, upper_white)
    
    # Find the big contours/blobs on the filtered image:
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    
    contoursPoly = [None] * len(contours)
    boundRect = []
    
    # Alright, just look for the outer bounding boxes:
    for i, c in enumerate(contours):
        if hierarchy[0][i][3] == -1:
            contoursPoly[i] = cv2.approxPolyDP(c, 3, True)
            boundRect.append(cv2.boundingRect(contoursPoly[i]))
    
    # Draw the bounding boxes on the (copied) input image:
    for i in range(len(boundRect)):
        color = (0, 255, 0)
        cv2.rectangle(inputCopy, (int(boundRect[i][0]), int(boundRect[i][1])),
                  (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3])), color, 5)
    

    你明白了:

    第二选择:

    使用reduce函数将二进制掩码减少为1 x columns矩阵,检测轮廓并提取内部坐标以裁剪框架:

    # Reduce matrix to a 1 row x n columns matrix:
    reducedMask = cv2.reduce(mask, 0, cv2.REDUCE_MAX)
    

    reduce 函数将创建一个1 x columns 矩阵(实际上是一个向量),其中包含所有列的MAX 值。二进制掩码中的 MAX 值 (255) 对应于感兴趣的 blob 的位置,因此,将图像缩小为这个新掩码:

    现在的想法是获取两个白色部分的坐标。这可以通过一种以上的方法来实现。现在让我们坚持寻找轮廓:

    # Find the big contours/blobs on the filtered image:
    contours, hierarchy = cv2.findContours(reducedMask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    
    contoursPoly = [None] * len(contours)
    boundRect = []
    
    # Alright, just look for the outer bounding boxes:
    for i, c in enumerate(contours):
        if hierarchy[0][i][3] == -1:
            contoursPoly[i] = cv2.approxPolyDP(c, 3, True)
            boundRect.append(cv2.boundingRect(contoursPoly[i]))
    

    到目前为止获得的轮廓描述了两个白色部分。我们需要这些线的内部坐标。让我们sort 绑定矩形列表从小到大。列表是一个tuple,每个tuple的第一个元素是top left(或水平)坐标,所以我们对列表排序后有第一个白色部分:

    # Sort rects from smallets to largest:
    sortedRectangles = sorted(boundRect, key = lambda topLeft: topLeft[0])
    
    # Set Top Left crop coordinate:
    startX = sortedRectangles[0][0] + sortedRectangles[0][2]
    
    # Set Lower Right crop coordinate:
    endX = sortedRectangles[1][0]
    

    现在,只需使用以下信息裁剪图像:

    # Crop the roi:
    imageSize = inputCopy.shape
    imageHeight = imageSize[0]
    croppedImg = inputCopy[0:0+imageHeight, startX:endX]
    

    你会得到最终的裁剪图像:

    【讨论】:

    • 哇。 24 小时以来,我一直在用头撞墙,没有意识到问题出在印刷品上,而不是发现问题上。你是一个绅士和学者。我需要更仔细地查看轮廓结构的描述。谢谢你,竖起大拇指。
    • @user2488847 没问题!很高兴我能帮上忙。
    猜你喜欢
    • 2017-04-23
    • 1970-01-01
    • 2019-12-13
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    • 2016-12-18
    相关资源
    最近更新 更多