到目前为止,所有的答案似乎都在使用形态学运算。这里有一些稍微不同的方法。如果线是水平的,这应该会给出相当不错的结果。
为此,我使用下面显示的样本图像的一部分。
![sample](https://istack.dev59.com/bwrcH.webp)
加载图像,将其转换为灰度并反转。
import cv2
import numpy as np
import matplotlib.pyplot as plt
im = cv2.imread('sample.jpg')
gray = 255 - cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
反相灰度图像:
![inverted-gray](https://istack.dev59.com/m2WXj.webp)
如果您扫描此反相图像中的一行,您会发现其轮廓根据线条的存在或缺失而有所不同。
plt.figure(1)
plt.plot(gray[18, :] > 16, 'g-')
plt.axis([0, gray.shape[1], 0, 1.1])
plt.figure(2)
plt.plot(gray[36, :] > 16, 'r-')
plt.axis([0, gray.shape[1], 0, 1.1])
绿色的个人资料行没有下划线,红色表示有下划线。如果你计算每个资料行的平均值,你会发现红色资料行的平均值更高。
![no-line](https://istack.dev59.com/Jcy4om.webp)
![line](https://istack.dev59.com/s2U5Vm.webp)
因此,使用这种方法,您可以检测下划线并将其删除。
for row in range(gray.shape[0]):
avg = np.average(gray[row, :] > 16)
if avg > 0.9:
cv2.line(im, (0, row), (gray.shape[1]-1, row), (0, 0, 255))
cv2.line(gray, (0, row), (gray.shape[1]-1, row), (0, 0, 0), 1)
cv2.imshow("gray", 255 - gray)
cv2.imshow("im", im)
以下为检测到的红色下划线和清晰图片:
![detected](https://istack.dev59.com/3felr.webp)
![cleaned](https://istack.dev59.com/2uR3F.webp)
清晰图片的Tesseract输出结果:
Convthed as th(
shot once in the
she stepped fr<
brother-in-lawii
collect on life in
applied for man
to the scheme i|
现在应该清楚为什么只使用图像的一部分了。因为原始图像中已经删除了个人身份信息,所以阈值不起作用。但是当您应用它进行处理时,这应该不是问题。有时候您可能需要调整阈值(16,0.9)。
结果看起来不太好,字母的一些部分被移除,一些淡线仍然存在。如果我能进一步改善它,我会更新的。
更新:
做了一些改进;清理并链接字母的缺失部分。我已经对代码进行了注释,因此我相信过程很清晰。您还可以检查生成的中间图像以了解它的工作原理。结果稍微好些了。
![1](https://istack.dev59.com/XnKlj.webp)
![1-clean](https://istack.dev59.com/UyWvN.webp)
清理图像后的tesseract输出:
Convicted as th(
shot once in the
she stepped fr<
brother-in-law. ‘
collect on life ix
applied for man
to the scheme i|
![2](https://istack.dev59.com/ilywQ.webp)
![2-clean](https://istack.dev59.com/9CIOd.webp)
清理后的图像的tesseract输出:
)r-hire of 29-year-old .
revolver in the garage ‘
red that the victim‘s h
{2000 to kill her. mum
250.000. Before the kil
If$| 50.000 each on bin
to police.
Python 代码:
import cv2
import numpy as np
import matplotlib.pyplot as plt
im = cv2.imread('sample2.jpg')
gray = 255 - cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
__, bw = cv2.threshold(cv2.dilate(gray, None), 128, 255, cv2.THRESH_BINARY or cv2.THRESH_OTSU)
gray = cv2.bitwise_and(gray, bw)
grayu = gray.copy()
imcpy = im.copy()
for row in range(gray.shape[0]):
avg = np.average(gray[row, :] > 16)
if avg > 0.9:
cv2.line(im, (0, row), (gray.shape[1]-1, row), (0, 0, 255))
cv2.line(gray, (0, row), (gray.shape[1]-1, row), (0, 0, 0), 1)
cont = gray.copy()
graycpy = gray.copy()
residual = gray.copy()
contours, hierarchy = cv2.findContours(cont, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
x, y, w, h = cv2.boundingRect(contours[i])
if 10 < h:
cv2.drawContours(im, contours, i, (0, 255, 0), -1)
cv2.drawContours(residual, contours, i, (0, 0, 0), -1)
else:
cv2.drawContours(im, contours, i, (255, 0, 0), -1)
cv2.drawContours(gray, contours, i, (0, 0, 0), -1)
st = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
residual = cv2.morphologyEx(residual, cv2.MORPH_OPEN, st, iterations=1)
__, residual = cv2.threshold(residual, 0, 255, cv2.THRESH_BINARY)
cv2.imshow("gray", gray)
cv2.imshow("residual", residual)
combined = cv2.bitwise_or(cv2.bitwise_and(graycpy, residual), gray)
st = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 7))
linked = cv2.morphologyEx(combined, cv2.MORPH_CLOSE, st, iterations=1)
cv2.imshow("linked", linked)
__, mask = cv2.threshold(linked, 0, 255, cv2.THRESH_BINARY)
clean = 255 - cv2.bitwise_and(grayu, mask)
cv2.imshow("clean", clean)
cv2.imshow("im", im)