You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用CV2检测并提取印刷文本中的非连续字符?

解决非连续字符的提取问题

针对非连续字符(如i、j的上点,:、;的上点)无法被外接轮廓完整提取的问题,用OpenCV可以通过以下两种思路处理,不需要依赖OCR引擎:

方法一:连通域分析+距离阈值合并

这种方法先找出所有独立的连通区域(包括字符主体和附属的点),再通过距离判断将属于同一字符的区域合并,最终得到完整的字符 bounding box。

实现步骤:

  1. 获取所有连通域的统计信息,包括每个区域的坐标、尺寸、中心位置
  2. 筛选出可能是"主体"的连通域(通常面积更大、高度更高)和"附属点"(面积小、高度低)
  3. 计算附属点与每个主体的距离,当距离小于设定阈值时,将附属点的范围合并到主体的 bounding box 中
  4. 去重后,裁剪每个合并后的区域并保存

代码示例:

import cv2
import numpy as np

# 假设已得到二值化图像(黑底白字,若为白底黑字需先反转)
gray_image = cv2.imread("text_img.png", cv2.IMREAD_GRAYSCALE)
binary_image = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY_INV)[1]

# 获取连通域及统计信息
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image, connectivity=8)

# 筛选主体与附属点:主体高度大于平均高度的一半,附属点面积远小于主体
avg_height = np.mean(stats[1:, cv2.CC_STAT_HEIGHT])  # 跳过背景(label=0)
main_components = []
dot_components = []

for i in range(1, num_labels):
    x, y, w, h, area = stats[i]
    if h > avg_height * 0.5:
        main_components.append((x, y, w, h, centroids[i]))
    else:
        dot_components.append((x, y, w, h, centroids[i]))

# 设定距离阈值:取主体平均高度的1/3,可根据字体调整
distance_threshold = avg_height * 0.33

# 合并附属点到对应主体
merged_boxes = []
for main_x, main_y, main_w, main_h, main_centroid in main_components:
    min_x, min_y = main_x, main_y
    max_x, max_y = main_x + main_w, main_y + main_h
    
    for dot_x, dot_y, dot_w, dot_h, dot_centroid in dot_components:
        # 计算中心欧氏距离
        dist = np.linalg.norm(np.array(main_centroid) - np.array(dot_centroid))
        if dist < distance_threshold:
            min_x = min(min_x, dot_x)
            min_y = min(min_y, dot_y)
            max_x = max(max_x, dot_x + dot_w)
            max_y = max(max_y, dot_y + dot_h)
    
    merged_boxes.append((min_x, min_y, max_x - min_x, max_y - min_y))

# 去重避免重复合并
merged_boxes = list(set(merged_boxes))

# 裁剪并保存每个字符
for idx, (x, y, w, h) in enumerate(merged_boxes):
    # 确保坐标在图像范围内
    x = max(0, x)
    y = max(0, y)
    w = min(binary_image.shape[1] - x, w)
    h = min(binary_image.shape[0] - y, h)
    char_img = binary_image[y:y+h, x:x+w]
    # 反转回白底黑字(可选)
    char_img = cv2.threshold(char_img, 127, 255, cv2.THRESH_BINARY_INV)[1]
    cv2.imwrite(f"char_{idx}.png", char_img)

方法二:局部膨胀+轮廓提取

如果文本排列整齐,可对每个字符的垂直区域做小范围横向膨胀,将字符主体和上方的点连接成一个连通区域,再提取轮廓。

实现步骤:

  1. 计算垂直投影,确定每个字符的列范围
  2. 对每个字符列区域做横向膨胀,让点和主体连通
  3. 对膨胀后的区域提取轮廓,得到完整字符的 bounding box

代码示例:

import cv2
import numpy as np

gray_image = cv2.imread("text_img.png", cv2.IMREAD_GRAYSCALE)
binary_image = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY_INV)[1]

# 计算垂直投影:统计每列黑像素数量
vertical_proj = np.sum(binary_image, axis=0)
# 找到有字符的列
char_cols = np.where(vertical_proj > 0)[0]
if len(char_cols) == 0:
    exit()

# 分割字符列:按连续非零投影列分组
char_regions = []
start_col = char_cols[0]
for i in range(1, len(char_cols)):
    if char_cols[i] != char_cols[i-1] + 1:
        char_regions.append((start_col, char_cols[i-1]))
        start_col = char_cols[i]
char_regions.append((start_col, char_cols[-1]))

# 定义横向膨胀核:让点与主体连通(可根据字体调整尺寸)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 1))

# 处理每个字符区域
for idx, (start_x, end_x) in enumerate(char_regions):
    # 裁剪字符列区域
    char_roi = binary_image[:, start_x:end_x+1]
    # 横向膨胀
    dilated_roi = cv2.dilate(char_roi, kernel, iterations=1)
    # 提取最大轮廓(完整字符)
    contours, _ = cv2.findContours(dilated_roi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours:
        max_contour = max(contours, key=cv2.contourArea)
        x, y, w, h = cv2.boundingRect(max_contour)
        final_char = char_roi[y:y+h, x:x+w]
        final_char = cv2.threshold(final_char, 127, 255, cv2.THRESH_BINARY_INV)[1]
        cv2.imwrite(f"char_{idx}.png", final_char)

注意事项:

  • 两种方法都需要根据实际字体大小调整参数(如距离阈值、膨胀核尺寸),可通过统计图像中字符的平均高度/宽度自动适配
  • 若图像有噪声,可先做cv2.medianBlur或cv2.GaussianBlur去噪,避免误识别噪声点为字符附属部分

内容的提问来源于stack exchange,提问作者Terence Eden

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:53:12