YOLO(You Only Look Once) 是一种基于深度学习的实时目标检测算法,由 Joseph Redmon 等人于 2016 年首次提出。与传统的两阶段目标检测方法(如 R-CNN 系列)不同,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 输出的是一个张量,形状通常是:
(batch_size, num_anchors * grid_size * grid_size, 4 + 1 + num_classes)
其中:
4
:边界框坐标(x_center, y_center, width, height)1
:对象置信度(objectness score)num_classes
:类别概率分布模型 | mAP (%) | 推理速度 (FPS) | 模型大小 | 应用场景 |
---|---|---|---|---|
YOLOv3 | ~44 | ~45 | 中 | 通用目标检测 |
YOLOv4 | ~47 | ~35 | 大 | 高精度需求 |
YOLOv5s | ~36 | ~100 | 小 | 移动端/嵌入式 |
YOLOv5l | ~49 | ~30 | 大 | 高精度 |
YOLOv7-tiny | ~37 | ~120 | 小 | 实时边缘设备 |
YOLOv8n | ~37 | ~130 | 超小 | 边缘部署 |
YOLOv8m | ~50 | ~45 | 中 | 平衡精度与速度 |
以下是使用 Python 实现 YOLO 的完整指南。
pip install opencv-python numpy torch torchvision
# 对于官方YOLOv5:
pip install ultralytics # 支持YOLOv5, v8等
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()
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()
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") # 可导出为多种格式
custom_dataset.yaml
)train: /path/to/train/images
val: /path/to/val/images
# 类别数量
nc: 3
# 类别名称
names: ['class1', 'class2', 'class3']
模型选择:根据需求选择合适大小的模型(nano, small, medium, large, xlarge)
推理优化:
results = model.predict(source, half=True) # 使用半精度
results = model.predict(source, stream=True) # 流式推理减少内存
TensorRT 加速:
model.export(format="engine") # 导出为TensorRT引擎
多线程处理:使用 Python 的 threading
或 multiprocessing
处理多路视频
Ultralytics YOLOv8 文档
YOLO 官方论文
OpenCV DNN 模块文档
YOLO 社区 GitHub
通过以上方法,您可以在 Python 中轻松实现 YOLO 目标检测,并根据需要训练自定义模型。
下面将展示如何使用 PyQt5 和 YOLOv8 (ultralytics) 开发一个完整的桌面端目标检测应用程序,包含摄像头/视频/图像检测功能。
pip install pyqt5 opencv-python numpy torch ultralytics
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_())
模型选择:
支持 YOLOv8 不同大小的模型 (nano, small, medium, large, xlarge)
可在运行时动态切换
输入源:
摄像头实时检测
视频文件检测
静态图像检测
检测控制:
可调整置信度阈值 (0.1-1.0)
开始/停止检测功能
性能优化:
使用独立线程进行目标检测,避免阻塞UI
实时显示检测结果
用户界面:
左侧控制面板,右侧显示区域
自适应图像显示大小
添加新功能:
保存检测结果(图像/视频)
添加检测统计信息(检测对象数量等)
支持更多模型格式(ONNX, TensorRT等)
性能优化:
使用半精度推理 (half=True
)
添加 TensorRT 加速支持
实现批量推理
界面改进:
添加检测结果列表
实现ROI(感兴趣区域)选择
添加更多可视化选项
首次运行会自动下载 YOLOv8 预训练模型
摄像头访问需要相应权限
视频处理性能取决于硬件配置
对于大型模型,可能需要更多显存
这个应用程序提供了完整的 YOLO 目标检测功能,并具有良好的用户界面。您可以根据需要进一步扩展功能或优化性能。