这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 开源硬件 » 【RaspberryPi5开发板方案创意赛】智能车牌识别系统--实时车牌识别系统

共1条 1/1 1 跳转至

【RaspberryPi5开发板方案创意赛】智能车牌识别系统--实时车牌识别系统

菜鸟
2025-12-18 19:21:07     打赏

在上一篇文章中,我们利用 OpenCV 自带的预训练模型实现了实时人脸与猫脸检测,验证了摄像头与开发环境的正确配置。本文将在此基础上,进一步介绍如何使用ONNX车牌检测与车牌识别模型,实现实时车牌识别功能。

1、模型介绍

实现车牌识别通常分为两个步骤:首先定位并检测出图像中的车牌区域,再对该区域进行字符识别,提取车牌号码。

本文使用的模型为 ONNX 格式的预训练模型。Open Neural Network Exchange(ONNX) 是一个开放的生态系统,旨在帮助 AI 开发者灵活选择并使用不同的工具,适应项目的发展需求。

环境配置步骤:

①安装 onnxruntime 库,用于加载和运行 ONNX 模型。在终端中执行以下命令:

sudo apt install python3-pip
sudo pip3 install onnxruntime --break-system-packages

②下载模型文件至树莓派本地目录 ~/models 中。模型来源: GitHub 地址:https://github.com/we0091234/Chinese_license_plate_detection_recognition.git 百度网盘:https://pan.baidu.com/s/1vk6OXdk-xj3KWfT4g8yRdw?pwd=izpy 提取码: izpy

2、实时车牌识别

打开Thonny IDE,将解释器设置为"远程Python 3 (SSH)",连接到树莓派5。新建文件usbcam_car.py,输入以下代码:

#!/usr/bin/env python3
import cv2
import numpy as np
import onnxruntime as ort
import time
from PIL import Image, ImageDraw, ImageFont

# ---------- 配置 ----------
DET_MODEL_PATH = "models/plate_detect.onnx"
REC_MODEL_PATH = "models/plate_rec_color.onnx"
FONT_PATH = "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"

CHARS = ["#", "京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁",
         "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "学", "警",
         "港", "澳", "挂", "使", "领", "民", "航", "危", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
         "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

# 结果缓存参数
MAX_CACHE_FRAMES = 3  # 保留识别结果最多 3 帧
cache_plate_text = ""
cache_bbox = None
cache_counter = 0

# 字体大小(可调)
FONT_SIZE = 28

# ---------- 初始化 ONNX 模型 ----------
print("加载车牌检测模型...")
det_sess = ort.InferenceSession(DET_MODEL_PATH, providers=['CPUExecutionProvider'])
det_in_name = det_sess.get_inputs()[0].name
det_out_name = det_sess.get_outputs()[0].name

print("加载车牌识别模型...")
rec_sess = ort.InferenceSession(REC_MODEL_PATH, providers=['CPUExecutionProvider'])
rec_in_name = rec_sess.get_inputs()[0].name

# ---------- 工具函数 ----------
def preprocess_for_detection(img):
    blob = cv2.resize(img, (640, 640))
    blob = blob.astype(np.float32) / 255.0
    blob = blob[:, :, ::-1].transpose(2, 0, 1)
    blob = np.expand_dims(blob, axis=0)
    return blob

def postprocess_detection(outputs, orig_shape, conf_thres=0.25, iou_thres=0.45):
    orig_h, orig_w = orig_shape[:2]
    boxes = []
    for pred in outputs:
        x, y, w, h, conf, cls_conf = pred[:6]
        if conf < conf_thres:
            continue
        x1 = int((x - w / 2) * orig_w / 640)
        y1 = int((y - h / 2) * orig_h / 640)
        x2 = int((x + w / 2) * orig_w / 640)
        y2 = int((y + h / 2) * orig_h / 640)
        boxes.append([x1, y1, x2 - x1, y2 - y1])
    if not boxes:
        return []
    scores = [1.0] * len(boxes)  # 简化 NMS,假设置信度一致
    indices = cv2.dnn.NMSBoxes(boxes, scores, conf_thres, iou_thres)
    if len(indices) == 0:
        return []
    return [boxes[i] for i in indices.flatten()]

def preprocess_for_recognition(plate_img):
    img = cv2.resize(plate_img, (168, 48))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32)
    img = (img / 255.0 - 0.5) / 0.5
    img = np.transpose(img, (2, 0, 1))
    img = np.expand_dims(img, axis=0)
    return img

def decode_prediction(pred):
    result = []
    for i in range(len(pred)):
        if pred[i] != 0 and (i == 0 or pred[i] != pred[i - 1]):
            if pred[i] < len(CHARS):
                result.append(CHARS[pred[i]])
    return ''.join(result)

def draw_chinese_pil(img, text, pos, font_size=28, color=(0, 255, 0)):
    pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(pil_img)
    try:
        font = ImageFont.truetype(FONT_PATH, font_size)
    except Exception as e:
        print(f"字体加载失败: {e}")
        font = ImageFont.load_default()
    draw.text(pos, text, fill=color[::-1], font=font)
    return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)

# ---------- 主程序 ----------
def main():
    global cache_plate_text, cache_bbox, cache_counter

    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print("无法打开摄像头")
        return

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

    cv2.namedWindow("Realtime License Plate Recognition", cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty("Realtime License Plate Recognition", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

    frame_count = 0
    start_time = time.time()

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        frame_count += 1
        display_frame = frame.copy()

        # 默认使用缓存结果(如果还在有效期)
        current_plate_text = ""
        current_bbox = None

        # 每 3 帧做一次检测(平衡速度与稳定性)
        if frame_count % 3 == 0:
            blob = preprocess_for_detection(frame)
            outputs = det_sess.run([det_out_name], {det_in_name: blob})[0]
            outputs = np.squeeze(outputs)
            boxes = postprocess_detection(outputs, frame.shape)

            if boxes:
                # 取第一个检测框(可扩展为多车牌)
                x, y, w, h = boxes[0]
                x1, y1 = max(0, x), max(0, y)
                x2, y2 = min(frame.shape[1], x + w), min(frame.shape[0], y + h)
                if x2 > x1 and y2 > y1:
                    plate_crop = frame[y1:y2, x1:x2]
                    rec_input = preprocess_for_recognition(plate_crop)
                    rec_output = rec_sess.run(None, {rec_in_name: rec_input})[0]
                    pred_indices = np.argmax(rec_output[0], axis=1)
                    plate_text = decode_prediction(pred_indices).strip()

                    if len(plate_text) >= 5:  # 合理长度才接受
                        current_plate_text = plate_text
                        current_bbox = (x1, y1, x2, y2)
                        # 更新缓存
                        cache_plate_text = plate_text
                        cache_bbox = (x1, y1, x2, y2)
                        cache_counter = MAX_CACHE_FRAMES

        # 缓存衰减
        if cache_counter > 0:
            cache_counter -= 1
            # 使用缓存结果绘制
            if cache_bbox is not None:
                x1, y1, x2, y2 = cache_bbox
                cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 255, 0), 3)
                text_y = y1 - FONT_SIZE - 5
                if text_y < 0:
                    text_y = y2 + FONT_SIZE + 5
                display_frame = draw_chinese_pil(
                    display_frame,
                    cache_plate_text,
                    (x1, text_y),
                    font_size=FONT_SIZE,
                    color=(0, 255, 0)
                )

        # 显示 FPS
        elapsed = time.time() - start_time
        fps = frame_count / elapsed if elapsed > 0 else 0
        cv2.putText(display_frame, f'FPS: {fps:.1f}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)

        cv2.imshow("Realtime License Plate Recognition", display_frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()
    print(f"程序结束,平均 FPS: {fps:.1f}")

if __name__ == "__main__":
    main()

完成代码编辑后,返回树莓派的本地终端(非 SSH 会话),执行以下命令以运行程序:

python usbcam_car.py

程序启动后,屏幕将全屏显示摄像头画面,系统会实时检测画面中的车牌,并在识别出的车牌周围绘制标注框,同时显示识别出的车牌号码。

视频放B站:https://www.bilibili.com/video/BV19BqpBLE29/

针对当前车牌识别系统的测试结果,发现以下几类典型问题:

  1. 复杂背景干扰:在车身图案复杂或背景杂乱的场景下,系统难以有效定位和检测车牌区域,导致漏检。

  2. 相似文本干扰:画面中出现与车牌形态相似的印刷文字(如广告、标识等)时,容易产生误识别,将非车牌区域判断为车牌。

  3. 图像质量影响:在车牌图像模糊的情况下,系统对汉字部分的识别准确率明显下降。

  4. 特种车牌适应不足:针对电动车牌等非典型车牌样式,当前模型检测不到。

后续可通过优化检测算法、增强模型训练数据多样性,以及结合图像预处理技术来改善系统的覆盖面与准确率。

至此,智能车牌识别系统的基础开发已经完成。理论上,下一步应当将系统部署至真实道路场景进行户外实测,以验证其在复杂环境下的识别准确性与实时性。然而,考虑到在实际道路测试中可能涉及车辆及行人的隐私信息采集与处理,存在一定的法律风险,因此本次项目暂不进行户外实测。







关键词: RaspberryPi5    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]