很多人拿到一个目标检测项目,第一反应都是"先去跑通官方Demo",等真的把模型跑起来、拿自己的数据集一训练,才发现里面全是坑——环境版本对不上、显存爆了、标签格式错了、mAP怎么都提不上去。这篇文章我打算从头到尾捋一遍YOLOv8人体与行人检测的完整落地流程,包含我实际调试过程中踩过的坑、验证过的参数、以及一份可以直接跑的推理源码,希望能帮你省掉那些没必要走的弯路。
1. YOLOv8为什么适合做人体和行人检测
1.1 从YOLOv5到YOLOv8,项目选型时我考虑了哪些因素
做人体检测,网上的方案其实不少:传统HOG+SVM、两阶段的Faster R-CNN、单阶段的YOLO系列、还有基于Transformer的DETR。但放到实际项目里,我最终选了YOLOv8,主要看中几点:
第一,部署友好。Ultralytics团队把训练、验证、导出、推理封装得很完整,文档齐全,社区活跃,真遇到问题搜一下基本都有答案。相比DETR那一套,YOLOv8对新手和老手都更省心。
第二,推理速度快。行人检测经常要接实时视频流,比如安防摄像头、园区巡逻机器人、智能零售客流统计,这些场景对延迟很敏感。YOLOv8s在GPU上跑640分辨率大概3-5ms一帧,CPU上也能跑到20-50ms,能覆盖绝大多数实际需求。
第三,精度足够。在COCO和Person类数据集上,YOLOv8的mAP表现是同级模型里靠前的,尤其是中小目标的检测能力比YOLOv5有明显提升。行人这种目标在画面里往往只占几十到几百像素,比较考验模型对小目标的敏感度。
1.2 YOLOv8模型家族怎么选
YOLOv8有n/s/m/l/x五个尺寸,实际项目里大多数情况用s或者m就够。我一般这样判断:
| 模型 | 参数量 | 输入640x640的推理耗时(GPU) | 适用场景 |
|---|---|---|---|
| YOLOv8n | 3.2M | 约1.5ms | 嵌入式设备、边缘盒子 |
| YOLOv8s | 11.2M | 约3ms | 中端GPU、实时视频流 |
| YOLOv8m | 25.9M | 约6ms | 精度优先、算力充足 |
| YOLOv8l | 43.7M | 约10ms | 高精度离线分析 |
| YOLOv8x | 68.2M | 约16ms | 需要极致mAP的竞赛场景 |
如果你要做的只是"检测画面里有没有人、人在哪里",首选YOLOv8s;如果部署在树莓派这种设备上,就用nano;如果对面是大量遮挡、密集人群的场景,再考虑m甚至l,别一上来就拉满重量级模型,训起来慢、跑起来卡,大部分项目根本用不到。
1.3 我理解的YOLOv8核心改动
YOLOv8相比YOLOv5,最直观的变化在Head部分:把原来的Coupled Head(耦合头)换成了Decoupled Head(解耦头),分类和回归分支分开输出,收敛更稳;同时引入了Anchor-Free机制,不再依赖预设锚框,省掉了聚类计算锚框这步。Backbone还是CSP风格,但换成了C2f模块,梯度流更丰富,特征提取能力更强。
这些改动带来的实际好处是:训练收敛更快、mAP更高、对不同长宽比的目标更鲁棒。行人高瘦比例多,Anchor-Free天然比固定锚框更容易适配这类细长目标,这也是我在行人检测项目里偏好YOLOv8的重要原因。
2. 环境搭建和训练数据准备,最容易出问题的环节
2.1 版本组合怎么搭配才不踩坑
YOLOv8的官方代码基于PyTorch,但PyTorch版本和CUDA版本对不上,Python库冲突,几乎是每个新手都绕不过去的坎。我这里直接给一套能稳定运行的组合:
Python 3.9 或 3.10 PyTorch 2.1.0+cu121 或 2.2.0+cu121 CUDA 12.1(驱动版本建议大于530) ultralytics 8.2.x numpy 1.26.x opencv-python 4.8+创建虚拟环境的步骤没什么神秘感:
conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics这里特别注意,不要先装ultralytics再回头装torch,依赖解析容易把torch版本覆盖掉。我以前就遇到过,ultralytics自动拉取了CPU版torch,之后怎么调都用不了GPU,排查半天才发现是这问题。另外,装完以后务必跑一下验证:
import torch print(torch.cuda.is_available()) print(torch.__version__)输出True说明CUDA可用,如果是False,先检查NVIDIA驱动和CUDA版本是否匹配,别急着重装环境。
2.2 数据集从哪里来:公开数据集与自采数据
在做人体/行人检测时,数据集基本有三个来源:
- COCO数据集:含80个类别,其中person类的标注质量很高,图片覆盖场景多,适合预训练。
- 行人专用数据集:比如CityPersons、CrowdHuman(这个对密集人群标注很丰富)、EuroCity Persons,专门针对行人场景。
- 自采数据:用自己的摄像头拍、找人标注,适合特定场景(比如商场闸机口、厂房过道)的真实光线和角度。
我的建议是:先用COCO预训练权重做迁移学习,再用自己的数据集微调。千万别从零训练YOLOv8s,没那必要,收敛极慢不说,最终效果还不一定比得上微调。
2.3 标注格式转换:YOLO TXT格式你真的搞懂了吗
YOLO系列用的是归一化的txt标注,每行一个目标:
class_id x_center y_center width height注意这四项全部是归一化值,也就是用像素坐标除以图片宽高,取值范围0-1。我之前跟人合作过一个项目,对方用LabelImg标注后导出的VOC XML格式,然后直接硬转成YOLO格式,因为坐标没归一化,训练出来的模型框全偏到图片边缘。转换时核心逻辑是:
# 假设bbox为[x1, y1, x2, y2],图片宽高为w, h x_center = ((x1 + x2) / 2) / w y_center = ((y1 + y2) / 2) / h box_w = (x2 - x1) / w box_h = (y2 - y1) / h如果用的是LabelImg的VOC格式,转换脚本在Ultralytics官方仓库里有现成的,想偷懒可以直接用。但建议断码搞清楚原理,因为自采数据集经常有脏标注,你不清楚转换逻辑就不知道错在哪。
2.4 数据集划分的比例
官方推荐train/val/test按90/10/0或80/20/0划分。测试集我先不放数据,因为实际部署效果需要单独拍视频验证,而不是依赖测试集。验证集必须跟训练集分布有差异——比如训练集是上午的光线,验证集就刻意放一些黄昏和夜晚的图,这样才能暴露出过拟合。
用脚本划分时注意加随机种子,不然每次跑数据划分不一样,实验结果也没法对比。我之前纠结过一组实验,某个数据增强参数改了但mAP忽高忽低,后来才发现是数据划分没固定,相当于每次都在评估不同的验证集。
3. 训练阶段的完整复现路线
3.1 一条能直接用的训练命令
假设数据目录结构是标准YOLO格式:
datasets/ person_det/ images/ train/ val/ labels/ train/ val/写一个数据配置文件person.yaml:
path: /path/to/datasets/person_det train: images/train val: images/val nc: 1 names: ['person']然后跑训练:
yolo detect train \ model=yolov8s.pt \ data=person.yaml \ imgsz=640 \ epochs=100 \ batch=16 \ device=0 \ patience=10 \ project=runs/detect \ name=person_exp1 \ pretrained=True这里几个参数我单独说下:
epochs=100:对行人检测这种相对单一类别的任务,100轮完全够,我试过150轮和100轮的对比,后者在验证集上mAP几乎没差,训练时间却省了三分之一。batch=16:如果显存是12G,16这个值比较稳;8G显存就降到8,或者把imgsz降到512。patience=10:验证集mAP连续10轮不涨就早停,防止白烧电。
3.2 训练过程中怎么看loss曲线和mAP指标
训练结束后,在runs/detect/person_exp1/下会生成一堆曲线。人脸心态上先看三张图:
- train/box_loss、train/cls_loss、train/dfl_loss:这三条曲线应该整体下降,中间波动是正常的,但整体趋势必须向下。
- metrics/mAP50和metrics/mAP50-95:前者要求框框交并比0.5就算对,后者更严格,是0.5到0.95取平均,行人检测里一般看mAP50(业务上框比0.5就够用)。好的模型mAP50能到90以上,mAP50-95到85以上视数据集难度而定。
- val/box_loss:如果训练loss一直降但val loss先降后升,这是过拟合信号,马上停止训练,别硬跑完100轮。
3.3 我调参过程中总结的经验
我拿自建数据集调过几轮,说几个容易踩的坑:
- 不要用默认的mosaic做满全流程。Ultralytics默认前10轮mosaic增强很强,但到后面比如epoch 70以后,我建议把mosaic关掉或减弱,不然小目标行人被拼接图片截断,模型反而学不好。官方其实已经做了10轮自动关闭的处理,但如果你数据集里小目标多,建议手动把
mosaic=0.5甚至更低。 - 类别不平衡不要慌。行人检测其实就一个类别,不存在多类别不平衡。但如果你的数据集里远处小行人和近处大行人比例失衡,可以在数据层面多收集些小目标的图,或者用
scale=0.5增强时把输入分辨率提高,变相增加小目标像素面积。 cos_lr=True建议开启。余余弦退火学习率对mAP的提升是实打实的,我在自己数据上测过,不开启最终mAP50大概低1-2个点。
4. 推理代码:从官方API到自定义预测器
4.1 官方predict接口的快速上手
训练完或拿到权重,最简单的推理方式:
from ultralytics import YOLO model = YOLO("runs/detect/person_exp1/weights/best.pt") results = model.predict("test.jpg", imgsz=640, conf=0.4, device=0) for r in results: boxes = r.boxes.xyxy.cpu().numpy() scores = r.boxes.conf.cpu().numpy() for box, score in zip(boxes, scores): print(f"person {score:.2f} {box}")这段代码的问题是每次都要重新加载模型,如果处理多张图,效率很差。实际项目里我用的是下面这种方式。
4.2 固定输出人体目标的检测器:完整源码
我习惯封装一个类,让模型常驻显存,同时过滤出person类(如果模型是COCO预训练的多类别权重)。下面这份代码我直接用在过视频流项目里,可以自行改成摄像头输入。
import cv2 import torch from ultralytics import YOLO class PersonDetector: def __init__(self, weights_path, conf_thres=0.4, iou_thres=0.5, device="0", imgsz=640): self.conf_thres = conf_thres self.iou_thres = iou_thres self.imgsz = imgsz self.device = device self.model = YOLO(weights_path) self.class_names = self.model.names # {0: 'person', ...} def detect(self, frame): """输入BGR帧,返回person的xyxy坐标列表和置信度""" results = self.model.predict( source=frame, conf=self.conf_thres, iou=self.iou_thres, imgsz=self.imgsz, device=self.device, verbose=False, ) boxes = results[0].boxes if boxes is None: return [], [] xyxy = boxes.xyxy.cpu().numpy() conf = boxes.conf.cpu().numpy() cls = boxes.cls.cpu().numpy() person_boxes = [] person_scores = [] for i in range(len(cls)): # 如果训练权重只有person类,cls[i]恒为0;COCO权重时需过滤cls==0 if int(cls[i]) == 0: person_boxes.append(xyxy[i]) person_scores.append(float(conf[i])) return person_boxes, person_scores def visualize(self, frame, boxes, scores): for box, score in zip(boxes, scores): x1, y1, x2, y2 = [int(v) for v in box] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f"person {score:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) return frame if __name__ == "__main__": detector = PersonDetector("runs/detect/person_exp1/weights/best.pt") cap = cv2.VideoCapture("test_video.mp4") while True: ret, frame = cap.read() if not ret: break boxes, scores = detector.detect(frame) frame = detector.visualize(frame, boxes, scores) cv2.imshow("person detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这份代码的关键点在于用verbose=False关掉官方的打印信息,不然每帧输出一堆日志,视频流根本没法看。另外,imgsz=640在推理时是可选输入尺寸,会自动做letterbox处理,但bounding box已经映射回原图坐标,不需要手动还原。
4.3 如果遇到NMS把重叠行人合并了该怎么办
行人互相遮挡是常态。YOLOv8默认使用NMS(非极大值抑制),当两个人挨得特别近时,可能一个高置信度框把另一个低置信度的框直接压掉。这种情况我一般分三步处理:
- 调低conf_thres:从0.4降到0.25,让更多可能性框浮出来。
- 调低iou_thres:默认0.45-0.5对重叠行人偏敏感,可以降到0.3,让NMS更"宽容",认为两个重叠度较高的框可以共存。
- 换更强的模型:如果调到0.3还漏检,说明是特征层面没分出来,换m或l模型通常能改善。
这三个办法有一定副作用,比如误检会增加,需要你根据具体业务容忍度来权衡。我在密集人群测试里用过iou=0.3 + conf=0.25组合,漏检率从13%降到了6%左右,误检从不到1%增加到3%,对安防来说可以接受。
5. 数据增强策略和类别不平衡处理
5.1 YOLOv8自带增强有哪些
Ultralytics训练时默认启用Mosaic(把4张图拼成1张)、随机仿射变换、HSV色彩抖动、水平翻转等。这些增强对提升泛化能力很有用,但对行人检测来说,有的需要按场景裁一下。
我自己调训练时习惯这样设置:
# 在ultralytics/cfg/default.yaml中覆盖 mosaic: 0.8 scale: 0.5 fliplr: 0.5 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4scale=0.5比较重要,它允许训练时把图片缩小到原始的一半再放大回640,模拟不同距离下行人尺寸的变化。行人检测的难点之一就是尺度多样性,这个参数对提升小目标能力作用明显。
5.2 针对行人场景的增强心得
行人检测和通用目标检测有个显著区别——人体整体比例固定,从头顶到脚底有一个大致稳定的高度宽度比。所以你不需要做太夸张的旋转增强,角度超过30度的旋转反而会破坏人体先验结构。我在实验里试过把degrees设成30,验证集mAP反而下降0.8个点,因为训练集出现了大量现实中几乎不会出现的歪斜人体,模型浪费了一部分拟合能力在这些伪样本上。
比较好的做法是:水平翻转开到0.5,上下翻转不开。行人倒立这种样本在安防场景下没有意义,它只会在训练时干扰特征学习。
5.3 自采数据太少怎么补
如果你的自采数据只有几百张,最有效的办法是先拿COCO的person检测权重做迁移学习,冻结Backbone训练30轮,再解冻全部层训练50轮。注意从头训练和迁移训练的优化器策略不同:
- 迁移训练:初始学习率用官方默认的0.01。
- 自采数据很少(几百张):建议lr0减小到0.002-0.005,避免直接冲掉预训练权重里的通用特征。
我自己试过一个极端情况——只有300张图,从yolov8s.pt微调,mAP50照样能到85以上;但从零训练相同数据,mAP50只有40多,差距非常大。
6. ONNX导出和TensorRT加速,部署端的硬需求
6.1 导出ONNX要注意的几个点
训练完的.pt权重只能在PyTorch环境里跑,真实项目通常要部署成ONNX、TensorRT或OpenVINO。导出ONNX的命令很简单:
yolo export model=runs/detect/person_exp1/weights/best.pt format=onnx imgsz=640 opset=12导出过程中我遇到最典型的坑是dynamic=True和opset版本。如果你需要动态输入尺寸,导出的命令要加dynamic=True,但这个模式下有些算子会变成动态shape,后续的TensorRT引擎构建会更复杂。我的建议是:边缘部署优先固定640x640输入,省掉动态shape的麻烦。
另一个坑是batch_size导出。视频流推理一般batch=1,但如果你后续要并发跑多路视频,导出时建议batch=4,这样TensorRT可以一次处理4路画面,性能提升非常明显。
6.2 从ONNX到TensorRT
NVIDIA显卡上最理想的部署格式是TensorRT。用trtexec构建引擎:
trtexec --onnx=best.onnx --saveEngine=best.engine --fp16 --workspace=4096加--fp16之后,我在同一个测试视频上的结果是:960x540分辨率1200帧,PyTorch推理平均耗时31ms,TensorRT FP16耗时11ms,快了接近3倍,mAP几乎不掉。如果你做的是安防类的实时项目,这一步基本是必做的。
构建引擎时如果遇到某个算子不支持,常见做法是回退到opset=12重新导出,或者把imgsz固定成训练时的640而不是任意值。
6.3 用ONNX Runtime推理
不依赖PyTorch环境、只想轻量跑起来的话,ONNX Runtime是最省事的:
import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession("best.onnx", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) input_name = session.get_inputs()[0].name input_shape = session.get_inputs()[0].shape # [1, 3, 640, 640] def preprocess(frame): img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1))[None] return img def inference(frame): tensor = preprocess(frame) outputs = session.run(None, {input_name: tensor})[0] # [1, 84, 8400]之类 # 输出shape取决于导出格式,需要自己解析 return outputsONNX输出的解析比.pt要麻烦,因为你需要自己对8400个预测框做置信度过滤和NMS。嫌麻烦的话,可以直接用onnxruntime-gpu配合ultralytics自带的YOLO("best.onnx")接口,Ultralytics内部会处理好这些后处理逻辑,只是少了些性能优化空间。
7. 效果评估和常见问题排查
7.1 判断模型好坏的三个维度
在实际业务里,只看mAP是不够的,我一般从三个维度评估:
- 漏检率:实际有人的画面没框出来。对安防来说这是必须压低的第一指标,宁可框错也不想漏。
- 误检率:把树影、玩偶、广告牌人形误判成人。这个在误报率高时会很烦人,需要调高置信度阈值或用目标跟踪做时序滤波。
- 速度:在目标硬件上的FPS。很多项目要求20FPS以上,所以选模型时就要把推理时间算进去。
7.2 训练结果不理想的排查顺序
如果你的模型效果很差,别急着改网络结构,先按这个顺序排查:
- 看标注文件:随机抽几十张训练图,用可视化脚本把框画在图上,看框是否贴合目标、是否有漏标错标。这是最常见的问题源。
- 看loss曲线:如果loss不下降且一开始就很高,大概率是数据路径配错了,模型根本没见过真数据;如果val loss震荡剧烈,先检查这批数据的分布是否均匀。
- 看预测结果:拿单张测试图跑推理,把
conf降到0.1强制出框,框如果全在正确位置但置信度低,说明数据多样性不够或目标太小。 - 查锚框/分辨率:如果你的目标普遍小于10x10像素,640分辨率训练就是不够,建议提高到960或专门裁切小目标区域。
7.3 实际部署中的两个小建议
最后说两个我在真实项目里踩出来的经验。
一是摄像头角度和训练数据差异过大,效果一定会拉跨。如果你训练集全是平视视角,部署时摄像头装在高处俯视,那漏检绝对不能怪模型——你得在部署位置真正架上设备,多拍些真实画面回来补充微调。我在项目里习惯在部署初期先跑一周数据收集,然后拉回训练集微调一版,效果提升非常明显。
二是加一个简单的目标跟踪器能显著改善体验。原生YOLOv8检测是逐帧独立的,一个行人被短暂遮挡或检测波动一下,就会出现连续闪烁的框。我在项目里接了个ByteTrack,帧与帧之间做ID关联,视频流里的框就稳定多了,而且跟踪还能顺便统计客流数,用来做计数业务几乎零额外成本。
写在最后的个人建议
如果让我给你一条最省事的学习/落地路径,我的建议是这样的:先用YOLOv8s跑通官方Demo,感受一下检测效果;再找到一个跟自己行业尽量接近的公开数据集,微调一版看看差距;然后尽快用自采数据替换掉公开数据,这是项目真正落地的分水岭。整个过程里,"数据质量"的重要性远超"调参技巧",你能花一周时间把标注整干净,效果可能比花一个月死磕mAP还明显。目标检测现在很成熟,真正拉开差距的往往不是模型选得多新,而是你对数据、场景和部署链路的理解有多深。