当前位置: 首页 > web >正文

Tesseract OCR之基线拟合和单词检测

1. 基线拟合(Baseline Fitting)

目标

找到文本行的虚拟基准线(如英文中字母底部对齐的线),即使字符存在断裂、倾斜或噪声干扰。

(1) 行提取(Line Extraction)
  • 水平投影峰值检测
    对二值图像逐行计算黑色像素数,通过寻找局部最大值定位文本行粗位置。
    h_proj = np.sum(binary_img, axis=1)  # 假设文字为黑色(0),背景为白色(255)
    peaks = []
    for i in range(1, len(h_proj)-1):if h_proj[i] < h_proj[i-1] and h_proj[i] < h_proj[i+1]:peaks.append(i)  # 局部极小值(行间空白)
    
  • 聚类优化(解决波峰不清晰问题):
    使用DBSCAN对字符blob的纵坐标聚类,自动合并同一行的blob。
    from sklearn.cluster import DBSCAN
    y_centers = [y + h/2 for (x,y,w,h) in blobs]  # 所有blob的纵向中心
    clustering = DBSCAN(eps=5, min_samples=3).fit(np.array(y_centers).reshape(-1,1))
    line_labels = clustering.labels_  # 同一标签的blob属于同一行
    
    (2) 基线计算
  • 下边界中位数法
    对每行的所有字符blob,取其底部坐标(y_blob + height_blob)的中位数作为基线初始值。
    line_bottoms = [y + h for (x,y,w,h) in blobs if label == line_num]
    baseline_y = np.median(line_bottoms)  # 中位数抗噪声
    
  • 最小二乘法拟合直线
    若文本行有倾斜,用所有blob底部点拟合直线 y = kx + b
    bottoms = np.array([(x + w/2, y + h) for (x,y,w,h) in blobs])  # (x, y_bottom)
    A = np.vstack([bottoms[:,0], np.ones(len(bottoms))]).T
    k, b = np.linalg.lstsq(A, bottoms[:,1], rcond=None)[0]  # 解最小二乘
    

场景示例

假设有一行歪歪扭扭的手写英文(模拟真实OCR场景):

"hello world" 的实际二值图像:h   e   l   l   o       w   o   r   l   d
(每个字母的坐标和宽高如下表)
1. 字符Blob数据(模拟检测结果)
字母x (左侧)y (顶部)宽度 (w)高度 (h)底部 (y + h)
h10581217
e20771017
l29341417
l35641319
o4187917
w55291416
o66571217
r7596817
l83441418
d89671117

第一步:行提取(Line Extraction)

目标:确认哪些Blob属于同一行(本例只有一行,省略DBSCAN聚类)。


第二步:基线拟合(Baseline Fitting)

方法1:中位数法
  1. 取所有字母的底部坐标:
    [17, 17, 17, 19, 17, 16, 17, 17, 18, 17]
  2. 计算中位数:排序后第5/6个值是17基线y=17

问题:字母l的底部是19,导致基线偏高(不准确)。

方法2:最小二乘法拟合直线
  1. 计算每个字母的参考点(取blob底部中心):
    points = [(10 + 8/2, 17),  # h → (14,17)(20 + 7/2, 17),  # e → (23.5,17)(29 + 4/2, 17),  # l → (31,17)(35 + 4/2, 19),  # l → (37,19) ← 异常点(41 + 7/2, 17),  # o → (44.5,17)(55 + 9/2, 16),  # w → (59.5,16)... 
    ]
    
  2. 用最小二乘拟合直线 y = kx + b
    • 输入:所有(x, y_bottom)
    • 输出:假设拟合结果为 y = -0.02x + 17.8
      (一条轻微下斜的线,更贴合实际)

2. 单词检测(Word Segmentation)

核心问题

如何区分字符间间隙(同一单词)与单词间间隙?

解决方案
(1) 固定间距文本(如打字机字体)
  • 直接均匀切分:若字符宽度标准差 < 阈值(如平均宽度的10%),则按固定间隔切分。
    char_widths = [w for (x,y,w,h) in blobs]
    if np.std(char_widths) / np.mean(char_widths) < 0.1:words = np.split(blobs, len(blobs) // avg_char_width)
    
(2) 非固定间距文本(常见情况)
  • 归一化间隙计算
    对相邻blob,计算水平间隙与平均字符宽的比值:
    dnorm=xnext−(xprev+wprev)avg_width d_{\text{norm}} = \frac{x_{\text{next}} - (x_{\text{prev}} + w_{\text{prev}})}{\text{avg\_width}} dnorm=avg_widthxnext(xprev+wprev)

    def calc_gaps(blobs):blobs.sort(key=lambda b: b[0])  # 按x排序gaps = []for i in range(len(blobs)-1):gap = blobs[i+1][0] - (blobs[i][0] + blobs[i][2])gaps.append(gap / np.mean([b[2] for b in blobs]))return gaps
    
  • 动态阈值切分
    d_norm > threshold(通常1.3~2.0),视为单词分界:

    gaps = calc_gaps(blobs)
    word_boundaries = [i+1 for i, gap in enumerate(gaps) if gap > 1.5]
    words = np.split(blobs, word_boundaries)
    
  • 动态阈值选择技巧

  • 双峰法:对间隙分布直方图找两个峰(字符内间隙/单词间间隙),取谷底作为阈值。

场景示例

1. 计算相邻字母间隙
字母对前字母右边界 (x + w)后字母左边界 (x)间隙宽度
h-e10 + 8 = 18202
e-l20 + 7 = 27292
l-l29 + 4 = 33352
l-o35 + 4 = 39412
o-w41 + 7 = 48557
w-o55 + 9 = 64662
2. 归一化间隙宽度
  • 平均字符宽度:avg_width = (8+7+4+4+7+9+7+6+4+7)/10 ≈ 6.3
  • 归一化间隙:
    • o-w间隙 = 7 / 6.3 ≈ 1.11
    • 其他间隙 ≈ 2 / 6.3 ≈ 0.32
3. 动态阈值分割

假设设定阈值 >1.0 为单词间隔:

  • o-w间隙=1.11 > 1.0 → 在此处切分
  • 其他间隙均小于阈值 → 不切分

最终分割结果
[h e l l o][w o r l d] (成功分离两个单词)

http://www.xdnf.cn/news/18915.html

相关文章:

  • 从0开始学习Java+AI知识点总结-26.web实战(Springboot原理)
  • Linux服务器安全配置与NTP时间同步
  • 【Python系列】Flask 和 FastAPI对比
  • 【深度学习新浪潮】SAM 2实战:Meta新一代视频分割模型的实时应用与Python实现
  • Boris FX Samplitude Suite 2025.0.0 音频录制/编辑和母带处理
  • springcloud篇5-微服务保护(Sentinel)
  • 数字IC前端设计——前仿篇(VCS,DVE,Verdi)
  • 企业级集群部署gpmall商城:MyCat+ZooKeeper+Kafka 环境部署与商城应用上线流程
  • Linux SSH 基于密钥交换的自动登录原理简介及配置说明
  • 税务岗位职场能力解析与提升路径规划
  • spring全家桶
  • VMware 中 Ubuntu 右上角网络图标消失的 5 种终极修复方案
  • Android 中使用开源库 ZXing 生成二维码图片
  • Android 播放MP4格式,大视频 几个小时的视频点击快进键视频进度会倒退一秒
  • 基于 Elasticsearch 解决分库分表查询难题
  • MySQL 索引:结构、对比与操作实践指南
  • OpenAI o1:OpenAI最新推出的AI大语言模型,更擅长推理也更贵
  • 使用JDK11标准 实现 图数据结构的增删查改遍历 可视化程序
  • Linux应急响应一般思路(三)
  • Vulkan 学习路线图
  • 【机器学习】(11) --回归树算法
  • 算法题打卡力扣第167题:两数之和——输入有序数组(mid)
  • AMH和cyberpanel等管理软件,哪个里面可以部署AI软件?
  • week4-[二维数组]平面上的点
  • 文件读取结束的判定方法:正确使用feof函数避免文件读取错误
  • 代码随想录算法训练营30天 | ​​01背包理论基础、416. 分割等和子集
  • Pandas 高效数据处理:apply、向量化与分组
  • Android用Coil 3检查媒体资源是否有效,Kotlin
  • LeetCode 面试经典 150_双指针_验证回文串(25_125_C++_简单)(双指针)
  • 基于多通道同步分析的智能听诊系统应用程序