OpenCV计算机视觉实战(33)------文字识别详解
0. 前言
在实际光学字符识别 (Optical Character Recognition, OCR) 项目中,文字往往出现在各种复杂的背景与排版环境里:从拍照文档到发票扫描,如何先准确定位文本,再高效调用 OCR 引擎,并最终将识别结果结构化,是完整系统的关键。本文在 EAST 文本检测的基础上,深入探讨了多尺度、旋转补偿、行合并排序与形态学细化等进阶策略,显著提高了文本区域定位的召回与精度;在 Tesseract 集成环节,引入了自适应二值化、开闭运算、批量识别与字符白名单,强化了 OCR 识别率与效率;最后,以发票识别为例,演示了模板匹配对齐与固定 ROI 的方法。
1. 文本区域检测
在进行 OCR 之前,需要先从复杂背景中准确定位出文本区域。本节中使用 OpenCV 的 EAST 文本检测器,基于深度学习检测任意方向的文本框,并对检测结果进行后处理获得最终文本 ROI。
1.1 实现过程
- 输入预处理
- 使用
blobFromImage将输入缩放到固定尺寸 (320×320),并减去ImageNet均值
- 使用
- EAST 模型推理
- 输出两个张量:
scores(文本存在概率)和geometry(每个像素的四边距离和旋转角)
- 输出两个张量:
- 解码
- 根据几何信息计算四边形坐标,再用
NMS去除冗余框
- 根据几何信息计算四边形坐标,再用
- 坐标映射
- 将检测坐标从网络输入尺寸映射回原图尺寸
python
import cv2
import numpy as np
# 功能:使用 EAST 文本检测模型,提取图像中的文本区域
def detect_text_regions(image, model_path='frozen_east_text_detection.pb',
min_confidence=0.5, width=320, height=320):
orig = image.copy()
H, W = image.shape[:2]
# 1. 构建输入 blob
blob = cv2.dnn.blobFromImage(image, 1.0, (width, height),
(123.68, 116.78, 103.94), swapRB=True, crop=False)
# 2. 加载 EAST 模型
net = cv2.dnn.readNet(model_path)
net.setInput(blob)
# 3. 指定要返回的层
layer_names = ['feature_fusion/Conv_7/Sigmoid', 'feature_fusion/concat_3']
scores, geometry = net.forward(layer_names)
# 4. 解码得分图和几何图
(num_rows, num_cols) = scores.shape[2:4]
rects, confidences = [], []
for y in range(num_rows):
scores_data = scores[0, 0, y]
x0_data = geometry[0, 0, y]
x1_data = geometry[0, 1, y]
x2_data = geometry[0, 2, y]
x3_data = geometry[0, 3, y]
angles_data = geometry[0, 4, y]
for x in range(num_cols):
if scores_data[x] < min_confidence:
continue
# 计算偏移
offset_x, offset_y = x * 4.0, y * 4.0
angle = angles_data[x]
cos = np.cos(angle); sin = np.sin(angle)
h = x0_data[x] + x2_data[x]
w = x1_data[x] + x3_data[x]
end_x = int(offset_x + (cos * x1_data[x]) + (sin * x2_data[x]))
end_y = int(offset_y - (sin * x1_data[x]) + (cos * x2_data[x]))
start_x = int(end_x - w)
start_y = int(end_y - h)
rects.append((start_x, start_y, end_x, end_y))
confidences.append(float(scores_data[x]))
# 5. 非极大值抑制
boxes = cv2.dnn.NMSBoxesRotated(
[(((r[0]+r[2])//2, (r[1]+r[3])//2), (r[2]-r[0], r[3]-r[1]), 0) for r in rects],
confidences, min_confidence, 0.4)
results = []
if len(boxes) > 0:
for i in boxes.flatten():
(sx, sy, ex, ey) = rects[i]
# 缩放回原图尺寸
rx = W / float(width); ry = H / float(height)
r = (int(sx * rx), int(sy * ry), int((ex-sx) * rx), int((ey-sy) * ry))
results.append(r)
return results
# 示例
img = cv2.imread('scanned_doc.png')
regions = detect_text_regions(img)
for (x,y,w,h) in regions:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
cv2.imshow('Text Regions', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

关键函数解析:
cv2.dnn.readNet():加载TensorFlow导出的.pb模型blobFromImage():执行归一化、尺寸变换与通道排序net.forward(layer_names):指定多个输出层,返回分数图和几何图cv2.dnn.NMSBoxesRotated():对旋转矩形进行NMS,剔除重叠框
1.2 优化思路
在复杂文档和拍照环境中,单一的 EAST 检测有时难以准确覆盖所有文字行。可以加入以下策略:
- 多尺度与旋转补偿
- 对输入图像按
0.5×、1×、1.5×多个尺度分别检测,再将结果映射回原图合并,用更大width×height能检测更大文字 - 对竖排文字或轻微倾斜场景,可在
±15°范围内对原图做小幅旋转,补检测遗漏框。
- 对输入图像按
- 文本行合并与排序
- 检测到的矩形按纵坐标分组,合并同一行的相邻框,形成完整行
ROI - 最终按行、列顺序排序,保证
OCR后文本顺序正确
- 检测到的矩形按纵坐标分组,合并同一行的相邻框,形成完整行
- 形态学细化
- 在解码后的二值得分图上做膨胀 (
cv2.dilate) 先将相近文本连通,再做轮廓提取 (cv2.findContours) 比NMS更稳健
- 在解码后的二值得分图上做膨胀 (
python
def merge_and_sort_boxes(boxes, y_thresh=10):
# 按 y 坐标排序
boxes = sorted(boxes, key=lambda b: b[1])
merged = []
for box in boxes:
x,y,w,h = box
if not merged:
merged.append([x,y,w,h])
continue
px,py,pw,ph = merged[-1]
# 同一行
if abs(y - py) < y_thresh:
# 合并横向相邻框
nx = min(px, x)
ny = min(py, y)
nw = max(px+pw, x+w) - nx
nh = max(py+ph, y+h) - ny
merged[-1] = [nx, ny, nw, nh]
else:
merged.append([x,y,w,h])
# 最终按行内 x 排序
for row in merged:
pass # already merged
return merged
# 在 detect_text_regions 调用后
regions = detect_text_regions(img)
regions = merge_and_sort_boxes(regions)
关键函数解析:
sorted(..., key=lambda):对检测框按顶端y排序,便于分行处理- 行合并:若两框
y差近似在行高范围内,则合并为一个更长的行ROI - 合并后保持按行、列顺序,保证
OCR文本阅读顺序
2. Tesseract 集成
利用 Tesseract OCR 引擎对预先检测出的文本区域进行文字识别,结合简单的图像预处理提高识别率。
2.1 实现过程
ROI预处理- 灰度化 +
Otsu二值化,提高字符对比度。
- 灰度化 +
- Tesseract 调用
--oem 1使用LSTM OCR引擎,--psm 7针对单行文本做优化
- 结果收集
- 返回每个区域的识别文本,供后续结构化解析
python
import cv2
import pytesseract
# 功能:对图像文本区域执行 OCR 识别
def ocr_on_regions(image, regions):
results = []
for (x,y,w,h) in regions:
roi = image[y:y+h, x:x+w]
# 预处理:转灰度 + 二值化
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 调用 Tesseract
config = '--oem 1 --psm 7' # LSTM OCR + 单行模式
text = pytesseract.image_to_string(thresh, config=config, lang='eng')
results.append(((x,y,w,h), text.strip()))
return results
pytesseract.pytesseract.tesseract_cmd = r'/usr/bin/tesseract'
img = cv2.imread('document.jpg')
regions = detect_text_regions(img)
ocr_results = ocr_on_regions(img, regions)
for ((x,y,w,h), text) in ocr_results:
cv2.rectangle(img, (x,y),(x+w,y+h), (255,0,0),2)
cv2.putText(img, text, (x,y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,0),1)
cv2.imshow('OCR Results', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键函数解析:
pytesseract.image_to_string():接受图像或ndarray,返回识别字符串threshold():Otsu自动阈值,分离文字与背景--oem/--psm:配置OCR引擎模式与页面分割模式
2.2 优化思路
在不同文档与拍照环境下,OCR 识别率受字体、光照和噪声影响很大。可以通过以下改进提高效果:
- 图像预处理
- 自适应阈值:对
gray使用cv2.adaptiveThreshold而非全局Otsu,应对不均匀光照 - 开闭运算:先做小核开运算去噪,再闭运算填充字符断裂
- 自适应阈值:对
- 批量识别
- 将多个
ROI合并到一张"大"图中,用一次image_to_data获取所有块文字和位置信息,减少Python↔Tesseract进程开销。
- 将多个
- 多语言与白名单
- 通过
-l chi_sim+eng --oem 1 --psm 6同时识别中英文字,并可用tessedit_char_whitelist=0123456789.限定数字识别
- 通过
python
def ocr_on_regions_batch(image, regions):
# 合并 ROI 到一行
heights = [h for (_,_,_,h) in regions]
max_h = max(heights)
# 拼接图像
canvas = np.zeros((max_h, sum([w for (_,_,w,_) in regions]), 3), dtype=np.uint8)
x_offset = 0
for (x,y,w,h) in regions:
roi = image[y:y+h, x:x+w]
# 预处理
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 3)
thresh = cv2.adaptiveThreshold(blur,255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,15,10)
# 放到 canvas
canvas[0:h, x_offset:x_offset+w] = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
x_offset += w
# 一次识别所有 ROI
config = '--oem 1 --psm 6 tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
data = pytesseract.image_to_data(canvas, config=config, output_type=pytesseract.Output.DICT)
# 按块提取
results = []
n_boxes = len(data['level'])
for i in range(n_boxes):
text = data['text'][i].strip()
if text:
# 计算相对 ROI
x_block, y_block, w_block, h_block = data['left'][i], data['top'][i], data['width'][i], data['height'][i]
# Map back to original regions[i]
# 省略映射逻辑,直接返回 canvas 坐标
results.append(((x_block, y_block, w_block, h_block), text))
return results
关键函数解析:
cv2.adaptiveThreshold(...):自适应阈值对光照不均场景更鲁棒pytesseract.image_to_data(..., output_type=...):一次返回所有识别块及其位置信息,支持批量解析tessedit_char_whitelist:限定可识别字符集,减少误识别
3. 发票识别案例
结合文本区域检测和 OCR,对发票图像进行关键字段(发票号码、日期、金额)识别,并用正则表达式提取结构化信息。
3.1 实现过程
- 文本汇总
- 将各
ROI的识别结果拼接成一段长文本
- 将各
- 正则匹配
- 使用
re.search提取模式匹配的关键字段
- 使用
- 容错与格式化
- 为防止
OCR误识别,可对提取结果做后处理(如去除逗号)
- 为防止
python
import re
# 功能:发票关键字段提取
def parse_invoice(ocr_results):
invoice_text = ' '.join([t for (_,t) in ocr_results])
data = {}
# 发票号码:通常为 8~12 位数字或字母组合
m = re.search(r'Invoice\s*No[::]?\s*([A-Z0-9\-]{8,12})', invoice_text)
data['InvoiceNo'] = m.group(1) if m else None
# 日期:YYYY-MM-DD 或 YYYY/MM/DD
m = re.search(r'(\d{4}[-/]\d{1,2}[-/]\d{1,2})', invoice_text)
data['Date'] = m.group(1) if m else None
# 金额:允许带千分位逗号与小数
m = re.search(r'Total\s*[::]?\s*([\d,]+\.\d{2})', invoice_text)
data['Total'] = m.group(1) if m else None
return data
# 示例
ocr_results = ocr_on_regions(img, regions)
invoice_data = parse_invoice(ocr_results)
print(invoice_data)
关键函数解析:
re.search(pattern, text):返回第一个匹配对象- 正则表达式:
\d{4}[-/]\d{1,2}[-/]\d{1,2}匹配多种日期格式[\d,]+\.\d{2}匹配带千分位的金额
3.2 优化思路
发票往往版面固定,可结合模板匹配或形状检测先对齐发票,再按固定字段区域提取 OCR,更加稳健:
- 发票版面对齐
- 在发票图中用
cv2.matchTemplate找到通用标志(如"发票"字样),根据位置做仿射或透视校正,将发票对齐到标准模板
- 在发票图中用
- 字段
ROI固定映射- 针对标准模板,预先标定"发票号码"、"日期"、"金额"等
ROI,相对模板图尺寸固定,校正后可直接截取
- 针对标准模板,预先标定"发票号码"、"日期"、"金额"等
- 正则增强与后处理
- 对
OCR提取的金额字符串做replace(',', '')去千分位符 - 对日期做
datetime.strptime校验合法性
- 对
python
# 1. 模板匹配对齐
template = cv2.imread('invoice_template.jpg',0)
gray_inv = cv2.cvtColor(inv_img, cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(gray_inv, template, cv2.TM_CCOEFF_NORMED)
_,_,_,max_loc = cv2.minMaxLoc(res)
# 计算仿射:假设模板与发票三点已知对应 pts_src, pts_dst
M = cv2.getAffineTransform(np.float32(pts_src), np.float32(pts_dst))
aligned = cv2.warpAffine(inv_img, M, template.shape[::-1])
# 2. 固定字段截取
fields = {
'InvoiceNo': (100,50,200,30),
'Date': (100,100,150,30),
'Total': (300,400,200,30)
}
data = {}
for k,(x,y,w,h) in fields.items():
roi = aligned[y:y+h, x:x+w]
text = pytesseract.image_to_string(roi, config='--psm 7').strip()
if k=='Total':
text = text.replace(',','')
data[k] = text
关键函数解析:
cv2.matchTemplate():在发票与模板间定位匹配区域,用于校正cv2.getAffineTransform() + warpAffine():对齐仿射变换至模板坐标- 固定
ROI:校正后直接按预设坐标截取字段,减少OCR误差
小结
本节围绕实际 OCR 系统构建,系统讲解了从文本区域检测、图像预处理、字符识别到结构化信息提取的完整流程。通过引入 EAST 模型、图像多尺度与旋转补偿策略,有效提升了文本检测的召回率与准确性;结合形态学与行排序技术,优化了 ROI 的提取质量;在 Tesseract 集成中,通过二值化增强、字符白名单与批量识别等手段,提高了字符识别的稳定性与效率。最后通过发票识别案例,展示了如何将 OCR 结果转化为结构化数据,为实战项目提供思路。
系列链接
OpenCV计算机视觉实战(1)------计算机视觉简介
OpenCV计算机视觉实战(2)------环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)------计算机图像处理基础
OpenCV计算机视觉实战(4)------计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)------图像基础操作全解析
OpenCV计算机视觉实战(6)------经典计算机视觉算法
OpenCV计算机视觉实战(7)------色彩空间详解
OpenCV计算机视觉实战(8)------图像滤波详解
OpenCV计算机视觉实战(9)------阈值化技术详解
OpenCV计算机视觉实战(10)------形态学操作详解
OpenCV计算机视觉实战(11)------边缘检测详解
OpenCV计算机视觉实战(12)------图像金字塔与特征缩放
OpenCV计算机视觉实战(13)------轮廓检测详解
OpenCV计算机视觉实战(14)------直方图均衡化
OpenCV计算机视觉实战(15)------霍夫变换详解
OpenCV计算机视觉实战(16)------图像分割技术
OpenCV计算机视觉实战(17)------特征点检测详解
OpenCV计算机视觉实战(18)------视频处理详解
OpenCV计算机视觉实战(19)------特征描述符详解
OpenCV计算机视觉实战(20)------光流法运动分析
OpenCV计算机视觉实战(21)------模板匹配详解
OpenCV计算机视觉实战(22)------图像拼接详解
OpenCV计算机视觉实战(23)------目标检测详解
OpenCV计算机视觉实战(24)------目标追踪算法
OpenCV计算机视觉实战(25)------立体视觉详解
OpenCV计算机视觉实战(26)------OpenCV与机器学习
OpenCV计算机视觉实战(27)------深度学习与卷积神经网络
OpenCV计算机视觉实战(28)------深度学习初体验
OpenCV计算机视觉实战(29)------OpenCV DNN模块
OpenCV计算机视觉实战(30)------图像分类模型
OpenCV计算机视觉实战(31)------人脸识别详解
OpenCV计算机视觉实战(32)------YOLO目标检测