如果你在停车场入口蹲过现场调试,大概率见过这种画面:烈日把车牌晒得发白,雨天泥水把字符糊成一团,晚上的LED补光灯又把蓝底牌照打得一片惨白。我第一次把 YOLO11 和 RapidOCR 组合起来做园区车牌识别应用时,也踩了不少这种真实的坑。所谓 AI 车牌识别,简单说就是用目标检测模型在画面里找到车牌区域,再用 OCR 模型把车牌上的字符读出来,最后输出一串结构化文本。这篇文章是一份完整项目记录,包含方案选型、代码实现、参数调整、性能优化和问题排查,适合正在做车辆识别、OCR落地的工程师参考,也适合想快速跑通一个“检测+识别”串联项目的新手。
1. 车牌识别不是“拍个照”那么简单:先看清问题再选方案
1.1 为什么我最终选择“检测+识别”两阶段方案
车牌识别这块,市面上有不少端到端方案,输入一张图直接输出车牌号,模型内部自己完成定位和字符序列预测。听起来很省事,但实际工程里,端到端方案的训练成本和调试成本都很高:车牌在整张画面里往往只占几个像素,而且角度、光照、遮挡千变万化,想让一个模型同时搞定“在哪里”和“是什么”,对数据量和算力都不友好。
我用的是两阶段方案:YOLO11 先做目标检测,把车牌框出来;RapidOCR 再对裁剪后的区域做字符识别。这样分工很干净——检测模型只负责定位,OCR模型只负责读字符。两阶段的好处一个是解耦:检测不准,改检测;识别不准,改识别,不用动对方。另一个是灵活:YOLO11 的检测结果可以做跟踪、计数、车流量统计等附加功能,OCR 结果只是其中一个下游消费方式。只要YOLO11框得准,RapidOCR 面对的就是一张尺寸相对统一的车牌小图,识别难度大大降低。
这套组合适合什么场景?停车场出入口、园区内部车辆管理、工地闸机、封闭道路的车型统计,都可以跑。比如我做的园区试点,摄像头固定角度,车牌基本是正前方或者略带倾斜,既不要求极高帧率,也不要求跨摄像头连续追踪,YOLO11n + RapidOCR 的组合完全够用,部署成本也低。
1.2 整体流程:一张图里的四个环节
从输入一张带车的照片,到输出“京A12345”这种结果,中间其实有四个环节:
输入画面 → YOLO11检测车牌区域 → 车牌图像预处理(裁剪、放大、增强) → RapidOCR字符识别 → 后处理过滤 → 结构化车牌号
- 检测环节:YOLO11 输出每个检测框的坐标、置信度和类别。这里我只需要类别是“license_plate”的框。
- 预处理环节:拿到框之后不能直接扔给OCR,因为检测框往往贴得比较紧,字符边缘可能被切掉;而且图像可能偏暗、模糊、倾斜,需要放大和外扩。
- 识别环节:RapidOCR 对预处理后的图像输出文本内容、置信度和字符坐标,通常一次性给出整行文本。
- 后处理环节:把OCR结果里的空格、低置信字符清掉,再用车牌格式校验一下,输出干净结果。
这四个环节看起来简单,但每一个都有不少细节。尤其是“预处理”这一步,很多人会忽略,直接导致 OCR 准确率掉一半。下一章先说明为什么选 YOLO11 和 RapidOCR,再具体讲代码。
2. 工具选型解析:YOLO11 和 RapidOCR 凭什么能搭
2.1 YOLO11:新一代轻量检测模型的取舍
YOLO11 是 Ultralytics 在 YOLOv8 之后推出的系列模型,保留了 YOLOv8 简洁易用的接口,但内部结构做了调整。它默认是 anchor-free 检测头,用了解耦分类和回归头,主干网络里引入了 C3k2 模块,整体计算量比同参数量上一代更省。对于车牌这种小目标,YOLO11 虽然没有像专门的大模型那种夸张的上下文建模能力,但通过调整输入分辨率和训练策略,在几百块钱的CPU机器上也能跑稳。
我选择 YOLO11n(nano版)作为基础,原因很简单:推理速度快。在GPU上单帧检测只要几毫秒,CPU上几百毫秒,对一个闸机场景完全够用。至于选哪个变体,要根据部署环境定。如果跑在带 GPU 的边缘盒子,可以考虑 yolo11s 或 yolo11m,检测精度会更高;如果跑在纯 CPU 的旧电脑上,yolo11n 是底线。
值得一提的还有“yolo11小目标增强模块”这个方向。YOLO11 默认在 80x80、40x40、20x20 三组特征图上检测,车牌在画面中经常小于 32x32 像素,属于小目标。如果训练时发现漏检多,可以做两件事:一是把输入分辨率从 640 提到 960 或 1280,让小目标在特征图上变大;二是在模型结构中加 P2 检测头(在 160x160 特征图上检测小目标),这需要改结构并重新训练。后文问题排查部分还会再说。
2.2 RapidOCR:好用的OCR引擎,但别忽略它的CPU胃口
RapidOCR 不是单个模型,而是一套 OCR 推理管线:先做文本检测,再对文本区域做方向分类,最后做文字识别。它底层跑的是 PaddleOCR 训练出来的模型,转换成了 ONNX 格式,所以部署时完全不依赖 PaddlePaddle 这个大框架,只需要 onnxruntime。对开发者来说,这意味着 pip 安装完就能跑,没有版本地狱。
但“太吃CPU”确实是个真实痛点,尤其是用 Python 直接跑的时候。原因有三点:
- RapidOCR 包含了多个模型串联,检测模型输入是 960x960,识别模型输入是动态宽高,加起来计算量不小。
- 默认使用 CPU 的 onnxruntime,在多核机器上会尽量占满线程,CPU 使用率飙升。
- 如果每一帧都新建 RapidOCR 实例,模型反复加载,开销直接翻倍。
我后面会专门讲怎么把 CPU 占用降下来,这里先记住一个大原则:RapidOCR 适合“低频识别”,不适合对每一帧视频都无脑全图识别。车牌识别场景下,先用 YOLO11 把区域裁剪出来,再调用 OCR,本身就是最好的降载手段。
2.3 为什么不用更重的 PaddleOCR / 端到端识别
很多朋友会问:直接用 PaddleOCR 整图识别不行吗?我在早期实验里试过,确实能识别出某些车牌,但当画面里同时出现广告牌、指示牌等文字时,PaddleOCR 会把所有文本区域都识别出来,还要额外判断哪一个是车牌,逻辑复杂且效率低。更重要的是,整图识别会把大量算力消耗在不相关的背景文字上,在连续视频流里完全没有性价比。
RapidOCR 相比 PaddleOCR 的完整框架,优势就是轻、接口简单、部署快。虽然底层模型源自 PaddleOCR,但通过 ONNX 格式省掉了框架依赖。当检测模型已经框出精确区域时,OCR 只需处理一块小图,识别速度非常快。反而没有太大必要引入整套重量级方案。
3. 从零搭环境:依赖、模型与目录规划
3.1 安装依赖:三行命令解决
我项目环境是 Python 3.10,操作系统用 Ubuntu 22.04。核心依赖就四个:ultralytics、rapidocr-onnxruntime、opencv-python、numpy。安装命令如下:
pip install ultralytics rapidocr-onnxruntime opencv-python-headless numpy这里用opencv-python-headless是为了避免在服务器上引入 GUI 依赖,如果是本地 Windows 环境,直接装opencv-python也行。注意 rapidocr-onnxruntime 和新增的 rapidocr 包不完全一样,RapidOCR 新版本做了包名调整,早期项目用 old APIfrom rapidocr_onnxruntime import RapidOCR,新版统一为from rapidocr import RapidOCR,我代码里用旧版 API 写,更通用。
版本上,Ultralytics 迭代很快,我实际用的是 ultralytics 8.3.x 版本。如果你遇到 API 不一致,优先看官方文档,不要盲目升级大版本。
3.2 把模型文件准备到位
YOLO11 可以从 Ultralytics 官方自动下载预训练权重,但我建议先准备一个针对车牌场景微调过的模型,精度差异很大。如果你不想从零训练,可以先下载yolo11n.pt,然后用自己的车牌数据集跑几十个 epoch 微调。
# 自动下载 yolo11n.pt yolo predict model=yolo11n.pt source=test.jpgRapidOCR 的模型会自动从模型仓库下载,保存在用户目录的.rapidocr下。但如果部署环境不能联网,需要手动下载 ONNX 模型,放到指定目录,并通过初始化参数传入。
目录结构建议这样规划:
plate_recognition/ ├── main.py # 主程序 ├── models/ │ ├── yolo11n.pt # 检测模型 │ └── ocr/ # rapidocr模型目录 ├── data/ │ ├── test_images/ # 测试图片 │ └── videos/ # 测试视频 └── output/ └── results.json把模型和代码分开,是为了切换不同模型时不用改代码,也方便打包部署。实际上训练好的 YOLO 权重最好转成 ONNX 再部署,可以减少依赖冲突,不过我本地调试用 pt 文件更灵活。
3.3 RapidOCR 初始化与模型加载
RapidOCR 初始化很简单,但要注意只初始化一次。我见过有人写在一个函数里,每张图进来都 new 一个引擎,CPU 直接被打爆,识别速度反而下降。
from rapidocr_onnxruntime import RapidOCR ocr_engine = RapidOCR() # 如果你下载了离线模型,可以用 model_path 参数指定 # ocr_engine = RapidOCR(model_path="./models/ocr")这段代码放在模块加载时执行一次,后面所有识别复用同一个引擎。YOLO 模型也一样,全局加载一次即可。
4. 核心代码:从视频帧到车牌号码的完整流水线
4.1 用 YOLO11 快速定位车牌区域
先看检测部分。加载模型后,对输入图片执行推理,拿到检测结果。YOLO11 的model.predict返回的结果对象里,boxes包含了框坐标、置信度和类别。
import cv2 from ultralytics import YOLO # 加载检测模型(全局只加载一次) det_model = YOLO("./models/yolo11n.pt") def detect_plate(image): # image 是 BGR 格式的 numpy 数组 results = det_model.predict( source=image, imgsz=640, conf=0.25, iou=0.45, verbose=False ) boxes = results[0].boxes if boxes is None or len(boxes) == 0: return None plate_boxes = [] for box in boxes: cls_id = int(box.cls[0]) if cls_id == plate_cls_id: # 你的模型里车牌类别的ID x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) conf = float(box.conf[0]) plate_boxes.append((x1, y1, x2, y2, conf)) # 一般取置信度最高的一个框 plate_boxes.sort(key=lambda x: x[4], reverse=True) return plate_boxes[0] if plate_boxes else None这里的imgsz=640是输入到模型的图像尺寸,YOLO 会把原始图缩放到 640x640。如果摄像头视角下车辆较远,车牌很小,建议把 imgsz 调到 960 或 1280,加了小目标检测头之后效果更好。conf=0.25是低阈值,宁可多检几个框也别漏掉;如果只想拿最高置信度的一个车牌,后面排序处理。
类别 ID 取决于你训练时的标签。如果用的是官方 COCO 预训练模型,没有“license_plate”类别,所以一定要微调数据或者直接训练一个自定义类别。我训练时把车牌统一标为唯一类别,类别 ID 为 0。
4.2 车牌图像预处理:切图、放大、增强
检测到框之后,很多人直接把img[x1:x2, y1:y2]扔给 OCR,结果识别率很低。原因很简单:检测框通常紧贴车牌外沿,字符边缘会被截断,尤其首尾字符。所以预处理第一件事是外扩。
def crop_plate(image, box, expand_ratio=0.05): x1, y1, x2, y2, _ = box w = x2 - x1 h = y2 - y1 ex_w = int(w * expand_ratio) ex_h = int(h * expand_ratio) x1 = max(0, x1 - ex_w) y1 = max(0, y1 - ex_h) x2 = min(image.shape[1], x2 + ex_w) y2 = min(image.shape[0], y2 + ex_h) return image[y1:y2, x1:x2]外扩比例我用的 5% 左右。外扩太大,会把周围背景也带进来,OCR 的文本检测可能被干扰;外扩太小,又解决不了截断问题。
拿到外扩区域后,车牌图像往往是倾斜的。我做的固定角度摄像头场景不需要复杂的透视矫正,但如果你拍的是不同角度入口,可以做一次矫正。简单方式是:用 YOLO 检测出的框是轴向矩形,如果角度大,需要用文本检测或角点回归的方法获取四角坐标。这里不展开,实际园区场景用轻度补正就够了。
预处理还有一个通用增强步骤:把裁剪区域放大到至少 2 倍,再转灰度,做轻度锐化。RapidOCR 对低分辨率小图特别容易识别错,放大之后效果立竿见影。
def preprocess_for_ocr(plate_img): # 放大2倍 h, w = plate_img.shape[:2] scale = 2 resized = cv2.resize(plate_img, (w * scale, h * scale), interpolation=cv2.INTER_CUBIC) # 转灰度 gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY) # 锐化核 kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], dtype=np.float32) sharp = cv2.filter2D(gray, -1, kernel) # 轻度对比度增强 enhanced = cv2.convertScaleAbs(sharp, alpha=1.2, beta=0) return enhanced4.3 RapidOCR识别:拿到文本和置信度
预处理完的图像可以直接传给 RapidOCR。注意 RapidOCR 接受的 numpy 数组是 RGB 格式,而 OpenCV 读出来是 BGR,需要转换。另外,RapidOCR 新版支持use_det参数,但如果输入已经是车牌区域,不需要再做整行文本检测,可以直接跳过检测环节?实际上 RapidOCR 的 API 没有直接的“跳过检测”开关,即使给一个小图,它依然会内部做检测。不过图像越小,检测耗时越短。如果想要极致性能,可以自己裁剪检测结果,但会复杂很多。
def recognize_plate(enhanced_img): # BGR -> RGB rgb_img = cv2.cvtColor(enhanced_img, cv2.COLOR_GRAY2RGB) result, elapse = ocr_engine(rgb_img) if not result: return None, None, None # result 是列表,每个元素是 [box, text, score] all_text = "" total_conf = 0.0 count = 0 for box, text, score in result: all_text += text total_conf += score count += 1 avg_conf = total_conf / count if count else 0.0 return all_text, avg_conf, elapse这里elapse返回的是一个字典,包含各阶段耗时,可以用于性能分析。我实测单块车牌区域识别在 CPU 上大约 30-100ms,和图像大小成正比。
4.4 后处理:用正则把OCR结果洗成车牌号
OCR 结果经常带一些脏字符:空格、竖线被识别成“1”、字母 O 和数字 0 混淆等。所以后处理是整个流水线里不能省的一步。国内常见民用牌照格式是:省份汉字 + 字母 + 6位字母数字组合(总7位)。我用正则做校验。
import re plate_pattern = re.compile(r"^[\u4e00-\u9fa5][A-Z][A-Z0-9]{5,6}$") def normalize_plate(raw_text): # 去掉所有非中英文、数字字符 clean = re.sub(r"[^A-Z0-9\u4e00-\u9fa5]", "", raw_text.upper()) # 去除容易混淆的字符 clean = clean.replace("O", "0").replace("I", "1").replace("Z", "2") # 如果过短或过长,可能是OCR拼接错误,尝试截取合适长度 if len(clean) >= 7 and plate_pattern.match(clean[:7]): return clean[:7] return None实际使用中不能完全依赖正则,因为新能源车牌有8位(省份汉字+字母+6位,比普通多一位),有些特种车牌格式也不同。我在后处理里做的是:先按正则匹配,匹配不通过则保留 OCR 原始文本,结合置信度决定是否返回。宁可返回“未识别”,也不要返回一个错号。
另外,RapidOCR 返回结果里带有每个字符的坐标,如果 OCR 把文字拆成多段,可以按 x 坐标排序后拼接,这样顺序不会乱。上面代码是简单把所有文本按结果顺序拼起来,如果遇到多行识别容易错乱,建议按坐标排序。
5. 实测性能与调优:把CPU占用拉下来的实操记录
5.1 不同硬件下的耗时对比
我跑过一次横向对比,输入是 1080p 的监控截图,YOLO11n 先用 640 推理,RapidOCR 只识别裁剪后的车牌区域。单帧完整流水线耗时如下:
| 硬件 | YOLO11检测耗时 | RapidOCR识别耗时 | 总耗时 |
|---|---|---|---|
| Intel i5-8400 CPU | 约 250ms | 约 80ms | 约 330ms |
| Intel i7-12700 CPU | 约 120ms | 约 50ms | 约 170ms |
| RTX 3060 GPU | 约 8ms | 约 10ms | 约 18ms |
| CPU + OpenVINO优化 | 约 90ms | 约 35ms | 约 125ms |
闸机场景一般不需要高帧率,单帧处理 200ms 完全可以接受。但如果要做实时视频流,CPU 版本会显得比较吃力,需要配合抽样检测和跟踪策略。
5.2 RapidOCR 不吃CPU的四个调整
前面说了 RapidOCR 太吃 CPU,我实践下来有三个最有效的调优手段。
第一,限制 onnxruntime 线程数。ONNX Runtime 默认会使用所有物理核,一旦和 YOLO 的推理并发,整机 CPU 被打满。给 RapidOCR 设置单线程或双线程,可以显著降低占用。
from rapidocr_onnxruntime import RapidOCR # 通过 config 设置 intra_op_num_threads config = { "global": { "intra_op_num_threads": 2, "use_cuda": False, } } ocr_engine = RapidOCR(params=config)不同版本的参数名略有不同,但核心就是限制线程数。我实际测过,从默认8线程改为2线程,CPU占用率能从 90% 降到 35%,单次识别耗时只增加几十毫秒。
第二,不要每帧都调用 OCR。连续视频帧中,同一辆车会在多帧画面里出现。我采用“检测帧 + 识别帧分离”的策略:每隔5帧做一次完整识别,中间只用 YOLO11 做目标存在性判断。这样 CPU 压力大幅下降,车牌号输出频率也足够。
第三,用 OpenVINO 作为 onnxruntime 的执行后端。如果你部署在 Intel CPU 上,可以安装openvino并让 onnxruntime 使用 OpenVINO EP,推理速度能提升 40% 以上。具体做法是安装onnxruntime-openvino包,然后把 provider 指定为["OpenVINOExecutionProvider"]。
第四,裁剪识别区域时,限制 OCR 输入尺寸。车牌区域识别不需要 960x960 大图,我在预处理阶段已经将字符区域缩放到合适大小,而不会直接对整帧调用 OCR,这本身就是最大的省资源手段。
5.3 处理视频流时的稳定化技巧
车牌识别最怕“抖动”——相邻几帧结果一会儿对一会儿错。我加了一个简单状态机:连续 3 帧识别到同一个车牌号,才认为这是最终结果;之后保持该结果一段时间,避免重复输出。
class PlateTracker: def __init__(self, window=3, cooldown=5): self.window = window self.cooldown = cooldown self.history = [] self.current_plate = None self.hold_count = 0 def update(self, plate): if plate is None: self.history.clear() return self.current_plate self.history.append(plate) if len(self.history) > self.window: self.history.pop(0) if len(self.history) == self.window and len(set(self.history)) == 1: self.current_plate = plate self.hold_count = self.cooldown elif self.hold_count > 0: self.hold_count -= 1 else: self.current_plate = None return self.current_plate这个代码简单实用。它可以在入口视频里过滤单帧误识别,也能避免一辆车在画面内逗留时反复输出同一条记录。
6. 常见问题与排查技巧实录
6.1 YOLO11 检测不到小目标车牌怎么办
最典型的场景:车道距离摄像头远,车牌在整帧画面里只有 20x20 像素。YOLO11 在 640 输入下,可能完全把它漏掉。
- 第一步:把 imgsz 提高到 960 或 1280,输入分辨率越大,小目标信息损失越少。
- 第二步:用训练集统计车牌的真实大小。如果大量框宽高小于 32 像素,需要在训练时启用基于 Shape 的采样策略,让模型多看到小目标。
- 第三步:结构上做小目标增强,常见做法是添加 P2 检测头,让模型在 160x160 特征图上做预测。Ultralytics 的 YOLO11 结构文件里可以自定义,但需要重新训练。
- 第四步:如果项目允许,调整摄像头安装角度,让车牌占画面高度超过 40 像素,这往往比改模型更省事。
另外注意 GPU 显存,imgsz 从 640 提到 1280,FLOPs 会涨大约 4 倍,低端边缘盒子可能带不动。
6.2 RapidOCR 把汉字识别错
RapidOCR 虽然支持中文识别,但对低分辨率、模糊、倾斜的汉字,误识别率会明显上升。最常见的是把“京”认成“示”或“京”多了一横,把“鲁”认成“鱼”。
解决步骤:
- 把裁剪图像放大 2 到 3 倍,用 INTER_CUBIC 插值,这是最有效的办法。
- 车牌图像做锐化,但锐化过度会产生白边,反而干扰字符。我用的是小的 3x3 拉普拉斯核,alpha 控制在 1.2。
- 如果识别结果置信度低于 0.5,宁愿丢弃,不要硬拼。
- 适当用多模型融合,比如同时跑 RapidOCR 和一个轻量字符级分类器,对单字符做投票。这个方案适合准确率要求极高的场景,但复杂度也更高。
我实际测试中,只做放大和锐化,汉字准确率能从 72% 提升到 90% 左右。
6.3 并发场景下 CPU 被打满
我接到过一个需求:四个道闸同时过车,同一个服务器上要跑四个视频流识别。如果每个流都创建独立引擎并并发跑,CPU 立刻爆炸。
我的做法是全局只创建一套 YOLO + OCR 引擎,加一个请求队列,所有摄像头共用一个推理池。识别请求排队处理,利用 deque 批量调度。单个引擎实例是线程安全的吗?在 Ultralytics 和 RapidOCR 的官方文档上没有明确保证,所以我在实现时加了 threading.Lock 保护,牺牲一点并发度,换取稳定。
另一个办法是分开部署:检测用 CPU 或 GPU,OCR 单独放在另一台 CPU 机器上,通过 HTTP 接口调用。实际项目里这种微服务改造收益明显,思路也清晰。
6.4 版本兼容与数据格式坑
YOLO11 的model.predict返回的results里,boxes.xyxy是 Tensor 对象,不是普通 list;直接做切片或比较时容易碰到类型问题,我统一用.tolist()转成 list。另外results的names属性是一个从类别 ID 到名称的字典,判断类别时不要写死 ID,最好动态读取。
RapidOCR 的返回结果格式也有变化。旧版result是 list,新版可能是 dict,按官方文档为准。我在代码里加了防御性判断,如果是 None 就直接返回空,避免后续解包报错。
还有一个小坑:RapidOCR 的elapse返回中包含了每个阶段耗时,但如果你只看总耗时,会高估实际处理速度,因为模型初始化耗时会混进去。建议单独计时推理部分。
7. 落地扩展:从单张图片到实时通道
7.1 接入 RTSP 视频流
当项目要处理实时视频流时,不能每帧都执行完整流水线。我用 OpenCV 的cv2.VideoCapture或流媒体框架拉流,然后按帧率抽帧。最稳妥做法是开一个独立线程拉流,一个线程做检测,中间用队列解耦。拉流线程只负责最新的帧,处理线程不追旧帧,保证延迟稳定。
import cv2 import threading import queue frame_queue = queue.Queue(maxsize=2) def capture_loop(stream_url): cap = cv2.VideoCapture(stream_url) while True: ret, frame = cap.read() if not ret: break if frame_queue.qsize() < 2: frame_queue.put(frame) def process_loop(): while True: frame = frame_queue.get() run_pipeline(frame)队列 maxsize 设为 2,是为了防止处理不及导致内存膨胀。实时性优先于完整性。
7.2 输出标准化 JSON 与 Webhook
车牌识别最终要对接业务系统。我设计的输出结构如下:
{ "plate": "京A12345", "confidence": 0.93, "timestamp": "2025-06-18T15:04:22+08:00", "image_id": "20250618-150422-0001", "box": [124, 566, 340, 606] }用 Webhook 方式推送,或者直接写 MQTT 消息,业务端订阅即可。这里建议在输出前做一次去重,用 image_id 加时间戳判断,避免重复入库。我经常看到有人只写文件,不做消息通知,结果系统集成时还要再写一堆适配代码。
7.3 模型裁剪与量化
如果想进一步降低CPU负载,可以把 YOLO11 和 RapidOCR 的 ONNX 模型做 INT8 量化。onnxruntime 支持 PTQ 量化,我试过把 YOLO11 的 ONNX 模型量化后,体积缩小 50%,速度提升约 1.5 倍,准确率几乎没有下降。RapidOCR 本身的 ONNX 模型也可以量化,但要小心文本检测部分的数值敏感度,建议量化后做回归测试。
7.4 一个屡试不爽的小技巧:识别前放大2倍
最后分享一个我每次都会用的技巧:无论检测到的车牌在画面里多大,裁剪后先放大 2 倍再给 OCR,这个操作几乎不增加多少耗时,但能把准确率提升一截。原因很简单,RapidOCR 的识别模型是在分辨率较高的训练数据上训练的,低分辨率输入会造成特征细节丢失。这个技巧我也用在了其他 OCR 项目里,值得放在工具箱里。
车牌识别这个应用,看起来是一个“小功能”,真正跑起来之后,你会碰到检测、跟踪、性能、并发、脏数据等一系列问题。能用 YOLO11 和 RapidOCR 把这条路走通,之后的很多视觉识别项目都可以复用这套框架。