如何使用 OCR 检测图像中的下标数字?
How to detect subscript numbers in an image using OCR?
我通过 pytesseract
绑定使用 tesseract
进行 OCR。不幸的是,我在尝试提取包含下标样式数字的文本时遇到困难 - 下标数字被解释为字母。
例如,在基本图像中:
我想将文本提取为 "CH3",即我不关心数字 3
是图像中的下标。
我使用 tesseract
的尝试是:
import cv2
import pytesseract
img = cv2.imread('test.jpeg')
# Note that I have reduced the region of interest to the known
# text portion of the image
text = pytesseract.image_to_string(
img[200:300, 200:320], config='-l eng --oem 1 --psm 13'
)
print(text)
不幸的是,这会错误地输出
'CHs'
根据psm
参数也可以得到'CHa'
。
我怀疑这个问题与"baseline"的文本跨行不一致有关,但我不确定。
如何从此类图片中准确提取文本?
更新 - 2020 年 5 月 19 日
看到 Achintha Ihalage 的回答后,它没有为 tesseract
提供任何配置选项,我探索了 psm
选项。
由于感兴趣的区域是已知的(在这种情况下,我使用 EAST 检测来定位文本的边界框),tesseract
的 psm
配置选项,在我的原始代码将文本视为单行,可能没有必要。 运行 image_to_string
针对上方边界框给出的感兴趣区域给出输出
CH
3
当然可以很容易地处理得到 CH3
.
这是因为下标字体太小了。您可以使用 python 包(例如 cv2
或 PIL
调整图像大小,并将调整后的图像用于 OCR,如下所示。
import pytesseract
import cv2
img = cv2.imread('test.jpg')
img = cv2.resize(img, None, fx=2, fy=2) # scaling factor = 2
data = pytesseract.image_to_string(img)
print(data)
输出:
CH3
您想在将图像输入 tesseract
之前对图像进行预处理,以提高 OCR 的准确性。我在这里使用 PIL
和 cv2
的组合来执行此操作,因为 cv2
具有很好的过滤器 blur/noise 去除(膨胀,腐蚀,阈值)并且 PIL
使增强对比度(将文本与背景区分开来)很容易,我想展示如何使用其中任何一种来完成预处理……(尽管并非 100% 需要同时使用两者,如下所示)。你可以写得更优雅——这只是一般的想法。
import cv2
import pytesseract
import numpy as np
from PIL import Image, ImageEnhance
img = cv2.imread('test.jpg')
def cv2_preprocess(image_path):
img = cv2.imread(image_path)
# convert to black and white if not already
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# remove noise
kernel = np.ones((1, 1), np.uint8)
img = cv2.dilate(img, kernel, iterations=1)
img = cv2.erode(img, kernel, iterations=1)
# apply a blur
# gaussian noise
img = cv2.threshold(cv2.GaussianBlur(img, (9, 9), 0), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# this can be used for salt and pepper noise (not necessary here)
#img = cv2.adaptiveThreshold(cv2.medianBlur(img, 7), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
cv2.imwrite('new.jpg', img)
return 'new.jpg'
def pil_enhance(image_path):
image = Image.open(image_path)
contrast = ImageEnhance.Contrast(image)
contrast.enhance(2).save('new2.jpg')
return 'new2.jpg'
img = cv2.imread(pil_enhance(cv2_preprocess('test.jpg')))
text = pytesseract.image_to_string(img)
print(text)
输出:
CH3
cv2
预处理生成的图像如下所示:
PIL
的增强功能为您提供:
在这个具体示例中,您实际上可以在 cv2_preprocess
步骤之后停止,因为这对于 reader:
来说已经足够清楚了
img = cv2.imread(cv2_preprocess('test.jpg'))
text = pytesseract.image_to_string(img)
print(text)
输出:
CH3
但是如果您正在处理不一定以白色背景开始的事情(即灰度缩放转换为浅灰色而不是白色)- 我发现 PIL
这一步确实有帮助。
要点是提高 tesseract
准确性的方法通常是:
- 修复 DPI(重新缩放)
- 修复图像brightness/noise
- 修复 tex size/lines
(skewing/warping 文字)
执行其中一项或全部三项会有所帮助...但是 brightness/noise 比其他两项更具有普遍性(至少根据我的经验)。
我觉得这种方式可以更适合一般情况
import cv2
import pytesseract
from pathlib import Path
image = cv2.imread('test.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] # (suitable for sharper black and white pictures
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1] # is OpenCV2.4 or OpenCV3
result_list = []
for c in contours:
x, y, w, h = cv2.boundingRect(c)
area = cv2.contourArea(c)
if area > 200:
detect_area = image[y:y + h, x:x + w]
# detect_area = cv2.GaussianBlur(detect_area, (3, 3), 0)
predict_char = pytesseract.image_to_string(detect_area, lang='eng', config='--oem 0 --psm 10')
result_list.append((x, predict_char))
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), thickness=2)
result = ''.join([char for _, char in sorted(result_list, key=lambda _x: _x[0])])
print(result) # CH3
output_dir = Path('./temp')
output_dir.mkdir(parents=True, exist_ok=True)
cv2.imwrite(f"{output_dir/Path('image.png')}", image)
cv2.imwrite(f"{output_dir/Path('clean.png')}", thresh)
更多参考资料
强烈建议您参考以下示例,这对OCR很有帮助。
我通过 pytesseract
绑定使用 tesseract
进行 OCR。不幸的是,我在尝试提取包含下标样式数字的文本时遇到困难 - 下标数字被解释为字母。
例如,在基本图像中:
我想将文本提取为 "CH3",即我不关心数字 3
是图像中的下标。
我使用 tesseract
的尝试是:
import cv2
import pytesseract
img = cv2.imread('test.jpeg')
# Note that I have reduced the region of interest to the known
# text portion of the image
text = pytesseract.image_to_string(
img[200:300, 200:320], config='-l eng --oem 1 --psm 13'
)
print(text)
不幸的是,这会错误地输出
'CHs'
根据psm
参数也可以得到'CHa'
。
我怀疑这个问题与"baseline"的文本跨行不一致有关,但我不确定。
如何从此类图片中准确提取文本?
更新 - 2020 年 5 月 19 日
看到 Achintha Ihalage 的回答后,它没有为 tesseract
提供任何配置选项,我探索了 psm
选项。
由于感兴趣的区域是已知的(在这种情况下,我使用 EAST 检测来定位文本的边界框),tesseract
的 psm
配置选项,在我的原始代码将文本视为单行,可能没有必要。 运行 image_to_string
针对上方边界框给出的感兴趣区域给出输出
CH
3
当然可以很容易地处理得到 CH3
.
这是因为下标字体太小了。您可以使用 python 包(例如 cv2
或 PIL
调整图像大小,并将调整后的图像用于 OCR,如下所示。
import pytesseract
import cv2
img = cv2.imread('test.jpg')
img = cv2.resize(img, None, fx=2, fy=2) # scaling factor = 2
data = pytesseract.image_to_string(img)
print(data)
输出:
CH3
您想在将图像输入 tesseract
之前对图像进行预处理,以提高 OCR 的准确性。我在这里使用 PIL
和 cv2
的组合来执行此操作,因为 cv2
具有很好的过滤器 blur/noise 去除(膨胀,腐蚀,阈值)并且 PIL
使增强对比度(将文本与背景区分开来)很容易,我想展示如何使用其中任何一种来完成预处理……(尽管并非 100% 需要同时使用两者,如下所示)。你可以写得更优雅——这只是一般的想法。
import cv2
import pytesseract
import numpy as np
from PIL import Image, ImageEnhance
img = cv2.imread('test.jpg')
def cv2_preprocess(image_path):
img = cv2.imread(image_path)
# convert to black and white if not already
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# remove noise
kernel = np.ones((1, 1), np.uint8)
img = cv2.dilate(img, kernel, iterations=1)
img = cv2.erode(img, kernel, iterations=1)
# apply a blur
# gaussian noise
img = cv2.threshold(cv2.GaussianBlur(img, (9, 9), 0), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# this can be used for salt and pepper noise (not necessary here)
#img = cv2.adaptiveThreshold(cv2.medianBlur(img, 7), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
cv2.imwrite('new.jpg', img)
return 'new.jpg'
def pil_enhance(image_path):
image = Image.open(image_path)
contrast = ImageEnhance.Contrast(image)
contrast.enhance(2).save('new2.jpg')
return 'new2.jpg'
img = cv2.imread(pil_enhance(cv2_preprocess('test.jpg')))
text = pytesseract.image_to_string(img)
print(text)
输出:
CH3
cv2
预处理生成的图像如下所示:
PIL
的增强功能为您提供:
在这个具体示例中,您实际上可以在 cv2_preprocess
步骤之后停止,因为这对于 reader:
img = cv2.imread(cv2_preprocess('test.jpg'))
text = pytesseract.image_to_string(img)
print(text)
输出:
CH3
但是如果您正在处理不一定以白色背景开始的事情(即灰度缩放转换为浅灰色而不是白色)- 我发现 PIL
这一步确实有帮助。
要点是提高 tesseract
准确性的方法通常是:
- 修复 DPI(重新缩放)
- 修复图像brightness/noise
- 修复 tex size/lines (skewing/warping 文字)
执行其中一项或全部三项会有所帮助...但是 brightness/noise 比其他两项更具有普遍性(至少根据我的经验)。
我觉得这种方式可以更适合一般情况
import cv2
import pytesseract
from pathlib import Path
image = cv2.imread('test.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] # (suitable for sharper black and white pictures
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1] # is OpenCV2.4 or OpenCV3
result_list = []
for c in contours:
x, y, w, h = cv2.boundingRect(c)
area = cv2.contourArea(c)
if area > 200:
detect_area = image[y:y + h, x:x + w]
# detect_area = cv2.GaussianBlur(detect_area, (3, 3), 0)
predict_char = pytesseract.image_to_string(detect_area, lang='eng', config='--oem 0 --psm 10')
result_list.append((x, predict_char))
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), thickness=2)
result = ''.join([char for _, char in sorted(result_list, key=lambda _x: _x[0])])
print(result) # CH3
output_dir = Path('./temp')
output_dir.mkdir(parents=True, exist_ok=True)
cv2.imwrite(f"{output_dir/Path('image.png')}", image)
cv2.imwrite(f"{output_dir/Path('clean.png')}", thresh)
更多参考资料
强烈建议您参考以下示例,这对OCR很有帮助。