2

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)
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Dolev Mitz
  • 103
  • 14
  • Please show your code. What grid lines do you want to add -- the 3 horizontal ones? Do you have a higher resolution version (larger scale)? – fmw42 Feb 14 '23 at 17:25
  • Hey @fmw42 as I said this image is just an example, I need a solution that gets as input an image with a table on it (the image can have only vertical lines, only horizontal, only some of them, or none at all) and it will return the image with all the lines drawn on the proper place. the code I've used is the same as the examples links I provided. – Dolev Mitz Feb 16 '23 at 11:38
  • And also like I said, the tables can be in every different sizes and resolutions (the example I gave is usually the worst resolution I'll get) – Dolev Mitz Feb 16 '23 at 14:08
  • Like I said below. Then remove all the lines with morphology and then redraw them all. Search this forum or Google for removing horizontal or vertical lines. – fmw42 Feb 16 '23 at 16:57

2 Answers2

0

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

fmw42
  • 46,825
  • 10
  • 62
  • 80
  • This solution is working great for the horizontal place only, I need it to also work if vertical lines are missing or if all are missing (in that case it will draw both horizontal and vertical ones). plus, I will appreciate if you could explain what you did in your code, I'm new to this and I would love to understand it better – Dolev Mitz Feb 16 '23 at 11:54
  • plus I need somehow that if there is a line already, either cut it or draw over it, but not draw around it because it messes up my algorithm – Dolev Mitz Feb 16 '23 at 12:07
  • Remove the horizontal lines and then redraw them all. You can remove them with morphology – fmw42 Feb 16 '23 at 16:52
  • The explanation is in the comments in my answer. The basic idea is the find the nearly white horizontal regions by thresholding. The threshold will be near white, but somewhat less if you have vertical dark lines. Once you have a good threshold, you can find contours, their bounding boxes and the vertical centers of the bounding boxes. For vertical lines, do the same, but get the horizontal centers of the bounding boxes. – fmw42 Feb 16 '23 at 16:55
  • I can just hold the horizontal `contours` and draw them later instead of removing them, but how can I find and draw the vertical lines I didn't manage to understand (I tried changing to center and using the width but without any luck) – Dolev Mitz Feb 16 '23 at 17:31
  • Your image already has vertical lines. If you have a table that does not and you need to add them, then you need a different script that is the same as above, but needs a different threshold possibly and you need to change `ycenter = y+h//2` to `xcenter = x+w//2` for the contour bounding box center and draw the vertical lines in the cv2.line statement that follows. Also above you need to average to one row rather than one column. That is in cv2.resze, change `(1,hh)`to `(ww,1)` – fmw42 Feb 16 '23 at 18:37
  • As I said this image is just an example, I can have horizontal lines, vertical, none, part, and more. I'll try to use your code in order to make a code that covers all of the options (this is what I needed since start), though I'm not sure how and which thresholds to use – Dolev Mitz Feb 16 '23 at 19:15
  • Ok, I was able to draw either horizontal lines or vertical lines, but I am unable to remove either one of them (can't remove horizontal and can't remove vertical lines), any idea how to do it? (I tried a few options from the internet, but none of them worked well enough), for the example image I will need first to remove the horizontal lines so it wont draw twice beside it as it does for example – Dolev Mitz Feb 16 '23 at 19:54
  • Please post your code for removing the lines – fmw42 Feb 16 '23 at 23:20
  • There is only one cv2.threshold() command in my script. – fmw42 Feb 16 '23 at 23:21
  • See for example, https://www.appsloveworld.com/opencv/100/1/removing-horizontal-lines-in-image-opencv-python-matplotlib and https://www.tutorialspoint.com/removing-horizontal-lines-in-image-opencv-python-matplotlib – fmw42 Feb 16 '23 at 23:28
  • I added an update, I use the code you suggested but it did not work for me properly, see update – Dolev Mitz Feb 17 '23 at 14:31
  • @DolevMiz In order to draw the vertical and horizontal you can use @fmw42 code but the part where he draw the lines keeps for after (the `cv2.line` loop for each direction (you can flip the direction as he explained to you in the comments) and after you detected and have `contours_horizontal` and `contours_vertical` you can use a for loop with center and center in order to draw them) – Lidor Eliyahu Shelef Feb 17 '23 at 15:08
  • Adjust your threshold for finding your lines and perhaps the length of the horizontal kernel. – fmw42 Feb 17 '23 at 16:18
0

You wrote that you tried to remove the lines using the code, but it did not work.

It works fine for me in Python/OpenCV.

  • Read the input
  • Convert to grayscale
  • Threshold to show the horizontal lines
  • Apply morphology open with a horizontal kernel to isolate the horizontal lines
  • Get their contours
  • Draw the contours on a copy of the input as white to cover over the black horizontal lines
  • Save the results

Input:

enter image description here

import cv2
import numpy as np

# read the input 
img = cv2.imread('table4.png')

# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# do morphology to detect lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25,1))
detected_lines = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)

# get contours
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

# draw contours as white on copy of input
result = img.copy()
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 2)

# save results
cv2.imwrite('table4_horizontal_lines_threshold.png', thresh)
cv2.imwrite('table4_horizontal_lines_detected.png', detected_lines)
cv2.imwrite('table4_horizontal_lines_removed.png', result)

# show results
cv2.imshow('thresh', thresh)
cv2.imshow('morphology', detected_lines)
cv2.imshow('result', result)
cv2.waitKey(0)

Threshold Image:

enter image description here

Morphology Detected Lines Image:

enter image description here

Result:

enter image description here

General Grievance
  • 4,555
  • 31
  • 31
  • 45
fmw42
  • 46,825
  • 10
  • 62
  • 80
  • I think his problem was that he used it on the image after drawing the lines so it deleted the line he drew but not the original one near it. – Lidor Eliyahu Shelef Feb 18 '23 at 11:07
  • @LidorEliyahuShelef yes you are right. I added an "update 2" please if you both can look and help I will appreciate it a lot – Dolev Mitz Feb 18 '23 at 12:06
  • Since you now have pure white columns and do not need to take into account the horizontal black lines, raise the threshold value to 254. If you do not get enough lines, then lower it to 253 and try again. Keep lowering it until you get a reasonable number of vertical lines. – fmw42 Feb 18 '23 at 17:03
  • I tried it, the problem is there are too many lines, look at update 3 (I now will add it) – Dolev Mitz Feb 19 '23 at 08:33
  • You did not remove the top and bottom lines for one thing. Also adjust the threshold until you get the right number of lines. Show your full code for update 3 results. – fmw42 Feb 19 '23 at 17:10
  • there is no line at the top and bottom, it just the end of the image, and I added the full code for update number 3 – Dolev Mitz Feb 19 '23 at 17:44
  • Did you try reducing the threshold to 253. If that does not work, then 252, etc until you get a good result? Show your input so that I can test with it, if needed – fmw42 Feb 19 '23 at 18:56
  • Yes I tried, I also tried more lowering of both, I added what I tried in text and input-output image – Dolev Mitz Feb 19 '23 at 19:23
  • You did not remove the top and bottom line or you are showing the wrong input image. Keep playing with the threshold or remove the top and bottom lines. Sorry, I am having trouble with Python OpenCV after trying to upgrade them. So I cannot test your code at the moment. – fmw42 Feb 19 '23 at 20:00
  • These are my input and output images, in both of them I can't see the top and bottom line you're talking about... I keep trying to change the threshold and even changing the threshold approach but still, nothing works properly, I will keep trying to figure this out after you will resolve your problem I will highly appreciate if you'll be able to assist me some more please :) – Dolev Mitz Feb 20 '23 at 08:28
  • Remove the top and bottom line, then try threshold=254 and reduce until it works. – fmw42 Feb 20 '23 at 17:09
  • I removed them plus extra space I removed and now the horizontal lines are drawn on top of the text, vertical are almost perfect still need a few more tweaks – Dolev Mitz Feb 21 '23 at 10:23
  • If I change `ycenter = y+h//2` to `ycenter = y+h + 2` then it draws good horizontal lines, but the vertical ones are still a mess. – Dolev Mitz Feb 21 '23 at 12:16
  • If you want comments, you need to show your input output and intermediate images along with your code. Post a new problem as this one is getting too complicated. – fmw42 Feb 21 '23 at 17:24
  • @fmw42 you mean in a new question or another update? – Dolev Mitz Feb 22 '23 at 04:40
  • New question. This one is getting too many comments – fmw42 Feb 22 '23 at 04:57
  • @fmw42 I just did, here is the link and thank you so much for the help :) https://stackoverflow.com/questions/75528488/table-without-a-grid-draw-horizontal-and-vertical-grid-lines – Dolev Mitz Feb 22 '23 at 05:08