How to join nearby bounding boxes in OpenCV Python

后端 未结 2 1638
别跟我提以往
别跟我提以往 2021-01-05 06:05

I am doing a college class project on image processing. This is my original image: \"enter

相关标签:
2条回答
  • 2021-01-05 07:03

    So, here comes my solution. I partially modified your (initial) code to my preferred naming, etc. Also, I commented all the stuff, I added.

    import cv2
    import numpy as np
    
    image = cv2.imread('images/example.png')
    
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    
    kernel = np.ones((5, 5), np.uint8)
    img_dilated = cv2.dilate(thresh, kernel, iterations = 1)
    
    cnts, _ = cv2.findContours(img_dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # Array of initial bounding rects
    rects = []
    
    # Bool array indicating which initial bounding rect has
    # already been used
    rectsUsed = []
    
    # Just initialize bounding rects and set all bools to false
    for cnt in cnts:
        rects.append(cv2.boundingRect(cnt))
        rectsUsed.append(False)
    
    # Sort bounding rects by x coordinate
    def getXFromRect(item):
        return item[0]
    
    rects.sort(key = getXFromRect)
    
    # Array of accepted rects
    acceptedRects = []
    
    # Merge threshold for x coordinate distance
    xThr = 5
    
    # Iterate all initial bounding rects
    for supIdx, supVal in enumerate(rects):
        if (rectsUsed[supIdx] == False):
    
            # Initialize current rect
            currxMin = supVal[0]
            currxMax = supVal[0] + supVal[2]
            curryMin = supVal[1]
            curryMax = supVal[1] + supVal[3]
    
            # This bounding rect is used
            rectsUsed[supIdx] = True
    
            # Iterate all initial bounding rects
            # starting from the next
            for subIdx, subVal in enumerate(rects[(supIdx+1):], start = (supIdx+1)):
    
                # Initialize merge candidate
                candxMin = subVal[0]
                candxMax = subVal[0] + subVal[2]
                candyMin = subVal[1]
                candyMax = subVal[1] + subVal[3]
    
                # Check if x distance between current rect
                # and merge candidate is small enough
                if (candxMin <= currxMax + xThr):
    
                    # Reset coordinates of current rect
                    currxMax = candxMax
                    curryMin = min(curryMin, candyMin)
                    curryMax = max(curryMax, candyMax)
    
                    # Merge candidate (bounding rect) is used
                    rectsUsed[subIdx] = True
                else:
                    break
    
            # No more merge candidates possible, accept current rect
            acceptedRects.append([currxMin, curryMin, currxMax - currxMin, curryMax - curryMin])
    
    for rect in acceptedRects:
        img = cv2.rectangle(image, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), (121, 11, 189), 2)
    
    cv2.imwrite("images/result.png", image)
    

    For your example

    I get the following output

    Now, you have to find a proper threshold to meet your expectations. Maybe, there is even some more work to do, especially to get the whole formula, since the distances don't vary that much.

    Disclaimer: I'm new to Python in general, and specially to the Python API of OpenCV (C++ for the win). Comments, improvements, highlighting Python no-gos are highly welcome!

    0 讨论(0)
  • 2021-01-05 07:05

    Here is a slightly different approach, using the OpenCV Wrapper library.

    import cv2
    import opencv_wrapper as cvw
    
    image = cv2.imread("example.png")
    
    gray = cvw.bgr2gray(image)
    thresh = cvw.threshold_otsu(gray, inverse=True)
    
    # dilation
    img_dilation = cvw.dilate(thresh, 5)
    
    # Find contours
    contours = cvw.find_external_contours(img_dilation)
    # Map contours to bounding rectangles, using bounding_rect property
    rects = map(lambda c: c.bounding_rect, contours)
    # Sort rects by top-left x (rect.x == rect.tl.x)
    sorted_rects = sorted(rects, key=lambda r: r.x)
    
    # Distance threshold
    dt = 5
    
    # List of final, joined rectangles
    final_rects = [sorted_rects[0]]
    
    for rect in sorted_rects[1:]:
        prev_rect = final_rects[-1]
    
        # Shift rectangle `dt` back, to find out if they overlap
        shifted_rect = cvw.Rect(rect.tl.x - dt, rect.tl.y, rect.width, rect.height)
        intersection = cvw.rect_intersection(prev_rect, shifted_rect)
        if intersection is not None:
            # Join the two rectangles
            min_y = min((prev_rect.tl.y, rect.tl.y))
            max_y = max((prev_rect.bl.y, rect.bl.y))
            max_x = max((prev_rect.br.x, rect.br.x))
            width = max_x - prev_rect.tl.x
            height = max_y - min_y
            new_rect = cvw.Rect(prev_rect.tl.x, min_y, width, height)
            # Add new rectangle to final list, making it the new prev_rect
            # in the next iteration
            final_rects[-1] = new_rect
        else:
            # If no intersection, add the box
            final_rects.append(rect)
    
    for rect in sorted_rects:
        cvw.rectangle(image, rect, cvw.Color.MAGENTA, line_style=cvw.LineStyle.DASHED)
    
    for rect in final_rects:
        cvw.rectangle(image, rect, cvw.Color.GREEN, thickness=2)
    
    cv2.imwrite("result.png", image)
    

    And the result

    The green boxes are the final result, while the magenta boxes are the original ones.

    I used the same threshold as @HansHirse.

    The equals sign still needs some work. Either a higher dilation kernel size or use the same technique vertically.

    Disclosure: I am the author of OpenCV Wrapper.

    0 讨论(0)
提交回复
热议问题