2

I have to get the contents of a table image using python and OpenCV.

Image is as follows, Lecturer detail table with lecturer name, subject code:

I need to get the text of the each row. For example:

Table row image

My current implementation (up to splitting the rows of the table) is as follows:

import cv2
import numpy as np

cropped_Image_Location = "/home/shakya/Desktop/Paramore/CM_bot/timeTableDetails/Cropped/"
segmentCount = 0

img = cv2.imread(cropped_Image_Location+"cropped_5.jpg")

edges = cv2.Canny(img,50,150,apertureSize = 3)
cv2.imwrite('edges-50-150.jpg',edges)
minLineLength = 100
lines = cv2.HoughLinesP(image=edges, rho=1, theta=np.pi/10, threshold=200, lines=np.array([]), minLineLength= minLineLength, maxLineGap=100)

a,b,c = lines.shape
for i in range(a):
    cv2.line(img, (lines[i][0][0], lines[i][0][1]), (lines[i][0][2], lines[i][0][3]), (0, 0, 255), 3, cv2.LINE_AA)

small = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
grad = cv2.morphologyEx(small, cv2.MORPH_GRADIENT, kernel)

_, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
# using RETR_EXTERNAL instead of RETR_CCOMP
_,contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

mask = np.zeros(bw.shape, dtype=np.uint8)

for idx in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[idx])
    mask[y:y+h, x:x+w] = 0
    cv2.drawContours(mask, contours, idx, (255, 255, 255), -1)
    r = float(cv2.countNonZero(mask[y:y+h, x:x+w])) / (w * h)

    if r > 0.45 and w > 8 and h > 8:
        cv2.rectangle(small, (x, y), (x+w-1, y+h-1), (0, 255, 0), 1)
        crop_img = small[y:y + h, x:x + w]
        segmentCount = segmentCount + 1
        imageNumber = str(segmentCount)

        cv2.imwrite(cropped_Image_Location+"Lecturer_cropped_" + imageNumber+".jpg", crop_img)

cv2.imwrite(cropped_Image_Location+'lectureAll.jpg', small)

cv2.waitKey(0)
cv2.destroyAllWindows()

I'm stuck with splitting the cells of a row.

glennsl
  • 28,186
  • 12
  • 57
  • 75
Shakya RDN
  • 21
  • 1
  • 6
  • 1
    Next step is OCR right? This tutorial has a successful implementation https://www.pyimagesearch.com/2017/07/17/credit-card-ocr-with-opencv-and-python/ – Nishan Apr 09 '18 at 16:41
  • If the cells of the table in the image are fixed, then just crop by coords is ok. – Kinght 金 Apr 10 '18 at 12:49
  • No it's not. The cells are getting changed with the details which are inside of the cell. – Shakya RDN Apr 10 '18 at 15:13
  • 1
    Let me know if you have found a solution I need exactly same functionality to crop rows from the table. – PiotrK Sep 24 '18 at 14:34
  • Shakya RDN, @PiotrK, I am also in similar situation :D Were you able to find the solution? If so, it would be really appreciative, if you could share the solution. – explorer Nov 21 '18 at 07:28
  • @ShakyaRDN yes I got this and text recognition you can find full code here https://github.com/PiotrKrosniak/ocrbot – PiotrK Dec 10 '18 at 14:30

2 Answers2

1

The goal is to find horizontal lines whose length is greater than a certain threshold. After finding horizontal lines you can find (x,y) position of lines to crop the rows. you can process the image like this

import numpy as np
import cv2

img = cv2.imread('D:\Libraries\Downloads\Capture.PNG')
# find edges in the image
edges = cv2.Laplacian(img, cv2.CV_8U)
# kernel used to remove vetical and small horizontal lines using erosion
kernel = np.zeros((5, 11), np.uint8)
kernel[2, :] = 1
eroded = cv2.morphologyEx(edges, cv2.MORPH_ERODE,
                          kernel)  # erode image to remove unwanted lines

# find (x,y) position of the horizontal lines
indices = np.nonzero(eroded)
# As indices contain all the points along horizontal line, so get unique rows only (indices[0] contains rows or y coordinate)
rows = np.unique(indices[0])
# now you have unique rows but edges are more than 1 pixel thick
# so remove lines which are near to each other using a certain threshold
filtered_rows = []
for ii in range(len(rows)):
    if ii == 0:
        filtered_rows.append(rows[ii])
    else:
        if np.abs(rows[ii] - rows[ii - 1]) >= 10:
            filtered_rows.append(rows[ii])

print(filtered_rows)
# crop first row of table
first_cropped_row = img[filtered_rows[0]:filtered_rows[1], :, :]

cv2.imshow('Image', eroded)
cv2.imshow('Cropped_Row', first_cropped_row)
cv2.waitKey(0)

you can use filtered_rows to crop the rows of table

UPDATE: working cod as of python 3.6.8 - fixed based on http://answers.opencv.org/question/198043/problem-using-morphologyex/?answer=198052#post-id-198052

Daniel Selvan
  • 959
  • 10
  • 23
user8190410
  • 1,264
  • 2
  • 8
  • 15
  • 1
    Some kind of explanation to go along with the uncommented code would make this answer much better. – Dan Mašek Apr 09 '18 at 19:52
  • Maybe you can try HoughLines method. https://stackoverflow.com/questions/19054055/python-cv2-houghlines-grid-line-detection – Nishan Apr 10 '18 at 11:49
  • @user8190410, Could you tell me, why kernel is of size `(5,11)`? – explorer Nov 21 '18 at 08:50
  • @user8190410, By the way, your solution worked for me. Thank you! – explorer Nov 21 '18 at 09:42
  • @user8190410, Another question, how do you use the same algorithm for vertical lines? – explorer Nov 21 '18 at 10:51
  • @explorer `(5,11)` is used according to the problem as above. Infact, it should be `(1,11)` as only 3rd row consists of 1's. – user8190410 Nov 21 '18 at 12:43
  • @explorer to use it for vertical lines, find unique column indices i.e. `cols = np.unique(indices[1])` – user8190410 Nov 21 '18 at 12:45
  • @explorer Also your kernel will become `(11,1)` because now you have to erode vertical lines – user8190410 Nov 21 '18 at 12:49
  • @user8190410 Thanks for your replies. Earlier, I figured that, ```kernel = np.zeros((5,11),dtype=int) kernel[2,:] = 1``` will create a matrix with 3rd row as ones, which will be used to match the horizontal lines (& then erode/dilate). Is that correct? Based on that, I created kernel for vertical lines as you suggested, which seems to work as expected. – explorer Nov 22 '18 at 04:38
0

first you identify all of the boxes by using contours.

https://docs.opencv.org/3.3.1/d4/d73/tutorial_py_contours_begin.html

then get the moment of each of box.

https://docs.opencv.org/3.1.0/dd/d49/tutorial_py_contour_features.html

then you can identify the row. moment represent the middle pixel of the contour area. doc says central of mass of the object

then check same contours with same x values. or in small range (you decide)

combine the ROIs togather. you will have the row.