1. 为什么选择PaddleOCR做车牌识别第一次接触车牌识别项目时我试过好几种方案。有的开源项目识别率太低有的商业方案又贵得离谱。直到发现PaddleOCR这个宝藏工具实测下来在中文场景下的表现确实稳。它最大的优势在于三点首先是预训练模型丰富开箱就能用其次是自定义灵活从数据到模型都能按需调整最重要的是部署方便从服务器到嵌入式设备都能跑。车牌识别和其他OCR场景有个关键区别——字符集特别固定。国内车牌无非就是那几个省的简称比如京、沪、字母A-Z去掉容易混淆的O和数字0-9。用通用OCR模型就像用宰牛刀杀鸡不仅效率低还容易误识别。PaddleOCR允许我们定制专属字符集这个功能简直是为车牌识别量身定制的。2. 准备专属字符集与数据集2.1 精简字符集实战在ppocr/utils/目录下新建ppocr_keys_car.txt文件内容只要71个字符川鄂甘赣贵桂黑沪吉冀晋津京辽鲁蒙闽宁青琼陕苏皖湘新渝豫粤云藏浙澳港警学使领 0123456789 ABCDEFGHJKLMNPQRSTUVWXYZ这里有个关键细节字母表故意跳过了O。这是我在实际项目中踩过的坑——车牌上的数字0和字母O肉眼几乎分不清。有次系统把粤A0B123识别成粤AOB123导致停车场系统死活找不到对应车辆。后来排查半天才发现是这个原因。2.2 数据集的黄金配比理想的车牌数据集需要满足两个条件真实性和均衡性。我通常采用真实合成的混合方案真实数据约1.5万张用手机在停车场拍摄不同角度、光照的车牌注意覆盖所有省份简称和特殊车牌警车、新能源等合成数据约1.5万张用工具生成带噪声、模糊、倾斜等效果的虚拟车牌合成数据推荐用Python的PIL库实现下面是个生成示例from PIL import Image, ImageDraw, ImageFont import random def generate_plate(text): img Image.new(RGB, (240, 80), (255, 255, 255)) draw ImageDraw.Draw(img) font ImageFont.truetype(simhei.ttf, 60) # 添加随机干扰 for _ in range(20): x random.randint(0, 240) y random.randint(0, 80) draw.point((x,y), fill(random.randint(0,255),)*3) draw.text((10, 5), text, fontfont, fill(0, 0, 0)) return img3. 模型训练全流程详解3.1 配置文件关键参数复制configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml并修改Global: character_dict_path: ppocr/utils/ppocr_keys_car.txt character_type: ch max_text_length: 8 # 国内车牌最长8字符 Train: dataset: label_file_list: - ./train_data/real/train.txt - ./train_data/synthetic/train.txt ratio_list: [1.0, 0.5] # 真实数据权重更高几个容易踩坑的参数max_text_length设太小会截断长车牌设太大会降低短车牌的识别率ratio_list建议真实数据权重设为合成数据的2倍batch_size_per_card显存小于4G的建议调到16或更低3.2 启动训练与监控多GPU训练命令单卡去掉-m参数python -m paddle.distributed.launch --gpus 0,1 tools/train.py \ -c configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0_car.yml \ -o Global.use_visualdlTrue训练过程建议用VisualDL监控visualdl --logdiroutput/vdl_log --port 8080在浏览器打开localhost:8080能看到这些关键指标train/loss降到0.1以下说明模型已收敛train/acc达到0.98就可以考虑提前停止eval/acc验证集准确率应与训练集差距小于3%4. 模型部署与优化技巧4.1 模型导出与测试导出为部署格式python tools/export_model.py \ -c configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0_car.yml \ -o Global.pretrained_modeloutput/rec_chinese_lite_v2.0_car/best_accuracy实测推理代码from paddleocr import PaddleOCR ocr PaddleOCR( rec_model_dirinference/rec_chinese_lite_v2.0_car, rec_char_dict_pathppocr/utils/ppocr_keys_car.txt ) result ocr.ocr(test_plate.jpg, clsFalse) print(result[0][1][0]) # 输出识别结果4.2 性能优化实战在树莓派上部署时发现推理速度慢约2秒/张通过以下调整优化到0.3秒量化压缩paddle_lite_opt --model_fileinference/model.pdmodel \ --param_fileinference/model.pdiparams \ --optimize_outquantized_model \ --quant_typeINT8调整输入尺寸# 修改config中的RecResizeImg RecResizeImg: image_shape: [3, 32, 240] # 原320宽度减到240启用MKLDNN加速ocr PaddleOCR(use_mkldnnTrue)5. 实际业务中的调优经验在停车场项目落地时我们发现夜间识别率骤降。通过分析发现两个问题一是强光反射导致字符反色二是低照度图像噪点多。解决方案是在数据增强中加入颜色反转和随机亮度RecAug: use_color_reverse: True random_brightness: True在推理前添加图像预处理def preprocess(image): # 直方图均衡化 if len(image.shape) 3: image cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) return cv2.equalizeHist(image)另一个常见问题是倾斜车牌。虽然PaddleOCR自带角度分类模型但对于大角度倾斜45度效果有限。后来我们加了个简单的矫正策略def correct_skew(image): gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) lines cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength100, maxLineGap10) angles [np.arctan2(y2-y1, x2-x1)*180/np.pi for line in lines for x1,y1,x2,y2 in line] median_angle np.median(angles) return cv2.warpAffine(image, cv2.getRotationMatrix2D((w/2,h/2), median_angle, 1), (w,h))