I have the following ImageMagick command that works very well for removing grid lines from a table image:
convert test.png -background white -deskew 40% -write mpr:img \
\( mpr:img -morphology close rectangle:30x1 -negate \) \
\( mpr:img -morphology close rectangle:1x30 -negate \) \
-evaluate-sequence add \
result.png
I can, of course, invoke this code from my Python script.
However, I would like to be able to translate this command into something pythonic like Wand, PIL, cv2 or whatever.
Namely for portability sake but also to avoid having to save the output.
Even though Wand, for instance, has a morphology function I do not seem to be able to figure out the kernel and so on.
As the real images contain confidential information, I have added a similar mock example. I have verified that the above command produces the desired results of entirely clearing the grid lines without affecting the quality of the text.
Here is a solution in Python/OpenCV.
Note I apply some (1 deg) rotation to your input for testing.
Input:
import cv2
import numpy as np
# read the input
img = cv2.imread('chart.png')
hh, ww = img.shape[:2]
# threshold on gray lines
lower=(190,190,190)
upper=(230,230,230)
thresh = cv2.inRange(img, lower, upper)
# get convex hull
points = np.column_stack(np.where(thresh.transpose() > 0))
hull = cv2.convexHull(points)
# draw convex hull vertices on input image
hull_img = img.copy()
cv2.polylines(hull_img, [hull], True, (0,0,255), 2)
# get rotated bounding box
rotrect = cv2.minAreaRect(hull)
box = cv2.boxPoints(rotrect)
box = np.intp(box)
# draw rotated rectangle on copy of img
rot_bbox = img.copy()
cv2.drawContours(rot_bbox,[box],0,(0,0,255),2)
# get angle relative to horizontal from rotated rectangle
angle = rotrect[-1]
# from https://www.pyimagesearch.com/2017/02/20/text-skew-correction-opencv-python/
# the `cv2.minAreaRect` function returns values in the
# range [-90, 0); as the rectangle rotates clockwise the
# returned angle tends to 0 -- in this special case we
# need to add 90 degrees to the angle
if angle < -45:
angle = -(90 + angle)
# otherwise, just take the negative of the angle to make
# it positive
else:
angle = -angle
print(angle,"deg")
# negate the angle for deskewing
neg_angle = -angle
# Get rotation matrix
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, neg_angle, scale=1.0)
# rotate the image to deskew it
deskewed = cv2.warpAffine(img, M, (ww,hh), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_CONSTANT, borderValue=(255,255,255))
# create morphology close images
img1 = deskewed.copy()
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (30,1))
img1 = cv2.morphologyEx(img1, cv2.MORPH_CLOSE, kernel)
img1 = np.invert(img1)
img2 = deskewed.copy()
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (1,30))
img2 = cv2.morphologyEx(img2, cv2.MORPH_CLOSE, kernel)
img2 = np.invert(img2)
# add input and morphology close images
result = 255*(deskewed.astype("float32")/255 + img1.astype("float32")/255 + img2.astype("float32")/255)
result = result.clip(0,255).astype("uint8")
# save results
cv2.imwrite("chart_threshold.jpg", thresh)
cv2.imwrite("chart_convexhull.jpg", hull_img)
cv2.imwrite("chart_rotated_rectangle.jpg", rot_bbox)
cv2.imwrite("chart_deskewed.jpg", deskewed)
cv2.imwrite("chart_result.png", deskewed)
# show results
cv2.imshow("THRESH", thresh)
cv2.imshow("CONVEX_HULL", hull_img)
cv2.imshow("ROTATED BBOX", rot_bbox)
cv2.imshow("DESKEWED", deskewed)
cv2.imshow("IMG1", img1)
cv2.imshow("IMG2", img2)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image
Convex Hull Image:
Rotated Rectangle Image:
Deskewed Image:
Result Image: