Search code examples
pythonopencvcomputer-visionbounding-box

Python & OpenCV: How to crop half-formed bounding boxes


I have a script that creates gridlines for a gridless table:

Before script:

enter image description here

After script:

enter image description here

Is there a simple way, using OpenCV, to crop the "after script" image so that it includes only the four-sided bounding boxes? Sample output:

enter image description here

EDIT:

I'm currently working on a solution that finds the first/last all-black pixel line going vertically/horizontally. It'll work, but was wondering whether there's something more elegant.


Solution

  • Here is one way to do that in Python/OpenCV by getting the min and max x and y from all the contours except the largest.

    Input:

    enter image description here

    import cv2
    import numpy as np
    
    # read image
    img = cv2.imread('test_table.png')
    hh, ww = img.shape[:2]
    
    # convert to gray
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # threshold
    thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)[1]
    
    # crop 1 pixel and add 1 pixel white border to ensure outer white regions not considered small contours
    thresh = thresh[1:hh-1, 1:ww-1]
    thresh = cv2.copyMakeBorder(thresh, 1,1,1,1, borderType=cv2.BORDER_CONSTANT, value=(255,255,255))
    
    # get contours
    contours = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    big_contour = max(contours, key=cv2.contourArea)
    
    # get min and max x and y from all bounding boxes larger than half the image size
    area_thresh = hh * ww / 2
    xmin = ww
    ymin = hh
    xmax = 0
    ymax = 0
    
    for cntr in contours:
        area = cv2.contourArea(cntr)
        if area < area_thresh:
            x,y,w,h = cv2.boundingRect(cntr)
            xmin = x if (x < xmin) else xmin
            ymin = y if (y < ymin) else ymin
            xmax = x+w-1 if (x+w-1 > xmax ) else xmax
            ymax = y+h-1 if (y+h-1 > ymax) else ymax
    
    
    # draw bounding box     
    bbox = img.copy()
    cv2.rectangle(bbox, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)
    
    # crop img at bounding box, but add 2 all around to keep the black lines
    result = img[ymin-3:ymax+3, xmin-3:xmax+3]
    
    # save results
    cv2.imwrite('test_table_bbox.png',bbox)
    cv2.imwrite('test_table_trimmed.png',result)
    
    # show results
    cv2.imshow("thresh", thresh)
    cv2.imshow("bbox", bbox)
    cv2.imshow("result", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Bounding Box of all the bounding boxes on input:

    enter image description here

    Trimmed Image:

    enter image description here