如何在OpenCV Python中分割连接的轮廓与粘连字符
嘿,我来帮你搞定粘连字符的分割问题!针对你要把两张图分别分割成MENT和IN的需求,咱们可以从OpenCV的几个实用技巧入手,调整你的现有代码并补充关键步骤:
一、先优化你的预处理步骤
你当前用了腐蚀操作,但5x5的核+2次迭代可能会过度破坏字符形状。建议先做开运算(先腐蚀再膨胀)来去除小噪点,同时尽量保留字符完整性:
import cv2 import numpy as np # 读取图像 im_in = cv2.imread("testResize.jpg", cv2.IMREAD_GRAYSCALE) # 自动阈值化(比固定220更适配不同图像) th, im_th = cv2.threshold(im_in, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 开运算:消除小噪点,避免干扰后续分割 kernel = np.ones((2,2), np.uint8) im_preprocessed = cv2.morphologyEx(im_th, cv2.MORPH_OPEN, kernel, iterations=1)
二、针对不同粘连程度的分割方法
方法1:距离变换+分水岭算法(适合重度粘连,比如MENT)
这个方法能精准识别每个字符的核心区域,再通过分水岭算法分割粘连的边缘:
# 距离变换:计算每个白色像素到最近黑色背景的距离,突出字符中心 dist_transform = cv2.distanceTransform(im_preprocessed, cv2.DIST_L2, 5) # 阈值化得到字符核心区域(种子点) ret, sure_fg = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) # 确定背景区域 sure_bg = cv2.dilate(im_preprocessed, kernel, iterations=3) # 找到粘连的边缘未知区域 unknown = cv2.subtract(sure_bg, sure_fg) # 标记种子点 ret, markers = cv2.connectedComponents(sure_fg) # 背景标记为1(避免和未知区域的0混淆) markers = markers + 1 markers[unknown == 255] = 0 # 应用分水岭分割 markers = cv2.watershed(cv2.cvtColor(im_in, cv2.COLOR_GRAY2BGR), markers) # 分割线会被标记为-1,这里用红色显示分割线(可选) im_in[markers == -1] = [255, 0, 0] # 提取每个字符的轮廓并分割 contours, _ = cv2.findContours(sure_fg.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) # 裁剪单个字符 single_char = im_th[y:y+h, x:x+w] cv2.imshow('Single Character', single_char) cv2.waitKey(0) cv2.destroyAllWindows()
方法2:垂直投影分割(适合轻度粘连,比如IN)
通过计算每列的白色像素数量,找到背景空白列作为分割线,操作更简单:
# 计算垂直投影:每列的白色像素总和 vertical_proj = np.sum(im_preprocessed, axis=0) # 寻找分割点:连续的背景列(像素总和为0) split_points = [] in_blank = False for col_idx, pixel_sum in enumerate(vertical_proj): if pixel_sum == 0 and not in_blank: split_points.append(col_idx) in_blank = True elif pixel_sum != 0: in_blank = False # 补充图像左右边界,确保覆盖所有字符 split_points = [0] + split_points + [im_preprocessed.shape[1]] # 遍历分割点,裁剪每个字符 for i in range(len(split_points)-1): start_col = split_points[i] end_col = split_points[i+1] # 过滤过窄的无效区域 if end_col - start_col < 5: continue single_char = im_th[:, start_col:end_col] cv2.imshow('Single Character', single_char) cv2.waitKey(0) cv2.destroyAllWindows()
三、实用小建议
- 阈值参数:如果固定阈值效果不好,用
cv2.THRESH_OTSU自动计算阈值(代码里已经加了),适配不同光照的图像 - 核大小:根据字符的实际尺寸调整形态学核的大小,比如小字符用(2,2),大字符用(3,3),避免过度变形
- 轮廓筛选:可以通过轮廓的面积、宽高比过滤掉噪点轮廓,只保留符合字符大小的区域
内容的提问来源于stack exchange,提问作者Menaga




