绥芬河海关 · 中俄车辆通道

多路车牌识别,结果上报业务系统

通道需同时识别中国牌和俄罗斯牌。拉流与识别拆成两个进程:采集持续写图,识别只处理该路目录中最新一帧。 识别结果以 TCP JSON 上报业务系统;是否放行、是否抬杆由海关业务侧完成,不进入识别进程。

5.81×现场 Windows,OpenVINO 检测+中国牌
2.3×M1 上 YOLO 使用 Metal,相对 CPU
0.66 msWindows 上 CRNN 读字,原先 13.8 ms
三后端PyTorch / MPS / OpenVINO,上报契约不变

现场问题与对策

部署过程中遇到的问题,以及对应的处理方式。

误报

邻道车辆进入检测

画面中常有对向车道、路边停放的车辆。提高检测阈值后,分数足够的邻道目标仍会被检出。

每路相机标定多边形 ROI,区域外涂白后再送入模型。不改权重,只识别本车道。

遮挡

牌面绑有铁丝

部分车牌横向绑有铁丝,字符被遮挡。这不属于阈值或后处理能覆盖的情况,模型输出不可靠。

不在识别程序中做猜测补全。现场要求司机拆除铁丝后再通行,按通道管理处理。

吞吐

视频持续采集,识别只处理最新帧

拉流进程持续写图。过车只关心当前画面;若将目录中积压的历史帧全部送入模型,延迟和算力都会被拖高。

识别按修改时间只取当前目录最新一张。积压帧不进入推理。

崩溃

OpenVINO 与 PaddleOCR 无法同进程加载

M1 上两者分别链接 TBB 与 GNU OpenMP。无论加载顺序如何,进程会 abort(exit 134),或阻塞在 mutex。

OpenVINO 放到独立子进程,只承担检测和中国牌;主进程运行 PaddleOCR。图像经队列传递。

读错

地区码 125 被截成 25

OCR 将主号与地区码读成连续字符串。后处理若只保留 6 个字符,三位地区码会被截断。

按 6 位主号加 2 或 3 位数字拼接,三位优先。T326XO-125 可以完整保留。

加速

同一套 IR,Windows 与 M1 加速比不同

检测+中国牌:现场 Windows 上 OpenVINO 64.6 ms → 11.1 ms;M1 上同样的 IR 为 50.3 → 31.4 ms。

模型未更换。Intel 可走 AVX / VNNI;M1 的 OpenVINO 目前只有 ARM CPU 插件,没有核显和神经引擎路径。

链路设计

模型在识别进程内只加载一份,多路相机共用。OpenVINO 单独子进程,避免与 PaddleOCR 的 OpenMP 运行时冲突。

1 拉流rtsphd 一直写图;识别只取目录最新一张
2 车道多边形外涂白,邻道进不了检测
3 检测YOLO + 中国牌 CRNN,可切 CPU / MPS / OpenVINO
4 读牌中国牌对上规则就出;否则同一框走俄牌 OCR
5 上报JSON 给业务端;放行与闸控不在识别进程
RTSP / USB
→
rtsphd 最新帧
→
车道涂白
→
PyTorch / MPS
/
OpenVINO 子进程
中国牌规则
→
对不上则 PaddleOCR 俄牌
→
TCP JSON
→
海关业务系统
→
抬杆 / 不抬

拉流与识别为何拆开

视频会断流、重连,抽帧节奏也与推理不对齐。识别只读取已写完且最新的 jpg。采集进程退出时,模型无需重新加载。

线程划分

配置中有几路相机,就启动几条识别线程。YOLO、CRNN、PaddleOCR 各一份,进程内共享,不是一路复制一套权重。

车道、读牌、上报

按推理顺序:先限车道,再读牌,最后上报。闸控不进入识别进程。

车道 ROI

在抓拍图上标定四个顶点,写入该路 config.ini。推理前将车道外区域涂白。未配置则整图识别。标定工具 tools/lane_config.py 与线上读取同一套坐标。

[boxes]
x1=120  y1=380
x2=1860 y2=400
x3=1920 y3=1080
x4=0    y4=1080

单张读牌

先由 YOLO 定位车牌,四点透视校正后由 CRNN 解码字符与颜色。满足 7 位、省份开头、蓝色或黄色,即判定为中国牌并结束该帧。

规则不匹配时,将同一检测框裁切后交给 PaddleOCR 3.x,按 6 位主号 + 地区码拼接俄牌。检测框常偏左,透视校正后再向右扩展,避免 125 被读成 25。

上报

识别只发送 device_id / plate / color / country / img / time。是否放行、是否抬杆由业务系统决定。一线 / 二线入区出区共用同一套字段,区别只在相机位置。

{
  "device_id": "43",
  "plate": "M009XK-25",
  "country": "rus"
}
检测框偏左,地区码几乎在框外
检测框常偏左,地区码 25 贴在框边
透视裁切后地区码被切掉
直接裁切时 OCR 只能看到主号,125 会被读成 25

Apple Silicon 上 PaddleOCR 2.x 首次推理会崩溃,已升级到 3.x。曾试用 RapidOCR,俄牌易被切成单字,未用于生产。

三个推理后端

.pt 导出为 ONNX(opset 12),再转换为 OpenVINO FP16。运行时加载 IR 或 .pt,不加载 ONNX。默认后端为 PyTorch CPU。

.pt / .pth
→
ONNX
→
OpenVINO xml+bin
.pt / .pth
→
job / job_mac
←
OpenVINO IR

后端切换

python job_mac.py --backend mps,或环境变量 PLATE_BACKEND。MPS 只执行卷积,NMS 仍在 CPU。权重先在 CPU 加载再拷到 GPU;直接 map_location=mps 会触发 float64 相关错误。

检测 IR 为何大于 .pt

.pt 约 1.12 MB,其中权重大约 0.89 MB。IR 的 bin 体量接近(0.91 MB),多出的 0.58 MB 来自 xml 中内联的 YOLO 解框计算图。.pt 中对应逻辑在 Python 侧,不计入文件大小。

切换后端是否改变识别结果

MPS 与 CPU 的检测框 IoU=1.000,OpenVINO 相对 CPU 约 0.993。中国牌多数一致,远景个别字符可能不同。上报字段不变,业务侧无需同步修改。

实测耗时

M1 Pro:YOLO 检测,独立进程,warmup 10,重复 30 次,8 张 640 图。MPS 含 Host-to-Device 拷贝。

YOLO 检测 CPU / MPS / OpenVINO 耗时对比
后端 平均 P50 P95 相对 CPU 框 IoU
PyTorch CPU 49.5 ms 49.4 ms 50.3 ms 1.0× —
PyTorch MPS 21.7 ms 21.6 ms 22.7 ms 2.3× 1.000
OpenVINO FP16 24.1 ms 24.2 ms 25.1 ms 2.1× 0.993

检测 + 中国牌 CRNN(不含俄牌 OCR)。Mac:8 张 640;Windows:现场单张 434×640。口径不完全一致,以加速倍数对照。

检测加中国牌识别 Mac 与 Windows 对比
平台 后端 平均 P50 P95 只读字 加速
Apple M1 Pro PyTorch CPU 50.3 ms 51.0 ms 53.6 ms — 1.0×
Apple M1 Pro OpenVINO 31.4 ms 30.7 ms 42.9 ms — 1.6×
Windows Intel PyTorch CPU 64.6 ms 65.5 ms 66.6 ms 13.77 ms 1.0×
Windows Intel OpenVINO 11.1 ms 10.8 ms 12.3 ms 0.66 ms 5.81×
模型体积对比
OCR 耗时

俄牌还需 PaddleOCR,大约 0.2–1 秒。端到端延迟的主要部分在 OCR,不在 YOLO。

样例

公开卡车图,非绥芬河海关近景,用于检查检测框与后处理。现场距离、角度不同,个别字符可能不一致。红框为检测结果。

部署与运维

配置收拢到 config/,根目录只保留入口。现场启停通过控制台完成,避免直接操作命令行。

plateAI 控制台:拉流、识别分开启停,可选 MPS / OpenVINO / CPU
Tkinter tools/control_panel.py · 拉流 / 识别分开启停。停止只杀这个窗口拉起的进程树,不会把机器上别的 Python 一起杀掉。

配置

Socket 和后端在 settings.py。Mac / Windows 各一份相机列表。日志在 config/logger.py。

控制台

一键启动按顺序拉起拉流和识别,日志输出到同一窗口。车道 ROI 按钮打开标定工具。

画车道

tools/lane_config.py 在图上标定多边形,写回该路 config.ini。识别读取同一份文件,标定错误会直接进入推理。

python tools/control_panel.py
python job_mac.py --once --backend mps
python job_mac.py --once --backend openvino
python tools/lane_config.py --dir img/imgs12

实现取舍

为何暂不量化到 INT8?

INT8 需要接近现场分布的校准数据,存在读错字的风险。Windows 上 FP16 已有 5.81 倍加速。路数继续增加时再评估,当前优先保证号牌正确。

漏检兜底放大后 OCR 为何会失败?

条带 ×2.5 曾将 2848 宽的图拉到 4272,超过 PaddleOCR 4000 边长上限。当前仅对小图放大,最长边限制为 1600。

技术栈

Python 3.11 YOLOv5 CRNN PaddleOCR 3.x PyTorch MPS OpenVINO FP16 OpenCV ROI Tkinter 控制台 TCP JSON