CRNN + CTC OCR 原理详解
本文面向 OCR 模型部署、轻量化文本识别、ONNX/MNN/C 推理落地等工程场景系统说明 CRNN CTC 的核心原理、网络结构、训练方式、解码流程、工程部署要点与适用场景。开源代码:crnn_ctc_ocr展示 OCR 模型从训练、导出、验证到 C/MNN 端侧部署的完整工程化流程。手戳代码已开源欢迎点击五角星。创作不易欢迎关注评论感谢1. 动态流程展示下图展示了 CRNN CTC OCR 从输入文本行图像到最终文本输出的完整过程整体流程可以概括为输入文本行图像 ↓ CNN 提取局部视觉特征 ↓ 按图像宽度方向展开为特征序列 ↓ BiLSTM / 序列模型进行上下文建模 ↓ CTC Head 输出每个时间步的字符概率 ↓ CTC Decode 合并重复字符并删除 blank ↓ 输出最终识别文本2. CRNN CTC 的定位CRNN CTC 是经典 OCR 文本识别模型常用于车牌识别、票据字段识别、工业喷码识别、仪表数字识别、商品标签识别、轻量化端侧 OCR、视频帧字幕或 HUD 文字识别等场景。CRNN 负责将图像转为序列特征CTC 负责解决“图像特征时间步”和“真实字符序列”之间无法直接对齐的问题。3. 为什么需要 CTC传统 OCR 常见思路是图像 → 字符切割 → 单字符分类 → 拼接文本但是字符切割在真实场景中非常困难。例如字符间距不固定字符可能粘连字符可能倾斜、模糊、断裂中文字符笔画复杂英文中的rn、m、ll等容易混淆工业喷码、票据、车牌字符常有噪声和变形。CTC 的核心优势是训练时只需要整行文本标签不需要逐字符位置标注。例如输入图像的标签是OCR2026模型不需要知道O、C、R、2、0、2、6分别位于图像第几列。CTC 会在训练过程中自动学习可能的对齐关系。4. CRNN 网络结构详解CRNN 通常由三部分组成CNN Backbone Sequence Encoder CTC Classification Head也可以写成图像特征提取层 序列建模层 字符分类层5. 输入图像预处理OCR 文本行识别通常会将输入图像归一化为固定高度例如H 32 或 48 W 根据文本长度自适应 C 1 或 3常见输入尺寸形式32 × W × 1 32 × W × 3 48 × W × 3高度固定宽度可变。这样模型可以处理不同长度的文本行例如OK、OCR2026、DEEPLEARNING、商品编号A20260604。在实际部署时为了方便 batch 推理常会将图像 resize 到固定高度再对宽度进行 padding。6. CNN 特征提取CNN 的作用是提取字符局部视觉特征例如边缘、笔画、拐角、局部纹理、字符结构等。输入图像Input: B × C × H × W经过 CNN 后得到特征图Feature Map: B × C × H × W在 CRNN 中通常会通过卷积和池化逐步压缩高度方向使得高度变为 1 或接近 1B × C × 1 × W此时宽度方向的每一列可以视为一个时间步x1, x2, x3, ..., xT于是二维图像被转换成一维序列X [x1, x2, x3, ..., xT]这是 CRNN 的关键思想将 OCR 图像识别问题转换为序列识别问题。7. 宽度方向为什么可以看作时间步对于文本行图像字符通常沿水平方向排列。图像左侧对应较早的字符图像右侧对应较后的字符。例如图像O C R 2 0 2 6 方向左 → 右CNN 输出的特征图宽度方向天然对应文本阅读顺序因此可以把每一列特征当成序列中的一个时间步。示意如下Feature Map: C × 1 × T 第 1 列 → x1 第 2 列 → x2 第 3 列 → x3 ... 第 T 列 → xT8. 序列建模层RNN / BiLSTMCNN 提取的是局部视觉特征但 OCR 识别需要上下文。例如0 和 O 很像 1 和 l 很像 S 和 5 很像 B 和 8 很像 rn 和 m 很像如果只看局部图像模型可能难以判断。BiLSTM 可以同时利用左侧和右侧上下文信息Forward LSTM: 左 → 右 Backward LSTM: 右 → 左因此每个时间步的输出都包含上下文信息。输入x1, x2, ..., xT输出h1, h2, ..., hT其中每个ht都融合了当前区域及其左右上下文的信息。9. CTC Head 字符分类序列建模后每个时间步都会输出一个隐藏状态ht。通过全连接层映射到字符类别空间yt Linear(ht)输出维度为num_classes 字符类别数 1额外的1是 CTC 的特殊符号blankblank 表示当前时间步不输出任何字符。例如字符集为0-9 A-Z那么类别可以设计为blank, 0, 1, 2, ..., 9, A, B, ..., Z通常 blank 放在 index 0方便工程实现。10. CTC 的核心思想模型每个时间步都会输出一个字符概率分布t1: P(blank), P(O), P(C), ... t2: P(blank), P(O), P(C), ... t3: P(blank), P(O), P(C), ... ... tT: P(blank), P(O), P(C), ...由于T通常大于真实文本长度L模型会输出一个较长的路径然后通过 CTC 规则压缩成最终文本。例如原始路径O O blank C blank R 2 0 blank 2 6CTC 解码规则1. 合并连续重复字符 2. 删除 blank解码过程O O blank C blank R 2 0 blank 2 6 ↓ 合并连续重复字符 O blank C blank R 2 0 blank 2 6 ↓ 删除 blank O C R 2 0 2 6最终结果OCR202611. blank 符号的作用blank 是 CTC 中非常重要的符号。它主要有两个作用。11.1 表示无字符输出很多时间步可能对应字符之间的空白区域或者只是过渡特征。这些时间步可以输出 blank。例如O blank C blank R blank 2 blank 0 blank 2 blank 6删除 blank 后得到OCR202611.2 区分连续重复字符如果要识别book其中包含连续两个o。如果路径是b o o kCTC 合并连续重复字符后会变成bok为了保留两个o可以使用 blank 分隔b o blank o k删除 blank 后book因此blank 是处理重复字符的关键机制。12. CTC Loss 训练原理训练数据形式输入图像: text_line.jpg 文本标签: OCR2026模型输出T × num_classes但是训练标签只有O C R 2 0 2 6没有每个字符对应的时间步位置。CTC Loss 的做法是枚举所有能够解码为正确标签的路径将它们的概率加起来并最大化这个总概率。可以理解为P(label | image) 所有可行路径概率之和训练目标Loss -log P(label | image)即让正确文本的总概率尽可能大。13. PyTorch 中的 CTC Loss 形式PyTorch 中常用torch.nn.CTCLossimporttorchimporttorch.nnasnn ctc_lossnn.CTCLoss(blank0,reductionmean,zero_infinityTrue)# logits: [B, T, C]# PyTorch CTCLoss 需要 [T, B, C]log_probslogits.log_softmax(dim2).permute(1,0,2)lossctc_loss(log_probs,# [T, B, C]targets,# 拼接后的真实标签input_lengths,# 每个样本的时间步长度 Ttarget_lengths# 每个样本真实文本长度)注意事项log_probs必须是 log-softmax 后的概率输入维度必须是[T, B, C]input_lengths表示每个样本经过 CNN 后的序列长度target_lengths表示真实文本长度targets通常是 batch 内所有标签拼接后的 1D 张量。14. CTC 解码方式CTC 常见解码方式有两种Greedy Decode Beam Search Decode15. Greedy DecodeGreedy Decode 是最简单的解码方式每个时间步选择概率最大的字符例如t1: O t2: O t3: blank t4: C t5: blank t6: R t7: 2 t8: 0 t9: blank t10: 2 t11: 6得到原始路径O O blank C blank R 2 0 blank 2 6然后执行合并连续重复字符 → 删除 blank最终输出OCR2026Greedy Decode 优点实现简单、推理速度快、适合端侧部署、适合实时 OCR。缺点只取局部最优对相似字符、低质量图像不够稳无法很好利用语言约束。16. Beam Search DecodeBeam Search 会在每个时间步保留多个候选路径而不是只保留概率最大的一个。例如保留候选OCR2026 0CR2026 OCR2O26 OCR2Q26然后综合路径概率选择最优输出。如果结合语言模型或业务字典可以进一步提高准确率。适用场景包括票据字段识别、车牌识别、固定格式编号识别、商品条码附近文字识别、英文单词识别、低质量图像识别等。17. 完整训练流程1. 准备文本行图像和对应字符串标签 2. 构建字符字典 char_dict 3. 将字符串标签编码为数字序列 4. 图像 resize / padding / normalize 5. 输入 CRNN 模型 6. CNN 提取视觉特征 7. 特征图按宽度方向展开为序列 8. BiLSTM 建模上下文 9. Linear Head 输出每个时间步字符概率 10. 使用 CTC Loss 计算损失 11. 反向传播更新模型18. 完整推理流程1. 输入待识别图像 2. 图像预处理 3. 输入 CRNN 模型 4. 得到 logits 5. softmax 得到概率 6. 每个时间步取最大概率字符 7. 合并连续重复字符 8. 删除 blank 9. 得到最终文本伪代码defctc_greedy_decode(pred_ids,blank_id0):result[]lastNoneforidxinpred_ids:ifidx!blank_idandidx!last:result.append(idx)lastidxreturnresult19. 模型结构示例一个典型 CRNN 结构如下Input: 1 × 32 × W CNN: Conv BN ReLU MaxPool Conv BN ReLU MaxPool Conv BN ReLU MaxPool Feature: C × 1 × T Sequence: Permute → T × B × C BiLSTM BiLSTM Head: Linear(hidden, num_classes) Output: T × B × num_classes20. 优点与局限性20.1 优点不需要字符级标注支持变长文本模型结构清晰推理速度快容易导出 ONNX适合 TensorRT、MNN、NCNN 等部署工程实现成熟对规则文本行识别效果稳定。20.2 局限性对弯曲文字识别能力较弱对强透视畸变文本识别能力有限对复杂二维排版不适合对长文本上下文建模不如 TransformerCTC 假设各时间步之间条件独立表达能力有限容易混淆相似字符。常见混淆O / 0 I / 1 / l S / 5 B / 8 rn / m cl / d21. 适用场景分析21.1 适合场景单行文本识别 规则文本识别 车牌识别 数字识别 工业字符识别 票据关键字段识别 低算力端侧 OCR 移动端 OCR 嵌入式 OCR C 实时推理21.2 不适合场景复杂文档版面理解 表格结构还原 公式识别 多行自然段识别 弯曲艺术字识别 复杂自然场景长文本识别这些场景更适合使用 SVTR、SAR、ASTER、ABINet、SATRN、TrOCR、PARSeq、Vision Transformer OCR 等方法。22. 和 Attention OCR / Transformer OCR 的区别方案核心思想优点缺点CRNN CTC帧级分类 CTC 对齐简单、快速、易部署上下文能力有限Attention OCREncoder Decoder Attention序列建模更灵活推理较慢部署复杂Transformer OCR全局注意力建模准确率高长文本能力强算力需求较高ABINet视觉模型 语言模型迭代纠错语义纠错能力强模型复杂PARSeqPermuted autoregressive decoding准确率高端侧部署成本较高23. 工程部署建议23.1 轻量化 Backbone 选择端侧部署推荐MobileNetV3 ShuffleNetV2 RepVGG-lite PP-LCNet ResNet-lite SVTR-tiny23.2 序列层选择如果追求精度可以使用 BiLSTM。如果追求部署简单可以考虑1D Conv Transformer Encoder Lite SVTR Block部分部署框架对 LSTM 支持不够友好因此在 MNN、NCNN、TensorRT 场景中可以考虑用卷积或轻量 Transformer 替代 BiLSTM。24. ONNX / MNN 部署注意事项24.1 导出建议导出 ONNX 时建议固定输入高度允许动态宽度尽量避免复杂控制流解码逻辑放在后处理端实现模型只输出 logits 或 log_probsCTC Decode 在 C 侧实现。推荐模型输出logits: [B, T, C]C 后处理执行argmax → merge repeated → remove blank → map id to char24.2 C 后处理伪代码std::stringctc_greedy_decode(conststd::vectorintpred_ids,conststd::vectorstd::stringcharset,intblank_id0){std::string result;intlast-1;for(intid:pred_ids){if(id!blank_idid!last){resultcharset[id];}lastid;}returnresult;}中文字符建议使用std::string保存 UTF-8 字符串列表避免直接按char拼接导致乱码。25. 字符集设计建议字符集需要与业务场景匹配。25.1 数字场景blank 0-9适合仪表读数、金额识别、计分板识别、编号识别。25.2 英文数字场景blank 0-9 A-Z a-z适合车牌、商品编码、快递单号、设备序列号。25.3 中文场景blank 常用汉字 数字 英文 标点适合票据、合同、商品标签、中文场景文本。字符集越大分类难度越高模型输出层越大训练数据需求也越高。26. 数据增强建议CRNN CTC 对图像质量较敏感因此训练时建议加入数据增强随机模糊 随机亮度 随机对比度 随机噪声 随机透视 轻微旋转 文字拉伸 背景干扰 JPEG 压缩 随机遮挡对于工业 OCR还可以增加喷码断裂 字符腐蚀 字符膨胀 低对比度 反光 运动模糊27. 常见问题与优化策略27.1 相似字符混淆问题O / 0 I / 1 / l S / 5 B / 8优化增加混淆样本 使用 Beam Search 加入业务字典 增加语言模型 提高输入分辨率 增强图像清晰度27.2 文本过长识别不完整原因输入宽度压缩过度 CNN 下采样过强 T 小于标签长度优化减少宽度方向下采样 增大输入宽度 检查 input_lengths 过滤异常长标签27.3 CTC Loss 变成 inf常见原因input_length target_length 标签字符不在字典中 blank id 设置错误 log_probs 维度错误解决设置 zero_infinityTrue 检查字典映射 检查 input_lengths 检查 target_lengths 确认 logits.log_softmax(dim2)27.4 中文识别乱码原因C 后处理按 char 拼接 字符集文件编码不是 UTF-8解决字符表逐行读取 UTF-8 字符 使用 std::string 保存每个字符 不要按单字节处理中文28. 推荐部署架构对于轻量化 OCR 部署可以采用如下结构Python 训练 ↓ PyTorch CRNN CTC ↓ 导出 ONNX ↓ ONNXRuntime 验证 ↓ 转换 MNN / NCNN / TensorRT ↓ C 推理 ↓ CTC Greedy Decode 后处理 ↓ 业务规则修正在生产系统中可以进一步加入检测模型 DBNet / YOLO ↓ 文本行裁剪 ↓ CRNN CTC 文本识别 ↓ 后处理规则 ↓ 结构化字段输出29. 与 OCR 检测模型的关系CRNN CTC 通常只负责识别不负责检测文本位置。完整 OCR 系统一般包括文本检测模型 文本识别模型例如DBNet / EAST / CRAFT / YOLO-Text ↓ 文本框裁剪与矫正 ↓ CRNN CTC ↓ 文本输出如果输入已经是裁剪好的文本行图像则可以直接使用 CRNN CTC。30. 总结CRNN CTC 的核心思想是用 CNN 将文本行图像转换成宽度方向的特征序列再用 RNN 或其他序列模型进行上下文建模最后通过 CTC 在没有字符级对齐标注的情况下完成文本序列识别。它的优势是结构简单、部署方便、训练标注成本低、推理速度快适合规则文本行识别。它的不足是复杂版面能力弱、弯曲文本能力弱、语义上下文能力有限、对相似字符较敏感。在端侧 OCR、工业 OCR、车牌识别、票据字段识别、游戏 HUD OCR、商品标签识别等场景中CRNN CTC 仍然是非常实用的基线方案。