手把手教你完成基于YOLOv11+CNN车牌识别系统,Opencv车牌矫正,基于深度学习的车牌识别系统_yolov11s.onnx车牌检测模型
引言
车牌识别系统是智能交通、安防监控等地方的关键技术,结合深度学习方法可提升识别模型准确率。传统方法依赖手工特征提取,而OpenCV的车牌矫正技术进一步优化倾斜或变形车牌的预处理,为后续识别提供高质量输入,基于 YOLOv11的目标检测与 CNN 分类器的结合,能够实现端到端的车牌定位与字符识别。
一、相关技术
技术路线如下:
- 先利用YOLOv11算法定位车牌位置
- 由于有些车牌倾斜比较大,识别难度大大增加,那么就使用 cv2 进行边缘检测获得车牌区域坐标,并将车牌图形矫正
- 车牌经过矫正后,利用 CNN 网络进行车牌多标签端到端识别
实验结果,整体识别还好,对于倾斜角度和比较模糊的图片识别率不是很高,后续可以考虑将 CNN 算法更换为 LPRNet 算法,系统识别结果如下:
二、环境配置
1.YOLOv11环境配置
YOLO 系列环境配置讲过很多次了,其中YOLOv11/YOLOv10/YOLOv9/YOLOv8/YOLOv7/YOLOv5环境都是通用的,只需要安装一次就行,如果以前安装过,可以忽略本章的环境安装
🍀🍀 1.pytorch环境安装
手把手安装YOLO环境教程 1(非常推荐)链接参考链接: YOLO环境配置教程视频
手把手pytorch安装教程 2 链接参考链接: pytorch环境安装教程视频
🍀🍀2.其他依赖安装
安装 requirements.txt 文件的环境,需要注释掉下面两行,前面的步骤已经安装了,不注释的话会覆盖前面的会安装最新版本的 pytorch,所以注释掉
没有这个文件可以自己新建一个requirements.txt,然后把下面代码复制进去就好了
# Ultralytics requirements# Example: pip install -r requirements.txt# Base ----------------------------------------matplotlib>=3.3.0numpy==1.24.4 # pinned by Snyk to avoid a vulnerabilityopencv-python>=4.6.0pillow>=7.1.2pyyaml>=5.3.1requests>=2.23.0scipy>=1.4.1tqdm>=4.64.0# Logging -------------------------------------# tensorboard>=2.13.0# dvclive>=2.12.0# clearml# comet# Plotting ------------------------------------pandas>=1.1.4seaborn>=0.11.0# Export --------------------------------------# coremltools>=7.0 # CoreML export# onnx>=1.12.0 # ONNX export# onnxsim>=0.4.1 # ONNX simplifier# nvidia-pyindex # TensorRT export# nvidia-tensorrt # TensorRT export# scikit-learn==0.19.2 # CoreML quantization# tensorflow>=2.4.1 # TF exports (-cpu, -aarch64, -macos)# tflite-support# tensorflowjs>=3.9.0 # TF.js export# openvino-dev>=2023.0 # OpenVINO export# Extras --------------------------------------psutil # system utilizationpy-cpuinfo # display CPU infothop>=0.1.1 # FLOPs computation# ipython # interactive notebook# albumentations>=1.0.3 # training augmentations# pycocotools>=2.0.6 # COCO mAP# roboflow
2.CNN环境配置
训练CNN模型需要安装单独安装 tensorflow,要么安装 gpu 版本或者 cpu 版本,下面给出各自的安装教程
如果安装 gpu 版本,电脑必须有英伟达显卡,并且先安装对应版本的 cuda 和 cudnn,安装教程看这篇文章: cuda和cudnn的安装教程(全网最详细保姆级教程),我安装的 cuda 版本是11.3,cudnn 版本是 8.2,建议安装跟我一样,避免报错
安装完 cuda 和 cudnn 之后,输入如下命令来安装 tensorflow gpu 版本 :
pip install tensorflow_gpu==2.7.0
测试tensorflow gpu 是否能用,代码如下:
# -*- coding: utf-8 -*-\"\"\"@Auth : 挂科边缘@File :Test.py@IDE :PyCharm@Motto:学习新思想,争做新青年@Email :179958974@qq.com\"\"\"import tensorflow as tfa = tf.test.is_built_with_cuda() # 判断CUDA是否可以用b = tf.test.is_gpu_available( cuda_only=False, min_cuda_compute_capability=None) # 判断GPU是否可以用print(a)print(b)
输出两个True证明能用,如下图所示
如果安装 cpu 版本就简单了,不用安装cuda和cudnn,直接输入下面命令安装就行,命令如下:
pip install tensorflow-cpu==2.7.0
3.PySide6安装
运行系统还需要安装PySide6
pip install PySide6==6.4.2
三、数据集准备和预处理
1.数据集准备
由于车牌数据集比较少,将采用开源数据集,本次实验均采用CCPD数据集,数据集详情参考链接:CCPD数据集参考
2.数据预处理
YOLOv11算法训练前需要做的工作:
训练前需要对数据集进行预处理,训练 YOLOv11 模型需要数据集进行标注,由于CCPD 数据集的特殊性,人家已经标注好了,只需要处理成 YOLO 格式即可,可以参考这篇文章进行处理:CCPD数据集处理成YOLO格式
CNN算法训练前需要做的工作:
CCPD 数据集我拿了 30000 张,CNN算法训练前需要裁剪车牌图片,裁剪后我们发现车牌有些是倾斜的,那么我们需要进行矫正,为了方便模型输入,将裁剪后的车牌进行统一大小为宽240,高80,处理好的训练样本如下所示:
对数据集进行字符统计如下图所示:
CCPD裁剪+矫正代码如下:
# -*- coding: utf-8 -*-\"\"\"@Auth : 挂科边缘@File :ccpd裁剪.py@IDE :PyCharm@Motto:学习新思想,争做新青年@Email :179958974@qq.com\"\"\"import shutilimport numpy as npimport cv2import oswords_list = [ \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"J\", \"K\", \"L\", \"M\", \"N\", \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\", \"X\", \"Y\", \"Z\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"]con_list = [ \"皖\", \"沪\", \"津\", \"渝\", \"冀\", \"晋\", \"蒙\", \"辽\", \"吉\", \"黑\", \"苏\", \"浙\", \"京\", \"闽\", \"赣\", \"鲁\", \"豫\", \"鄂\", \"湘\", \"粤\", \"桂\", \"琼\", \"川\", \"贵\", \"云\", \"西\", \"陕\", \"甘\", \"青\", \"宁\", \"新\"]def order_points(pts): rect = np.zeros((4, 2), dtype=\'float32\') s = pts.sum(axis=1) # 每行像素值进行相加;若axis=0,每列像素值相加 rect[0] = pts[np.argmin(s)] # top_left,返回s首个最小值索引,eg.[1,0,2,0],返回值为1 rect[2] = pts[np.argmax(s)] # bottom_left,返回s首个最大值索引,eg.[1,0,2,0],返回值为2 # 分别计算左上角和右下角的离散差值 diff = np.diff(pts, axis=1) # 第i+1列减第i列 rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rectdef four_point_transform(image, pts): rect = order_points(pts) (tl, tr, br, bl) = rect # 计算新图片的宽度值,选取水平差值的最大值 widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) maxWidth = max(int(widthA), int(widthB)) # 计算新图片的高度值,选取垂直差值的最大值 heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) maxHeight = max(int(heightA), int(heightB)) # 构建新图片的4个坐标点,左上角为原点 dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype=\"float32\") M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) return warpedif __name__ == \"__main__\": global points total = [] input_dir = r\'D:/Desktop/ccpd/ccpd/\' # CCPD数据集路径,英文路径 output_dir = r\'D:/Desktop/ccpd/new_ccpd\' # 保存路径,英文路径 if os.path.exists(output_dir): for filename in os.listdir(output_dir): file_path = os.path.join(output_dir, filename) try: if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) elif os.path.isdir(file_path): shutil.rmtree(file_path) except Exception as e: print(f\'删除 {file_path} 失败. 原因: {e}\') else: os.makedirs(output_dir, exist_ok=True) for item in os.listdir(os.path.join(input_dir)): img_path = os.path.join(input_dir, item) img = cv2.imread(img_path) # 解析文件名部分 parts = item.split(\'-\') if len(parts) < 7: print(f\"文件名格式错误: {item}\") continue _, _, bbox, points_str, label_str, _, _ = parts # 解析点坐标 points = points_str.split(\'_\') tmp = points points = [] for p in tmp: coords = p.split(\'&\') if len(coords) >= 2: try: points.append([int(coords[0]), int(coords[1])]) except ValueError: print(f\"坐标解析错误: {p} in {item}\") continue # 解析车牌标签 label_parts = label_str.split(\'_\') if len(label_parts) < 7: print(f\"标签格式错误: {label_str} in {item}\") continue try: con = con_list[int(label_parts[0])] words = [words_list[int(num)] for num in label_parts[1:7]] plate_number = con + \'\'.join(words) except (IndexError, ValueError) as e: print(f\"车牌解析错误: {e} in {item}\") continue line = item + \'\\t\' + plate_number + \'\\n\' total.append(line) # 还原像素位置 if len(points) < 4: print(f\"点坐标不足: {item}\") continue points_np = np.array(points, dtype=np.float32) warped = four_point_transform(img, points_np) warped = cv2.resize(warped, (240, 80), interpolation=cv2.INTER_CUBIC) safe_plate_number = plate_number.encode(\'utf-8\').decode(\'utf-8\') save_path = os.path.join(output_dir, safe_plate_number + \'.jpg\') # 检查文件是否已存在,避免覆盖 counter = 1 while os.path.exists(save_path): save_path = os.path.join(output_dir, f\"{safe_plate_number}_{counter}.jpg\") counter += 1 success, encoded_image = cv2.imencode(\'.jpg\', warped) if success: with open(save_path, \'wb\') as f: f.write(encoded_image.tobytes()) else: print(f\"图像编码失败: {save_path}\")
代码需要修改的地方如下,填一下输入数据集路径和保存的路径即可,需要注意的是路径不能有中文
CCPD原始数据集我只拿30000张,需要 训练好的模型+完整源码+原始数据集+处理好的数据集 在文章末尾发送 车牌识别 即可获取
四、模型训练
1.YOLOv11模型训练
先训练YOLOv11车牌定位模型,因为 YOLO 系列训练讲过很多了,这里训练的话参考这篇文章即可:YOLOv11来了,使用YOLOv11训练自己的数据集和推理(附YOLOv11网络结构图)
2.CNN车牌识别训练
在第三章我们已经处理好数据集了,直接拿来训练即可
修改自己数据集路径即可
训练结果如下:
我画图出现乱码,在训练代码加上下面代码即可
plt.rcParams[\"font.family\"] = [\"SimHei\"]plt.rcParams[\"axes.unicode_minus\"] = False # 正确显示负号
下面的图分别代表 模型损失、第一个字符准确率、第七个字符准确率、平均字符准确率
四、系统设计与实现
通过Qt Desiger设计好界面,如下图所示
之后实现后端代码即可,最终运行MainUI.py结果如下:
五、总结和源码获取
- 当前系统的优势与不足:倾斜角度较大和模糊的图片识别效果不佳
- 未来改进方向:更换更换的车牌识别模型,如 LprNet 模型
CCPD原始数据集我只拿30000张,需要 训练好的模型+完整源码+原始数据集+处理好的数据集 在文章末尾发送 车牌识别 即可获取