YOLOv8行人检测实战:数据集转换、训练调参与PyQt5界面部署一步到位
发布时间:2026/10/10 18:13:54 锦皓数字建站

简介面向计算机视觉初学者、算法工程师及智能交通开发者的YOLOv8行人检测完整方案集成数据集、训练权重与PyQt可视化界面解决街道和交通场景中行人实时检测及界面化部署需求。压缩包共2000个文件涵盖1991个txt标注文件、2个Python脚本、4个Markdown说明和3个PDF教程整体456.56MBtxt为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_okTrue) 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_okTrue) os.makedirs(lbl_out, exist_okTrue) 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 动态调整增强但实操中直接在训练命令里传mosaic0.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 python3.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 16yolov8s 就减到 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 datapedestrian.yaml modelyolov8s.pt epochs100 imgsz640 batch16 device0 patience15逻辑说明modelyolov8s.pt表示加载 COCO 预训练权重ultralytics 会自动下载迁移学习让收敛速度比随机初始化快很多。patience15是早停参数连续 15 个 epoch 验证集 mAP 没提升就自动停止省时间也防过拟合。参数说明imgsz640是性价比最高的输入尺寸行人检测大多数场景用这个。如果画面里行人很小比如无人机俯拍可以试imgsz960甚至 1280但显存和训练时间会成倍上涨6G 卡直接别想。epochs100对单类行人检测足够自标小数据集建议 150~200配合早停就不怕白等。batch16是 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 默认 lr00.01小数据集上可以调到 0.005 试试。如果调完还是不动去翻几个训练图片和 txt 的对应关系八成是标签转错了比如坐标全部为 0 或者类别 id 越界。第三种情况更隐蔽loss 降得很漂亮mAP50 到了 0.9但 mAP50-95 只有 0.4 左右。这说明框的位置不够精准和真实标注的 IoU 在 0.5~0.95 区间里掉得厉害。常见原因是imgsz太小行人是小目标640 的输入下像素占比本来就少定位精度上不去。这时可以试imgsz960或者从 yolov8n 换到 yolov8smAP50-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继承 QThreadrun()里放推理循环。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, source0, imgsz640): 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, imgszself.imgsz, verboseFalse) 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, imgszself.imgsz)返回一个 Results 列表每一帧取results[0]就是当前检测结果。msleep(1)让出线程调度否则子线程会吃掉一个 CPU 核心。参数说明source0是默认摄像头传视频文件路径就是离线检测。imgsz我建议做成界面上的下拉框640 和 960 之间切换用户根据实际场景自己选。verboseFalse关闭控制台刷屏否则每帧都会打印一串检测信息界面没卡但终端卡得没法看。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 加device0防止它尝试用多卡每次推理后调torch.cuda.empty_cache()回收缓存界面 APP 里每次检测完成后清理变量引用不要把所有帧的 results 都存在列表里。5.4 检测框乱跳、密集行人漏检NMS 与置信度阈值调节现象同一个行人在前后帧的框忽大忽小或者视频里几个人紧挨着走只检测出一个框另一个被压掉了。原因置信度阈值太低让低质量框大量存活NMS 的 IoU 阈值太高导致相邻行人的框互相抑制。YOLOV8 默认conf0.25, iou0.45这在密集场景下不够用。解决密集行人场景把置信度调到conf0.35NMS 的iou从 0.45 降到 0.3 左右减少相邻框的互相压榨。我在安全帽检测项目里试过这个组合对“两个人挨着站”的场景改善非常明显。更推荐的做法是把 conf 和 iou 做成界面上的滑动条让使用者现场调因为不同摄像头的视角和距离对这两个参数的需求差很多。5.5 加载太慢/推理太慢模型尺寸与输入分辨率的选择现象界面启动后点击开始两秒多才出现画面1080P 视频推理帧率只有个位数。原因加载的是 yolov8x 权重推理分辨率还是 1280。解决界面等于演示工具默认加载 yolov8n 或 yolov8s 就够追求效果的场景再给用户提供切换到大模型的入口推理分辨率固定 6401080P 视频帧先cv2.resize缩到 640 再送进模型检测结果画到原始帧上这样既保持画质又控制耗时。这里有个取舍imgsz越低延迟越小但小目标漏检会变多所以 640 是演示和效果的平衡点不要擅自降到 480除非场景里行人非常大。6. 把界面做成能交付的包模型导出与 PyInstaller 打包的进阶操作6.1 模型导出 ONNX/OpenVINOCPU 部署时推理速度的差别训练完的best.pt是 PyTorch 权重界面 APP 直接加载它也能跑但有两个问题torch 依赖体积巨大打包动辄 2GB 以上GPU 推理的代码在目标机器上没有 CUDA 环境时直接歇菜。更稳的做法是导出成 ONNX用 onnxruntime 推理CPU 也能达到实际可用的速度。yolo export modelbest.pt formatonnx imgsz640 opset12逻辑说明formatonnx 生成best.onnx这是一个与 PyTorch 解耦的模型文件。导出后代码里的模型加载要换一套接口输入要做归一化和通道转换输出要解析成框坐标。换成 ONNX 后PyQt 项目不再依赖 torch只装onnxruntime就行整个启动速度和内存占用都会好很多。参数说明opset12 是兼容性比较好的算子集版本老机器也能跑。CPU 推理场景下用 OpenVINO 加载 ONNX 会比 onnxruntime 快 30%~50% 左右。如果目标是 RK3588 这类带 NPU 的嵌入式板子常见的做法是导出 ONNX 后再转成 RKNN 并量化到 INT8行人检测的推理延迟能压到几十毫秒一级。那一套流程是另一个话题这里提一句先在电脑上用 ONNX 验证精度再上板子量化能少走很多弯路。6.2 PyInstaller 打包 PyQt YOLOV8 的注意事项与体积控制打包命令本身很简单坑都在依赖排查上。常用命令如下。pyinstaller -D -w --hidden-importultralytics --hidden-importcv2 main.py逻辑说明-D目录模式比-F单文件模式更稳启动速度快一倍杀毒软件误报率也低。-w隐藏命令行窗口。--hidden-import手动把 ultralytics 和 cv2 这些动态导入的模块塞进包否则运行时报 ModuleNotFoundError。参数说明打包完体积压不下去核心原因是 PyTorch 被打进去了。用 ONNX Runtime 替代 torch 后体积能从 2GB 级别降到 300MB 左右这是一条最有效的路径。如果还得用 torch就加--exclude-modulematplotlib --exclude-moduletkinter排除用不到的模块能再省几十 MB。模型文件best.onnx放在 exe 同级的 weights 目录下不要写进打包路径里方便以后直接替换模型不用重新打包。写这套流程时我回想了一下自己第一次打包 PyQt YOLOV8exe 在开发机上跑得好好的拿到师弟机器上就报缺 cudnn。折腾半天才明白开发机上有 CUDA 环境所以 torch 能跑目标机器没有就崩了。后来学乖了所有交付项目全部转 ONNX推理代码用 onnxruntime打包后再也不用管 CUDA 环境。这个习惯保了我后来所有项目的命。希望这篇能帮你少走点弯路。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。