如何使用Python和OpenCV实现发票图像的文本块分割?Kmeans及轮廓检测尝试后遇异常
我来帮你搞定发票文本区域提取的问题!先聊聊你现有方法的问题,再给你靠谱的解决方案:
问题分析
1. Kmeans方法的问题
你用Kmeans做颜色聚类的思路本身就不太适合文本区域提取——发票的文本和背景颜色差异往往不大,聚类结果容易把无关区域混在一起。另外你的代码还有个小bug:聚类后res.reshape((img.shape))里的img已经被你reshape成二维数组了,应该用原始图像的形状来恢复。而且cv2.waitKey(2)时间太短,窗口会一闪而过,改成waitKey(0)才能正常查看结果。
2. 轮廓检测的问题
你当前的轮廓检测会提取所有小轮廓(包括单个字母),是因为没做预处理合并相邻文本块,而且用了cv2.RETR_LIST这种会返回所有层级轮廓的模式,导致单个字母都被框出来了。
解决方案:改进的OpenCV文本区域提取流程
下面是针对发票场景优化的完整流程,能准确提取整个文本段的区域:
步骤拆解
- 预处理:降噪+自适应二值化(适配发票光照不均的情况)+ 形态学膨胀(合并同一行的字母成一个连通块)
- 轮廓提取:只取最外层轮廓,过滤掉噪声小轮廓
- 区域提取:把每个文本段单独保存成小图像
完整代码
import cv2 import numpy as np # 读取原始图像 img = cv2.imread("image1.jpg") original_img = img.copy() # 1. 预处理流程 # 转灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊降噪 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 自适应二值化:比固定阈值更适合光照不均的发票 thresh = cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2 ) # 形态学膨胀:用横向核合并同一行的文本块 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 3)) dilated = cv2.dilate(thresh, kernel, iterations=1) # 2. 提取文本区域轮廓 # 只取最外层轮廓,过滤内部小轮廓 contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 3. 过滤噪声并提取文本区域 min_area = 200 # 根据你的发票尺寸调整这个值 for cnt in contours: area = cv2.contourArea(cnt) if area > min_area: x, y, w, h = cv2.boundingRect(cnt) # 在原图上绘制文本区域矩形 cv2.rectangle(original_img, (x, y), (x+w, y+h), (0, 0, 255), 2) # 提取单个文本区域并保存 text_region = img[y:y+h, x:x+w] cv2.imwrite(f"text_segment_{x}_{y}.jpg", text_region) # 查看结果 cv2.imshow("Extracted Text Regions", original_img) cv2.waitKey(0) cv2.destroyAllWindows()
代码关键点说明
- 自适应二值化:自动根据局部区域调整阈值,完美解决发票局部明暗不均的问题
- 形态学膨胀:横向拉长的卷积核会把同一行的字母“粘”成一个块,这样提取的就是整个文本段,而不是单个字母
- 轮廓过滤:通过面积排除噪声小轮廓,只保留真正的文本区域
进阶方案:EAST文本检测器
如果你的发票文本布局复杂(比如倾斜、多行混合),可以用OpenCV的EAST文本检测器,它能更精准地检测各种形态的文本块。你需要提前下载预训练模型frozen_east_text_detection.pb,核心代码框架如下:
import cv2 import numpy as np # 加载EAST模型 net = cv2.dnn.readNet("frozen_east_text_detection.pb") img = cv2.imread("image1.jpg") h, w = img.shape[:2] # 调整图像尺寸为32的倍数(模型要求) new_w, new_h = (w//32)*32, (h//32)*32 img_resized = cv2.resize(img, (new_w, new_h)) # 构建模型输入 blob = cv2.dnn.blobFromImage( img_resized, 1.0, (new_w, new_h), (123.68, 116.78, 103.94), swapRB=True, crop=False ) net.setInput(blob) # 获取检测结果:得分和几何信息 scores, geometry = net.forward(["feature_fusion/Conv_7/Sigmoid", "feature_fusion/concat_3"]) # 解析结果并提取文本区域(可参考EAST官方解析代码实现)
内容的提问来源于stack exchange,提问作者taga
相关产品推荐
相关产品推荐

