车牌识别方案对比与实现总结(GUI 三方法:lock / test / rec2)
本文面向实际工程应用,系统梳理当前 GUI 集成的三种车牌识别方法(lock、test、rec2)的技术亮点、设计思路、模型选择、实现过程与关键代码,帮助快速理解与持续优化。目标是:在统一界面中,对比“传统候选+文字识别过滤”的方案(lock)、“端到端检测-识别-分类”方案(test),以及“形态学/轮廓候选+识别过滤”的方案(rec2),支持图片/文件夹/视频多形态输入,直观呈现识别效果差异。
一、总体架构与设计思路
-
核心目标:
- 在统一 GUI 中提供三种方法的可选运行模式;
- 对比传统 vs 端到端的精度、鲁棒性与效率;
- 统一中文标注与多输入适配(图像/视频)。
-
模块职责:
- 界面层:方法选择、输入/输出配置、预览与“大图”查看;
- 业务层:三种方法在“单图/视频帧”上的处理逻辑、可视化与结果导出;
- 公共组件:中文文本绘制(PIL)、视频快照节流、结果持久化(图像/JSON)。
-
关键设计原则:
- 单一职责:方法逻辑与界面解耦,便于新增方法与维护;
- 可视化一致性:所有方法输出“筛选前/筛选后/检测或二值图”;
- 可扩展性:新增方法仅需实现标准接口并注册到 GUI 下拉中;
- 统一中文绘制:用 PIL 绘制中文,彻底规避
cv2.putText
中文乱码。
技术实现(细化)
本节基于当前代码实现,按“GUI 触发 → 业务函数 → 文件输出”的链路,细化可复现的工程细节,便于维护与扩展。
-
入口与界面交互(
/gui.py
)- 方法选择:
QComboBox
(lock
/test
/rec2
)。 - 输入选择:图片/文件夹/视频三类(
_choose_input
)。 - 输出目录:默认为
/runs
,可自定义(_choose_output
)。 - 模型与参数(仅
test
):det/rec/cls
路径,det_size
输入尺寸。 - 运行按钮:
_on_run
根据输入类型分派到图片/文件夹/视频流程,并将日志与预览三图更新到界面。
- 方法选择:
-
单图处理(图片文件)
lock
:调用/app.py::method_lock_on_image(img_path, out_dir)
。- 内部使用:
锁定筛选.py::recognize_on_candidates
生成候选与识别结果;draw_quads
可视化保留候选。 - 产出(存于
out_dir
):- 二值图
*_bin.jpg
- 筛选前
*_pre.jpg
- 筛选后
*_post.jpg
- 结果 JSON
*_lock_results.json
(字段:text/conf/quad
)。
- 二值图
- GUI 预览:
img1←bin
,img2←pre
,img3←post
。
- 内部使用:
rec2
:调用/app.py::method_recognize2_on_image
(识别2.py
函数化)。- 形态学/轮廓获取候选 → 可选识别与字符规则过滤(与
lock
同步)。 - 产出:
*_m2_pre.jpg
、*_m2_post.jpg
与结构化results
返回(GUI 用于日志与预览)。
- 形态学/轮廓获取候选 → 可选识别与字符规则过滤(与
test
:调用/app.py::method_test_on_image
。- 模型:
standalone_modules
- 检测
MultiTaskDetectorORT
(det
,默认输入(det_size, det_size)
) - 识别
RecognitionORT
(rec
,输入(48,160)
) - 分类
ClassificationORT
(cls
,输入(96,96)
)
- 检测
- 裁剪:
standalone_modules/crop.py::crop_plate_by_landmarks
- 中文标签:
app.py::put_chinese_text
- 产出:可视化
*_test_vis.jpg
,结果 JSON*_test_results.json
(bbox/text/rec_conf/plate_type_idx
)。 - GUI 预览:
img1←vis
。
- 模型:
-
批量图片处理(文件夹)
gui.py::_on_run
通过app.py::list_images_in_dir
顺序处理;- 按所选方法调用对应单图函数;
- 日志逐文件写入,输出组织与单图一致。
-
视频处理(
/app.py::process_video
)- 公共控制:
frame_stride
:抽帧处理(默认 5)max_frames
:最多保存快照数量上限(避免产出过多,默认 60/120)snapshot_stride
:每 N 帧保存一张(默认 30)snapshot_on_change
:识别文本集合变化时保存(True
)save_video
:是否保存带可视化叠加的视频(MP4V 编码)
- 方法分支:
lock
:- 每帧:
recognize_on_candidates
→ 合成四宫格(pre/post/bin/raw)作为帧级可视化。 - 快照:按步长/内容变化保存
basename_lock_{frame_idx}.jpg
。 - 文本集合:
{t for (t,_,_) in recs}
用于变化检测。
- 每帧:
rec2
:- 每帧:
method_recognize2_on_frame
→ 左右拼接(pre/post)。 - 快照命名:
basename_rec2_{frame_idx}.jpg
。
- 每帧:
test
:- 首帧前加载模型(避免重复初始化);
- 每帧:检测→裁剪→识别→分类→叠加中文标签;
- 快照命名:
basename_test_{frame_idx}.jpg
。
- 输出:
- 结果汇总 JSON:
{basename}_{method}.json
(按帧的结果列表); - 可选视频:
{basename}_{method}.mp4
; - 快照若干(GUI 取前三张用于预览)。
- 结果汇总 JSON:
- 公共控制:
-
模型默认路径(自动回退)
app.py::default_paths()
:从用户目录~/.hyperlpr3/20230229/onnx/
读取默认det/rec/cls
;rec2
若可用会尝试RecognitionORT
同目录模型;路径不存在时自动跳过识别,仅输出候选可视化。
-
中文可视化
- 统一使用
app.py::put_chinese_text
(PIL 渲染,失败回退cv2.putText
)。 - GUI 不直接绘制文字,所有文本叠加在业务层完成,保证跨方法一致性。
- 统一使用
-
文件 I/O 规范
- 读:统一
cv2.imdecode(np.fromfile(...))
兼容中文路径; - 写:
cv2.imencode(...).tofile(path)
,避免 Windows 路径与编码问题; - 目录:所有方法输出按
runs/{method}/...
归档,保证方法间结果互不冲突。
- 读:统一
-
扩展与参数外露建议
- 新方法接入:实现
method_xxx_on_image/imgs/video
同名风格函数并在gui.py
注册; - 参数外露:将
frame_stride/max_frames/snapshot_stride/snapshot_on_change/识别阈值/形态学核大小
等映射到 GUI 控件; - 评估闭环:在
process_video
统计每帧结果,后处理按时间聚合同文本事件,便于计算 Precision/Recall/F1。
- 新方法接入:实现
二、方法一(lock):传统候选定位 + 仅文字识别(对比基准)
2.1 技术亮点
- 不依赖检测模型,部署轻量,速度快;
- 传统 CV(边缘+色域+形态学)快速召回候选区域;
- 仅对候选做 OCR 识别,再以“车牌字符规则”过滤,直观对比传统候选误检/漏检与“后端识别”的关系。
2.2 候选定位(示意代码)
def pipeline(img_bgr):gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)blur = cv2.blur(gray, (3, 3))edge = cv2.Canny(blur, 30, 100)hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)# 蓝/黄/白/绿常见车牌颜色掩膜blue = cv2.inRange(hsv, (100, 40, 40), (130, 255, 255))yellow = cv2.inRange(hsv, (15, 40, 40), (35, 255, 255))white = cv2.inRange(hsv, (0, 0, 180), (180, 60, 255))green = cv2.inRange(hsv, (55, 40, 40), (95, 255, 255))color_mask = blue | yellow | white | greenfinal = cv2.bitwise_and(edge, edge, mask=color_mask)final = cv2.morphologyEx(final, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (22, 4)), 1)final = cv2.morphologyEx(final, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (1, 5)), 1)return finaldef locate_plates(bin_img, rgb_img):contours, _ = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)h_img, w_img = bin_img.shapecandidates = []for cnt in contours:rot_box = cv2.minAreaRect(cnt)(cx, cy), (w, h), angle = rot_boxif w < h:w, h = h, waspect = w / max(h, 1e-6)area = w * hif (800 <= area <= 0.05 * h_img * w_img and 2.0 <= aspect <= 6.0):pts = cv2.boxPoints(rot_box)candidates.append(np.int32(np.round(pts)))vis = rgb_img.copy()for pts in candidates:cv2.drawContours(vis, [pts], -1, (0, 0, 255), 2)return candidates, vis
2.3 仅 OCR 与规则过滤(示意代码)
def get_rotate_crop_image(img, points):points = points.astype(np.float32)w = int(max(np.linalg.norm(points[0]-points[1]), np.linalg.norm(points[2]-points[3])))h = int(max(np.linalg.norm(points[0]-points[3]), np.linalg.norm(points[1]-points[2])))dst = np.float32([[0,0],[w,0],[w,h],[0,h]])M = cv2.getPerspectiveTransform(points, dst)return cv2.warpPerspective(img, M, (w, h), flags=cv2.INTER_CUBIC)def plate_code_valid(code: str) -> bool:code = (code or '').strip().upper()if len(code) < 6 or len(code) > 8:return Falseif re.search(r"[\u4e00-\u9fa5]", code):return re.search(r"[A-Z0-9]{3,}", code) is not Noneif len(code) == 7 and re.match(r"^[A-Z0-9]{7}$", code):return Trueif len(code) == 8 and re.match(r"^[A-Z0-9]{8}$", code):return Truereturn Falsedef recognize_on_candidates(img_bgr, recognizer):bin_img = pipeline(img_bgr)candidates, pre_vis = locate_plates(bin_img, img_bgr)results, kept = [], []for quad in candidates:crop = get_rotate_crop_image(img_bgr, quad.astype(np.float32))if crop is None or crop.size == 0 or crop.shape[0] < 8 or crop.shape[1] < 16:continuetext, conf = recognizer(crop)if text and plate_code_valid(text):results.append((text, float(conf), quad))kept.append(quad)return results, candidates, kept, bin_img, pre_vis
2.4 可视化输出
- 二值图(bin):展示边缘与色域结合后的响应;
- 筛选前(pre):候选外接轮廓图;
- 筛选后(post):通过字符规则过滤后保留的候选图。
三、方法二(test):端到端检测→裁剪→识别→分类
3.1 技术亮点
- 完整管线:检测(bbox+关键点+单双层)→ 透视裁剪 → 识别(CTC)→ 分类(颜色/类型);
- 鲁棒性好,泛化能力强;
- 与方法一形成鲜明对比:传统候选召回 vs 学习型检测器召回。
3.2 关键逻辑(示意代码)
def run_full(img, det, rec, cls):outputs = det(img) # [x1,y1,x2,y2, score, 8个关键点, layer]vis = img.copy()results = []for out in outputs:x1, y1, x2, y2 = out[:4].astype(int)pad = crop_plate_by_landmarks(img, out) # 根据四点透视裁剪text, conf = rec(pad)type_idx = int(np.argmax(cls(pad)))cv2.rectangle(vis, (x1, y1), (x2, y2), (0, 255, 0), 2)label = f"{text} {conf:.2f} 类型:{type_idx}"vis = put_chinese_text(vis, label, (x1, max(0, y1 - 5)))results.append(dict(bbox=[int(x1), int(y1), int(x2), int(y2)],text=text, rec_conf=float(conf), plate_type_idx=type_idx))return vis, results
3.3 中文可视化(PIL 绘制)
def put_chinese_text(img_bgr, text, position, font_path="C:/Windows/Fonts/simhei.ttf", font_size=18, color_bgr=(0,255,0)):from PIL import Image, ImageDraw, ImageFontimg_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)pil_img = Image.fromarray(img_rgb)draw = ImageDraw.Draw(pil_img)color_rgb = (color_bgr[2], color_bgr[1], color_bgr[0])font = ImageFont.truetype(font_path, font_size)draw.text(position, str(text), font=font, fill=color_rgb)return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
3.4 视频处理节流
- 抽帧(
frame_stride
):降低帧级处理频率; - 快照(
snapshot_stride
):固定间隔保存快照; - 内容变化触发(
snapshot_on_change
):识别文本集合变化即保存快照。
四、方法三(rec2):形态学/轮廓候选 + 文字识别过滤
4.1 技术亮点
- 传统图像处理形态学管线,可快速生成候选;
- 标准化为函数接口,补齐 OCR 与字符规则过滤,使其可与 lock 方法直接对比;
- 便于教学/研究或固定场景快速搭建基线。
4.2 候选生成(示意代码)
def rec2_candidates(img_bgr):raw = img_bgrblur = cv2.GaussianBlur(raw, (3, 3), 0)gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)sobelx = cv2.Sobel(gray, cv2.CV_16S, 1, 0)absx = cv2.convertScaleAbs(sobelx)_, th = cv2.threshold(absx, 0, 255, cv2.THRESH_OTSU)close = cv2.morphologyEx(th, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)), iterations=3)kx, ky = cv2.getStructuringElement(cv2.MORPH_RECT, (20,1)), cv2.getStructuringElement(cv2.MORPH_RECT, (1,19))mor = cv2.dilate(close, kx); mor = cv2.erode(mor, kx)mor = cv2.erode(mor, ky); mor = cv2.dilate(mor, ky)med = cv2.medianBlur(mor, 15)contours, _ = cv2.findContours(med, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)pre_vis = raw.copy(); cv2.drawContours(pre_vis, contours, -1, (0,255,0), 2)candidates, crops = [], []for cnt in contours:x, y, w, h = cv2.boundingRect(cnt)if h > 0 and (w > 3*h) and (w < 5*h):candidates.append(np.array([[x,y],[x+w,y],[x+w,y+h],[x,y+h]], dtype=np.int32))crops.append(raw[y:y+h, x:x+w])return pre_vis, candidates, crops
4.3 OCR 与过滤(示意)
与 lock 相同,直接对 crops
调用识别器并用相同字符规则过滤,输出 post
图与 results
。
五、模型选择与部署建议
- 识别模型(CTC):字符表覆盖汉字与英数,兼容省份简称;
- 检测模型:多任务头(bbox + 顶点 + 单/双层),对不规则倾斜牌照提供对齐依据;
- 分类模型:颜色/类型辅助决策;
- 部署路径:推荐统一放置到用户目录下的模型缓存,或工程自带的 models 目录,避免路径分散引发加载失败。
六、统一中文绘制方案(无乱码)
6.1 原因
cv2.putText
基于底层绘制函数,多数仅支持单字节 ASCII,无法正确渲染中文,导致显示“???” 或乱码。
6.2 解决方案(PIL 渲染)
核心思路:OpenCV BGR → PIL RGB,PIL 绘制中文(指定系统字体),再转回 BGR。
def put_chinese_text(img_bgr, text, position, font_path="C:/Windows/Fonts/simhei.ttf", font_size=18, color_bgr=(0,255,0)):from PIL import Image, ImageDraw, ImageFontimg_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)pil_img = Image.fromarray(img_rgb)draw = ImageDraw.Draw(pil_img)color_rgb = (color_bgr[2], color_bgr[1], color_bgr[0])font = ImageFont.truetype(font_path, font_size)draw.text(position, str(text), font=font, fill=color_rgb)return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
字体建议:
- Windows:
C:/Windows/Fonts/simhei.ttf
(黑体)、C:/Windows/Fonts/msyh.ttc
(微软雅黑)。 - 若项目跨平台,建议把字体文件随项目分发,并以相对路径加载。
七、视频处理的“节流与高价值帧”策略
- 抽帧(
frame_stride
):降低处理频率(如每 5 帧处理一次); - 快照间隔(
snapshot_stride
):每 N 帧保存一张可视化快照; - 内容变化触发(
snapshot_on_change
):当识别文本集合发生变化时保存快照,捕捉“发生关键变化”的瞬间; - 最大处理帧数(
max_frames
):避免长视频处理时间上限不可控。
该策略既兼顾可视化体验(能看到关键帧),又控制输出量(避免“每帧一图”的冗余)。
八、方法对比与适用建议
-
lock(传统候选 + OCR 过滤)
- 优点:轻量快速;无需检测模型;
- 局限:对复杂背景/光照敏感;召回受限;
- 适用:固定场景的快速基线、与端到端方案对比实验。
-
test(端到端检测-识别-分类)
- 优点:鲁棒性与泛化最佳;精度稳定;
- 局限:对模型与算力依赖较高;
- 适用:通用/生产场景的主方案。
-
rec2(形态学/轮廓候选 + OCR 过滤)
- 优点:流程直观、可控性强;
- 局限:依赖阈值/核大小等手工参数,对噪声敏感;
- 适用:教学研究、固定场景的快速试验与对比。
九、工程优化与下一步
-
参数外露到 GUI:
- 抽帧间隔、快照间隔、最大帧数、是否按内容变化保存;
- 识别阈值、字符规则开关、形态学核大小与迭代次数。
-
评估工具:
- 与 GT JSON 对齐,统计 Precision / Recall / F1;
- 视频事件聚合:按时间合并连续相同文本,避免重复统计。
-
性能优化:
- 视频批量预处理 + 线程/进程并行;
- 断点续处理与结果缓存。
十、结语
本文完成了三种方法在统一 GUI 下的整合与对比,从传统候选到端到端,再到形态学候选,覆盖了业界最常见的三条路径。通过一致的中文可视化、统一的输出(bin/pre/post/vis/JSON)、以及视频节流策略,既提升了可用性也保证了可比性。实际落地时,推荐以 test 方案作为生产主路径,同时结合 lock/rec2 在特定环境下建立轻量基线或进行可解释性分析;未来可继续引入参数可视化调优、自动评估、批量处理与数据闭环,形成从实验到部署的完整链路。