简介:一套面向目标检测学习与安防场景应用的警察/民警识别数据集,包含357张带标注图片,支持以YOLO26框架训练出区分警察与非警察两类目标的检测模型。数据包共715个文件,以357个jpg原图、357个txt标注文件和1个yaml配置为主,压缩包体积约13.76MB,结构简洁,便于直接接入常用检测训练流程。每张图片均配有对应txt标注,标注内容与图片文件名一一对应,yaml文件定义了类别与路径参数,可减少数据预处理工作量。数据场景覆盖多种环境下的民警样本,适合用于智慧安防、人员识别等方向的算法验证与课程实践。目前已有25人学习下载,作者提供免费技术咨询,适合刚接触YOLO目标检测的初学者或需要扩充训练数据的研究者。
1. 带标注的警察识别数据集只有357张图:先弄清它能干什么
带标注的警察民警识别数据集是个能直接喂给目标检测训练流程的数据包:357张图片上已经画好边界框,标签只有两类,警察和非警察。没在小数据上摔过的人容易低估357这个数字——它恰好卡在“预训练权重不够用、从零训练又过拟合”的临界区间;而两类样本分布稍微偏一点,训练出的模型就只会认警察不认别的。这篇笔记从数据集格式讲起,到训练命令、参数取舍、5个常见翻车现场,最后落在“怎么验证一个357张小数据集训练出的模型能不能扛真实场景”。适合手里有识别类检测任务、想用少量标注数据快速出模型,又不希望被小数据集坑一把的工程师照着做。
2. 把警察/非警察数据集接进YOLO26:目录约定与标签核验
拿到数据集第一步不是直接跑训练,而是先搞清楚它长什么样、能不能被训练入口正确读进去。357张图配2个标签,听上去简单,但格式细节错了,训练时要么报错要么静默吃掉错误标签,最后得到一张看起来正常、实际全乱的损失曲线。先花20分钟把目录和标注内容核一遍,比事后重训划算得多。
2.1 YOLO26的输入长什么样:txt标签文件与归一化坐标
YOLO26的数据组织方式和YOLO系列一脉相承:一个图片目录、一个标签目录、一个描述类别与路径的yaml文件。图片和标签靠“同名不同后缀”对应,images/train/img_0001.jpg对应labels/train/img_0001.txt。一个txt文件里每一行是一个目标,格式是五个数字:类别编号、归一化中心点x、中心点y、归一化框宽、归一化框高。
0 0.451 0.332 0.264 0.581 0 0.688 0.593 0.211 0.437 1 0.832 0.637 0.170 0.322第一行的含义是:类别0(警察)的边界框中心位于图片宽度的45.1%、高度的33.2%,框宽占整张图宽度的26.4%,框高占整张图高度的58.1%。这里必须强调“归一化”:YOLO系格式不存像素坐标,全部换算成0到1之间的小数,图片分辨率变了也不用重标。第三行开头的1代表类别1(非警察)。所谓“非警察”不是背景,而是标注者认为“像警察但不是”的负样本目标,比如保安、身形相似的便衣路人。
有了这层格式理解,就能写出对应的data.yaml。放在数据集根目录下,内容如下:
path: /work/datasets/police_dataset train: images/train val: images/val nc: 2 names: 0: police 1: non_police关键参数有三个。path是数据集绝对路径,你的数据集解压在哪就写哪;train和val是相对path的图片目录,YOLO26读取这两个目录后自动去同级labels目录找同名txt;nc写2,对应两个类别。names的顺序必须和txt里类别编号一致,0对应警察,1对应非警察。如果你把names里0和1的顺序写反,训练依然能跑通,但验证集上的AP曲线会错位,警察的分数算到非警察头上。
有条件的话,用cvat或labelimg这类标注工具打开数据集原档看一眼,确认框是“目标检测框”而不是“分类标签”。有些从分类数据集转过来的数据,会给整张图打一个大框,这种框在目标检测里没有意义,训练出来的模型永远只能给出全图级别的判断,定位能力为零。
2.2 训练前必做的标注体检:数量、类别、漏标与格式错误
数据标注质量在小数据集上的影响被放大得很明显。357张图本来就少,如果有20张漏标、30张坐标越界,模型的可学信息直接缩水十分之一。我习惯在第一次训练前跑一个脚本,把三个东西一次性查完:每类目标数量、空标签文件、坐标越界与格式错误的行。
from collections import Counter from pathlib import Path labels_dir = Path("police_dataset/labels/train") cls_counter = Counter() empty_txt = [] bad_txt = [] for txt in sorted(labels_dir.glob("*.txt")): lines = [line.strip() for line in txt.read_text().splitlines() if line.strip()] if not lines: empty_txt.append(txt.name) continue for line in lines: parts = line.split() if len(parts) != 5: bad_txt.append((txt.name, "字段数不为5")) continue try: cls = int(parts[0]) x, y, w, h = map(float, parts[1:]) except ValueError: bad_txt.append((txt.name, "数字解析失败")) continue if not (0 <= cls < 2): bad_txt.append((txt.name, f"类别编号越界: {cls}")) if not (0.0 <= x <= 1.0 and 0.0 <= y <= 1.0): bad_txt.append((txt.name, "中心点坐标越界")) if w <= 0 or h <= 0 or w > 1.0 or h > 1.0: bad_txt.append((txt.name, "宽高异常")) cls_counter[cls] += 1 print("类别数量统计:", sorted(cls_counter.items())) print("空标签文件:", len(empty_txt), empty_txt[:5]) print("异常标签行所在文件:", len(bad_txt), bad_txt[:10])这段脚本做的事情很直接:读入每个txt的每一行,先检查字段个数和数字解析,再检查类别编号是否落在0和1之间,坐标与宽高是否在合理范围内。统计结果里最需要警惕的是空标签文件——它通常意味着某张图被漏标了,而训练时YOLO26会把这张图当作纯背景处理。漏标多了,模型会把警察目标在训练时强行当作负样本压下去,最后表现为验证集上警察类的召回率极低。
数量统计拿到之后,还要看两类目标的比例。如果警察类有600个框,非警察类只有80个,这个比例决定了后续要不要做类别均衡处理。先记下这个数,第3章调参时会用到。
坐标体检只能发现“格式错误”,发现不了“框错位置”。我一般还会挑出5到10张图,把标签画回原图,用肉眼核对框是否落在人身上、有没有只框半个身体的情况:
import cv2 from pathlib import Path img_dir = Path("police_dataset/images/train") label_dir = Path("police_dataset/labels/train") for txt_path in sorted(label_dir.glob("*.txt"))[:5]: img = cv2.imread(str(img_dir / (txt_path.stem + ".jpg"))) if img is None: print("缺图:", txt_path.name) continue h, w = img.shape[:2] for line in txt_path.read_text().splitlines(): cls, cx, cy, bw, bh = line.split() cx, cy, bw, bh = map(float, (cx, cy, bw, bh)) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = (0, 0, 255) if cls == "0" else (255, 0, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite("check_" + txt_path.stem + ".jpg", img)输出的check_*.jpg上,红色框是警察,蓝色框是非警察。重点看三类情况:框是不是把整个人包进去了、有没有把路牌或树干框进去、同一张图上该标的人是否全部标了。这种可视化检查在357张的小数据集上特别值钱,因为一张错框图对损失函数的污染远大于大样本场景。
3. 用YOLO26训出能用的警察检测模型:4个命令与3个关键参数
格式和标注都体检过了,才轮到训练。小数据集的训练节奏和大数据集不一样:大数据集可以靠堆epochs慢慢收敛,小数据集必须在欠拟合和过拟合之间找到平衡点。参数设得不对,要么百来轮还在欠拟合,要么中途mAP跳水你还没察觉。
3.1 最小可用训练命令与训练日志解读
假设环境里已经装好了PyTorch和对应CUDA版本,训练入口命令是这种形式:
pip install -U ultralytics yolo train data=police_dataset/data.yaml model=yolo26n.pt epochs=200 imgsz=640 batch=16 patience=40 seed=42我建议先跑这样一份“最朴素”配置,之后再按显存和数据分布去调。data=police_dataset/data.yaml指向第2章的配置文件;model=yolo26n.pt使用预训练权重作为初始值,而不是从随机初始化开始。357张图对从零训练来说太少,必须迁移预训练特征。epochs=200是训练轮数上限,patience=40表示连续40轮验证指标没有提升就提前停。这两个参数是配合使用的:200轮是天花板,实际多数小数据集在80到150轮之间就触发早停了。
训练起来之后,别只盯着终端刷屏,要看两类信号。一类是box_loss和cls_loss,它们应该在开始几十轮快速下降,之后趋于平缓;另一类是每个epoch末尾打印的mAP50与mAP50-95,它们比loss更直观。经常遇到的情况是loss还在稳步下降,但mAP已经不动了,这说明模型在拟合数据中的噪声而不是学习通用特征,早停机制会在这里兜底。
训练结束后,runs/detect/trainN/weights/目录下会生成两个权重文件。best.pt是验证集上分数最高的权重,last.pt是最后一步的权重。小数据集上我永远只用best.pt,原因稍后在第4章展开。
3.2 三个必调参数:epochs、batch、imgsz的取舍
针对357张的规模,参数不要按默认值硬扛。默认套路是给大数据集设计的,直接用会出问题。我整理了一份针对这个小数据集的起点参数表,第一次训练就照着这个范围来:
| 参数 | 建议起点 | 说明 |
|---|---|---|
| epochs | 200到300 | 配合patience使用,跑不满自动早停 |
| patience | 40 | 连续不提升40轮就停,防止后段过拟合 |
| imgsz | 640 | 输入分辨率;目标太小可试832,但显存开销增加 |
| batch | 16 | 显存不足就降到8,低于4则训练极不稳定 |
| seed | 42 | 固定随机种子,换机器复现的关键 |
小数据集最容易踩的参数坑是epochs设太大。有人习惯把职业习惯带过来,看到357张图直接填epochs=500,心想反正meta没限制。如果不开早停,模型到250轮后几乎必然开始记训练集的噪声,验证mAP掉头向下。而把patience设到40,大部分情况下200轮上限都跑不满,模型会在150轮前后自己停下来。seed参数常被忽略,但它直接决定了数据增强和初始权重扰动。不固定seed,同一个数据集、同一个命令,两次训练的mAP能差出3到5个百分点。357张图数据量小,这个波动会被放大,所以必须在命令里固定种子。
batch的选择则要看显存。16是个常见起点,因为常用的消费级显卡跑640分辨率、16batch勉强能放得下。如果显存只有8G,启动时报OOM,就按下面这个降配组合跑:
nvidia-smi --query-gpu=memory.free --format=csv yolo train data=police_dataset/data.yaml model=yolo26n.pt epochs=300 imgsz=640 batch=8 half=True seed=42 workers=4half=True让模型用半精度计算,显存占用几乎减半,代价是训练结果可能有微小波动,在检测任务上通常可以忽略。workers=4控制数据加载线程数,Windows上设成0能避开一些进程抢占问题,Linux下保持4到8即可。交错的坑在workers:设太大时,如果磁盘本身是机械硬盘,数据加载会成为瓶颈,反而拖慢训练。
3.3 验证与导出:用val跑出两类指标再选择导出格式
训练完不能直接拿去部署,先跑一次验证,看每个类别单独的AP:
yolo val data=police_dataset/data.yaml model=runs/detect/train/weights/best.pt split=val这条命令会在best.pt上做推理并和标注对比,输出每个类别的precision、recall和AP。对小数据集,我更关注单类别的recall而不是总mAP。比如警察类recall要的是90%以上,非警察类如果只有60%,说明负样本被大量误判成警察,按下文第4章的思路去调。验证目录下会生成混淆矩阵图和PR曲线,建议都打开看一眼。混淆矩阵可以看出警察类被误判成了哪些类别。
确认指标能接受后,再决定导出格式。常见做法是导成ONNX,方便后续在推理框架里跑:
yolo export model=runs/detect/train/weights/best.pt format=onnx opset=12 imgsz=640导出后的best.onnx不携带类别名,推理端拿到的输出只有两个类别的置信度分数。部署时要把0和1映射回警察和非警察,这个映射表要单独保存在推理代码里。有些项目图省事,直接在导出后的模型里猜类别名,结果上线后输出全反了,这属于部署环节最不值当的翻车。ONNX导出后可以顺手用onnxruntime跑一次单张图,确认输出张量尺寸是1x2xN的格式,N是建议框数量,再进入后续部署流程。
4. 警察识别数据集训练避坑:357张小数据集的5种翻车现场
这部分是血泪经验汇总。小数据集训练出的问题症状高度趋同,但原因各不相同。我把实际工作中最常见的5个现象、原因和解决办法列出来,每条都对得上357张这个量级的警察识别任务。
4.1 现象:Loss正常下降但验证mAP一直上不去
训练过程中box_loss和cls_loss都很正常地下降,看着就要收敛了,但验证集上的mAP50始终在0.3上下徘徊。排除了学习率问题后,最可疑的是train/val划分出了事。如果这357张图里有从视频里连续抽帧的片段,前几帧和第几帧内容高度相似。随机划分时,往往同一段画面一半进了训练集、一半进了验证集。模型把训练集里那几帧的纹理记住了,验证集里相似画面本该得分很高,但如果相似画面分配不均匀,验证分数就忽高忽低。
解决方法是按场景划分,而不是按文件随机划分。先根据文件名前缀或拍摄时间把数据分组,保证同一组画面只出现在训练集或只出现在验证集。357张图本来就少,划分前先看一眼有没有连续帧的痕迹,这是我拿到任何小数据集的第一反应。
4.2 现象:警察类AP很高,非警察类AP低得没法看
验证结果显示警察类AP达到85%,非警察类只有40%。打开标签统计一看,警察类有600个框,非警察类只有不到100个。这就是类别严重不平衡。YOLO26默认的损失函数里,多数类对梯度更新的贡献更大,少类学不够。非警察类本身还要覆盖保安、骑行路人、深色衣服行人等多种形态,类内差异大,样本又少,AP自然上不去。
解决思路有两个分支。第一种是给少类做扩充:水平翻转、随机亮度抖动、随机裁剪后,把新增的增强样本同时复制到对应的txt标签文件里。第二种更省事:把类别分布差异过大的情况反馈给数据生产环节,补充一批非警察样本,让400个框以上。如果短期内没有数据补充,可以将两类权重调成0.6, 0.4,让少类样本对损失的贡献权重提高,但这种方法治标不治本,数据均衡才是正路。
4.3 现象:模型把穿深色制服的背影误检成警察
这是警察识别场景特有的误报。357张图里如果警察样本大多是正面或侧面角度,模型容易把深蓝色保安制服、深色棉衣的背影学成“警察特征”。表现在推理阶段就是正面识别得不错,一到背影或侧脸就会误报。原因不是模型坏了,而是标注样本的视角多样性不足以覆盖真实场景的拍摄角度。
解决方法是两条腿走路。训练阶段强化数据增强,水平翻转、旋转10度以内、亮度对比度扰动,能让模型对视角变化更钝感;数据层面,建议把容易混淆的深色制服非警察单独多标几个框,让模型见过足够的“非正面警察”负样本。357张图规模下,对混淆样本的记忆还没被大量数据稀释,多标负样本是最有效的办法。
4.4 现象:训练刚跑几个step就报OOM,进程退出
训练命令看着没问题,数据检查和yaml也正常,但第一步还没跑完就OOM。通常是batch和imgsz组合超出了显存容量。默认batch若设成auto,一些版本会根据显存自动选择,结果在显存小的卡上仍然选得过于乐观。另外一种情况是开了half=True但仍然不够,或者imgsz从640提到832之后显存翻倍上涨。
解决顺序是固定往下调:先把batch降到8,若还OOM就把imgsz降到416,两者都降后显存占用会缩到原来的三分之一以内。若在4G显存的环境里,batch=4、imgsz=416、half=True是能跑通的底线组合。357张图虽然训练量小,但推理端如果也要用低显存设备,模型输入尺寸固定为640时就别勉强开batch=16训练,训练端的batch不会影响推理端性能。
4.5 现象:换一台机器重训,指标和之前对不上
同一个人、同一个数据集、同样的命令,换到另一台显卡机器上重新训练,验证mAP差了5个百分点。如果没固定随机种子,数据增强的随机性每次都不一样,结果对不上是正常的。另一个隐蔽原因是预训练权重版本不一致,前一次用的是yolo26n某个版本权重,后一次跑的目录里可能混了更新过的权重文件。还有一个更隐蔽的:data.yaml里path写的是绝对路径,换机器后路径变了,但文件内容没随之更新,yaml自动改为相对引用,可能导致训练验证数据划分变化。
解决方法是把训练参数固化下来。命令行里固定seed,权重文件复制到项目目录后用相对路径引用,训练结束把args.yaml备份一份。做到这三步,换机器重训即使不至于完全一致,指标差距也能控制在可接受范围内。
5. 357张图的模型怎么才叫“能上线”:K则交叉验证与压力测试
小数据集的一次验证分不说明问题,可能只是运气好。我现在的习惯是,任何少于500张图的数据集,在宣布模型可用之前先过三关:交叉验证测稳定性、真实场景截图做压力测试、训练配置做快照留底。
5.1 用KFold交叉验证测数据集的真实水平
把357张图切成5份,每次拿4份训练、1份验证,轮着来一遍,取平均mAP,这就是5折交叉验证。它能看出模型到底是真的学到了通用特征,还是某个fold运气好:
from sklearn.model_selection import KFold from pathlib import Path images = sorted(Path("police_dataset/images").glob("*.jpg")) kf = KFold(n_splits=5, shuffle=True, random_state=42) for fold, (train_idx, val_idx) in enumerate(kf.split(images)): fold_dir = Path(f"fold_{fold}") fold_dir.mkdir(exist_ok=True) (fold_dir / "train.txt").write_text("\n".join( f"/abs/path/police_dataset/images/{images[i].name}" for i in train_idx)) (fold_dir / "val.txt").write_text("\n".join( f"/abs/path/police_dataset/images/{images[i].name}" for i in val_idx))YOLO26的data.yaml里,train和val除了写目录,也支持写一个txt路径列表文件。脚本生成的train.txt和val.txt就是这个用途。每折生成一个data_yaml,train指向对应fold的train.txt,val指向val.txt,然后训练5次。如果5折结果mAP50在0.5到0.7之间波动,说明模型对数据分布敏感,需要回看标注质量;如果数值接近,说明模型总体稳定。357张图做5折时每折验证只有七八十张,分数波动大也正常,但方向性异常一眼就能看出来。
5.2 对着夜视截图和远距离截图做压力测试
交叉验证通过之后,不要直接拿模型上线。从真实场景里挑一些训练集没覆盖的画面:夜间灯光下的执勤画面、远处的人群、背影、雨雾天。对这些画面跑推理,看两类问题:漏检警察、误检路人。357张训练数据不可能覆盖所有真实光照,因此这部分测试的结果是决定补拍数据还是调低置信度阈值的依据。如果夜间误报太多,一个务实做法是把置信度阈值从0.25提到0.35,误报会明显下降,代价是少量低分警察目标被滤掉。这个阈值取舍要针对实际场景压测后定,不是我拍脑袋帮你定。
5.3 训练配置快照备份
训练结束把配套文件留底,这是我吃过亏之后养成的习惯。放到项目目录下的固定文件夹里,命名带上日期或版本:
mkdir -p model_archive_v001 cp -r runs/detect/train/weights model_archive_v001/ cp police_dataset/data.yaml model_archive_v001/ cp runs/detect/train/args.yaml model_archive_v001/args.yaml是训练入口自动记录的完整参数,包含batch、imgsz、epochs、seed以及数据增强开关。日后模型在线上表现异常要回溯时,这份快照能告诉你当时到底怎么训的;换人接手也看得懂。没有这份记录,后悔药都找不到。
我这套流程走完,357张图训练出的警察识别模型通常能稳定在mAP50约0.7上下,够扛住场景相对固定的内部演示或受控测试。如果把模型直接撒到开放街道场景,必须补充数据重新评估。希望你按这套步骤前先补两条自己的验证数据:一条是训练集里警察类和非警察类的真实框数比值,另一条是你实际部署场景的视频截图。有了这两条,后面所有参数调整都有依据,希望帮到你。
本文还有配套的精品资源,点击获取