检测OCR文本图像是否颠倒

44

我有几百张图片(扫描文档),其中大部分都被倾斜了。我想用Python对它们进行去斜校正。
这是我使用的代码:

import numpy as np
import cv2

from skimage.transform import radon


filename = 'path_to_filename'
# Load file, converting to grayscale
img = cv2.imread(filename)
I = cv2.cvtColor(img, COLOR_BGR2GRAY)
h, w = I.shape
# If the resolution is high, resize the image to reduce processing time.
if (w > 640):
    I = cv2.resize(I, (640, int((h / w) * 640)))
I = I - np.mean(I)  # Demean; make the brightness extend above and below zero
# Do the radon transform
sinogram = radon(I)
# Find the RMS value of each row and find "busiest" rotation,
# where the transform is lined up perfectly with the alternating dark
# text and white lines
r = np.array([np.sqrt(np.mean(np.abs(line) ** 2)) for line in sinogram.transpose()])
rotation = np.argmax(r)
print('Rotation: {:.2f} degrees'.format(90 - rotation))

# Rotate and save with the original resolution
M = cv2.getRotationMatrix2D((w/2,h/2),90 - rotation,1)
dst = cv2.warpAffine(img,M,(w,h))
cv2.imwrite('rotated.jpg', dst)

这段代码在大多数文档中运行良好,但对于一些角度(180和0)以及(90和270),经常被检测为相同的角度(即不区分(180和0)和(90和270))。因此我得到了很多倒置的文档。

这里是一个例子:
enter image description here

得到的图像与输入图像相同。

有没有建议使用Opencv和Python检测图像是否倒置?
PS:我尝试使用EXIF数据检查方向,但没有解决方案。


编辑:
可以使用Tesseract(Python的pytesseract)检测方向,但仅当图像包含大量字符时才可能。对于可能需要此功能的任何人:

import cv2
import pytesseract


print(pytesseract.image_to_osd(cv2.imread(file_name)))
如果文档包含足够多的字符,Tesseract可以检测方向。然而,当图像只有几行时,Tesseract建议的方向角度通常是错误的。因此,这并不能是一个100%的解决方案。

如果文档包含足够多的字符,Tesseract可以检测方向。然而,当图像只有几行时,Tesseract建议的方向角度通常是错误的。因此,这并不能是一个100%的解决方案。


8
虽然不是解决方案,但你可以尝试另一种启发式方法(假设你在阅读拉丁文脚本),即比较左半部分和右半部分或上半部分和下半部分的黑色数量。如果页面右侧(换行符)和/或底部明显更多黑色,我猜它很可能颠倒了。 - jdehesa
3
论文里面总会有标题吗?您能说一下是否有要遵循的格式吗?我想把OCR作为最后的选择... 它会更容易检测到白色斑点,创建一个矩形并测量其大小。比如在标题和正文之间的空白处的白色斑点。 - GDias
1
@singrium 嗯,我不确定,如果它们是固定大小的话,你可以使用一些卷积滤波器,看看它们是正立还是倒置时是否效果更好(你会得到更多的“匹配”)。否则我不确定(说实话我对CV并不是很了解),我的意思是你肯定可以创建一个神经网络或者其他分类器来处理这个问题,但那需要更多的工作。 - jdehesa
1
对于那些带有蓝线的文档,您可以读取图像的蓝色通道并创建一个蓝色阈值。如果它检测到蓝色的存在,并且在文档中间以下,您可以说该文档是颠倒的。 - GDias
2
您可以对页面进行预处理,完全将其转换为高对比度的灰度图像,然后按照 jdehesa 的建议应用黑白文本。但是在进行OCR或任何检测之前,您始终需要标准化处理。 - knh190
显示剩余10条评论
3个回答

34

Python3/OpenCV4脚本用于校准扫描文档。

旋转文档并求行和。当文档有0度和180度旋转时,图像中会有很多黑色像素:

旋转查找斑马纹最大值

使用评分方法。为每个图像打分,评估它们与斑马纹相似的程度。得分最高的图像具有正确的旋转角度。你链接的图像偏差了0.5度。出于可读性的考虑,我省略了一些函数,完整的代码可以在这里找到

# Rotate the image around in a circle
angle = 0
while angle <= 360:
    # Rotate the source image
    img = rotate(src, angle)    
    # Crop the center 1/3rd of the image (roi is filled with text)
    h,w = img.shape
    buffer = min(h, w) - int(min(h,w)/1.15)
    roi = img[int(h/2-buffer):int(h/2+buffer), int(w/2-buffer):int(w/2+buffer)]
    # Create background to draw transform on
    bg = np.zeros((buffer*2, buffer*2), np.uint8)
    # Compute the sums of the rows
    row_sums = sum_rows(roi)
    # High score --> Zebra stripes
    score = np.count_nonzero(row_sums)
    scores.append(score)
    # Image has best rotation
    if score <= min(scores):
        # Save the rotatied image
        print('found optimal rotation')
        best_rotation = img.copy()
    k = display_data(roi, row_sums, buffer)
    if k == 27: break
    # Increment angle and try again
    angle += .75
cv2.destroyAllWindows()

最佳旋转角度

如何判断文件是否颠倒了?请填充从文档顶部到图像中第一个非黑色像素的区域,然后测量黄色区域的面积。面积最小的图像将是正确的方向:

正确朝向颠倒朝向

# Find the area from the top of page to top of image
_, bg = area_to_top_of_text(best_rotation.copy())
right_side_up = sum(sum(bg))
# Flip image and try again
best_rotation_flipped = rotate(best_rotation, 180)
_, bg = area_to_top_of_text(best_rotation_flipped.copy())
upside_down = sum(sum(bg))
# Check which area is larger
if right_side_up < upside_down: aligned_image = best_rotation
else: aligned_image = best_rotation_flipped
# Save aligned image
cv2.imwrite('/home/stephen/Desktop/best_rotation.png', 255-aligned_image)
cv2.destroyAllWindows()

3
这是个很好的回答。但倒置检测可能会在每章的最后一页等位置失败。我猜你可以另外对左右边距进行类似的分析,因为段落结尾比开头平均更深缩进。 - visibleman
我建议从顶部和左侧分别汇总非黑色元素,因为英文文本从左上角开始。 - Cireo
3
为了检测倒置,你可以利用大写字母的光环效应以及像 t、h、k 这样字母的频率。在上面的静态图像中,光环位于白色条带下方。也就是说,在白色条带之间切开的面积之和的较亮一侧需要在顶部。 - Andrew Allen

8
假设您已经对图像进行了角度校正,您可以尝试以下方法来查找它是否被翻转:
  1. 将校正后的图像投影到y轴上,这样每行就会得到一个“峰”。重要提示:实际上几乎总是有两个子峰!
  2. 通过与高斯卷积平滑此投影,以消除细节结构、噪声等。
  3. 对于每个峰,检查较强的子峰是否在顶部或底部。
  4. 计算具有底部子峰的峰的比例。这是您的标量值,可为您提供正确定向图像的置信度。
第3步中的峰值查找是通过查找具有高于平均值的区域来完成的。然后通过argmax找到子峰。
下面是一张插图来说明这种方法;您示例图像的几行
  • 蓝色:原始投影
  • 橙色:平滑的投影
  • 水平线:整个图像平滑投影的平均值。
bla 以下是执行此操作的一些代码:
import cv2
import numpy as np

# load image, convert to grayscale, threshold it at 127 and invert.
page = cv2.imread('Page.jpg')
page = cv2.cvtColor(page, cv2.COLOR_BGR2GRAY)
page = cv2.threshold(page, 127, 255, cv2.THRESH_BINARY_INV)[1]

# project the page to the side and smooth it with a gaussian
projection = np.sum(page, 1)
gaussian_filter = np.exp(-(np.arange(-3, 3, 0.1)**2))
gaussian_filter /= np.sum(gaussian_filter)
smooth = np.convolve(projection, gaussian_filter)

# find the pixel values where we expect lines to start and end
mask = smooth > np.average(smooth)
edges = np.convolve(mask, [1, -1])
line_starts = np.where(edges == 1)[0]
line_endings = np.where(edges == -1)[0]

# count lines with peaks on the lower side
lower_peaks = 0
for start, end in zip(line_starts, line_endings):
    line = smooth[start:end]
    if np.argmax(line) < len(line)/2:
        lower_peaks += 1

print(lower_peaks / len(line_starts))

这将打印给定图像的0.125,因此它未正确定向,必须翻转。

请注意,如果图像中存在未组织成行的图片、数学公式等内容,则此方法可能会严重破坏。另一个问题是行数太少,导致统计数据不佳。

另外,不同的字体可能会导致不同的分布。您可以在几个图像上尝试此方法,看看是否有效。我没有足够的数据。


1
这个答案需要说明为什么采用了这种方法以及它为什么有些奏效。两个主要的峰值是由于像o、b、q、e等字母的“o-ness”。通过平滑,你在这里失去了可靠性。忽略这两个主要的峰值,集中关注由于大写字母和像t、h、l、d这样的字母频率而产生的两个子峰值。在你的高斯图像中,这些子峰使得图像倒置变得明显。 - Andrew Allen
你所说的在理想情况下是正确的。然而,检测小峰需要更敏感的检测,并且更容易受到扫描中的不规则性的影响(例如示例扫描边缘处的垂直黑线)。因此,我对投影进行了平滑处理。 - Obay
主峰包含很多噪音,子峰包含信号。我尊重地不同意在现实世界中平滑处理即对噪声和信号进行平均处理更好的观点。 - Andrew Allen

1

您可以使用Alyn模块。安装它的方法如下:

pip install alyn

然后使用它来矫正图像(从主页中获取):
from alyn import Deskew
d = Deskew(
    input_file='path_to_file',
    display_image='preview the image on screen',
    output_file='path_for_deskewed image',
    r_angle='offest_angle_in_degrees_to_control_orientation')`
d.run()

请注意,Alyn 仅用于文本校正。

2
你试过你发布的代码吗?当我运行它时,我得到了这个错误 ImportError: cannot import name 'Deskew' - singrium
1
如果将“deskew”改为小写,则可以正常工作,但是会出现另一个错误。似乎不适用于Python 3.7(?) - L.C.
@L.C. -- 不,这不是针对Python 3的;但只有一些小的更改。 - xilpex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接