尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PaddleOCR自定义训练实战:从数据标注到模型部署全流程

PaddleOCR自定义训练实战:从数据标注到模型部署全流程 1. 为什么我要自己训练OCR模型OCR这东西用现成的接口和开源模型跑个通用场景确实够用但一旦落到具体业务里通用模型的表现往往让人抓狂。我最早接触PaddleOCR是因为手头有一批工业质检报告需要批量结构化表格线密集、字体是那种老式针式打印机的点阵字还夹杂手写批注。拿开源预训练模型直接推理数字“0”和“8”混淆率超过15%小数点经常被吞掉整个流程跑下来人工复核的工作量比手动录入还大。后来我花了两周时间从零训练了一个针对该场景的检测识别模型识别准确率从82%拉到了97%以上单张推理耗时控制在300毫秒以内。这篇文章就是把这套流程完整拆开从环境搭建、数据标注、格式转换、配置文件修改、训练调参到推理部署每一步都配上可运行的代码和踩坑记录。适合已经了解Python基础、用过PaddleOCR做推理但没自己训练过模型的开发者也适合需要针对特定场景定制OCR能力的算法工程师。PaddleOCR是百度飞桨生态下的OCR工具库覆盖文字检测、方向分类、文字识别三个核心模块支持中英文、数字、多语言混合场景。它的优势在于模型轻量、部署链路完整、中文场景优化到位而且训练配置相对友好不需要从零写训练框架。但“相对友好”不等于“无脑跑通”数据格式、配置文件、学习率策略这几块如果没搞明白训练出来的模型大概率还不如预训练权重。下面我按实际项目推进的顺序来讲先讲整体设计思路再拆数据、配置、训练、推理四个环节最后把常见报错和排查方法整理成速查表。2. 整体方案设计与技术选型思路2.1 检测识别两阶段架构的取舍PaddleOCR默认走的是“检测方向分类识别”三段式流水线。检测模型负责把图中的文字区域框出来方向分类判断文字是否旋转180度识别模型再把每个文本框里的内容转成字符序列。这个架构的好处是模块解耦你可以单独替换检测或识别模型也可以根据场景决定要不要方向分类。我一开始想过用端到端的方案比如直接用一个模型输出文本位置和内容省去中间环节。但实际评估下来端到端方案在小样本场景下收敛困难而且标注成本更高——你需要标注文本的精确位置和内容对应关系而两阶段方案里检测和识别可以分开标注、分开训练。对于工业质检报告这种版面固定但文字密集的场景两阶段方案的鲁棒性明显更好。检测模型我选的是DBDifferentiable Binarization算法PaddleOCR里对应的是ch_PP-OCRv4_det系列。DB的核心思路是把二值化过程做成可微的让分割网络在训练时就能直接优化文字区域的边界。相比传统的EAST或PSENetDB在弯曲文本和密集小字上的表现更稳而且后处理简单阈值一调就能控制框的松紧程度。识别模型选的是CRNNCTC架构PaddleOCR里对应ch_PP-OCRv4_rec。CRNN用CNN提取特征序列RNN建模字符间的上下文关系CTC解决不定长序列对齐问题。这套组合在中文识别上已经非常成熟训练时不需要字符级对齐标注只需要整行文本的转录结果标注成本低。2.2 预训练权重到底要不要用这个问题我被问过很多次。我的建议是一定要用但要知道怎么用。PaddleOCR提供的预训练权重是在大规模通用数据上训出来的特征提取层已经学到了边缘、纹理、笔画这些底层特征。你用自己的数据微调时底层特征可以冻结或者用很小的学习率主要更新的是高层语义部分。我试过从随机初始化开始训同样数据量下收敛轮次多了三倍而且最终精度还差了两个点。后来改成加载预训练权重、检测模型学习率设0.001、识别模型设0.0005收敛速度和最终指标都明显更好。这里的关键是学习率不能设太大否则预训练学到的特征会被冲掉也就是所谓的“灾难性遗忘”。2.3 数据量到底要多少才够网上有人说几百张就能训有人说要几万张。我的经验是检测模型对数据量要求相对低500到1000张标注图就能出一个可用的模型识别模型对数据量要求高因为你要覆盖所有可能出现的字符组合建议至少准备5000到10000条文本行样本。但数据量不是唯一指标多样性更重要。我第一版识别模型只用了3000条样本但覆盖了不同字体、不同字号、不同模糊程度的文本行效果比后来用10000条单一字体样本训出来的还好。所以标注的时候要有意识地覆盖正常字、加粗字、倾斜字、模糊字、断裂字、粘连字每种情况都要有。3. 训练环境搭建与依赖安装3.1 硬件与系统环境选择训练OCR模型对显存有一定要求。检测模型训练时输入图像分辨率通常设为960×960batch size设8的话显存占用大概在6到8GB。识别模型输入高度32、宽度320batch size设64的话显存占用在4到6GB。所以一张8GB显存的卡可以跑检测但识别模型想跑大batch最好有12GB以上。我自己的训练机是Ubuntu 20.04 RTX 3060 12GBCUDA 11.7cuDNN 8.4。这个配置跑检测和识别都够用训练一个识别模型大概需要6到8小时10万轮左右。如果只有CPU训练会非常慢不建议。Windows环境也能跑但数据加载和编译依赖容易出问题有条件尽量用Linux。3.2 PaddlePaddle GPU版本安装安装PaddlePaddle GPU版本是第一步也是最容易卡住的一步。官方命令会根据CUDA版本和系统自动生成但实际安装时经常遇到版本不匹配的问题。我的做法是先确认CUDA版本nvcc --version然后去PaddlePaddle官网找到对应版本的安装命令。以CUDA 11.7为例python -m pip install paddlepaddle-gpu2.5.1.post117 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html安装完成后验证import paddle paddle.utils.run_check()如果输出“PaddlePaddle is installed successfully!”并且能看到GPU信息说明安装成功。如果报错找不到CUDA库检查LD_LIBRARY_PATH是否包含CUDA的lib64目录。注意不要混用conda安装的cudatoolkit和系统CUDA版本冲突会导致训练时随机崩溃。建议统一用系统CUDAconda环境只装Python依赖。3.3 PaddleOCR源码克隆与依赖安装不要直接用pip install paddleocr那个是推理包训练需要克隆源码git clone https://github.com/PaddlePaddle/PaddleOCR.git cd PaddleOCR pip install -r requirements.txtrequirements.txt里的依赖比较多安装过程中如果遇到opencv-python编译问题可以换成opencv-python-headless。另外注意numpy版本PaddlePaddle 2.5.x对numpy 1.24以上版本支持有问题建议锁定numpy1.23.5。安装完成后进入PaddleOCR目录运行一个简单的推理测试python tools/infer/predict_system.py --image_dir./doc/imgs/11.jpg --det_model_dir./ch_PP-OCRv4_det_infer/ --rec_model_dir./ch_PP-OCRv4_rec_infer/如果能看到识别结果输出说明环境基本没问题。4. 数据集制作与标注规范4.1 检测数据标注用PPOCRLabel还是LabelImg检测模型的标注格式是“图像路径 文本框坐标”坐标格式是四个点的x,y值按顺时针排列。标注工具我推荐PPOCRLabel它是PaddleOCR官方出的半自动标注工具内置了预训练模型可以自动预标注文本框你只需要修正和补充。安装PPOCRLabelpip install PPOCRLabel PPOCRLabel --lang ch打开后导入图片目录点击“自动标注”模型会先把可能的文字区域框出来。你手动调整框的位置然后在右侧输入框里填写该区域的文本内容。注意检测标注只需要框位置文本内容是为识别模型准备的但PPOCRLabel会同时保存两者方便后续生成识别数据。如果场景特别复杂比如文字和背景对比度极低自动标注效果差那就用LabelImg手动拉框。LabelImg的标注格式是Pascal VOC XML需要写脚本转成PaddleOCR的检测格式。转换脚本我放在下一节。4.2 识别数据标注文本行裁剪与转录识别模型的标注格式是“图像路径 制表符 文本内容”每行一条。图像是裁剪好的文本行图片高度统一缩放到32像素宽度按比例缩放但不超过320像素。从检测标注生成识别数据可以用PPOCRLabel的导出功能它会自动裁剪文本框并生成识别标注文件。但自动裁剪有个问题框的松紧程度会影响识别效果。框太紧会切掉笔画边缘框太松会引入背景噪声。我的做法是导出后在裁剪时向外扩5个像素同时做一次灰度化和对比度增强。如果已经有检测标注文件det_train.txt可以用下面的脚本批量裁剪import cv2 import os import numpy as np def crop_text_regions(image_path, label_path, output_dir): img cv2.imread(image_path) with open(label_path, r, encodingutf-8) as f: lines f.readlines() for idx, line in enumerate(lines): parts line.strip().split(\t) if len(parts) 2: continue coords list(map(int, parts[0].split(,))) text parts[1] pts np.array(coords).reshape(-1, 2) x_min, y_min pts.min(axis0) x_max, y_max pts.max(axis0) pad 5 x_min max(0, x_min - pad) y_min max(0, y_min - pad) x_max min(img.shape[1], x_max pad) y_max min(img.shape[0], y_max pad) crop img[y_min:y_max, x_min:x_max] crop cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) crop cv2.resize(crop, (320, 32)) out_name f{os.path.basename(image_path).split(.)[0]}_{idx}.jpg cv2.imwrite(os.path.join(output_dir, out_name), crop)这个脚本会把每个文本框裁剪出来统一缩放到320×32保存为单独图片。然后生成识别标注文件with open(rec_train.txt, w, encodingutf-8) as f: for img_name in os.listdir(output_dir): text img_name_to_text[img_name] f.write(f{os.path.join(output_dir, img_name)}\t{text}\n)4.3 数据增强策略什么时候该增强什么时候不该PaddleOCR内置了多种数据增强在配置文件里可以开关。我的经验是检测模型适合用随机缩放、随机裁剪、颜色抖动识别模型适合用随机模糊、随机噪声、轻微透视变换。但增强不是越多越好过度增强会让模型学到错误的特征。比如识别模型如果加了大幅度的旋转增强模型会花大量容量去学旋转不变性但实际场景中文本行基本都是水平的这些容量就浪费了。我一般只开随机模糊模拟拍摄模糊和随机亮度调整模拟光照变化其他都关掉。注意数据增强只在训练时生效验证和推理时一定要关掉。PaddleOCR的配置文件里Train和Eval的transforms是分开的别改错了地方。5. 配置文件修改与训练参数设置5.1 检测模型配置文件详解检测模型的配置文件在configs/det/目录下我基于ch_PP-OCRv4_det_student.yml修改。关键参数如下Global: pretrained_model: ./pretrain_models/ch_PP-OCRv4_det_train/best_accuracy save_model_dir: ./output/det_custom/ epoch_num: 500 save_epoch_step: 50 eval_batch_step: [0, 200] Train: dataset: data_dir: ./train_data/det/ label_file_list: [./train_data/det/train.txt] transforms: - DecodeImage: {img_mode: BGR, channel_first: false} - DetLabelEncode: {} - RandomScale: {scale_range: [0.5, 2.0]} - RandomCrop: {size: [960, 960]} - MakeShrinkMap: {min_text_size: 8, shrink_ratio: 0.4} - NormalizeImage: {scale: 1./255., mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225]} - ToCHWImage: {} loader: batch_size_per_card: 8 num_workers: 4 Optimizer: lr: learning_rate: 0.001 decay: function: cosine warmup_epoch: 5pretrained_model指向预训练权重目录里面要有best_accuracy.pdparams文件。epoch_num设500是因为我的数据量不大800张需要多跑几轮。如果数据量上万200轮就够了。RandomScale的scale_range设[0.5, 2.0]是让模型适应不同尺度的文字。MakeShrinkMap的shrink_ratio控制文本框收缩比例0.4是经验值太小会导致框重叠太大会漏检小字。学习率用cosine衰减warmup 5轮。检测模型的学习率可以设大一点0.001是安全的起点。如果训练loss震荡厉害降到0.0005。5.2 识别模型配置文件详解识别模型配置文件在configs/rec/目录下基于ch_PP-OCRv4_rec.yml修改Global: pretrained_model: ./pretrain_models/ch_PP-OCRv4_rec_train/best_accuracy save_model_dir: ./output/rec_custom/ epoch_num: 300 character_dict_path: ./ppocr/utils/ppocr_keys_v1.txt Train: dataset: data_dir: ./train_data/rec/ label_file_list: [./train_data/rec/train.txt] transforms: - DecodeImage: {img_mode: BGR, channel_first: false} - RecAug: {} - CTCLabelEncode: {} - RecResizeImg: {image_shape: [3, 32, 320]} - KeepKeys: {keep_keys: [image, label, length]} loader: batch_size_per_card: 64 num_workers: 4 Optimizer: lr: learning_rate: 0.0005 decay: function: cosine warmup_epoch: 3character_dict_path是字符字典默认的ppocr_keys_v1.txt包含6623个中文字符、数字和标点。如果你的场景有特殊符号比如工业符号、数学符号需要自己扩充字典。扩充方法是在文件末尾追加字符每行一个然后重新训练。RecAug是识别模型的数据增强包含模糊、噪声、颜色抖动。如果训练loss下降很慢可以暂时关掉等loss稳定后再开。RecResizeImg的image_shape是[3, 32, 320]表示输入图像高度32、宽度320。如果你的文本行特别长比如整行发票号可以把宽度设大一点比如[3, 32, 640]但显存占用会翻倍。5.3 学习率与batch size的配合关系学习率和batch size是联动的。经验公式是lr base_lr * sqrt(batch_size / base_batch_size)。PaddleOCR默认检测batch size是8识别是64。如果你显存不够要降batch size学习率也要按比例降。我试过检测batch size从8降到4学习率从0.001降到0.0007最终精度只差了0.3个点。但如果batch size降了学习率没降训练会非常不稳定loss经常跳变。注意训练初期如果loss出现NaN先检查学习率是不是太大再检查数据里有没有空标签或非法字符。空标签会导致CTC loss计算异常。6. 启动训练与过程监控6.1 检测模型训练命令与日志解读启动检测训练python tools/train.py -c configs/det/ch_PP-OCRv4_det_student.yml -o Global.pretrained_model./pretrain_models/ch_PP-OCRv4_det_train/best_accuracy训练日志会输出loss、学习率、当前epoch和评估指标。检测模型关注loss和hmeanF1分数。正常情况loss从1.0左右开始下降500轮后降到0.1以下hmean从0.6升到0.9以上。如果loss下降很慢检查数据标注质量。我遇到过标注框严重偏移的情况loss卡在0.8下不去重新标注后正常。如果hmean震荡可能是学习率太大或者batch size太小。6.2 识别模型训练命令与准确率曲线启动识别训练python tools/train.py -c configs/rec/ch_PP-OCRv4_rec.yml -o Global.pretrained_model./pretrain_models/ch_PP-OCRv4_rec_train/best_accuracy识别模型关注acc字符级准确率和norm_edit_dis编辑距离。正常情况acc从0.5左右开始上升300轮后到0.95以上。如果acc卡在0.8上不去大概率是字符字典不匹配——训练数据里出现了字典里没有的字符这些字符会被忽略导致模型学不到。检查方法遍历训练标注文件找出不在字典里的字符with open(ppocr/utils/ppocr_keys_v1.txt, r, encodingutf-8) as f: dict_chars set(f.read().splitlines()) with open(train_data/rec/train.txt, r, encodingutf-8) as f: for line in f: text line.strip().split(\t)[1] for ch in text: if ch not in dict_chars: print(f未在字典中: {ch})6.3 训练过程可视化与中断恢复PaddleOCR支持用VisualDL可视化训练过程visualdl --logdir ./output/det_custom/ --port 8080浏览器打开http://localhost:8080就能看到loss和指标曲线。如果训练中断可以从保存的checkpoint恢复python tools/train.py -c configs/det/ch_PP-OCRv4_det_student.yml -o Global.checkpoints./output/det_custom/latestlatest文件记录了最新的epoch和优化器状态恢复后从断点继续不会从头开始。7. 模型评估与推理部署7.1 评估指标解读hmean、acc、编辑距离检测模型的hmean是precision和recall的调和平均反映框的准确程度。hmean到0.9以上基本可用0.95以上算优秀。如果precision高但recall低说明漏检多需要调低检测阈值如果recall高但precision低说明误检多需要调高阈值。识别模型的acc是字符级准确率norm_edit_dis是归一化编辑距离。acc到0.95以上、编辑距离到0.98以上说明识别效果很好。如果acc高但编辑距离低说明模型在长文本上容易出错需要增加长文本样本。7.2 导出推理模型与预测训练完成后导出推理模型python tools/export_model.py -c configs/det/ch_PP-OCRv4_det_student.yml -o Global.pretrained_model./output/det_custom/best_accuracy Global.save_inference_dir./inference/det_custom/识别模型同理。导出后会生成inference.pdmodel和inference.pdiparams两个文件部署时只需要这两个文件。预测单张图片python tools/infer/predict_system.py --image_dir./test.jpg --det_model_dir./inference/det_custom/ --rec_model_dir./inference/rec_custom/ --use_gpuTrue如果识别结果乱码检查字符字典路径是否和训练时一致。字典不一致会导致解码错误输出完全无关的字符。7.3 推理速度优化技巧推理速度主要受输入分辨率、batch size和模型精度影响。检测模型输入从960降到640速度提升约40%但小字检测率会下降。识别模型batch size从1增到8吞吐量提升约3倍但单张延迟会增加。如果部署在CPU上可以用OpenVINO加速python tools/infer/predict_system.py --image_dir./test.jpg --det_model_dir./inference/det_custom/ --rec_model_dir./inference/rec_custom/ --use_openvinoTrueOpenVINO在Intel CPU上通常有2到3倍加速。如果部署在GPU上可以用TensorRT但需要额外转换步骤适合对延迟要求极高的场景。8. 常见问题与排查速查表8.1 训练报错与解决方案报错信息可能原因解决方法CUDA out of memorybatch size太大或图像分辨率太高降低batch size或输入尺寸Loss is NaN学习率太大或数据有空标签降低学习率检查标注文件KeyError: xxx字符字典不匹配检查字典文件是否包含所有字符AssertionError: image shape图像通道数或尺寸不对检查图像是否为三通道尺寸是否统一Permission denied文件权限问题用chmod修改权限或换目录8.2 识别效果差的排查思路识别效果差通常有三个原因检测框不准、图像质量差、模型欠拟合。排查顺序是先看检测框是否准确覆盖文字区域如果框偏了先调检测模型如果框准但识别错看裁剪图像是否清晰模糊图像需要做增强如果图像清晰但识别错看训练数据是否覆盖了该字体或字号补充样本重新训练。我遇到过一个案例模型对数字“1”和字母“l”混淆严重。原因是训练数据里数字样本太少模型没学到区分特征。后来专门补充了2000条纯数字样本混淆率从12%降到2%以下。8.3 部署时的坑与规避方法部署时最常见的问题是推理结果和训练时不一致。原因通常是预处理不一致——训练时用了Normalize推理时忘了加或者训练时图像是BGR推理时用了RGB。PaddleOCR的推理脚本默认和训练配置对齐但如果自己写推理代码一定要对照训练配置的transforms逐项检查。另一个坑是模型文件路径。导出推理模型后inference.pdmodel和inference.pdiparams必须在同一目录且文件名不能改。如果改了名推理时会报找不到参数文件。注意如果部署环境没有GPU导出模型时不要指定--use_gpu否则模型会绑定GPU设备CPU环境加载失败。9. 我个人在实际操作中的体会这套流程我前前后后跑了不下十次最大的体会是数据质量决定上限参数调优决定下限。很多人花大量时间调学习率、换优化器但效果提升有限回头一看标注数据里有一堆框偏了、文本标错了。我的做法是每次训练前随机抽100条样本人工检查确认标注质量后再启动训练。另一个体会是不要迷信预训练模型。预训练权重能加速收敛但如果你的场景和通用场景差异极大比如古籍竖排文字、工业点阵字预训练模型的特征反而会成为负担。这种情况下可以先在预训练权重上微调几轮然后解冻所有层从头训效果可能更好。最后分享一个小技巧训练识别模型时可以把检测模型输出的框稍微放大一点再裁剪这样识别模型能看到更多上下文对模糊字符的区分能力更强。我实测下来框放大5%到10%能让准确率提升1到2个点代价是推理时裁剪区域变大速度略降。这个取舍可以根据实际场景的精度要求来定。
返回列表