如何分离倾斜不均的文本行优化OCR?霍夫变换问题及方案咨询
解决每行倾斜不同的文本OCR去歪与分行问题
问题分析
你要识别的文本块存在每行倾斜程度不一致的情况,整块去歪无法解决部分行的识别问题;尝试用霍夫变换检测行间隔的水平线,结果却只检测到垂直线——核心原因在于霍夫参数未针对水平线优化,且行间隔未必是连续的直线,换用更可靠的分行方法会更高效。
方案一:调整霍夫变换参数,精准检测水平线
你的霍夫变换当前未限定角度范围,导致垂直线被优先检测,修改参数聚焦水平区域即可:
- 限定检测角度为接近水平的区间(85°-95°,对应弧度
np.pi*85/180到np.pi*95/180),过滤垂直线候选 - 调整
minLineLength和maxLineGap:行间隔可能不是完全连续的,适当调大maxLineGap(比如5-10)、减小minLineLength(比如100),适配行间隔的实际宽度 - 可选:先对原图做水平膨胀,强化行间隔的空白区域,让霍夫更容易检测到连续线
修改后的代码示例:
import cv2 import numpy as np import imutils def hough_lines_horizontal(cvImage): img = cvImage.copy() edges = cv2.Canny(img, 50, 150, apertureSize=3) inv = np.invert(edges) # 限定角度在85-95度(接近水平) angle_min = np.pi * 85 / 180 angle_max = np.pi * 95 / 180 # 调整参数适配行间隔特征 linesP = cv2.HoughLinesP(inv, 1, np.pi/180, threshold=100, minLineLength=100, maxLineGap=5) img2 = cv2.cvtColor(inv, cv2.COLOR_GRAY2BGR) if linesP is not None: for l in linesP[:, 0]: cv2.line(img2, (l[0], l[1]), (l[2], l[3]), (0, 255, 0), 2) cv2.namedWindow('Resized', cv2.WINDOW_NORMAL) cv2.resizeWindow('Resized', 600, 900) cv2.imshow("Resized", imutils.resize(img2, width=500)) cv2.waitKey(0) cv2.destroyAllWindows() return linesP
方案二:用水平投影法分行(更稳定可靠)
霍夫检测水平线依赖连续空白,而水平投影法直接基于每行的像素分布,更适合分割不规则倾斜的文本行:
- 对二值化图像计算水平方向像素投影:统计每一行的白色像素总数
- 设定阈值(比如投影值低于总行像素数的5%),找出投影值极低的行,即为行间隔
- 根据这些间隔位置,切割出每行文本
代码示例:
def split_lines_by_projection(cvImage): img = cvImage.copy() # 确保是灰度图,已预处理可跳过 if len(img.shape) == 3: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # 计算水平投影 h, w = binary.shape horizontal_proj = np.sum(binary, axis=1) # 设定阈值筛选行间隔 threshold = np.max(horizontal_proj) * 0.05 line_gaps = np.where(horizontal_proj < threshold)[0] # 合并连续间隔行,确定分割点 split_points = [] start = line_gaps[0] for i in range(1, len(line_gaps)): if line_gaps[i] - line_gaps[i-1] > 1: split_points.append((start + line_gaps[i-1])//2) start = line_gaps[i] split_points.append((start + line_gaps[-1])//2) # 切割每行文本,过滤过窄行 lines = [] prev = 0 for point in split_points: if point - prev > 5: line_img = binary[prev:point, :] lines.append(line_img) prev = point # 处理最后一行 if h - prev > 5: lines.append(binary[prev:h, :]) # 展示分割结果 for idx, line in enumerate(lines): cv2.imshow(f"Line {idx}", imutils.resize(line, width=500)) cv2.waitKey(0) cv2.destroyAllWindows() return lines
方案三:单行文独立去歪方法
分割出每行后,用以下方法校正单行文的倾斜:
- 最小外接矩形法:找到行文本的轮廓,计算最小外接矩形的旋转角度,旋转校正
- 主轴方向法:计算文本轮廓的主轴角度,以此为基准旋转
代码示例(单行文去歪):
def deskew_single_line(line_img): # 找到文本轮廓 contours, _ = cv2.findContours(line_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return line_img # 合并所有轮廓的外接矩形 rect = cv2.minAreaRect(np.concatenate(contours)) angle = rect[2] # 调整角度:minAreaRect返回角度范围为[-90,0),转换为实际旋转角度 if angle < -45: angle = 90 + angle # 旋转图像校正倾斜 (h, w) = line_img.shape[:2] center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, angle, 1.0) deskewed = cv2.warpAffine(line_img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) return deskewed
整体流程建议
- 用水平投影法分割每行文本(比霍夫变换更稳定)
- 对每行文本分别用最小外接矩形法去歪
- 对校正后的单行文本执行OCR识别
内容的提问来源于stack exchange,提问作者anon
相关产品推荐
相关产品推荐

