视频演示
基于yolo12进行深度学习的机动车车牌检测
前言
大家好,我是Coding茶水间。
今天分享一个基于YOLOv12的深度学习项目:机动车车牌检测算法。
这个项目使用YOLOv12模型进行车牌检测和分割,支持图片和视频输入,并通过PyQt5构建了一个简洁的图形界面。
整个系统可以实时检测车牌位置,并在界面上显示原始图像和分割后的车牌区域,还支持保存结果。
项目概述
核心技术
- YOLOv12模型:Ultralytics库提供的YOLO系列最新版本,用于目标检测。模型训练后可以准确识别机动车车牌,包括传统车牌和新能源车牌。
- PyQt5:用于构建图形用户界面(GUI),界面简洁,包括选择图片/视频、分割检测和保存结果的功能。
- OpenCV:处理图像和视频帧,进行显示和保存。
- 数据集:使用了约1300张训练图像,包括不同角度、颜色、透明度和尺度变化的车牌图片,以增强模型鲁棒性。数据集分为训练集、验证集和测试集,每张图像有对应的标注文件。
界面设计
界面布局如下:
- 顶部标题:显示项目名称。
- 按钮区域:四个按钮——“选择图片”、“选择视频”、“分割”、“保存结果”。
- 显示区域:左侧显示原始图像或视频帧,右侧显示分割后的车牌图像。

环境配置
运行本项目需要以下环境:
- Python 3.8+
- 安装依赖库:
text
下载YOLOv12模型权重。
如果需要自己训练模型,还需准备车牌数据集(格式为YOLO标注:images和labels文件夹)。
代码实现
下面是完整的主程序代码(main.py)。代码使用PyQt5创建窗口,集成YOLO模型进行检测。
python
""" 版权所有 Coding茶水间。保留所有权利。 本代码仅限个人学习、研究之用,禁止用于任何商业用途。 未经许可,不得公开分发、复制、修改或用于盈利性项目。 """ import sys import cv2 from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QWidget, QMessageBox from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt, QTimer from ultralytics import YOLO class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("基于yolo12进行深度学习的机动车车牌检测") self.setGeometry(100, 100, 800, 600) self.setStyleSheet("background-color: white;") # 主布局 self.main_widget = QWidget() self.setCentralWidget(self.main_widget) self.main_layout = QVBoxLayout() self.main_widget.setLayout(self.main_layout) # 标题 self.title_label = QLabel("基于yolo12进行深度学习的机动车车牌检测") self.title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #333; margin-bottom: 10px; margin-top: 10px;") self.title_label.setAlignment(Qt.AlignCenter) self.main_layout.addWidget(self.title_label) # 按钮区域 self.button_layout = QHBoxLayout() self.main_layout.addLayout(self.button_layout) self.select_image_btn = QPushButton("选择图片") self.select_image_btn.setStyleSheet("QPushButton { background-color: #f0f0f0; border: 1px solid #ccc; padding: 10px; font-size: 14px; }") self.button_layout.addWidget(self.select_image_btn) self.select_video_btn = QPushButton("选择视频") self.select_video_btn.setStyleSheet("QPushButton { background-color: #f0f0f0; border: 1px solid #ccc; padding: 10px; font-size: 14px; }") self.button_layout.addWidget(self.select_video_btn) self.process_btn = QPushButton("分割") self.process_btn.setStyleSheet("QPushButton { background-color: #4CAF50; color: white; border: none; padding: 10px; font-size: 14px; }") self.button_layout.addWidget(self.process_btn) self.save_btn = QPushButton("保存结果") self.save_btn.setStyleSheet("QPushButton { background-color: #2196F3; color: white; border: none; padding: 10px; font-size: 14px; }") self.button_layout.addWidget(self.save_btn) # 展示区域(固定尺寸) self.display_layout = QHBoxLayout() self.main_layout.addLayout(self.display_layout) # 原始图像显示区域 self.original_display = QLabel() self.original_display.setAlignment(Qt.AlignCenter) self.original_display.setStyleSheet("border: 1px solid #ccc; background-color: #f9f9f9;") self.original_display.setFixedSize(400, 500) # 固定展示区域尺寸 self.display_layout.addWidget(self.original_display) # 分割后图像显示区域 self.segmented_display = QLabel() self.segmented_display.setAlignment(Qt.AlignCenter) self.segmented_display.setStyleSheet("border: 1px solid #ccc; background-color: #f9f9f9;") self.segmented_display.setFixedSize(400, 500) # 固定展示区域尺寸 self.display_layout.addWidget(self.segmented_display) # 作者信息(固定高度) self.author_label = QLabel("作者:Coding茶水间") self.author_label.setStyleSheet("font-size: 12px; color: #777; margin-top: 10px;") self.author_label.setAlignment(Qt.AlignCenter) self.main_layout.addWidget(self.author_label) # 连接信号 self.select_image_btn.clicked.connect(self.select_image) self.select_video_btn.clicked.connect(self.select_video) self.process_btn.clicked.connect(self.process) self.save_btn.clicked.connect(self.save_result) # 创建定时器,控制帧率 self.video_timer = QTimer(self) self.video_timer.timeout.connect(self.update_frame) self.isprocess = False self.isPrcVideo = True self.mode = YOLO("runs/weights/weights/best.pt") # 加载你的模型 # 初始化存储处理后的帧数组 self.processed_frames = [] def select_image(self): file_path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Image Files (*.png *.jpg *.jpeg)") if file_path: pixmap = QPixmap(file_path) self.im_path = file_path self.original_display.setPixmap(pixmap.scaled(self.original_display.size(), Qt.KeepAspectRatio)) self.isPrcVideo = False def select_video(self): file_path, _ = QFileDialog.getOpenFileName(self, "选择视频", "", "Video Files (*.mp4 *.avi *.mov)") if file_path: # 清除原有显示内容 if hasattr(self, 'video_capture'): self.video_capture.release() if hasattr(self, 'video_timer'): self.video_timer.stop() # 初始化 OpenCV 视频捕获 self.video_capture = cv2.VideoCapture(file_path) # 检查视频是否成功打开 if not self.video_capture.isOpened(): QMessageBox.warning(self, "错误", "无法打开视频文件!") return self.isprocess = False self.isPrcVideo = True self.video_timer.start(30) # 根据帧率设置定时器间隔 def update_frame(self): # 读取下一帧 ret, frame = self.video_capture.read() if ret: # 将 OpenCV 帧转换为 QImage if self.isprocess: ori_frame = frame.copy() results = self.mode.predict(frame) frame = results[0].plot() # 存储处理后的帧 self.processed_frames.append(frame.copy()) # 提取车牌边界框并显示在右侧窗口 if hasattr(results[0], 'boxes') and len(results[0].boxes) > 0: # 获取边界框坐标 box = results[0].boxes.xyxy[0].cpu().numpy() x1, y1, x2, y2 = map(int, box) # 裁剪车牌区域 cropped_plate = ori_frame[y1:y2, x1:x2] # # 转换为RGB格式 cropped_plate = cv2.cvtColor(cropped_plate, cv2.COLOR_BGR2RGB) # 显示裁剪后的车牌 h, w, ch = cropped_plate.shape bytes_per_line = ch * w q_image_plate = QImage(cropped_plate.data, w, h, bytes_per_line, QImage.Format_RGB888) self.plate_pixmap = QPixmap.fromImage(q_image_plate) self.segmented_display.setPixmap(self.plate_pixmap.scaled( self.segmented_display.width(), self.segmented_display.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = frame.shape bytes_per_line = ch * w q_image = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_pixmap = QPixmap.fromImage(q_image) self.original_display.setPixmap(self.video_pixmap.scaled( self.original_display.width(), self.original_display.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) else: self.video_timer.stop() pass def process(self): if self.isPrcVideo: if hasattr(self, 'video_capture') and self.video_capture is not None: # 重置视频到第一帧 self.video_capture.set(cv2.CAP_PROP_POS_FRAMES, 0) self.processed_frames = [] self.isprocess = True if hasattr(self, 'video_timer'): self.video_timer.stop() self.video_timer.start(30) else: QMessageBox.warning(self, "错误", "请先选择视频!") else: if self.im_path: im0 = cv2.imread(self.im_path) results = self.mode.predict(im0) self.img_result = results[0].plot() # 显示原始图像 img_result2 = cv2.cvtColor(self.img_result, cv2.COLOR_BGR2RGB) h, w, ch = img_result2.shape bytes_per_line = ch * w q_image = QImage(img_result2.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_pixmap = QPixmap.fromImage(q_image) self.original_display.setPixmap(self.video_pixmap.scaled( self.original_display.width(), self.original_display.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) # 提取车牌边界框并显示在右侧窗口 if hasattr(results[0], 'boxes') and len(results[0].boxes) > 0: # 获取边界框坐标 box = results[0].boxes.xyxy[0].cpu().numpy() x1, y1, x2, y2 = map(int, box) # 裁剪车牌区域 cropped_plate = im0[y1:y2, x1:x2] #保存最后裁剪结果 self.result_cropped = cropped_plate # 转换为RGB格式 cropped_plate = cv2.cvtColor(cropped_plate, cv2.COLOR_BGR2RGB) # 显示裁剪后的车牌 h, w, ch = cropped_plate.shape bytes_per_line = ch * w q_image_plate = QImage(cropped_plate.data, w, h, bytes_per_line, QImage.Format_RGB888) self.plate_pixmap = QPixmap.fromImage(q_image_plate) self.segmented_display.setPixmap(self.plate_pixmap.scaled( self.segmented_display.width(), self.segmented_display.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) else: # 如果没有检测到车牌,显示提示信息 self.segmented_display.setText("未检测到车牌区域") else: QMessageBox.warning(self, "错误", "请先选择图片或视频!") def save_result(self): if self.isPrcVideo: if len(self.processed_frames) > 0: # 弹出保存视频对话框 file_path, _ = QFileDialog.getSaveFileName(self, "保存视频", "", "Video Files (*.mp4 *.avi *.mov)") if file_path: # 获取第一帧的尺寸 height, width, _ = self.processed_frames[0].shape # 初始化视频写入器 fourcc = cv2.VideoWriter_fourcc(*'mp4v') video_writer = cv2.VideoWriter(file_path, fourcc, 30.0, (width, height)) # 写入所有帧 for frame in self.processed_frames: video_writer.write(frame) # 释放资源 video_writer.release() QMessageBox.information(self, "成功", "视频保存成功!") else: QMessageBox.warning(self, "错误", "请先处理视频!") else: if self.im_path: file_path, _ = QFileDialog.getSaveFileName(self, "保存图片", "", "Image Files (*.png *.jpg *.jpeg)") if file_path: cv2.imwrite(file_path, self.result_cropped) QMessageBox.information(self, "成功", "图片保存成功!") else: QMessageBox.warning(self, "错误", "请先选择图片或视频!") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
