Python编程:使用 YOLO 目标检测

YOLO(You Only Look Once) 是一种基于深度学习的实时目标检测算法,由 Joseph Redmon 等人于 2016 年首次提出。与传统的两阶段目标检测方法(如 R-CNN 系列)不同,YOLO 将目标检测任务视为一个单一的回归问题,直接在图像上进行一次推理即可预测边界框和类别概率。


YOLO 的核心思想

  • 单次前向传播(Single Shot Detection):YOLO 只需对输入图像进行一次神经网络推理,就能同时输出所有检测到的目标及其类别。
  • 将图像划分为网格单元(Grid Cells):图像被划分为 S×S 的网格,每个网格负责检测中心点落在该区域的目标。
  • 每个网格预测多个边界框(Bounding Boxes):每个网格预测多个边界框(例如 2 个),并给出置信度分数(confidence score)表示这个框是否包含目标。
  • 类别概率预测:每个网格还会预测该区域属于各个类别的概率。

YOLO 架构演进

版本 年份 特点
YOLOv1 2016 原始版本,首次实现单次目标检测
YOLOv2 / YOLO9000 2017 引入 Anchor Boxes、Batch Normalization、多尺度预测等改进
YOLOv3 2018 多尺度预测、更复杂的主干网络(Darknet-53)、支持多类目标
YOLOv4 2020 引入 CSPDarknet、PANet、Mosaic 数据增强等技术,精度大幅提升
YOLOv5 2020(Ultralytics) PyTorch 实现,结构模块化,易于训练和部署
YOLOv6 2022(美团) 面向工业应用优化,提升推理速度和部署效率
YOLOv7 2022(Alexey Bochkovskiy 团队) 动态标签分配、扩展高效层聚合网络(E-ELAN)等,达到更高精度与速度平衡
YOLOv8 2023(Ultralytics) 支持图像分类、检测、分割一体化,模型结构进一步优化

YOLO 的工作流程(以 YOLOv5 为例)

1. 输入图像预处理

  • 图像被缩放为统一尺寸(如 640×640)
  • 归一化(通常除以 255)

2. 主干网络(Backbone)

  • 使用 CSPDarknet 或其他轻量级 CNN 提取特征

3. 特征金字塔(Neck)

  • 如 PANet(Path Aggregation Network)用于融合多尺度特征

4. 检测头(Head)

  • 输出每个边界框的位置(x, y, w, h)、对象置信度、类别概率

5. 后处理(Post-processing)

  • 非极大值抑制(NMS):去除重叠的冗余预测框
  • 阈值过滤:只保留置信度高于某个阈值的预测结果

YOLO 输出格式说明

YOLO 输出的是一个张量,形状通常是:

(batch_size, num_anchors * grid_size * grid_size, 4 + 1 + num_classes)

其中:

  • 4:边界框坐标(x_center, y_center, width, height)
  • 1:对象置信度(objectness score)
  • num_classes:类别概率分布

性能指标对比(以 COCO 数据集为例)

模型 mAP (%) 推理速度 (FPS) 模型大小 应用场景
YOLOv3 ~44 ~45 通用目标检测
YOLOv4 ~47 ~35 高精度需求
YOLOv5s ~36 ~100 移动端/嵌入式
YOLOv5l ~49 ~30 高精度
YOLOv7-tiny ~37 ~120 实时边缘设备
YOLOv8n ~37 ~130 超小 边缘部署
YOLOv8m ~50 ~45 平衡精度与速度

YOLO 的优缺点

优点:

  • 实时性强:适用于视频流、监控系统等实时应用场景
  • 结构简单:推理速度快,适合移动端或嵌入式部署
  • 统一框架:图像分类、检测、分割可统一处理(如 YOLOv8)

缺点:

  • 对小目标检测效果较弱(可通过多尺度预测改善)
  • 对密集目标容易漏检或误检(可通过改进 NMS 或增加 anchor boxes 数量缓解)

YOLO使用Python 实现

以下是使用 Python 实现 YOLO 的完整指南。

安装必要的库

pip install opencv-python numpy torch torchvision
# 对于官方YOLOv5:
pip install ultralytics  # 支持YOLOv5, v8等

使用官方 YOLOv5/YOLOv8

基本检测代码

from ultralytics import YOLO
import cv2

# 加载模型 (会自动下载预训练权重)
model = YOLO("yolov8n.pt")  # 也可以使用 yolov5n.pt, yolov8s.pt等

# 图像检测
results = model("image.jpg")  # 预测
results[0].show()  # 显示结果

# 视频检测
cap = cv2.VideoCapture(0)  # 摄像头
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
        
    results = model(frame)  # 预测
    annotated_frame = results[0].plot()  # 可视化结果
    
    cv2.imshow("YOLO Detection", annotated_frame)
    if cv2.waitKey(1) == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

使用 OpenCV 的 DNN 模块运行 YOLO

import cv2
import numpy as np

# 下载配置和权重文件
# YOLOv3: https://pjreddie.com/darknet/yolo/
# YOLOv4: https://github.com/AlexeyAB/darknet

# 加载模型
net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
layer_names = net.getLayerNames()
output_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers()]

# 加载类别标签
with open("coco.names", "r") as f:
    classes = [line.strip() for line in f.readlines()]

# 加载图像
img = cv2.imread("image.jpg")
height, width, channels = img.shape

# 预处理
blob = cv2.dnn.blobFromImage(img, 0.00392, (416, 416), (0, 0, 0), True, crop=False)
net.setInput(blob)
outs = net.forward(output_layers)

# 解析检测结果
class_ids = []
confidences = []
boxes = []
for out in outs:
    for detection in out:
        scores = detection[5:]
        class_id = np.argmax(scores)
        confidence = scores[class_id]
        if confidence > 0.5:  # 置信度阈值
            center_x = int(detection[0] * width)
            center_y = int(detection[1] * height)
            w = int(detection[2] * width)
            h = int(detection[3] * height)
            
            x = int(center_x - w / 2)
            y = int(center_y - h / 2)
            
            boxes.append([x, y, w, h])
            confidences.append(float(confidence))
            class_ids.append(class_id)

# 非极大值抑制
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)

# 绘制结果
font = cv2.FONT_HERSHEY_PLAIN
colors = np.random.uniform(0, 255, size=(len(classes), 3))
for i in range(len(boxes)):
    if i in indexes:
        x, y, w, h = boxes[i]
        label = str(classes[class_ids[i]])
        color = colors[class_ids[i]]
        cv2.rectangle(img, (x, y), (x + w, y + h), color, 2)
        cv2.putText(img, label, (x, y + 30), font, 3, color, 3)

cv2.imshow("Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

训练自定义 YOLO 模型

使用 YOLOv8 训练自定义数据

from ultralytics import YOLO

# 加载预训练模型
model = YOLO("yolov8n.pt")  # 小模型

# 训练模型
results = model.train(
    data="custom_dataset.yaml",  # 数据集配置文件
    epochs=100,
    imgsz=640,
    batch=8,
    name="custom_yolo"
)

# 导出模型
model.export(format="onnx")  # 可导出为多种格式

数据集 YAML 文件示例 (custom_dataset.yaml)

train: /path/to/train/images
val: /path/to/val/images

# 类别数量
nc: 3

# 类别名称
names: ['class1', 'class2', 'class3']

性能优化

  1. 模型选择:根据需求选择合适大小的模型(nano, small, medium, large, xlarge)

  2. 推理优化

    results = model.predict(source, half=True)  # 使用半精度
    results = model.predict(source, stream=True)  # 流式推理减少内存
  • TensorRT 加速

    model.export(format="engine")  # 导出为TensorRT引擎
  1. 多线程处理:使用 Python 的 threadingmultiprocessing 处理多路视频

学习资源

  • Ultralytics YOLOv8 文档

  • YOLO 官方论文

  • OpenCV DNN 模块文档

  • YOLO 社区 GitHub

通过以上方法,您可以在 Python 中轻松实现 YOLO 目标检测,并根据需要训练自定义模型。

PyQt 开发完整的 YOLO 目标检测程序

下面将展示如何使用 PyQt5 和 YOLOv8 (ultralytics) 开发一个完整的桌面端目标检测应用程序,包含摄像头/视频/图像检测功能。

1. 安装依赖

pip install pyqt5 opencv-python numpy torch ultralytics

2. 完整代码实现

import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QVBoxLayout, 
                            QHBoxLayout, QPushButton, QFileDialog, QWidget, 
                            QComboBox, QSpinBox, QDoubleSpinBox)
from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from ultralytics import YOLO

class DetectionThread(QThread):
    """用于后台运行YOLO检测的线程"""
    finished = pyqtSignal(np.ndarray)  # 发送检测结果的信号
    
    def __init__(self, frame, model, conf_threshold):
        super().__init__()
        self.frame = frame
        self.model = model
        self.conf_threshold = conf_threshold
    
    def run(self):
        """执行检测"""
        results = self.model(self.frame, conf=self.conf_threshold, verbose=False)
        annotated_frame = results[0].plot()  # 获取带标注的帧
        self.finished.emit(annotated_frame)

class YOLODetectorApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("YOLO目标检测系统")
        self.setGeometry(100, 100, 1200, 800)
        
        # YOLO模型
        self.model = YOLO("yolov8n.pt")  # 自动下载预训练模型
        self.current_model_size = "nano"
        
        # 视频捕获
        self.cap = None
        self.video_path = ""
        self.is_camera = False
        self.is_detecting = False
        self.conf_threshold = 0.5
        
        # 初始化UI
        self.init_ui()
        
        # 定时器
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_frame)
        
    def init_ui(self):
        """初始化用户界面"""
        # 主窗口布局
        main_widget = QWidget()
        main_layout = QHBoxLayout()
        
        # 左侧控制面板
        control_panel = QWidget()
        control_panel.setFixedWidth(300)
        control_layout = QVBoxLayout()
        
        # 模型选择
        model_label = QLabel("模型大小:")
        self.model_combo = QComboBox()
        self.model_combo.addItems(["nano (最快)", "small", "medium", "large", "xlarge (最准)"])
        self.model_combo.setCurrentIndex(0)
        self.model_combo.currentIndexChanged.connect(self.change_model)
        
        # 置信度阈值
        conf_label = QLabel("置信度阈值:")
        self.conf_spinbox = QDoubleSpinBox()
        self.conf_spinbox.setRange(0.1, 1.0)
        self.conf_spinbox.setSingleStep(0.05)
        self.conf_spinbox.setValue(0.5)
        self.conf_spinbox.valueChanged.connect(self.update_conf_threshold)
        
        # 按钮
        self.camera_btn = QPushButton("打开摄像头")
        self.camera_btn.clicked.connect(self.toggle_camera)
        
        self.video_btn = QPushButton("打开视频文件")
        self.video_btn.clicked.connect(self.open_video_file)
        
        self.image_btn = QPushButton("打开图像文件")
        self.image_btn.clicked.connect(self.open_image_file)
        
        self.detect_btn = QPushButton("开始检测")
        self.detect_btn.clicked.connect(self.toggle_detection)
        self.detect_btn.setEnabled(False)
        
        # 添加到控制面板
        control_layout.addWidget(model_label)
        control_layout.addWidget(self.model_combo)
        control_layout.addWidget(conf_label)
        control_layout.addWidget(self.conf_spinbox)
        control_layout.addWidget(self.camera_btn)
        control_layout.addWidget(self.video_btn)
        control_layout.addWidget(self.image_btn)
        control_layout.addWidget(self.detect_btn)
        control_layout.addStretch()
        
        control_panel.setLayout(control_layout)
        
        # 右侧显示区域
        self.display_label = QLabel()
        self.display_label.setAlignment(Qt.AlignCenter)
        self.display_label.setStyleSheet("background-color: black;")
        
        # 添加到主布局
        main_layout.addWidget(control_panel)
        main_layout.addWidget(self.display_label)
        
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
    
    def change_model(self, index):
        """切换YOLO模型大小"""
        sizes = ["n", "s", "m", "l", "x"]
        self.current_model_size = sizes[index]
        self.model = YOLO(f"yolov8{sizes[index]}.pt")
    
    def update_conf_threshold(self, value):
        """更新置信度阈值"""
        self.conf_threshold = value
    
    def toggle_camera(self):
        """切换摄像头开关"""
        if self.cap is None or not self.is_camera:
            self.open_camera()
        else:
            self.stop_video()
    
    def open_camera(self):
        """打开摄像头"""
        self.stop_video()  # 先停止现有视频源
        
        self.cap = cv2.VideoCapture(0)  # 0表示默认摄像头
        if not self.cap.isOpened():
            self.show_error("无法打开摄像头")
            return
        
        self.is_camera = True
        self.video_path = ""
        self.camera_btn.setText("关闭摄像头")
        self.detect_btn.setEnabled(True)
        self.timer.start(30)  # 30ms更新一次
    
    def open_video_file(self):
        """打开视频文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择视频文件", "", "视频文件 (*.mp4 *.avi *.mov)")
        
        if file_path:
            self.stop_video()  # 先停止现有视频源
            
            self.cap = cv2.VideoCapture(file_path)
            if not self.cap.isOpened():
                self.show_error("无法打开视频文件")
                return
            
            self.video_path = file_path
            self.is_camera = False
            self.video_btn.setText("关闭视频")
            self.detect_btn.setEnabled(True)
            self.timer.start(30)
    
    def open_image_file(self):
        """打开图像文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图像文件", "", "图像文件 (*.jpg *.png *.bmp)")
        
        if file_path:
            self.stop_video()  # 停止任何视频源
            
            frame = cv2.imread(file_path)
            if frame is None:
                self.show_error("无法打开图像文件")
                return
            
            # 显示原始图像
            self.show_frame(frame)
            
            # 启用检测按钮
            self.detect_btn.setEnabled(True)
            self.is_detecting = False
            self.detect_btn.setText("开始检测")
    
    def toggle_detection(self):
        """切换检测状态"""
        self.is_detecting = not self.is_detecting
        self.detect_btn.setText("停止检测" if self.is_detecting else "开始检测")
        
        # 如果是静态图像且开始检测,则立即处理
        if self.is_detecting and self.cap is None:
            self.process_static_image()
    
    def process_static_image(self):
        """处理静态图像"""
        # 从当前显示的QLabel获取图像
        pixmap = self.display_label.pixmap()
        if pixmap is None:
            return
        
        # 将QPixmap转换为OpenCV格式
        img = pixmap.toImage()
        ptr = img.bits()
        ptr.setsize(img.byteCount())
        frame = np.array(ptr).reshape(img.height(), img.width(), 4)  # RGBA
        frame = cv2.cvtColor(frame, cv2.COLOR_RGBA2BGR)
        
        # 创建并启动检测线程
        self.detection_thread = DetectionThread(frame, self.model, self.conf_threshold)
        self.detection_thread.finished.connect(self.show_detection_result)
        self.detection_thread.start()
    
    def update_frame(self):
        """更新视频帧"""
        if self.cap is not None:
            ret, frame = self.cap.read()
            if ret:
                if self.is_detecting:
                    # 创建并启动检测线程
                    self.detection_thread = DetectionThread(frame, self.model, self.conf_threshold)
                    self.detection_thread.finished.connect(self.show_detection_result)
                    self.detection_thread.start()
                else:
                    # 直接显示原始帧
                    self.show_frame(frame)
            else:
                # 视频结束
                if not self.is_camera:
                    self.stop_video()
    
    def show_detection_result(self, frame):
        """显示检测结果"""
        self.show_frame(frame)
    
    def show_frame(self, frame):
        """在QLabel上显示帧"""
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = frame.shape
        bytes_per_line = ch * w
        q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
        pixmap = QPixmap.fromImage(q_img)
        
        # 缩放以适应显示区域
        display_size = self.display_label.size()
        scaled_pixmap = pixmap.scaled(
            display_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        
        self.display_label.setPixmap(scaled_pixmap)
    
    def stop_video(self):
        """停止视频捕获"""
        if self.cap is not None:
            self.timer.stop()
            self.cap.release()
            self.cap = None
        
        self.is_camera = False
        self.video_path = ""
        self.camera_btn.setText("打开摄像头")
        self.video_btn.setText("打开视频文件")
        self.detect_btn.setEnabled(False)
        self.is_detecting = False
        self.detect_btn.setText("开始检测")
        
        # 清空显示
        self.display_label.clear()
        self.display_label.setStyleSheet("background-color: black;")
    
    def show_error(self, message):
        """显示错误消息"""
        self.display_label.setText(f"{message}")
    
    def closeEvent(self, event):
        """关闭窗口时释放资源"""
        self.stop_video()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = YOLODetectorApp()
    window.show()
    sys.exit(app.exec_())

功能说明

  1. 模型选择

    • 支持 YOLOv8 不同大小的模型 (nano, small, medium, large, xlarge)

    • 可在运行时动态切换

  2. 输入源

    • 摄像头实时检测

    • 视频文件检测

    • 静态图像检测

  3. 检测控制

    • 可调整置信度阈值 (0.1-1.0)

    • 开始/停止检测功能

  4. 性能优化

    • 使用独立线程进行目标检测,避免阻塞UI

    • 实时显示检测结果

  5. 用户界面

    • 左侧控制面板,右侧显示区域

    • 自适应图像显示大小

如何扩展

  1. 添加新功能

    • 保存检测结果(图像/视频)

    • 添加检测统计信息(检测对象数量等)

    • 支持更多模型格式(ONNX, TensorRT等)

  2. 性能优化

    • 使用半精度推理 (half=True)

    • 添加 TensorRT 加速支持

    • 实现批量推理

  3. 界面改进

    • 添加检测结果列表

    • 实现ROI(感兴趣区域)选择

    • 添加更多可视化选项

 注意事项

  1. 首次运行会自动下载 YOLOv8 预训练模型

  2. 摄像头访问需要相应权限

  3. 视频处理性能取决于硬件配置

  4. 对于大型模型,可能需要更多显存

这个应用程序提供了完整的 YOLO 目标检测功能,并具有良好的用户界面。您可以根据需要进一步扩展功能或优化性能。

你可能感兴趣的:(python,开发语言)