How to find the cordinates of the lines in this image?

301 Views Asked by At

I am new to OpenCV and I would like to know how to find the coordinates of vertical lines in an image. I do have a sample image

image

(This image is a row of a table. Since the data is sensitive i cannot display the info inside these cells.)

There are 4 vertical lines and i like to have the coordinates of each lines.

What I am aiming to do is to identify the vertical lines and cut each cells between these vertical lines.

If i get the coordinates of the lines then it is easy to identify these cells

This is what I have tried:

file_path = 'unnamed.jpg'


img = Image.open(file_path)
width = img.width
height = img.height

# Get table list
document_img = cv2.imread(file_path)
table_list = [np.array(document_img, copy=True)]

# Identifying the Vertical lines in the image
img = cv2.cvtColor(table_list[0], cv2.COLOR_BGR2GRAY)
print("img \n")
cv2_imshow(img)
img_height, img_width = img.shape
thresh, img_bin = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY)
print("img_bin \n")
cv2_imshow(img_bin)
img_bin_inv = 255 - img_bin
print("img_bin_inv \n")
cv2_imshow(img_bin_inv)
kernel_len_ver = max(10, img_height // 50)

ver_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_len_ver))  # shape (kernel_len, 1) inverted! xD
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
# Use vertical kernel to detect and save the vertical lines in a jpg
image_1 = cv2.erode(img_bin_inv, ver_kernel, iterations=3)
print("image_1 \n")
cv2_imshow(image_1)
vertical_lines = cv2.dilate(image_1, ver_kernel, iterations=4)
print("vertical_lines \n")
cv2_imshow(vertical_lines)
# print("\n\n\n")
Image.fromarray(vertical_lines, mode='L').save('vertical_lines.jpg')
file_path = 'unnamed.jpg'


img = Image.open(file_path)
width = img.width
height = img.height

# Get table list
document_img = cv2.imread(file_path)
table_list = [np.array(document_img, copy=True)]

# Identifying the Vertical lines in the image
img = cv2.cvtColor(table_list[0], cv2.COLOR_BGR2GRAY)
print("img \n")
cv2_imshow(img)
img_height, img_width = img.shape
thresh, img_bin = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY)
print("img_bin \n")
cv2_imshow(img_bin)
img_bin_inv = 255 - img_bin
print("img_bin_inv \n")
cv2_imshow(img_bin_inv)
kernel_len_ver = max(10, img_height // 50)

ver_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_len_ver))  # shape (kernel_len, 1) inverted! xD
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
# Use vertical kernel to detect and save the vertical lines in a jpg
image_1 = cv2.erode(img_bin_inv, ver_kernel, iterations=3)
print("image_1 \n")
cv2_imshow(image_1)
vertical_lines = cv2.dilate(image_1, ver_kernel, iterations=4)
print("vertical_lines \n")
cv2_imshow(vertical_lines)
# print("\n\n\n")
Image.fromarray(vertical_lines, mode='L').save('vertical_lines.jpg')

img = cv2.imread('vertical_lines.jpg')

# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Apply a threshold to the grayscale image
ret, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)

# Apply a morphological operation to close gaps in the lines
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)

# Find the contours in the binary image
contours, hierarchy = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Initialize a list to store the coordinates of the vertical lines
vertical_lines = []

# Iterate through each contour and extract the coordinates of the bounding rectangle if it's a vertical line
for contour in contours:
    x,y,w,h = cv2.boundingRect(contour)
    vertical_lines.append((x,y,x+w,y+h))

vertical_lines = sorted(vertical_lines)

img2 = cv2.imread('unnamed.jpg')
count = 0

for i in range(len(vertical_lines)):
  if i < len(vertical_lines) - 1:
    count = count + 1
    roi = img2[0:width, vertical_lines[i][0]:vertical_lines[i+1][0]]
    cv2.imwrite("/content/roi/roi_"+ str(count) +".jpg", roi)

Using this code i only get thick black vertical lines. Not the lines that i mentioned in the image.

1

There are 1 best solutions below

1
ProfDFrancis On

If you know your image will look like that

For example, you may know that the lines will be vertical, black and of the same height, and there will be no other items in the image.

In this case you don't need OpenCV. You could do this:

  • Convert the image to a Numpy array
  • Calculate array D, the differences between each pixel and the pixel to the left (putting 0 for the leftmost pixel of each row)
  • Sum D vertically, to obtain a single row of numbers. The 4 lowest values in D should be the X coordinates of your lines. (You may need to do a little extra work to prevent double-recognition of single lines if the brightness varies gradually)
  • Sum D horizontally, to obtain a single column of numbers. The result will have a long series of very low values in the middle, surrounded by very high values at the start and end. The start and end of the middle zone give you the Y1 and Y2 values (shared between all the line segments).

If what you have drawn is only a simulation of what you will be reading

For example:

  • Your real image will be a photo
  • The lines may be not quite vertical
  • They may be not quite straight
  • You don't know what colour they will be
  • There may be other things, e.g. handwriting in the boxes

Then you should use OpenCV. Read up on the Hough transform, which will enable you to find the orientation and position of the major "lines" on an image. You can limit the Hough transform to look for lines that are close to vertical.

It will give you the horizontal position, but you will have to find the vertical ends of the lines yourself.

https://opencv24-python-tutorials.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html

Example OpenCV code

import cv2
import numpy as np
image = cv2.imread('image.jpg')
greyscaled_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

_, thresholded_image = cv2.threshold(greyscaled_image, 127, 255, cv2.THRESH_BINARY)

# Detect the lines
lines = cv2.HoughLinesP(
  thresholded_image, 
  rho=1, 
  theta=np.pi/180, 
  threshold=100, 
  minLineLength=10,  
  maxLineGap=10
)

# Filter the detected lines to keep only lines that are:
#    - closer to vertical than horizontal (you might want to be stricter than this)
#    - at least 50 pixels long (you will want to tweak this)

vertical_lines = []
for line in lines:
    x1, y1, x2, y2 = line[0]
    if abs(x2 - x1) < abs(y2 - y1) and abs(y2 - y1) >= 50:
        vertical_lines.append(line)

print(vertical_lines)