news 2026/10/10 18:13:23

YOLOv8行人检测实战:数据集转换、训练调参与PyQt5界面部署一步到位

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv8行人检测实战:数据集转换、训练调参与PyQt5界面部署一步到位

简介:面向计算机视觉初学者、算法工程师及智能交通开发者的YOLOv8行人检测完整方案,集成数据集、训练权重与PyQt可视化界面,解决街道和交通场景中行人实时检测及界面化部署需求。压缩包共2000个文件,涵盖1991个txt标注文件、2个Python脚本、4个Markdown说明和3个PDF教程,整体456.56MB;txt为LabelImg标注的行人数据,py负责检测及界面逻辑,PDF讲解环境配置与运行流程。已有817人学习下载。资源内含数千张街道场景jpg行人图片,类别统一为person,基于该数据集训练的权重mAP达到90%以上,可直接用于YOLO系列算法训练与迁移。PyQt界面支持视频流实时检测可视化,模块化代码方便按需修改;配套教程分两部分完整演示YOLOv8环境搭建与操作步骤,便于快速复现和二次开发。

1. 为什么 YOLOV8 行人检测偏偏卡在“交付”:裸模型离可用工具还差两步半

做行人检测这事儿,最容易被低估的不是模型选型,而是“交付”这两个字。很多人在终端里跑通了 YOLOV8,测试图片上也能框出人来,但一到要给导师演示、给客户看效果、或者放进现场小机器里运行,就卡住了:数据集格式不对、没有界面、程序跑一半崩掉。我见过不少团队在做 YOLOV8 行人检测,三个月里有将近两个月在跟数据集标注格式和 PyQt 界面死磕。这篇文章把完整链路拆开讲一遍:数据集怎么选、怎么转成 YOLOV8 要的格式,训练参数怎么设不容易翻车,PyQt5 界面怎么做得不卡死,以及落地时最常见的几个坑。适合正在做毕设、安全帽检测、行人统计这类垂直场景的工程师,也适合想快速做一套可视化原型的团队。

2. 行人检测数据集从哪来:公开数据与自制标注的格式转换和划分参数

2.1 公开数据集怎么选:CrowdHuman、BDD100K、COCO person 类的底细对比

数据集决定了模型的上限。用 YOLOV8 训练行人检测,最省力的路线不是自己拿着 labelImg 标几千张图,而是先从公开数据集里挑接近业务场景的。真正做行人检测时,我一般会在这几个里面选:CrowdHuman、BDD100K、COCO 的 person 类,偶尔用 VOC 的 person 类做脚本调试。

数据集规模特点适合场景
CrowdHuman约 2.5 万张训练图、约 34 万行人框密集行人、严重遮挡,标注带 ignore 区域人流统计、安全帽检测、密集场所
BDD100K10 万张,含行人、车辆、交通标志自动驾驶车载视角,白天、夜晚、雨雾都覆盖车载感知、道路监控
COCO person 类约 12 万张含 person 的图片通用场景,单人、多人都有快速原型验证、通用检测
VOC 2012 person 类约 2 千张数据量小、场景简单教学演示、转换脚本调试

选型理由很简单:做园区、商场这类密集行人场景,CrowdHuman 的遮挡样本对 YOLOV8 的训练帮助最大,mAP 上限也明显高一些;如果是车载或者道路监控,BDD100K 的夜间场景是 COCO 给不了的;如果只是想把整套训练到界面的流程跑通,COCO 的 person 类最省事,下载工具和生态都成熟。还有一个容易忽略的点:CrowdHuman 的标注里带 ignore 区域,这些框在训练时要过滤掉,不然会把 loss 带偏。这个过滤逻辑要放在转换脚本里,不是数据放进 YOLOV8 就完事的。

2.2 VOC/COCO 标注转 YOLO 格式:转换脚本与四个边界坑

YOLOV8 的 ultralytics 包训练时读的是 YOLO 格式:一张图片对应一个同名的 txt 文件,每行是class_id x_center y_center width height,坐标全部归一化到 0~1。公开数据集大多是 COCO JSON 或 VOC XML,所以格式转换是绕不开的一步。我自己常用 labelImg 标数据,导出的是 VOC XML,这里给一段 VOC 转 YOLO 的脚本,COCO JSON 转 YOLO 思路一样,只是解析逻辑换成 json 的 annotations 数组。

import os import xml.etree.ElementTree as ET from PIL import Image CLASSES = ['person'] # 类别 id 顺序必须和训练用 data.yaml 的 names 一致 def voc_to_yolo(xml_path, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) tree = ET.parse(xml_path) root = tree.getroot() img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) with Image.open(img_path) as img: w, h = img.size lines = [] for obj in root.findall('object'): cls = obj.find('name').text if cls not in CLASSES: continue # 只保留 person 类,其余丢弃 cls_id = CLASSES.index(cls) box = obj.find('bndbox') x1 = float(box.find('xmin').text) y1 = float(box.find('ymin').text) x2 = float(box.find('xmax').text) y2 = float(box.find('ymax').text) # 边界裁剪:手标时偶尔会把框标到图片外沿 x1 = max(0, min(x1, w)) x2 = max(0, min(x2, w)) y1 = max(0, min(y1, h)) y2 = max(0, min(y2, h)) if x2 <= x1 or y2 <= y1: continue x_center = (x1 + x2) / 2 / w y_center = (y1 + y2) / 2 / h box_w = (x2 - x1) / w box_h = (y2 - y1) / h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") out_txt = os.path.join(out_dir, os.path.splitext(img_name)[0] + '.txt') with open(out_txt, 'w') as f: f.write('\n'.join(lines)) # 遍历某目录下所有 xml,逐个调用 voc_to_yolo(xml, img_dir, out_dir)

逻辑说明:读取 XML 里的object节点,把bndbox的绝对像素坐标转成相对图片宽高的归一化坐标。CLASSES列表的顺序直接决定类别 id,训练配置里的names必须和它保持一致,顺序错了,检测结果就会张冠李戴。脚本里对坐标做了边界裁剪,因为 labelImg 手标时偶尔会出一个小数点误输入,导致框超出图幅,YOLOV8 训练时会警告甚至丢弃该框。

参数说明:图片的宽高必须和 XML 标注时一致。如果 XML 的 size 节点里没写 width 和 height,我一般直接用 PIL 打开图片读取,图上文件损坏时 PIL 会抛异常,这反而是好事——训练前就能揪出脏数据。转出来的空 txt 文件不要跳过不写,空文件代表该图没有目标,训练时 YOLOV8 也会正确读到;如果跳过,图片就丢失了对应的标签文件,训练时会被直接忽略。

这里还有四个容易踩的边界坑。第一个是类别顺序,VOC 的names和 YOLO 的names必须逐一对上,很多人把 person 放在第二位,结果训练完的检测结果显示的类别名永远错一位。第二个是路径分隔符,Windows 下用os.path.join生成路径,不要手写/或\\,否则导出到别的机器上就崩。第三个是图片通道和位深,16 位 PNG 转成 RGB 后再跑转换,YOLOV8 对输入有预处理,但标签归一化不受影响,问题出在后续加载时内存翻倍。第四个是大图缩放,如果原图是 4000x3000 的行人航拍图,直接按原尺寸归一化没问题,但训练时 ultralytics 会自动 resize 到 imgsz,小目标可能直接缩没了,这种场景建议先裁剪成滑窗再做标注。

2.3 训练集/验证集划分与增强策略:数据量不足时的实操参数

数据划分我习惯用 8:1:1,训练、验证、测试三份分开。YOLOV8 的data.yaml里只需要images/train和labels/train这种目录结构,ultralytics 会自动把 images 下的图片对应到相同相对路径的 labels 下的同名 txt。下面这段脚本把图片和 txt 一起按比例复制到目标目录。

import os import random import shutil src_dir = 'labeled_data' # 里面是 img.jpg 和 img.txt 平铺 dst_root = 'pedestrian_dataset' ratio = (0.8, 0.1, 0.1) # train, val, test files = [f[:-4] for f in os.listdir(src_dir) if f.endswith('.jpg')] random.seed(42) random.shuffle(files) n_train = int(len(files) * ratio[0]) n_val = int(len(files) * ratio[1]) for split, idxs in [('train', files[:n_train]), ('val', files[n_train:n_train + n_val]), ('test', files[n_train + n_val:])]: img_out = os.path.join(dst_root, 'images', split) lbl_out = os.path.join(dst_root, 'labels', split) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for name in idxs: shutil.copy(os.path.join(src_dir, name + '.jpg'), img_out) shutil.copy(os.path.join(src_dir, name + '.txt'), lbl_out)

逻辑说明:按文件名配对复制,.jpg和.txt必须同名同目录,否则 YOLOV8 在训练时报No labels found,那时候排查起来很费劲。random.seed(42)固定随机种子,保证每次划分结果一致,方便复现实验结果。

参数说明:ratio可以根据数据量调整,数据只有几百张时测试集可以缩到 5%,把更多样本留给训练;数据量如果超过 3 万张,测试集 10% 已经很多,不用再增加。

数据增强方面,YOLOV8 自带一套默认增强:mosaic 拼接、HSV 扰动、随机翻转、平移缩放。对小数据集来说,mosaic 是提升最大的一项,它把四张图拼在一起,等于变相增大样本多样性。默认参数可以直接用,但有两个值得改的地方:密集行人场景里把mixup从 0.0 开到 0.1 或 0.2,相当于让模型见过更多重叠遮挡的复合场景;如果你的业务图本身是旋转不定的(比如无人机视角),可以加一点degrees。下面这张表是我常用的增强参数范围。

参数默认值行人检测建议说明
hsv_h0.0150.01 ~ 0.02色相扰动,光照变化大的场景开大点
degrees0.00 ~ 10旋转角度,无人机视角建议 15 左右
translate0.10.1 ~ 0.2平移幅度,小目标多的场景开大
scale0.50.3 ~ 0.5缩放范围,行人占比小的图开大
mosaic1.00.8 ~ 1.0四图拼接,小数据集不要关
mixup0.00.1 ~ 0.2图像混合,模拟密集遮挡场景

注意一点:mosaic 在最后十几个 epoch 建议关掉或者降权重,因为拼接图里的行人和真实分布有差距,一直开着会让模型在小目标定位上精读不够。ultralytics 支持按 epoch 动态调整增强,但实操中直接在训练命令里传mosaic=0.5也行,不必追求过度精细。

3. 在本地把 YOLOV8 训练跑通:环境配置、训练命令与必调参数

3.1 环境配置与模型选型:yolov8n/s/m/l 按显存怎么选

环境配置这件事,网上教程一大堆,但实际翻车点往往不在大版本,而在 CUDA 和小版本兼容。我的推荐组合是 Python 3.10 + PyTorch 2.x + ultralytics 最新版。如果是 GTX1660Ti 这种 6G 显存的卡,torch 用 cu118 或 cu121 构建版本就够,不要追最新的 cu124,新版 CUDA 运行时在旧卡上偶尔会有兼容问题,装完跑yolo detect train直接报错会很影响心态。

conda create -n yolov8 python=3.10 conda activate yolov8 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

逻辑说明:conda 创建独立环境避免和系统 Python 冲突。--index-url指定 cu121 的 PyTorch 源,这一步决定了 torch 是否能正确调用 GPU。装完用python -c "import torch; print(torch.cuda.is_available())"验证,输出 True 才能继续。

参数说明:cu121 是 CUDA 12.1 的简写,GTX1660Ti 驱动版本较老时 cu118 更稳。如果机器是纯 CPU,安装默认的 torch CPU 版即可,训练很慢但至少流程能跑通。

模型选型是第一个分水岭。YOLOV8 有 n/s/m/l/x 五个尺寸,行人检测这种单类任务,n 和 s 是最常用的。

模型参数量6G 显存能否训练行人检测效果推理速度
yolov8n3.2M可以,batch 16够用,漏检略多最快
yolov8s11.2M可以,batch 8比较好,推荐快
yolov8m25.9M悬,batch 4好中等
yolov8l43.7M不行很好慢
yolov8x68.2M不行最好最慢

GTX1660Ti 6G 的卡,yolov8n 就选 batch 16,yolov8s 就减到 batch 8。8G 卡可以跑 s 的 batch 16 或者 m 的 batch 8。显存只是下限,真正决定选哪个的还是场景:密集行人且遮挡严重,s 起步,n 在小目标上漏检明显;场景简单、摄像头固定,n 的性价比最高,部署也轻松。

3.2 一次能跑的训练命令:data yaml、imgsz、batch、epochs 的取值逻辑

先把data.yaml写好,这是训练入口的核心配置。放在数据集根目录下,内容如下。

path: /data/pedestrian_dataset # 数据集根目录,用绝对路径 train: images/train val: images/val test: images/test nc: 1 names: ['person']

逻辑说明:path定了根目录,train和val是相对路径。ultralytics 会拼接成path/train/images去找图片,path/train/labels去找标签。nc是类别数,行人检测就是 1。names的 person 必须和转换脚本里的 CLASSES 顺序完全一致。

训练命令如下:

yolo detect train data=pedestrian.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0 patience=15

逻辑说明:model=yolov8s.pt表示加载 COCO 预训练权重,ultralytics 会自动下载,迁移学习让收敛速度比随机初始化快很多。patience=15是早停参数,连续 15 个 epoch 验证集 mAP 没提升就自动停止,省时间也防过拟合。

参数说明:imgsz=640是性价比最高的输入尺寸,行人检测大多数场景用这个。如果画面里行人很小,比如无人机俯拍,可以试imgsz=960甚至 1280,但显存和训练时间会成倍上涨,6G 卡直接别想。epochs=100对单类行人检测足够,自标小数据集建议 150~200,配合早停就不怕白等。batch=16是 6G 卡上 yolov8s 的极限,报显存不足就降到 8,不要硬撑。

训练起来后,结果默认输出到runs/detect/train,里面有训练好的weights/best.pt、每次验证的标签图,还有results.png这个汇总曲线图。注意看三点:loss 是不是平滑下降、mAP50 是否在稳步上升、val 的 loss 有没有突然回头。下一步就讲怎么看这些曲线。

3.3 损失函数曲线怎么看:不收敛、过拟合、mAP 上不去的定位路径

训练完成后打开runs/detect/train/results.png,里面画了 box_loss、cls_loss、dfl_loss 以及 mAP 曲线。YOLOV8 的 loss 由三部分组成:box_loss 管回归框的位置准不准,cls_loss 管分类对不对,dfl_loss 管框边界的置信度分布。它们不是同一个量纲,不要放在一起比较大小,只看各自的下降趋势。

第一种情况:train loss 一直降,val loss 在某个 epoch 后开始反弹,这就是过拟合。处理办法是提前停掉训练,把best.pt拿出来用;或者增加数据增强里的mixup和scale,让模型在训练时看到更多变化;再不行就换小一号的模型,yolov8s 过拟合时换 n 往往比强行加数据更有效。

第二种情况:三个 loss 全部不降,甚至还在跳。先检查是不是学习率太大。ultralytics 默认 lr0=0.01,小数据集上可以调到 0.005 试试。如果调完还是不动,去翻几个训练图片和 txt 的对应关系,八成是标签转错了,比如坐标全部为 0 或者类别 id 越界。

第三种情况更隐蔽:loss 降得很漂亮,mAP50 到了 0.9,但 mAP50-95 只有 0.4 左右。这说明框的位置不够精准,和真实标注的 IoU 在 0.5~0.95 区间里掉得厉害。常见原因是imgsz太小,行人是小目标,640 的输入下像素占比本来就少,定位精度上不去。这时可以试imgsz=960,或者从 yolov8n 换到 yolov8s,mAP50-95 会有明显提升。

# 训练过程中可以用 tensorboard 看实时曲线,比 etc/ 下的静态图更直观 tensorboard --logdir runs/detect

逻辑说明:tensorboard 是排查训练过程的利器,尤其是 val loss 何时开始反弹这种细节。graph 里能看到每个 batch 的 loss 波动,比只看训练结束后的汇总图更能定位问题。

以及一个经验:行人检测里,Recall 掉得比 Precision 快,说明漏检多,优先调整推理时的置信度阈值而不是重新训练;Precision 掉得比 Recall 快,说明误检多,这时候才考虑加难负样本或者调数据增强。这两句话在排查阶段能省不少来回试错的时间。

4. 用 PyQt5 把检测做成界面:线程模型、视频流与画框刷新的完整链路

4.1 PyQt5 界面骨架与推理线程分离:为什么不能把 detect 塞进主线程

PyQt5 界面的第一个设计原则:所有耗时的操作都不能放在主线程里。主线程负责跑 Qt 的事件循环,处理按钮点击、窗口绘制。如果直接把model.predict()塞进按钮的槽函数,那一帧检测的运行时间里,窗口的事件循环被阻塞,表现就是界面假死,标题栏变成“未响应”。模型加载更严重,yolov8s 加载就要一两秒,放在主线程里窗口直接白屏。正确的做法是用 QThread 开一个工作线程,UI 只通过信号槽接收结果。

import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel from PyQt5.QtCore import QThread, pyqtSignal class DetectWorker(QThread): result_ready = pyqtSignal(object) def __init__(self): super().__init__() self.running = True def run(self): # 子线程里做模型加载与推理,绝不碰 UI 控件 pass class MainWindow(QMainWindow): def __init__(self): super().__init__() self.btn = QPushButton('开始检测', self) self.label = QLabel('等待开始', self) self.worker = DetectWorker() self.worker.result_ready.connect(self.show_result) self.btn.clicked.connect(self.worker.start) def show_result(self, result): self.label.setText(str(result)) # 主线程只需要更新 UI if __name__ == '__main__': app = QApplication(sys.argv) win = MainWindow() win.show() sys.exit(app.exec_())

逻辑说明:DetectWorker继承 QThread,run()里放推理循环。clicked信号直接连到worker.start(),点击按钮后子线程独立跑,主线程立刻恢复响应。推理结果通过pyqtSignal(object)发回主线程的槽函数,槽函数里只做 UI 刷新。

参数说明:pyqtSignal(object)的object类型是关键,因为要传递 numpy 数组和 YOLOV8 的 Results 对象,用object可以绕过 PyQt 对 Python 原生类型的转换开销,避免大数组传输卡顿。还有一个细节:worker.start()只能调用一次,如果想重复启动必须先worker.stop()再wait(),否则 Qt 会报QThread: Destroyed while thread is still running。下次启动检测时,最好重新实例化一个 worker,而不是复用旧的。

4.2 视频/摄像头实时检测:QThread + OpenCV 的帧循环写法

视频流检测的完整循环比单个按钮复杂在帧的连续性和资源的释放。这里给一个常用的写法,摄像头或视频文件都可以接进来。

import cv2 from PyQt5.QtCore import QThread, pyqtSignal class DetectWorker(QThread): frame_ready = pyqtSignal(object, object) # 原始帧 + 检测结果 def __init__(self, model_path, source=0, imgsz=640): super().__init__() self.model_path = model_path self.source = source self.imgsz = imgsz self.running = True def run(self): from ultralytics import YOLO # 延迟导入,加速界面启动 self.model = YOLO(self.model_path) cap = cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame = cap.read() if not ret: break results = self.model(frame, imgsz=self.imgsz, verbose=False) self.frame_ready.emit(frame, results) self.msleep(1) # 让出 CPU,避免空转 cap.release() def stop(self): self.running = False self.quit() self.wait()

逻辑说明:YOLO模型放在run()里初始化而不是__init__,这样窗口弹出来时不会因为模型加载而卡住。self.model(frame, imgsz=self.imgsz)返回一个 Results 列表,每一帧取results[0]就是当前检测结果。msleep(1)让出线程调度,否则子线程会吃掉一个 CPU 核心。

参数说明:source=0是默认摄像头,传视频文件路径就是离线检测。imgsz我建议做成界面上的下拉框,640 和 960 之间切换,用户根据实际场景自己选。verbose=False关闭控制台刷屏,否则每帧都会打印一串检测信息,界面没卡但终端卡得没法看。stop()里wait()是必须的,它保证线程真正退出后再释放资源,不然会出现“关闭窗口后摄像头灯还亮着”的情况。

4.3 画框、标签与 FPS 刷新:QPixmap 显示和性能取舍

检测结果要显示在 QLabel 上,中间牵扯一次格式转换:YOLOV8 返回的 Results 对象可以用自带的plot()方法画出带标注的 BGR 图,然后转成 QImage 给 QLabel 显示。这一步的坑在于字节对齐,一旦写错,高分辨率下画面会出斜纹。

import cv2 from PyQt5.QtGui import QImage, QPixmap def results_to_display(frame, results): plotted = results[0].plot() # BGR 格式,已画好框和标签 return plotted def numpy_to_pixmap(arr): rgb = cv2.cvtColor(arr, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) return QPixmap.fromImage(qimg) # 在槽函数里使用 def on_frame(self, frame, results): plotted = results_to_display(frame, results) pixmap = numpy_to_pixmap(plotted) scaled = pixmap.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.label.setPixmap(scaled)

逻辑说明:results[0].plot()是 ultralytics 提供的便捷方法,框、类别、置信度一次画完,比手写cv2.rectangle省事且不会画错坐标。QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)里ch * w是每行字节数,这个参数漏了或者写错,图像显示会错位。

参数说明:scaled按 QLabel 控件尺寸等比缩放,避免拉伸导致行人变形影响观察。Qt.KeepAspectRatio保持宽高比,SmoothTransformation在缩放时做插值,显示更平滑,代价是 CPU 开销,低配机器可以换FastTransformation。FPS 计算用时间差累计,每 30 帧更新一次标题栏文本:fps = frame_count / (now - start_time)。注意别用固定time.sleep去控帧率,因为检测耗时是波动的,应该记录每帧开始时间,用msleep补到目标间隔,不然视频播放会一顿一顿的。

5. 行人检测 + PyQt 落地时的 5 个常见坑:从显存崩溃到界面卡死

5.1 训练 loss 正常但 mAP 上不去:类别不平衡与背景误检

现象:训练时三个 loss 都平滑下降,看曲线挺漂亮,但验证集 mAP50 只到 0.6,远低于预期。原因分析:行人检测是典型的小目标类别不平衡任务,一张图里行人像素占比常常不超过几个百分点,其余全是背景。模型很容易学会“有纹理就框”的偷懒策略,误检集中在树影、栏杆这类竖条纹理区域。解决的办法有三个:先换成 CrowdHuman 这类行人密集的数据集做预训练,让模型从更多正样本里学“人”的共性;推理时把置信度阈值从默认 0.25 提高到 0.35 甚至 0.4,先保证精度;第三个手段是收集典型误检样本加入训练集,专门标这些背景区域为背景类,让模型知道哪些地方全是负样本。

5.2 PyQt 界面卡死/无响应:time.sleep 与 QThread 的错误用法

现象:点击“打开视频”按钮后,窗口立刻变白,标题栏出现“未响应”,过几秒又自己恢复。原因:把cap.read()和模型推理直接写在按钮clicked信号的槽函数里,主线程被阻塞,Qt 事件循环空转,整个窗口失去响应。解决:把耗时代码全部搬进 QThread 子线程,主线程只保留 UI 刷新的代码。注意一个细节:子线程里不要直接调self.label.setText(),一旦 QLabel 在主线程销毁而子线程还在写,程序会直接崩溃,必须通过pyqtSignal发回主线程再更新。我在最早版本里犯过这个错,界面一关就崩,连错误信息都不给,排查半天才发现是跨线程访问 UI 控件。

5.3 CUDA out of memory:显存爆炸的四种解法

现象:训练跑了一两个 epoch 后突然报CUDA out of memory,推理阶段也会因为长时间运行触发这个错误。原因:batch 和 imgsz 组合超出显存上限;多进程同时加载同一个模型时显存被重复占用;长时运行积累的显存碎片。GTX1660Ti 6G 是最容易被这问题卡死的卡。解决:训练时把batch减半,从 16 降到 8,再不行就换 yolov8n 或把imgsz降到 480;推理时给 predict 加device=0防止它尝试用多卡;每次推理后调torch.cuda.empty_cache()回收缓存;界面 APP 里每次检测完成后清理变量引用,不要把所有帧的 results 都存在列表里。

5.4 检测框乱跳、密集行人漏检:NMS 与置信度阈值调节

现象:同一个行人在前后帧的框忽大忽小,或者视频里几个人紧挨着走,只检测出一个框,另一个被压掉了。原因:置信度阈值太低让低质量框大量存活,NMS 的 IoU 阈值太高导致相邻行人的框互相抑制。YOLOV8 默认conf=0.25, iou=0.45,这在密集场景下不够用。解决:密集行人场景把置信度调到conf=0.35,NMS 的iou从 0.45 降到 0.3 左右,减少相邻框的互相压榨。我在安全帽检测项目里试过,这个组合对“两个人挨着站”的场景改善非常明显。更推荐的做法是把 conf 和 iou 做成界面上的滑动条,让使用者现场调,因为不同摄像头的视角和距离对这两个参数的需求差很多。

5.5 加载太慢/推理太慢:模型尺寸与输入分辨率的选择

现象:界面启动后点击开始,两秒多才出现画面,1080P 视频推理帧率只有个位数。原因:加载的是 yolov8x 权重,推理分辨率还是 1280。解决:界面等于演示工具,默认加载 yolov8n 或 yolov8s 就够,追求效果的场景再给用户提供切换到大模型的入口;推理分辨率固定 640,1080P 视频帧先cv2.resize缩到 640 再送进模型,检测结果画到原始帧上,这样既保持画质又控制耗时。这里有个取舍:imgsz越低延迟越小,但小目标漏检会变多,所以 640 是演示和效果的平衡点,不要擅自降到 480,除非场景里行人非常大。

6. 把界面做成能交付的包:模型导出与 PyInstaller 打包的进阶操作

6.1 模型导出 ONNX/OpenVINO:CPU 部署时推理速度的差别

训练完的best.pt是 PyTorch 权重,界面 APP 直接加载它也能跑,但有两个问题:torch 依赖体积巨大,打包动辄 2GB 以上;GPU 推理的代码在目标机器上没有 CUDA 环境时直接歇菜。更稳的做法是导出成 ONNX,用 onnxruntime 推理,CPU 也能达到实际可用的速度。

yolo export model=best.pt format=onnx imgsz=640 opset=12

逻辑说明:format=onnx 生成best.onnx,这是一个与 PyTorch 解耦的模型文件。导出后代码里的模型加载要换一套接口,输入要做归一化和通道转换,输出要解析成框坐标。换成 ONNX 后,PyQt 项目不再依赖 torch,只装onnxruntime就行,整个启动速度和内存占用都会好很多。

参数说明:opset=12 是兼容性比较好的算子集版本,老机器也能跑。CPU 推理场景下,用 OpenVINO 加载 ONNX 会比 onnxruntime 快 30%~50% 左右。如果目标是 RK3588 这类带 NPU 的嵌入式板子,常见的做法是导出 ONNX 后再转成 RKNN 并量化到 INT8,行人检测的推理延迟能压到几十毫秒一级。那一套流程是另一个话题,这里提一句:先在电脑上用 ONNX 验证精度,再上板子量化,能少走很多弯路。

6.2 PyInstaller 打包 PyQt + YOLOV8 的注意事项与体积控制

打包命令本身很简单,坑都在依赖排查上。常用命令如下。

pyinstaller -D -w --hidden-import=ultralytics --hidden-import=cv2 main.py

逻辑说明:-D目录模式比-F单文件模式更稳,启动速度快一倍,杀毒软件误报率也低。-w隐藏命令行窗口。--hidden-import手动把 ultralytics 和 cv2 这些动态导入的模块塞进包,否则运行时报 ModuleNotFoundError。

参数说明:打包完体积压不下去,核心原因是 PyTorch 被打进去了。用 ONNX Runtime 替代 torch 后,体积能从 2GB 级别降到 300MB 左右,这是一条最有效的路径。如果还得用 torch,就加--exclude-module=matplotlib --exclude-module=tkinter排除用不到的模块,能再省几十 MB。模型文件best.onnx放在 exe 同级的 weights 目录下,不要写进打包路径里,方便以后直接替换模型不用重新打包。

写这套流程时我回想了一下,自己第一次打包 PyQt + YOLOV8,exe 在开发机上跑得好好的,拿到师弟机器上就报缺 cudnn。折腾半天才明白,开发机上有 CUDA 环境所以 torch 能跑,目标机器没有就崩了。后来学乖了,所有交付项目全部转 ONNX,推理代码用 onnxruntime,打包后再也不用管 CUDA 环境。这个习惯保了我后来所有项目的命。希望这篇能帮你少走点弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 18:12:16

Task未观察异常与SQLite.Interop.dll入口点丢失:.NET后台任务崩溃排查

1. 这报错是两层问题叠在一起&#xff1a;Task 没观察 SQLite.Interop.dll 入口点丢失前阵子朋友发我一张截图&#xff0c;Windows 服务在跑批任务时毫无预兆地退出&#xff0c;事件日志里的异常长这样&#xff1a;未通过等待任务或访问任务的 Exception 属性观察到任务的异常…

作者头像 李华
网站建设 2026/10/10 17:54:25

NumPy手写BP神经网络实现多输入多输出回归

简介&#xff1a;本资源是一份面向机器学习初学者与Python实践者的BP神经网络教学实操包&#xff0c;聚焦多输入多输出回归建模这一典型任务&#xff0c;适用于智能预测、工程建模、科研数据拟合等场景。压缩包为ZIP格式&#xff0c;共3个文件&#xff08;2个Excel数据表1个Pyt…

作者头像 李华
网站建设 2026/10/10 17:52:41

粒子群算法优化SVR超参数:从手动调参到自动寻优的完整实战

简介&#xff1a;一套基于粒子群优化支持向量机&#xff08;PSO-SVR&#xff09;的回归拟合实验资源包&#xff0c;面向机器学习初学者与需要调参实战的算法工程师&#xff0c;专注于解决SVR惩罚因子C与核参数γ难确定、易陷入局部最优的问题&#xff0c;可应用于非线性数据预测…

作者头像 李华
网站建设 2026/10/10 17:52:17

知网查重过了但AIGC高达70实测降AI效果最好消红方案与工具TOP1

知网查重过了但AIGC高达70%&#xff1f;实测降AI效果最好消红方案与工具TOP1【实测测评结论速览】 针对“文字查重合格&#xff08;<5%&#xff09;但知网 AIGC 爆红&#xff08;>70%&#xff09;”的高危双检痛点&#xff0c;2026 年实测降 AI 效果最好、稳居消红榜首 T…

作者头像 李华