pythonopencvimage-processingcomputer-visionbounding-box

Draw grid on a gridless (fully or partially) table image


I have an image that contains a table, the table can be in many sizes and the image too, and the table can be fully gridded (with only some blank spot that needs to be filled), it can be with only vertical grid lines and can be only with horizontal grid lines.

I've searched the web for a long time and found no solution that worked for me.

I found the following questions that seem to be suitable for me:

My code is taken from the answers to the above questions and the "best" result I got from the above question codes is that it drew 2 lines one at the rightmost part and one on the leftmost part.

I'm kind of new to OpenCV and the image processing field so I am not sure how can I fix the above questions codes to suit my needs or how to accomplish my needs exactly, I would appreciate any help you can provide.

Example of an image table:

example of a table with not fully grid

Update:

To remove the horizontal lines I use exactly the code you can find in here, but the result I get on the example image is this:

trying to remove horizontal

as you can see it removed most of them but not all of them, and then when I try to apply the same for the vertical ones (I tried the same code with rotation, or flipping the kernel) it does not work at all...

I also tried this code but it didn't work at all also.

Update 2:

I was able to remove the lines using this code:

def removeLines(result, axis) -> np.ndarray:
    img = result.copy()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    if axis == "horizontal":
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 25))
    elif axis == "vertical":
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 1))
    else:
        raise ValueError("Axis must be either 'horizontal' or 'vertical'")
    detected_lines = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
    cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    result = img.copy()
    for c in cnts:
        cv2.drawContours(result, [c], -1, (255, 255, 255), 2)
    return result

gridless = removeLines(removeLines(cv2.imread(image_path), 'horizontal'), 'vertical')

Result: new gridless

Problem: After I remove lines, when I try to draw the vertical lines using this code:

# read image
img = old_image.copy() # cv2.imread(image_path1)
hh, ww = img.shape[:2]
# convert to grayscale 
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# average gray image to one column
column = cv2.resize(gray, (ww,1), interpolation = cv2.INTER_AREA)
# threshold on white
thresh = cv2.threshold(column, 248, 255, cv2.THRESH_BINARY)[1]
# get contours
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
# Draw vertical
for cntr in contours_v:
    x,y,w,h = cv2.boundingRect(cntr)
    xcenter = x+w//2
    cv2.line(original_image, (xcenter,0), (xcenter,hh-1), (0, 0, 0), 1)

I get this result: Vertical lines problem

Update 3:

when I try even thresh = cv2.threshold(column, 254, 255, cv2.THRESH_BINARY)[1] (I tried lowering it 1 by 1 until 245, for both the max value and the threshold value, each time I get a different or similar result but always too much lines or too less lines) I get the following:

Input: new gridless

Output: too many lines grid vertical

It's putting too many lines instead of just 1 line in each column

Code:

# read image
img = old_image.copy() # cv2.imread(image_path1)
hh, ww = img.shape[:2]
# convert to grayscale 
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# average gray image to one column
column = cv2.resize(gray, (ww, 1), interpolation = cv2.INTER_AREA)
# threshold on white
thresh = cv2.threshold(column, 254, 255, cv2.THRESH_BINARY)[1]
# get contours
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
    x, y, w, h = cv2.boundingRect(cntr)
    xcenter = x + w // 2
    cv2.line(original_image, (xcenter,0), (xcenter, hh_-1), (0, 0, 0), 1)

Solution

  • Here is one way to get the lines in Python/OpenCV. Average the image down to 1 column. Then threshold and get the contours. Then get the bounding boxes and find the vertical centers. Draw lines at those places.

    If you do not want the extra lines, crop your image first to get the inside of the table.

    Input:

    enter image description here

    import cv2
    import numpy as np
    
    # read image
    img = cv2.imread("table4.png")
    hh, ww = img.shape[:2]
    
    # convert to grayscale 
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # average gray image to one column
    column = cv2.resize(gray, (1,hh), interpolation = cv2.INTER_AREA)
    
    # threshold on white
    thresh = cv2.threshold(column, 248, 255, cv2.THRESH_BINARY)[1]
    
    # get contours
    contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    
    # loop over contours and get bounding boxes and ycenter and draw horizontal line at ycenter
    result = img.copy()
    for cntr in contours:
        x,y,w,h = cv2.boundingRect(cntr)
        ycenter = y+h//2
        cv2.line(result, (0,ycenter), (ww-1,ycenter), (0, 0, 255), 1)
    
    # write results
    cv2.imwrite("table4_lines3.png", result)
    
    # display results
    cv2.imshow("RESULT", result)
    cv2.waitKey(0)
    

    Result:

    enter image description here