YOLO11行人检测:VOC/COCO/YOLO数据转换与GPU/CPU/Mac三平台训练实战 简介面向行人目标检测算法训练与项目落地提供1000张真实场景图片的行人检测数据集覆盖校园、街景、道路以及遮挡/严重遮挡行人等多元场景适合公共场所监控下的行人检测项目也可作为通用监控场景数据集的补充。资源包为单个PDF文件大小约4.17MB内附数据集情况介绍、labelimg标注示意截图以及百度网盘获取方式通过网盘可获取VOC(xml)、COCO(json)、YOLO(txt)三种格式标签和YOLO11一键训练脚本支持GPU(GPUs)、CPU、Mac(M芯片)多平台训练并附有博主训练结果日志供调参参考。标注采用labelimg工具完成格式规范可直接用于YOLO系列等主流模型训练减少数据预处理成本。目前已有1043人学习浏览适合需要标准格式数据集和完整训练方案的目标检测开发者。1. 1000张行人图先别急着点训练这套数据集和脚本到底在解决什么一个做安防的同行问我手里刚好有1000张行人目标检测数据集对应VOC/COCO/YOLO三种格式的标签还配套了GPU/CPU/Mac三平台的YOLO11一键训练脚本能不能直接拿去训我的回答是能但如果你拿到手的前半小时直接敲训练命令大概率会得到一份“能跑、但看不懂”的结果。行人和车辆这类常规目标数据集的质量和划分方式对最终模型的影响远大于你选yolo11n还是yolo11s。1000张图能做什么它能覆盖一个具体场景比如某个园区出入口的行人检测让你在1到2小时内跑通从数据到模型的完整链路它做不了的是通用的、跨场景的开放域检测。下面的内容按数据集验收、格式转换、三平台训练、踩坑排查、增量迭代的顺序展开新手照做能跑通熟手可以直接对照参数和边界。2. 三种标注格式的开箱检查VOC/COCO/YOLO的核心差异与数据质量体检拿到数据集的第一件事不是训练而是核对标签。我见过太多“跑起来很顺最后mAP出不来”的项目根因往往不是模型而是数据本身。这一章把三种格式的差异讲清楚再用一个脚本给标注做体检。2.1 1000张图对这个任务意味着什么先看分布再谈训练行人检测的公开数据集很容易找到几千上万张的但那是通用场景。一个商业项目里真正有价值的往往是你自己场景的几百张标注图。1000张图的合理预期是在固定机位、固定光照条件下把行人检测AP刷到80%以上是可行的如果还要求夜间、雨天、远距离小目标全都要好那这1000张图只是起点。所以先看分布图里是白天多还是夜间多行人占画面比例是大是小有没有遮挡平均每张图几个人这些参数直接决定了你的验证集怎么划分、训练时要不要调小目标增强。我一般会先把标注统计跑一遍再决定后续动作。下面的脚本对VOC格式的XML做统计输出每张图的标注数量、框的尺寸分布、小目标占比。如果是COCO或YOLO格式先转成VOC或直接改解析逻辑结论一样。import glob from xml.etree import ElementTree as ET xmls glob.glob(Annotations/*.xml) total_boxes 0 img_with_box 0 small_boxes 0 all_w [] all_h [] for x in xmls: root ET.parse(x).getroot() boxes list(root.iter(object)) if boxes: img_with_box 1 for obj in boxes: total_boxes 1 xmin float(obj.findtext(bndbox/xmin)) ymin float(obj.findtext(bndbox/ymin)) xmax float(obj.findtext(bndbox/xmax)) ymax float(obj.findtext(bndbox/ymax)) w xmax - xmin h ymax - ymin all_w.append(w) all_h.append(h) if max(w, h) 32: small_boxes 1 print(f图片总数: {len(xmls)}) print(f有效标注图片: {img_with_box} ({img_with_box / len(xmls) * 100:.1f}%)) print(f标注框总数: {total_boxes}) print(f平均每张图 {total_boxes / len(xmls):.2f} 个行人) print(f小目标(32px)占比: {small_boxes / max(total_boxes, 1) * 100:.1f}%)逻辑说明每张图取object节点计算w/h并记录。max(w,h)32的判断参考COCO小目标定义面积小于32×32对行人这类细长目标取长边更直观。输出结果里如果“无标注图片”超过5%先查是不是某批XML没解析出来常见坑是XML的编码或标签名不一致。我会额外关注“平均每张图几个人”如果平均只有1个人说明大量是单人场景或负样本图训练出来的模型对密集人群基本没有泛化能力。2.2 VOC、COCO、YOLO三种格式到底差在哪先给出一张对比表后面转换脚本和训练脚本都会围绕这张表展开。维度VOC XMLCOCO JSONYOLO TXT文件形态一张图一个XML全量标注一个JSON一张图一个TXT坐标体系xmin,ymin,xmax,ymax像素x,y,width,height像素x_center,y_center,w,h归一化类别ID字符串name无IDcategory_id从1开始整数ID从0开始附加能力无segmentation、area无训练友好度需解析需写转换直接喂给YOLO差异里最有坑的两个点一是COCO的category_id从1开始YOLO的class_id从0开始转YOLO时如果直接拿COCO的id去写txt类别错位是必然的二是COCO的bbox是[x,y,width,height]x和y是左上角坐标不是中心点很多第一次转的人把中心坐标填进去模型输出的框整体偏右下。另一个容易被忽略的是VOC没有显式类别ID全靠name字符串对应这意味着转换脚本里必须维护一个names列表YOLO训练脚本的data.yaml里names顺序和这个列表完全一致。比如names[person]YOLO的class_id就是0COCO的category_id就是1。2.3 用 inspect_data.py 给标注做一次体检上面的统计脚本只输出了几个汇总值实际动手前我还会做一轮更细的检查找出坐标越界的框、宽度或高度为0的框、类别名意外超出预期的框。这些脏数据如果不处理训练时不一定报错但会让loss曲线出现莫名其妙的抖动。下面的脚本把所有异常标注输出到一个txt里方便逐条核对。import glob from xml.etree import ElementTree as ET BAD [] for x in glob.glob(Annotations/*.xml): root ET.parse(x).getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) fname root.findtext(filename) for i, obj in enumerate(root.iter(object)): name obj.findtext(name) xmin float(obj.findtext(bndbox/xmin)) ymin float(obj.findtext(bndbox/ymin)) xmax float(obj.findtext(bndbox/xmax)) ymax float(obj.findtext(bndbox/ymax)) w xmax - xmin h ymax - ymin if xmax xmin or ymax ymin: BAD.append(f{fname}: 无效框 i{i} {xmin} {ymin} {xmax} {ymax}) elif xmin 0 or ymin 0 or xmax img_w or ymax img_h: BAD.append(f{fname}: 越界框 i{i} 图片{img_w}x{img_h}) elif name not in [person]: BAD.append(f{fname}: 未知类别 {name}) for b in BAD: print(b) print(f共发现 {len(BAD)} 处异常)逻辑说明前两个判断分别处理“框退化”和“坐标越界”。第三个判断把不知道往哪放的类别名列出来避免转换时静默丢弃。对1000张图来说这个脚本跑完应该基本无输出如果输出很多先把数据问题处理干净再进入下一步这是后面所有训练的前提。关于数据分布这里再补一句目标检测常用标注工具labelImg、CVAT、X-AnyLabeling导出的坐标系各不相同尤其CVAT导出COCO时可能把keypoints或attributes带进来转换前先确认object节点里只有目标框没有额外属性否则解析逻辑会漏。3. 把格式转换跑通VOC转COCO与VOC转YOLO的自包含脚本标题里写了“对应VOC/COCO/YOLO三种格式标签”实际使用中你一定会在某一步只需要其中一种格式。与其找在线转换网站不如在本地维护两个自包含脚本。这一章给出两个可以直接落地的脚本以及转换后的校验命令。3.1 为什么用VOC做转换中枢而不是COCO我在数据集处理上习惯用VOC格式做中枢。原因是XML的可读性最好出问题时打开文件直接能看到坐标和类别名而且VOC结构简单一张图一个文件增量更新只要往里丢XML就行不需要处理JSON的合并冲突。相比之下COCO一个JSON在大数据集里动辄几十MB改错一个括号整个文件报废YOLO的txt信息量太少坐标越界了不打开图片根本发现不了。如果数据集只给了COCO常见做法是先用ultralytics自带的转换功能把COCO转成YOLO txt再用YOLO txt反推VOC或者直接在COCO基础上做两套转换。但更省事的路径是把VOC作为中间格式VOC到COCO和VOC到YOLO两个方向各写一个脚本后面的训练脚本只认YOLO格式。对行人检测这种单类别任务来说这套流程完全够用。3.2 VOC转COCO坐标、面积与掩码的三个关键字段COCO JSON最核心的三个部分images、annotations、categories。annotations里的bbox是[x, y, width, height]像素坐标area是框面积segmentation作为polygon类型存在。pycocotools对area有小数的要求转出的代码里要保证这是float否则评估mAP时可能翻车。下面脚本把person类别的VOC标注转成COCO。import json import glob from xml.etree import ElementTree as ET classes [person] images, annotations [], [] ann_id 1 for idx, x in enumerate(sorted(glob.glob(Annotations/*.xml))): root ET.parse(x).getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) filename root.findtext(filename) images.append({ id: idx 1, file_name: filename, width: img_w, height: img_h }) for obj in root.iter(object): name obj.findtext(name) if name not in classes: continue xmin float(obj.findtext(bndbox/xmin)) ymin float(obj.findtext(bndbox/ymin)) xmax float(obj.findtext(bndbox/xmax)) ymax float(obj.findtext(bndbox/ymax)) w xmax - xmin h ymax - ymin annotations.append({ id: ann_id, image_id: idx 1, category_id: 1, bbox: [xmin, ymin, w, h], area: w * h, iscrowd: 0, segmentation: [[xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax]] }) ann_id 1 coco { images: images, annotations: annotations, categories: [{id: 1, name: person}] } with open(annotations.json, w, encodingutf-8) as f: json.dump(coco, f, indent2) print(fCOCO: {len(images)} images, {len(annotations)} boxes)逻辑说明images的id从1开始自增与annotations的image_id对应polygon按[x1,y1,x2,y2,...]绕框一周写8个值足够轻量。注意area用w*h计算后保留float别在写入时加int()否则pycocotools的评估会报类型错误。如果你不打算用pycocotools算指标这一步可以跳过但既然数据集给了COCO格式建议把这条链路也留着后面做模型对比时不用回头补。3.3 VOC转YOLO归一化与Clip越界框YOLO标签是训练时唯一直接读取的格式也是三个脚本里最容易出问题的环节。归一化的坑集中在两点一是中心点坐标用(xminxmax)/2除以图宽有人连续轴一起除以了2二是如果原始XML里坐标已经越界不处理的话txt里会出现大于1或小于0的值ultralytics训练时虽然不崩但会输出WARNINGmAP也会有微小损耗。我在下面的脚本里强制做clip。import os import glob from xml.etree import ElementTree as ET labels_dir labels os.makedirs(labels_dir, exist_okTrue) classes [person] for x in glob.glob(Annotations/*.xml): root ET.parse(x).getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) txt_name os.path.basename(x)[:-4] .txt lines [] for obj in root.iter(object): name obj.findtext(name) if name not in classes: continue xmin max(0, float(obj.findtext(bndbox/xmin))) ymin max(0, float(obj.findtext(bndbox/ymin))) xmax min(img_w, float(obj.findtext(bndbox/xmax))) ymax min(img_h, float(obj.findtext(bndbox/ymax))) if xmax xmin or ymax ymin: continue xc (xmin xmax) / 2.0 / img_w yc (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f0 {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(labels_dir, txt_name), w) as f: f.write(\n.join(lines) \n) print(f标签已写入 {labels_dir}/, 共 {len(glob.glob(os.path.join(labels_dir, *.txt)))} 个文件)逻辑说明clip边界后如果框宽高小于等于0就跳过这一行这是对脏数据的兜底避免训练时读入空标签或无意义标签。六位小数对归一化坐标足够640分辨率下1e-6的误差远小于一个像素。如果你后续要开mosaic增强越界坐标会被再度裁切所以转换阶段就clip干净最省心。3.4 转换后的“后悔药”快速校验三类文件是否对齐转换完先别急着训练用下面几条命令做交叉检查。重点核对labels里txt数量和Annotations里xml数量一致图片文件名、xml文件名、txt文件名三者同名。文件名不一致在Windows和Mac上特别常见因为文件系统对大小写的敏感度不同。ls Annotations | wc -l ls labels | wc -l ls JPEGImages | wc -l # 随机抽一个文件人工核对坐标 cat labels/0001.txt python - EOF import glob names [x.split(/)[-1][:-4] for x in glob.glob(Annotations/*.xml)] labels [x.split(/)[-1][:-4] for x in glob.glob(labels/*.txt)] imgs [x.split(/)[-1][:-4] for x in glob.glob(JPEGImages/*.jpg)] print(XML vs TXT:, OK if len(set(names) - set(labels)) 0 else MISMATCH) print(XML vs IMG:, OK if len(set(names) - set(imgs)) 0 else MISMATCH) EOF逻辑说明第一组命令对比三类文件数量第二段脚本直接拿集合差找不匹配的文件名。如果出现MISMATCH不要手动一个个改名用shell循环把XML文件名作为基准批量改TXT和JPG这才是“后悔药”。把这一步做在训练前能省下后面排查标注错位的大量时间。4. 一键训练脚本的三平台落地GPU/CPU/Mac 的检测逻辑与参数选择标题里“支持GPU(GPUs)/CPU/Mac三平台YOLO11一键训练脚本”是很多人关注的重点。这一章先把目录划分和环境安装讲清楚再给出训练脚本本体。脚本的核心不是把yolo命令包一层而是自动识别当前平台把batch、workers、device三个最容易踩坑的参数一次性配好。4.1 训练前的目录划分固定随机种子按场景拆验证集1000张图如果随机打乱后按8:2划分train/val遇到连续帧抽取的数据集时很容易数据泄漏同一段视频的相邻帧被分到两边验证集指标虚高。更稳的做法是按场景划分先按图片来源分组再把组随机分到训练和验证。下面脚本给出固定随机种子并把图片、标签按YOLO目录结构放好。import os import random import shutil from collections import defaultdict random.seed(42) src_img JPEGImages src_lab labels dst dataset os.makedirs(f{dst}/images/train, exist_okTrue) os.makedirs(f{dst}/images/val, exist_okTrue) os.makedirs(f{dst}/labels/train, exist_okTrue) os.makedirs(f{dst}/labels/val, exist_okTrue) # 没有场景标注时用文件名前3位作为分组依据 groups defaultdict(list) for f in os.listdir(src_img): groups[f[:3]].append(f) items list(groups.values()) random.shuffle(items) split int(len(items) * 0.8) for g in items[:split]: for f in g: shutil.copy(f{src_img}/{f}, f{dst}/images/train/) shutil.copy(f{src_lab}/{f[:-4]}.txt, f{dst}/labels/train/) for g in items[split:]: for f in g: shutil.copy(f{src_img}/{f}, f{dst}/images/val/) shutil.copy(f{src_lab}/{f[:-4]}.txt, f{dst}/labels/val/) print(train:, len(os.listdir(f{dst}/images/train)), val:, len(os.listdir(f{dst}/images/val)))逻辑说明以文件名的前3位作为场景分组键是一种启发式分组。如果文件名本身是随机hash需要改成按图片创建时间或拍摄位置分组。对没有场景属性的数据集按文件名前缀分组至少能避免连续编号帧落在两侧。随机种子固定为42保证每次复现同一份划分后续调参时对比结果才是干净的。4.2 yolo11环境配置Linux、Windows、Mac 三条安装命令的分叉yolo11环境配置在官方文档里只有一条pip命令但三平台的实际差异在torch的安装。Linux GPU装CUDA版torchWindows建议用condaMac用MPS需要pytorch在1.12以上且macOS在12.3以上。这里给三条分叉命令按自己平台执行。# Linux NVIDIA GPU pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124 # Linux / Windows CPU 或 Mac pip install ultralytics pip install torch torchvision # Windows 建议用 conda 隔离环境没有 conda 就用 python -m venv conda create -n yolo11 python3.11 -y conda activate yolo11 pip install ultralytics torch torchvision逻辑说明ultralytics会自动装CPU版torch但GPU机器上必须显式装CUDA版否则nvidia-smi正常、训练却一直走CPU。Mac用户在MPS后端不稳定时把torch升级到最新版或者干脆devicecpu训练1000张图在Apple Silicon上用CPU跑不算慢。环境装完后跑下面命令自检能出预测图就说明安装没问题python -c import ultralytics; print(ultralytics.__version__) yolo predict modelyolo11n.pt sourcehttps://ultralytics.com/images/bus.jpg4.3 dataset.yaml与训练参数先看显存和CPU再定batch和workersyolo11模型训练的原理不外乎数据加载、增强、前向、反向、更新权重小数据集下真正影响结果的是batch size、workers、epochs和是否开cache。下面是dataset.yaml的参考内容注意path用相对路径方便脚本在不同机器间移动。path: ./dataset train: images/train val: images/val nc: 1 names: 0: person参数建议表参数推荐值为什么batchGPU: 16或32CPU/Mac: 8~16显存不足会OOM太小则训练慢且噪声大imgsz640YOLO11默认分辨率行人细长目标够用epochs1001000张图100轮足够配合早停workersGPU: CPU核数Windows: 0Windows上workers0容易卡在数据加载device自动检测脚本里按平台写死workers在Windows上的坑非常典型Ultralytics在Windows下如果workers0会在第一个epoch前卡死或CPU占用率飙高。原因与多进程spawn机制有关稳妥做法就是Windows下强制workers0。GPU机器上workers给到CPU核数的一半通常够用CPU训练时workers取2到4再多反而在线程切换上浪费CPU。4.4 train.sh / train.bat三平台自动识别与一键训练下面两个脚本放在项目根目录命令行执行即可。train.sh跑在Linux/macOS的bash或Windows的Git Bash里train.bat跑在Windows cmd里。脚本核心是先探测GPU再按平台设置device、workers、batch。训练脚本不是黑匣子但跨平台时没人提前告诉你会遇到什么所以我习惯把检测逻辑直接写进脚本。#!/bin/bash # 一键训练脚本: 自动检测 GPU / CPU / Mac MPS set -e cd $(dirname $0) # 1. 检测GPU if command -v nvidia-smi /dev/null 21 nvidia-smi -L /dev/null 21; then echo [INFO] 检测到 NVIDIA GPU DEVICE0 BATCH16 elif [ $(uname) Darwin ]; then echo [INFO] 检测到 macOS, 使用 MPS DEVICEmps BATCH8 else echo [INFO] 未检测到 GPU, 使用 CPU DEVICEcpu BATCH8 fi # 2. Windows (Git Bash) 下 workers 强制0 case $OSTYPE in msys*|cygwin*) WORKERS0 ;; *) WORKERS$(sysctl -n hw.ncpu 2/dev/null || nproc 2/dev/null || echo 4) ;; esac # 3. 训练首次运行会自动下载预训练权重 yolo detect train \ datadataset.yaml \ modelyolo11n.pt \ epochs100 \ imgsz640 \ batch$BATCH \ device$DEVICE \ workers$WORKERS \ projectruns \ namepedestrian \ patience20逻辑说明nvidia-smi是否存在作为GPU判据比基于环境变量的判断更可靠macOS要在Linux分支之前判断因为uname在Mac上是Darwin多数Linux主机上也能拿到这个值workers在Windows下强制为0避开dataloader卡死。batch在GPU上给16而不是自动选择是因为自动模式要额外读取显存并做一次试算小数据集下没必要。多卡机器把device改成0,1batch按卡数翻倍即可。Windows的cmd版本对应如下逻辑一致。echo off cd /d %~dp0 set DEVICEcpu set BATCH8 set WORKERS0 nvidia-smi -L nul 21 if %errorlevel%0 ( set DEVICE0 set BATCH16 echo [INFO] GPU detected ) yolo detect train datadataset.yaml modelyolo11n.pt epochs100 imgsz640 batch%BATCH% device%DEVICE% workers%WORKERS% projectruns namepedestrian patience20逻辑说明cmd里没有OSTYPE变量用nvidia-smi的errorlevel判GPU更可移植。%~dp0取得批处理所在目录cd /d切换过去避免在别的目录启动脚本时找不到dataset.yaml。如果机器上有显卡但驱动异常nvidia-smi无法正常输出版本信息脚本会走CPU分支这时先装驱动再跑GPU。训练启动后关注三件事第一个epoch的耗时、显存占用是否稳定、loss是否在下降。如果第一个epoch就极慢优先查workers和cache配置如果显存占用超过90%把batch降到8内存充足的话加cacheTrue把图片缓存进内存能明显缩短每个epoch的时间代价是额外吃几百MB到2G内存。5. 训练路上的五个常见坑现象、原因与解决办法这一章不写理论直接给结论。这五条是我在不同机器和不同数据集上真实翻车后留下来的记录按“现象、原因、解决”的格式写你在训练时遇到同款问题可以直接照着处理。5.1 验证集AP虚高部署却漏检数据划分泄漏现象训练结束val mAP50到了94%把模型放到真实现场检测效果却差得离谱漏掉三分之一的行人。原因数据集是按视频抽帧做的划分脚本用random.shuffle把同一条视频的相邻帧同时分进了train和val。模型在训练时“见过”验证帧的上下文val指标失去了评估意义。解决按场景来源划分而非逐图随机划分。拿不到场景标识时按文件名前缀分组或用每张图的拍摄时间信息聚类确保同一段连续画面的帧全部落在同一边。判断泄漏的关键特征train loss和val loss曲线几乎完全重合、不分离这在正常训练里很少见。5.2 GPU利用率只有 20%epoch 却跑得极慢现象nvidia-smi看着GPU利用率在10%到30%之间跳一个epoch要跑3分钟任务管理器里CPU和磁盘却是满的。原因batch太小喂不饱GPU或者CPU数据预处理跟不上。1000张图640分辨率如果batch2、workers1GPU每算完一步要干等很久。解决batch提到16或32workers提到CPU核数的一半再把cacheTrue加进训练命令把图片以uint8格式放在内存里数据读取时间能降一个数量级。注意加了cache后内存占用显著上升8G内存的机器和Mac要谨慎。5.3 pycocotools 报错或 mAP 直接变成 nan现象转好COCO后想用pycocotools评估程序直接报TypeError或者输出的mAP全是nan。原因COCO的bbox、area需要float类型很多简化的转换脚本用int序列化进JSONpycocotools做iou计算时对整型和浮点混用会直接翻车。解决转换脚本里在json.dump前强制floatarea保持w*h算完不取整。pycocotools在读取时不检查类型问题只在eval时暴露所以转换完先跑一次cocoEval验证别等到训练完才回头查格式。5.4 YOLO 标签里出现 1.03 这样的越界坐标现象训练日志里反复输出“WARNING 标签越界”模型最后输出的框偏小或位置整体偏移。原因XML的bndbox坐标本身越过了图片边界归一化后得到大于1的值。ultralytics对这类标签只警告不拒绝脏数据就这么混进了训练集。解决转换脚本里对xmin、ymin做max(0,·)对xmax、ymax做min(img_w,·)clip完宽高小于等于0就跳过该框再在训练前写一段循环扫描labels目录里所有值是否在0到1区间。我在3.3节的脚本里已内置了clip逻辑如果你用的是其他库转的标签务必补上这一步。5.5 模型微调崩了MPS 后端的玄学问题现象M系列芯片的Mac上第一次跑训练直接崩报错指向MPS backend或某个算子不支持重跑一次却又正常或者换个模型尺寸就没事。原因ultralytics对MPS的支持已经覆盖大部分算子但不同torch版本对MPS的实现细节有差异某些增强分支如Mosaic会走到尚未支持MPS的代码路径。同一份代码在不同环境里表现不同这类问题很难一次定位。解决把torch升级到目标平台最新版训练时固定batch8、不开cache减少内存压力如果仍然崩直接devicecpu跑1000张图在M系列Mac上用CPU训练100轮大概一到两小时完全在可接受范围。真正推理部署时再用MPS那个路径稳得多。6. 增量式迭代从 yolo11n 到 yolo11s 的调优路径与验证指标1000张图训完的模型通常只是基线把项目做成迭代闭环才是让它产生价值的关键先跑通再收集badcase再增量训练。这一章给出我固定使用的迭代路径。第一步是选对起点模型。1000张行人数据上yolo11n用8G显存就能跑yolo11s需要12G以上显存或更长的训练时间。第一次迭代用yolo11n跑通流程、验证数据有效性第二次再切yolo11s看收益。如果yolo11n和yolo11s的val mAP差距不超过两个点问题多半在数据而不是模型容量这时候换更大的模型没有意义。第二步是看对指标。我同时看val mAP50和mAP50-95前者反映检出率后者反映框的定位精度。只盯mAP50会漏掉一种情况模型框住了行人但框偏大或偏小这在安防系统里会导致跟踪算法跳变。更细的做法是把目标按像素高度分组统计召回率通常会看到2米以内和10米以外的行人召回率差20个百分点。第三步是增量训练。把部署环境中漏检的图片收集起来用上一章的转换脚本转成YOLO格式追加到dataset的images和labels目录。然后加载上一次的best.pt继续训练yolo detect train datadataset.yaml modelruns/pedestrian/weights/best.pt epochs50 batch16 device0逻辑说明加载best.pt做增量训练时epochs可以比从头训练短早停patience保持20即可。新增类别不要混进来加了类别要重写dataset.yaml的names和nc。增量训练的另一个坑是数据不均衡如果新加的漏检样本远多于旧样本模型会把旧场景的检测能力冲掉。常见做法是把新样本复制两到三份放进去让新旧样本比例接近1:1。第四步是验证闭环。每次增量训练后拿一批固定不变的历史验证图测同一组指标确保新版本没有把老场景做坏。我习惯在项目根目录留一个val_legacy目录每次训练完都跑一遍yolo predict modelruns/pedestrian/weights/best.pt sourceval_legacy imgsz640 conf0.3逻辑说明conf调成0.3比默认的0.25更接近安防场景的实际部署阈值。如果预测结果在老场景上召回下降明显回滚到上一个best.pt把新样本重新筛一遍再训。最后说一个我的固定流程小项目永远先跑yolo11n基线用v8的成熟模型和v11做对比哪个在验证指标和推理速度上都达标落地就用哪个不为了新架构强行换模型。这套从数据集验收、格式转换到增量训练的流程我在几个不同的行人项目里跑通过希望帮到你。本文还有配套的精品资源点击获取