当前位置: 首页 > news >正文

车牌识别

车牌识别方案对比与实现总结(GUI 三方法:lock / test / rec2)

本文面向实际工程应用,系统梳理当前 GUI 集成的三种车牌识别方法(lock、test、rec2)的技术亮点、设计思路、模型选择、实现过程与关键代码,帮助快速理解与持续优化。目标是:在统一界面中,对比“传统候选+文字识别过滤”的方案(lock)、“端到端检测-识别-分类”方案(test),以及“形态学/轮廓候选+识别过滤”的方案(rec2),支持图片/文件夹/视频多形态输入,直观呈现识别效果差异。


一、总体架构与设计思路

  • 核心目标:

    • 在统一 GUI 中提供三种方法的可选运行模式;
    • 对比传统 vs 端到端的精度、鲁棒性与效率;
    • 统一中文标注与多输入适配(图像/视频)。
  • 模块职责:

    • 界面层:方法选择、输入/输出配置、预览与“大图”查看;
    • 业务层:三种方法在“单图/视频帧”上的处理逻辑、可视化与结果导出;
    • 公共组件:中文文本绘制(PIL)、视频快照节流、结果持久化(图像/JSON)。
  • 关键设计原则:

    • 单一职责:方法逻辑与界面解耦,便于新增方法与维护;
    • 可视化一致性:所有方法输出“筛选前/筛选后/检测或二值图”;
    • 可扩展性:新增方法仅需实现标准接口并注册到 GUI 下拉中;
    • 统一中文绘制:用 PIL 绘制中文,彻底规避 cv2.putText 中文乱码。

技术实现(细化)

本节基于当前代码实现,按“GUI 触发 → 业务函数 → 文件输出”的链路,细化可复现的工程细节,便于维护与扩展。

  • 入口与界面交互(/gui.py

    • 方法选择:QComboBoxlock / 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←binimg2←preimg3←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
        • 检测 MultiTaskDetectorORTdet,默认输入 (det_size, det_size)
        • 识别 RecognitionORTrec,输入 (48,160)
        • 分类 ClassificationORTcls,输入 (96,96)
      • 裁剪:standalone_modules/crop.py::crop_plate_by_landmarks
      • 中文标签:app.py::put_chinese_text
      • 产出:可视化 *_test_vis.jpg,结果 JSON *_test_results.jsonbbox/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 取前三张用于预览)。
  • 模型默认路径(自动回退)

    • 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 在特定环境下建立轻量基线或进行可解释性分析;未来可继续引入参数可视化调优、自动评估、批量处理与数据闭环,形成从实验到部署的完整链路。

http://www.wxhsa.cn/company.asp?id=6831

相关文章:

  • 告别人工标注瓶颈!Reward-RAG:用 CriticGPT 打造更懂人类偏好的检索模型
  • Latex 中百分号怎么打
  • 文件上传-条件竞争绕过
  • 文件包含漏洞
  • 9.17 CSP-S模拟23/多校A层冲刺NOIP2024模拟赛19 改题记录
  • Java基本语法
  • 在AI技术快速实现创想的时代,挖掘前端学习新需求成为关键——某知名编程教育平台需求洞察
  • 负载均衡层详解part3-lvs
  • 4. MySQL 索引优化实战
  • 算法课第一周作业
  • 线段树懒标记模板
  • 谁在我这位置遗留或丢失了一颗口罩爆珠(好像是桃子味)?
  • python小计划——学生管理系统
  • C++ 并发
  • UML 5章
  • 《微服务事务管理》 - 教程
  • python之socket udp服务器实现
  • kylin SP3安装mysql 8.4.5
  • Unity中是否可以禁用GC
  • 经典SQL语句大全
  • 开源软件图形库
  • IvorySQL 与 deepin 完成兼容性认证,共创开源生态新篇章
  • 在 Nginx 上搭建静态站点
  • 测试
  • 认真做膜你赛
  • 使用GitHub Dork快速发现漏洞:我的第一个Bugcrowd漏洞挖掘实战
  • kylin SP3安装mysql8.0.41
  • DIFY 项目中通过 Makefile 调用 Dockerfile 并采用 sudo make build-web 命令构建 web 镜像的方法和注意事项
  • 代码随想录算法训练营第一天 | 704. 二分查找、27. 移除元素、209.长度最小的子数组
  • 从 MLPerf Storage v2.0 看 AI 训练中的存储性能与扩展能力