☰
基于PaddleOCR的车牌识别算法:从检测到识别的全流程实战与优化
2026/9/28 22:21:04 网站建设 项目流程

简介:本资源面向计算机视觉初学者与进阶开发者,提供一套基于PaddleOCR的车牌识别完整项目源码,帮助读者从零搭建可运行的车牌检测与识别系统,解决车牌定位、字符识别及模型部署等实际问题。压缩包共416个文件,约37MB,以90个Python脚本、59张jpg与46张png图像、49份md文档为主,另含C++源码、yml配置、模型文件与移动端工程文件,覆盖训练、推理与部署全流程。已有253人学习下载,适合对照源码理解工程结构。项目涵盖DB文字检测与CRNN字符识别两大核心模块,包含数据预处理、模型训练脚本、超参数配置、推理代码及量化压缩方案,读者可据此掌握数据增强、模型调优与轻量化部署思路,并借助文档与配置快速复现实验,积累车牌识别方向的实战经验。

1. 车牌识别项目为什么总在“检测”和“识别”之间翻车

车牌识别是 OCR 落地最典型的场景之一,也是很多算法工程师从 PaddleOCR 入门视觉项目的第一站。标题里这个“基于 PaddleOCR 实现的车牌识别算法”项目,核心链路其实就两段:先用检测模型把车牌框出来,再用识别模型把框里的字符读出来。听起来简单,但真正跑起来,翻车点往往不在模型本身,而在检测框的倾斜角度、字符分割的边界、以及中文省份简称的识别上。很多新手直接拿 PaddleOCR 的通用模型去跑停车场照片,结果蓝牌能读个七七八八,黄牌、绿牌、双层牌就集体失灵。这个项目适合两类人:一是想用 PaddleOCR 快速搭一个可演示的车牌识别 demo 的开发者,二是想借车牌场景理解 OCR 检测与识别如何串联的算法工程师。下面我按自己实际跑通这套方案的顺序,把选型、数据、训练、推理和踩坑一条线讲清楚。

2. PaddleOCR 车牌识别方案选型:为什么不用通用模型直接跑

2.1 通用 OCR 模型在车牌场景的三个硬伤

PaddleOCR 官方提供的通用中文模型是在大量文档、街景、自然场景文本上训练的,它的强项是横排、规整、背景干净的印刷体。但车牌图像有几个特殊属性,直接决定了通用模型会翻车。

第一是长宽比。中国蓝牌是 440mm×140mm,比例约 3.14:1,黄牌和新能源绿牌比例也接近。通用检测模型训练时见过的文本框长宽比分布很广,但车牌这种极端扁长的目标,在默认的 DB 检测后处理里容易被切碎或合并错误。我实测过,用ch_PP-OCRv4_det直接跑一张倾斜 15 度的蓝牌,检测框会裂成两个,把“京A”和后面的数字分开。

第二是字符集。车牌字符集和通用中文 OCR 的字符集差异很大。蓝牌是 7 位:省份简称 + 字母 + 5 位字母数字混合。新能源绿牌是 8 位。通用识别模型的字典有六千多字,而车牌实际用到的字符不到 70 个。字典越大,模型在相似字符上的混淆概率越高,比如“0”和“D”、“8”和“B”、“1”和“I”。我见过最离谱的 case 是把“京A12345”读成“京A1234S”,就是因为通用字典里“S”和“5”的视觉特征太接近。

第三是颜色和光照。车牌有蓝底白字、黄底黑字、绿底黑字、白底红字等多种配色,而且停车场入口的补光灯往往会在车牌表面形成高光。通用模型对反色文本和强光遮挡的鲁棒性不够,尤其是黄牌在夜间补光下,检测置信度会掉到 0.3 以下。

所以这个项目的核心思路不是“拿 PaddleOCR 直接推理”,而是“用 PaddleOCR 的框架和预训练权重,在车牌数据上做微调”。检测模型保留 DB 的骨干网络,只换数据重新训练;识别模型保留 CRNN 或 SVTR 的结构,把字典换成车牌专用字符集。

2.2 检测与识别两阶段的参数怎么配

PaddleOCR 的配置文件分检测和识别两部分。检测侧我一般用ch_PP-OCRv4_det的骨干,输入尺寸从默认的 960 改成 640×640 的矩形输入,因为车牌在图像里通常只占一条,不需要正方形。识别侧用ch_PP-OCRv4_rec,但要把rec_char_dict_path指向自定义的车牌字典文件。

下面是我实际用的检测配置文件关键片段,基于 PaddleOCR 的 yaml 格式:

# det_db_mv3_lpr.yml Global: use_gpu: true epoch_num: 500 save_model_dir: ./output/det_lpr character_dict_path: ./dict/lpr_dict.txt # 车牌专用字典 Architecture: model_type: det algorithm: DB Backbone: name: MobileNetV3 scale: 0.5 model_name: large Neck: name: FPN out_channels: 256 Head: name: DBHead k: 50 Train: dataset: name: SimpleDataSet data_dir: ./train_data/lpr_det/ label_file_list: ["./train_data/lpr_det/train.txt"] transforms: - DecodeImage: {img_mode: BGR, channel_first: false} - DetLabelEncode: {} - IaaAugment: augmenter_args: - {type: Fliplr, args: {p: 0.0}} # 车牌不能水平翻转 - {type: Affine, args: {rotate: [-10, 10]}} - {type: Resize, args: {size: [640, 640]}} - MakeShrinkMap: {shrink_ratio: 0.4, min_text_size: 8} - NormalizeImage: {scale: 1./255., mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225]} - ToCHWImage: {} - KeepKeys: {keep_keys: ['image', 'shrink_map', 'shrink_mask']} loader: batch_size_per_card: 8 num_workers: 4

这里有几个参数值得展开。Fliplr的概率我设成 0,因为车牌水平翻转后省份简称会变成镜像,这种增强只会给模型引入噪声。Affine的旋转范围设 ±10 度,覆盖停车场入口常见的轻微倾斜,再大就会让检测框的回归目标变得不稳定。shrink_ratio设 0.4 而不是默认的 0.4 到 0.5 之间,是因为车牌字符间距小,收缩太多会让相邻字符的 mask 粘连。

识别侧的配置重点是字典和输入尺寸:

# rec_svtr_lpr.yml Global: use_gpu: true epoch_num: 300 save_model_dir: ./output/rec_lpr character_dict_path: ./dict/lpr_dict.txt max_text_length: 8 # 新能源车牌8位 Architecture: model_type: rec algorithm: SVTR_LCNet Transform: Backbone: name: PPLCNetV3 scale: 0.95 Head: name: MultiHead head_list: - CTCHead: Neck: name: svtr dims: 120 depth: 2 hidden_dims: 120 kernel_size: [1, 3] use_guide: true Head: fc_decay: 0.00001 - SARHead: enc_dim: 512 max_text_length: 8 Train: dataset: name: SimpleDataSet data_dir: ./train_data/lpr_rec/ label_file_list: ["./train_data/lpr_rec/train.txt"] transforms: - DecodeImage: {img_mode: BGR, channel_first: false} - RecConAug: {prob: 0.5, ext_data_num: 2, max_text_length: 8} - RecAug: {} - MultiLabelEncode: {gtc_encode: CTCLabelEncode, gtc_encode: SARLabelEncode} - KeepKeys: {keep_keys: ['image', 'label_ctc', 'label_sar', 'length', 'valid_ratio']} loader: batch_size_per_card: 64 num_workers: 4

max_text_length设 8 是为了兼容新能源车牌,普通蓝牌 7 位,训练时短于 8 位的用 padding 补齐。RecConAug是 PaddleOCR 提供的拼接增强,把两张车牌图横向拼接成一张,模拟多车牌场景,但概率我只给 0.5,因为拼接后的字符间距和真实车牌不一致,给太高反而掉点。MultiHead同时用 CTC 和 SAR 两个头,CTC 负责快速收敛,SAR 负责注意力对齐,推理时取 CTC 的输出,因为 CTC 对字符切分的容错更好。

2.3 车牌字典文件怎么建

字典文件是纯文本,一行一个字符。顺序很重要,因为训练和推理必须用同一个字典。我用的顺序是:先省份简称,再字母,再数字,最后是特殊字符。

京 津 冀 晋 蒙 辽 吉 黑 沪 苏 浙 皖 闽 赣 鲁 豫 鄂 湘 粤 桂 琼 渝 川 贵 云 藏 陕 甘 青 宁 新 A B C D E F G H J K L M N P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 -

注意字母里没有 I 和 O,因为车牌规范里这两个字母容易和 1、0 混淆,实际不发。字典里保留“-”是为了处理挂车和使领馆车牌。字典建好后,训练脚本和推理脚本都要指向同一个路径,否则会出现“训练时能读,推理时乱码”的玄学问题。

3. 从标注到训练:车牌数据集的准备与增强策略

3.1 标注格式与转换脚本

PaddleOCR 检测任务用的是四点坐标标注,格式是图像路径\t[{"transcription": "京A12345", "points": [[x1,y1],[x2,y2],[x3,y3],[x4,y4]]}]。识别任务用的是图像路径\t车牌号。如果你手头是 LabelImg 或 Labelme 的标注,需要先转成这个格式。

下面是我常用的 Labelme JSON 转 PaddleOCR 检测格式的脚本:

import json import os import glob def labelme_to_paddle_det(json_dir, output_txt): """ 将 Labelme 的矩形标注转为 PaddleOCR 检测格式 json_dir: 存放 .json 文件的目录 output_txt: 输出的标注文件路径 """ with open(output_txt, 'w', encoding='utf-8') as fout: for json_path in glob.glob(os.path.join(json_dir, '*.json')): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_name = os.path.basename(json_path).replace('.json', '.jpg') img_path = os.path.join(os.path.dirname(json_dir), 'images', img_name) for shape in data['shapes']: points = shape['points'] # Labelme 矩形只有两个点,需要补成四点 if len(points) == 2: x1, y1 = points[0] x2, y2 = points[1] points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] label = shape['label'] line = f"{img_path}\t[{{\"transcription\": \"{label}\", \"points\": {points}}}]" fout.write(line + '\n') if __name__ == '__main__': labelme_to_paddle_det('./raw/labelme', './train_data/lpr_det/train.txt')

这个脚本的关键点在于:Labelme 的矩形标注只有左上和右下两个点,而 PaddleOCR 的 DB 检测需要四个角点。补点顺序必须是左上、右上、右下、左下,顺时针方向,否则训练时MakeShrinkMap生成的 mask 会错位。另外图像路径我习惯用绝对路径,避免训练时因为工作目录变化找不到图。

识别任务的标注更简单,直接从检测标注里提取 transcription 字段:

import json def det_to_rec(det_txt, rec_txt): """ 从检测标注中提取识别标注 """ with open(det_txt, 'r', encoding='utf-8') as f, \ open(rec_txt, 'w', encoding='utf-8') as fout: for line in f: parts = line.strip().split('\t') img_path = parts[0] ann = json.loads(parts[1]) for item in ann: text = item['transcription'] # 过滤空标注和长度异常的 if text and 5 <= len(text) <= 8: fout.write(f"{img_path}\t{text}\n")

这里我加了长度过滤,车牌号最短 5 位(使领馆车牌可能更短),最长 8 位。超出这个范围的标注大概率是标错了,直接丢掉比让模型去学噪声要好。

3.2 数据增强里哪些能开、哪些不能开

PaddleOCR 的IaaAugment支持很多增强算子,但车牌场景有几个禁忌。我列一个实际用的开关表:

增强算子是否开启参数建议原因
Fliplr关闭p=0.0水平翻转后省份简称镜像,语义错误
Flipud关闭p=0.0垂直翻转车牌无意义
Affine 旋转开启rotate=[-10,10]覆盖轻微倾斜,再大检测框回归不稳
Affine 缩放开启scale=[0.8,1.2]模拟不同拍摄距离
亮度对比度开启默认模拟地库和户外光照差异
模糊开启概率 0.3模拟运动模糊和失焦
裁剪开启默认模拟车牌被部分遮挡

识别侧的增强我一般只开RecAug里的颜色抖动和轻微透视,不开随机裁剪,因为裁剪会切掉字符,导致标签和图像不对应。RecConAug的拼接增强前面说了,概率给 0.5 就够了。

3.3 训练命令与显存不够时的降级方案

检测训练命令:

python tools/train.py -c configs/det/det_db_mv3_lpr.yml \ -o Global.pretrained_model=./pretrain_models/ch_PP-OCRv4_det_train/best_accuracy \ Global.save_model_dir=./output/det_lpr

识别训练命令:

python tools/train.py -c configs/rec/rec_svtr_lpr.yml \ -o Global.pretrained_model=./pretrain_models/ch_PP-OCRv4_rec_train/best_accuracy \ Global.save_model_dir=./output/rec_lpr

如果显存只有 8G,检测的batch_size_per_card从 8 降到 4,同时把num_workers从 4 降到 2。识别侧从 64 降到 32。降 batch 后学习率也要按比例降,否则 loss 会震荡。我一般用线性缩放:batch 减半,学习率乘 0.5。PaddleOCR 的学习率在优化器配置里,检测默认是 0.001,识别默认是 0.0005。

训练过程中重点看两个指标:检测看hmean,识别看acc。检测的 hmean 在车牌数据上一般能到 0.95 以上,如果卡在 0.8 左右上不去,大概率是标注框有重叠或者漏标。识别的 acc 如果卡在 0.9 以下,先检查字典文件有没有多余空行,空行会导致 CTC 解码时多出一个 blank 类别,直接拉低准确率。

4. 推理部署:从单张图片到视频流的完整链路

4.1 单张图片推理与结果可视化

训练完导出推理模型后,用 PaddleOCR 的predict_system.py或者自己写一个轻量推理脚本。我习惯自己写,因为可以控制后处理逻辑。

import cv2 import numpy as np from paddleocr import PaddleOCR # 初始化时指定自定义模型路径 ocr = PaddleOCR( det_model_dir='./output/det_lpr/inference', rec_model_dir='./output/rec_lpr/inference', rec_char_dict_path='./dict/lpr_dict.txt', use_gpu=True, det_db_thresh=0.3, det_db_box_thresh=0.5, det_db_unclip_ratio=1.8, # 车牌框需要适当外扩 rec_batch_num=6 ) def recognize_plate(img_path): img = cv2.imread(img_path) result = ocr.ocr(img, cls=False) plates = [] for line in result: if line is None: continue for item in line: box = item[0] text, score = item[1] # 过滤低置信度和长度异常的 if score > 0.7 and 5 <= len(text) <= 8: plates.append({'text': text, 'score': score, 'box': box}) return plates if __name__ == '__main__': res = recognize_plate('./test_imgs/plate_01.jpg') for p in res: print(f"车牌号: {p['text']}, 置信度: {p['score']:.3f}")

这里det_db_unclip_ratio设 1.8 而不是默认的 1.5,是因为车牌检测框如果贴得太紧,识别模型输入会切掉边缘字符。外扩 1.8 倍后,框里会多出一点背景,但字符完整率明显提升。det_db_box_thresh从默认 0.6 降到 0.5,是为了召回那些被补光灯照得发白的车牌,代价是可能多出几个误检框,后面用识别置信度过滤掉。

4.2 视频流推理的帧间去重与缓存

视频流场景下,同一辆车会在连续多帧里出现,如果每帧都输出一次识别结果,日志会刷屏。我一般加一个简单的帧间缓存:用检测框的 IOU 做匹配,同一个车牌在连续 10 帧内只输出一次最高置信度的结果。

from collections import deque class PlateTracker: def __init__(self, max_age=10, iou_thresh=0.5): self.tracks = [] # 每个元素: {'box': box, 'text': text, 'score': score, 'age': 0} self.max_age = max_age self.iou_thresh = iou_thresh def _iou(self, box1, box2): # 计算两个四边形的 IOU,简化用外接矩形 x1 = max(min(p[0] for p in box1), min(p[0] for p in box2)) y1 = max(min(p[1] for p in box1), min(p[1] for p in box2)) x2 = min(max(p[0] for p in box1), max(p[0] for p in box2)) y2 = min(max(p[1] for p in box1), max(p[1] for p in box2)) if x2 <= x1 or y2 <= y1: return 0.0 inter = (x2 - x1) * (y2 - y1) area1 = (max(p[0] for p in box1) - min(p[0] for p in box1)) * \ (max(p[1] for p in box1) - min(p[1] for p in box1)) area2 = (max(p[0] for p in box2) - min(p[0] for p in box2)) * \ (max(p[1] for p in box2) - min(p[1] for p in box2)) return inter / (area1 + area2 - inter + 1e-6) def update(self, detections): # detections: [{'box': box, 'text': text, 'score': score}, ...] for det in detections: matched = False for track in self.tracks: if self._iou(det['box'], track['box']) > self.iou_thresh: if det['score'] > track['score']: track.update(det) track['age'] = 0 matched = True break if not matched: self.tracks.append({**det, 'age': 0}) # 老化淘汰 self.tracks = [t for t in self.tracks if t['age'] < self.max_age] for t in self.tracks: t['age'] += 1 return self.tracks

这个 tracker 的逻辑很简单:新检测框和已有轨迹做 IOU 匹配,匹配上就更新,匹配不上就新建轨迹。轨迹超过max_age帧没更新就删掉。实际用的时候,我只输出age刚被重置为 0 且 score 大于 0.85 的结果,这样同一辆车只会在进入画面时输出一次。

4.3 推理速度优化:从 200ms 到 50ms 的调整

PaddleOCR 默认推理在 GPU 上单张图大概 150 到 200ms,其中检测占 70%,识别占 30%。如果要做实时视频,需要压到 50ms 以内。我试过几个有效的调整:

第一,检测输入尺寸从 960 降到 640。车牌在画面里通常只占 1/4 宽度,640 的输入足够检测到。这一项能省 40% 的检测时间。

第二,识别 batch 从 1 改成 6。PaddleOCR 的识别模型支持 batch 推理,把一帧里的多个车牌拼成一个 batch,GPU 利用率明显提升。但 batch 不能太大,超过 8 后显存增长快,收益递减。

第三,用 TensorRT 加速。PaddleOCR 支持导出 ONNX 再转 TensorRT,FP16 精度下检测能到 15ms,识别能到 8ms。但 TensorRT 的坑在于动态 shape 支持,车牌数量每帧不一样,需要开 optimization profile。我一般设 min_shape=[1,3,640,640],opt_shape=[1,3,640,640],max_shape=[4,3,640,640],覆盖 1 到 4 个车牌的情况。

第四,检测和识别异步流水线。用两个线程,一个跑检测,一个跑识别,中间用队列传递。检测线程出框后立刻丢给识别线程,自己继续处理下一帧。这样整体吞吐能提升 30% 左右,但延迟不变。

5. 车牌识别避坑记录:从标注到部署的五个血泪教训

5.1 现象:训练 loss 正常下降,但推理时检测框全部偏移

原因:标注格式里 points 的顺序不是顺时针。PaddleOCR 的MakeShrinkMap依赖多边形的顶点顺序来计算收缩后的 mask,如果顺序是左上、左下、右下、右上这种逆时针或者交叉顺序,生成的 mask 会翻转,模型学到的框位置就是错的。

解决:写一个校验脚本,对每个标注框计算多边形面积,如果面积为负就说明顶点顺序反了。面积公式用鞋带公式:

def polygon_area(points): n = len(points) area = 0.0 for i in range(n): j = (i + 1) % n area += points[i][0] * points[j][1] area -= points[j][0] * points[i][1] return area / 2.0 # 面积为负时反转顶点顺序 if polygon_area(points) < 0: points = points[::-1]

5.2 现象:识别模型把“京A12345”读成“京A1234S”

原因:字典里同时有“5”和“S”,且训练数据里“S”开头的车牌样本太少,模型没有学到足够的区分特征。另外 CTC 解码时,如果“5”和“S”的 logits 接近,argmax 会随机选一个。

解决:两个方向。一是增加混淆字符的训练样本,专门收集“S”和“5”、“B”和“8”、“D”和“0”同时出现的车牌图,各补 200 张以上。二是在后处理里加规则校验,车牌第 2 位必须是字母,第 3 到 7 位是字母或数字,如果识别结果违反这个规则,就用编辑距离找最接近的合法组合。比如“京A1234S”第 7 位是字母,但蓝牌第 7 位应该是数字,就把“S”纠正为“5”。

5.3 现象:黄牌在夜间补光下检测置信度低于 0.3,直接漏检

原因:训练数据里黄牌夜间样本太少,模型对高光反光的鲁棒性不够。另外 DB 检测的det_db_thresh默认 0.3,高光区域的概率图会被压低到阈值以下。

解决:一是在训练数据里补充夜间黄牌样本,用亮度增强模拟补光效果。二是推理时把det_db_thresh降到 0.2,同时把det_db_box_thresh从 0.5 降到 0.4,用识别置信度做二次过滤。三是如果场景固定,可以在检测前加一个简单的图像预处理,用 CLAHE 做局部对比度增强,把高光区域的细节拉回来。

5.4 现象:新能源绿牌 8 位字符,识别结果总是少最后一位

原因:max_text_length设成了 7,CTC 解码时超过 7 位的序列被截断。或者识别模型的输入宽度不够,8 个字符在宽度方向被压缩得太厉害,最后一个字符的特征丢失。

解决:把max_text_length改成 8,同时检查识别模型的输入尺寸。PaddleOCR 识别默认输入高度 48,宽度按比例缩放,但最大宽度有限制。如果车牌图的长宽比超过 8:1,需要把rec_image_shape从[3, 48, 320]改成[3, 48, 480],给宽度留更多空间。改完后重新导出推理模型,旧模型不生效。

5.5 现象:TensorRT 加速后,同一张图两次推理结果不一致

原因:TensorRT 的 FP16 精度在字符分类的 logits 上引入了微小误差,当两个字符的 logits 差距小于 0.01 时,argmax 结果会在两次推理间跳变。另外动态 shape 的 optimization profile 如果设得太窄,实际输入超出范围时会回退到 FP32,导致精度和速度都不稳定。

解决:一是在 TensorRT 导出时保留 FP32 的识别头,只对骨干网络做 FP16。二是把 optimization profile 的 max_shape 设得比实际最大车牌数多 2,留余量。三是在后处理里加一个置信度阈值,低于 0.9 的结果标记为“待确认”,不直接输出。

6. 把车牌识别准确率从 92% 推到 98% 的两个后处理技巧

训练和推理链路跑通后,剩下的准确率提升基本靠后处理。我实测最有效的两个技巧,一个针对字符混淆,一个针对多帧融合。

第一个是混淆字符的规则纠正。车牌有固定的格式约束:蓝牌第 1 位是省份简称,第 2 位是字母,第 3 到 7 位是字母或数字,且字母 I 和 O 不出现。利用这个约束,可以建一个混淆映射表:

易混淆对纠正规则
0 / D第 2 位必须是字母,若识别为 0 则改为 D
1 / I车牌不含 I,若识别为 I 则改为 1
5 / S第 7 位必须是数字,若识别为 S 则改为 5
8 / B第 3 到 7 位若识别为 B 且上下文都是数字,则改为 8
2 / Z第 2 位必须是字母,若识别为 2 则改为 Z

这个表不是拍脑袋定的,是从我自己的测试集里统计混淆矩阵得出的。实际用的时候,先跑一遍识别,拿到原始结果,再按位应用规则。注意规则只对置信度低于 0.95 的结果生效,高置信度的不动,避免把对的改错。

第二个是多帧投票。视频流里同一辆车连续出现 5 到 10 帧,每帧的识别结果可能有个别字符不同。我一般取最近 10 帧的结果,按位做多数投票。比如 10 帧里第 3 位有 7 帧是“A”,3 帧是“4”,就取“A”。投票前先按整体置信度排序,只保留置信度前 8 帧的结果,避免低质量帧拉偏投票。

from collections import Counter def multi_frame_vote(results): """ results: [{'text': '京A12345', 'score': 0.95}, ...] 返回投票后的车牌号 """ if not results: return None # 按置信度排序,取前8个 sorted_res = sorted(results, key=lambda x: x['score'], reverse=True)[:8] # 对齐长度,短的补空 max_len = max(len(r['text']) for r in sorted_res) padded = [r['text'].ljust(max_len, ' ') for r in sorted_res] # 逐位投票 final = [] for i in range(max_len): chars = [p[i] for p in padded if p[i] != ' '] if chars: final.append(Counter(chars).most_common(1)[0][0]) return ''.join(final)

这个投票逻辑对新能源 8 位车牌同样适用,只要保证参与投票的结果长度一致。实际部署时,我把投票窗口设成 10 帧,超过 10 帧的旧结果自动丢弃。投票后的结果再走一遍混淆字符纠正,最终准确率能从单帧的 92% 左右推到 98% 以上。

最后说一个我自己的习惯:每次改完后处理规则,一定拿一个固定的测试集跑一遍全量对比,记录改动前后的准确率变化。我见过太多人凭感觉调规则,结果把原本对的 case 改错了,整体反而掉点。测试集不用大,200 张覆盖蓝牌、黄牌、绿牌、白天、夜间、倾斜、遮挡就够了。这个习惯帮我省了很多后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询