
简介面向目标检测开发者的警察/民警识别数据集支持YOLO26格式包含“警察”和“非警察”两类标注可应用于安防监控、智慧城市、重点区域人员识别等场景。资源共715个文件包括357张jpg原始图像、357个txt标注文件以及1个yaml配置文件压缩包大小13.76MB。其中txt文件采用YOLO标准格式记录目标边界框坐标与类别IDyaml文件用于定义数据集的类别及基础配置数据与标签一一对应可直接输入YOLO系列模型进行训练。数据集内图像与标注一一对应可直接用于模型训练和效果验证对于需要快速构建自定义目标检测任务、避免从零标注数据的开发者和研究人员而言这份资源可显著缩短数据准备周期。目前已有25人浏览学习适合用作课题实验、算法竞赛及安防项目的初始数据集。1. 带标注的警察民警识别数据集357张图到底能做什么事先说我看到这个标题时的第一反应357张图、两种标签、警察和非警察这摆明了不是要做一个通用大模型而是一个垂直场景里的快速响应式模型。做安防、做执法记录仪、做重点区域布控的人手里经常会有这类需求——人能看出谁是民警但摄像头每帧都要人去盯就失去了意义。把「警察/非警察」识别做成一个离线可跑的检测模型在这类定点监控和闸机核验场景里比纯人脸方案更鲁棒因为不依赖正脸、不受口罩影响判断依据是制服和装备特征。这套数据集的真实价值在于它给了你一个极小的起点357张图做原型验证绰绰有余难的是把它训到能过内部验收、敢往现场推。我的建议很直接拿它跑通完整的「标注→转换→训练→评估→小规模实测」链路这个投入收益比极高但别急着上生产小数据集要补的是针对性采集和难例循环。2. 数据集构成和标签规范357张图里藏着哪些容易误判的细节2.1 警察/非警察二分类在目标检测里的定位这个任务在检测模型里属于「场景约束较强的小类别目标识别」。它和人脸检测、车辆检测这类通用任务有一个关键区别判断依据不是目标本身的几何形态而是目标身上的一组属性特征——制服颜色、反光背心、警用装备、甚至佩戴的执法记录仪。这意味着模型学习的是一个「属性组合」而不是「物体形状」。说人话就是模型对单张图的纹理敏感性极高对光照、拍摄角度、目标姿态非常挑剔。同样的一个人穿常服时模型可能判断为「非警察」穿警服执勤时判断为「警察」这是完全合理的——因为训练标签本身就定义成了「以视觉特征为准」。这带出一个对后续训练影响很大的结论你不能用通用目标检测的思维方式去期待这个模型。它不会「认出」警察这个职业它只会认出「看起来像执勤民警的视觉组合」。这一点决定了数据标注阶段的所有规则。如果标注者把穿着便衣、但处于执法场景的警察标成「警察」模型会学到一批互相矛盾的纹理特征推理时必然翻车。2.2 357张图的目录结构、图片规格和标注承载拿到这套数据后第一步不是急着训练而是盘清楚目录和标注格式。常见做法是这类带标注的数据集会按以下结构组织police_dataset/ ├── images/ │ ├── train/ # 约250张 │ └── val/ # 约100张也可能混在一个目录里自己划分 ├── labels/ │ ├── train/ │ └── val/ ├── classes.txt # 类别定义 └── dataset.yaml # 供YOLO训练脚本读取的配置标注格式通常有两种承载Pascal VOC的XML或者YOLO的txt。区别在于YOLO格式存的是归一化的中心点坐标和宽高XML存的是像素级左上角/右下角坐标。如果拿到的是XML必须先转一次格式再进训练流程。图片规格方面357张这个量级拍摄来源很杂——可能有监控截图、手机拍摄、新闻照片尺寸差异会非常大。我建议先跑一个统一脚本把所有图的分辨率、通道数、EXIF信息列出来重点看有没有灰度图、有没有PNG带Alpha通道的图。YOLO训练管线内部会做resize但灰度图和格式不统一的图会导致数据加载器报错这类问题在几百张图的小数据集上尤其烦人因为报错信息会掩盖在训练日志里。# 检查数据集完整性的脚本片段 import cv2 import glob from collections import Counter imgs glob.glob(police_dataset/images/*/*.jpg) glob.glob(police_dataset/images/*/*.png) size_counter Counter() bad_files [] for img_path in imgs: img cv2.imread(img_path) if img is None: bad_files.append(img_path) continue h, w img.shape[:2] size_counter[(w, h)] 1 print(异常文件, bad_files) print(常见尺寸分布, size_counter.most_common(10))这段代码的逻辑很简单遍历所有图片读取失败的文件直接计入黑名单然后把所有图的尺寸做成分布统计。参数和逻辑上值得注意的点是cv2.imread读到None说明文件损坏或者不是合法图片编码这类文件必须清理尺寸分布的价值在于判断原图是否来自同一批采集设备——如果出现五六个完全不同的宽高比说明数据来源混杂训练时要认真做随机裁剪增强否则模型会过拟合到特定构图。2.3 标签检查越小的数据集越要在转换前把标注「洗干净」小数据集的训练效果60%取决于标注质量这是玄学之外的真实比例。357张图、每张1到3个目标总数可能不足800个标注框。这个量级下任何一个坏框都会对模型产生肉眼可见的影响——它不像大规模数据集里能被其他样本「稀释」掉。首先要做的是「统计标注偏置」。我一般会把所有标注框的宽高比拉出来看一下这会直接暴露标注人员的习惯如果一个标注员习惯把警察的整个身体连同周围半米都框进去另一个只框躯干模型学到的锚框匹配差异就会很大。YOLO系列的anchor匹配对宽高比变化有一定容忍度但800个框里20%是异常比例框的话训练时的正样本质量会跟着崩。我把检查逻辑整理成一个标准流程解析所有标注文件统计每个类别的框数量看类别是否失衡。警察框只有100个、非警察框却有600个这种分布要用类别权重或者focal loss相关配置去调。检查框是否越界。YOLO格式的坐标是归一化的框的x、y、w、h理论上都在0到1之间但有些标注工具导出时会把坐标算成负数或者超过1这类box在训练时会被自动裁剪或忽略表现为「莫名其妙少了一部分目标」。检查空标注文件。「图片存在但标签文件里没有任何框」的情况经常发生导致模型把整张图当背景训练这对小数据集是真实的伤害。# 标签文件质量检查脚本 import os label_dir police_dataset/labels/train class_counts {police: 0, civilian: 0} empty_labels [] out_of_bound [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue lines open(os.path.join(label_dir, f)).read().strip().splitlines() if not lines: empty_labels.append(f) continue for line in lines: parts line.split() cls int(parts[0]) x, y, w, h map(float, parts[1:]) if x 0 or y 0 or x w 1 or y h 1: out_of_bound.append(f) if cls 0: class_counts[police] 1 else: class_counts[civilian] 1 print(空标注文件, empty_labels) print(越界标注文件, out_of_bound) print(类别分布, class_counts)这个脚本在进入训练之前必须跑而且要看具体数值而不只是有无异常。举个例子越界标注可能是标注工具精度问题导致的xw1.0001这类对训练影响不大但如果是x0.5、w0.8这种说明标注框中心点算错了对应的目标框有一大半在图片外模型学的就是一个残缺特征。怎么判断打印出具体的越界数值微小的浮点误差可以手工忽略结构性的越界必须修正。3. 把标注数据落到YOLO的训练格式转换脚本与数据集划分3.1 标签格式转换VOC XML转YOLO txt的完整脚本刚才提到这套数据集的标注可能是XML格式。YOLO系列训练统一吃txt格式每行一个框格式是「class_id x_center y_center width height」全部归一化到0-1区间。如果你拿到的数据已经内置了YOLO格式的labels目录这一步可以直接跳过否则需要自己写转换。这里我要说一个实际场景数据标注团队交付的时候很多只交付XML或JSON不会自动帮你转好YOLO格式。用labelimg这类工具标注出来的就是XML用labelme标注出来的是JSON。JSON转XML再转YOLO会丢失坐标精度吗不会因为标注框本质上是矩形的四个坐标点转来转去只是表现形式不同但转换过程中的坐标换算公式必须写对特别是归一化的分母是原图宽高。# VOC XML 转为 YOLO txt 格式 import xml.etree.ElementTree as ET import os def xml_to_yolo(xml_file, out_dir, class_map): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) yolo_lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue cls_id class_map[name] bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) x_center ((x1 x2) / 2) / img_w y_center ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_name os.path.basename(xml_file).replace(.xml, .txt) with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(yolo_lines)) class_map {police: 0, civilian: 1} # 与数据集classes.txt保持一致 xml_dir annotations_xml out_dir labels_yolo os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if f.endswith(.xml): xml_to_yolo(os.path.join(xml_dir, f), out_dir, class_map)这段脚本有几个参数需要按实际数据调整。class_map里的类别编号必须和后续dataset.yaml里的类别顺序严格一致如果顺序错了模型训练出来就是「警察被预测成非警察非警察被预测成警察」这种灾难性映射。img_w和img_h是从XML里的size字段读的不是从实际图片读的如果标注工具和原始图片之间有任何一端做过resize这里就存在偏差隐患——所以我在转换后通常会抽查10个框把归一化坐标还原成像素坐标在图上画框人工对比确认位置没偏。3.2 训练集与验证集划分357张图不能简单按9比1YOLO官方的训练习惯是train/val按91:9或9:1划分这是建立在数据集规模上千的基础上的。357张图如果按9比1划验证集只有35张、总共可能70个目标算出来的mAP波动会非常大——模型这轮跑了0.85下一轮同样的数据换一个随机种子变成0.72你的「改进」到底是有效还是随机根本无法判断。我给小数据集的建议是val集合控制在40到60张train集合剩余。验证集太小指标没有区分度验证集太大训练数据进一步萎缩355张里抽掉100张成本很高。40-60张的验证集对357这个量级是性价比平衡点。# 按目标数量约束划分训练/验证集 import os import random import shutil from collections import defaultdict random.seed(42) images_dir police_dataset/images/all labels_dir police_dataset/labels/all train_img_dir police_dataset/images/train val_img_dir police_dataset/images/val train_lbl_dir police_dataset/labels/train val_lbl_dir police_dataset/labels/val for d in [train_img_dir, val_img_dir, train_lbl_dir, val_lbl_dir]: os.makedirs(d, exist_okTrue) # 统计每张图片的目标数量 img_objects defaultdict(int) for f in os.listdir(labels_dir): if not f.endswith(.txt): continue lines open(os.path.join(labels_dir, f)).read().strip().splitlines() img_objects[f.replace(.txt, .jpg)] len(lines) # 目标多的图优先划入训练集 sorted_imgs sorted(img_objects.keys(), keylambda x: img_objects[x], reverseTrue) random.shuffle(sorted_imgs) # 打乱顺序但不改变相对分布 val_imgs set() val_target_count 80 # 期望验证集里累计约80个目标 cur_count 0 for img in sorted_imgs: if cur_count val_target_count: break val_imgs.add(img) cur_count img_objects[img] for img in sorted_imgs: img_src os.path.join(images_dir, img) lbl_src os.path.join(labels_dir, img.replace(.jpg, .txt)) if img in val_imgs: shutil.copy(img_src, os.path.join(val_img_dir, img)) shutil.copy(lbl_src, os.path.join(val_lbl_dir, img.replace(.jpg, .txt))) else: shutil.copy(img_src, os.path.join(train_img_dir, img)) shutil.copy(lbl_src, os.path.join(train_lbl_dir, img.replace(.jpg, .txt)))这段代码的思路是「按目标数而非图片数划分」。357张图里有的图只有1个目标有的图有5个以上目标如果纯随机划分可能验证集拿到30张图却只含60个目标、训练集拿到327张图但目标密度更高最终指标失衡。麻烦点在于统计目标数需要先读标签文件然后按目标数降序排列后从目标多的往目标少的做「配额制」取样。这里的val_target_count80是可以按需调整的如果你的验证集需要保持类别平衡警察、非警察各40个框可以进一步细化为按类别配额取框。3.3 dataset.yaml 的写法二分类任务的关键配置项YOLO系列训练必须要有一个数据集配置文件指向图片目录和类别名。这个文件写得不对后面的训练命令再标准也白搭。# police_dataset.yaml path: /home/user/police_dataset # 数据集根目录 train: images/train val: images/val nc: 2 names: 0: police 1: civilian重点说明几个容易出错的参数path最好用绝对路径相对路径在部分YOLO版本里解析会出问题报错还比较隐蔽只提示「No labels found」让你以为是标签格式错了实际是目录没找到。nc: 2是类别数必须和标签文件里出现过的class_id最大值1相等。names里的顺序就是标签文件里数字编号的顺序这个错位过一次就会在输出结果里看到「predicted: civilian」但画出来的框明明在警察身上的诡异情况其实是names配错了不是模型学错了。4. 训练参数配置与命令357张小样本如何防止过拟合4.1 从头训练还是迁移学习这题只能选迁移357张图无论新YOLO版本的网络能力多强从头训练都注定是灾难。目标检测的Backbone比如CSPDarknet系列里有大量卷积层这几百万个参数在没有预训练的情况下学不完——你的训练轮次可能只跑了50轮就开始过拟合但val精度一直上不去因为Backbone还没有建立基本的纹理表征能力。所以顺序应该是先下载预训练权重COCO上的80类7.7万张训练图然后冻结Backbone层只训练检测头等检测头的损失曲线开始收敛了再解冻Backbone用很小的学习率做全量微调。有人把这条路戏称为「后悔药流程」因为你有一次机会可以在过拟合之前回退到冻结阶段的权重重新调节参数。4.2 训练命令与关键超参epoch、batch、imgsz的取舍# 冻结Backbone阶段的训练命令 yolo detect train \ datapolice_dataset.yaml \ modelyolo12n.pt \ epochs80 \ batch8 \ imgsz640 \ freeze10 \ lr00.005 \ optimizerAdamW \ patience15 \ projectruns/police \ namestage1_frozen这个命令的freeze10指的是冻结模型前10层对YOLO系列而言基本覆盖了Backbone的大部分卷积块。batch8在357张图的数据集上已经是合理值显存不够就往下调到4或者2不要用更大的batch小数据集里大batch会导致BN层的统计量不稳定。patience15表示连续15轮验证集指标不提升就早停对小数据集是有保护作用的——防止你出门吃饭回来模型已经过拟合到MaP又掉下来了。参数上容易踩坑的是imgsz。很多人为了保留小目标信息把imgsz拉到1024甚至1280这在357张图上是有代价的图片越大模型注意力越容易专注在背景细节上而你的背景样本只有357张图的量过拟合会更快到来。我建议640起步如果验证集mAP确实低再尝试一次960做个对比实验前后各花一晚上训练时间不值得再高。在解冻微调阶段训练命令调整为# 微调阶段解冻Backbone小学习率全量微调 yolo detect train \ datapolice_dataset.yaml \ modelruns/police/stage1_frozen/weights/last.pt \ epochs40 \ batch4 \ imgsz640 \ lr00.0005 \ freeze0 \ optimizerAdamW \ patience10 \ projectruns/police \ namestage2_finetune微调阶段的lr0从0.005降10倍到0.0005这是为了不破坏已经学到的Backbone特征。batch从8降到4因为全量微调时BN统计量对所有层都要更新小batch下梯度噪声更大反而能在这类小数据集上起到正则化作用。这个两阶段流程的核心逻辑是先让检测头在Ground Truth上学会「哪里有人、这个人的框多大」再让整个网络在很低的梯度扰动下适应「穿制服的到底是哪个类」。如果直接用单阶段训练检测头和Backbone互相拉扯357张图根本撑不到收敛。4.3 数据增强参数的调节小数据集的「伪造样本」YOLO系列训练默认打开mosaic增强把4张图拼成一张喂进去。这个增强对通用数据集是福音但对「警察/非警察」这个任务有个副作用4张图拼接后图中的警察可能被裁掉一半、或者和非警察在边界处重叠导致标注框内容混乱。关键是这样引起的漏检你很难归因到数据增强因为训练日志里loss是正常的。我一般会在YOLO的配置文件里做针对性调整把mosaic的比例降下来同时提高平移和旋转增强# augment.yaml 训练增强参数调整 mosaic: 0.3 # 默认1.0降到0.3 mixup: 0.1 translate: 0.2 # 轻微平移增强 scale: 0.3 fliplr: 0.5 # 左右翻转 hsv_h: 0.01 # 色调微调制服颜色不应当被大幅改变 hsv_s: 0.3这个配置的核心逻辑是警察识别依赖制服颜色和纹理hsv_h色调扰动必须压到0.01否则模型会把「蓝色」和「黑色」混为一谈而平移增强开0.2可以有效缓解小数据集里目标总是居中的偏置——因为采集照片时拍摄者习惯把人物放在画面中心。mosaic: 0.3的意思是30%的batch用拼接图剩下70%是完整原图保证模型能学到完整的目标形态而不是经常看到拼接缝。5. 实战避坑357张警察数据集训练中最常见的5个问题5.1 训练loss正常下降但验证mAP几乎为0标签和图片没有对上现象loss从3.5降到0.8看起来一切正常但验证集每一张图的预测结果全部是背景mAP在0.01附近打转。原因图片文件名和标签文件名对不上。常见的是标注工具导出的图片是IMG_001.JPG标签文件转换时被统一改成了小写img_001.jpg在Linux服务器上这两个文件指向同一张图吗指向的因为大多数代码用glob去匹配时大小写不敏感但在YOLO的数据加载器里它是用图片路径去替换后缀找标签文件大小写不一致直接导致标签加载不到模型拿到的全是「背景图」。解决用脚本遍历labels目录和images目录把两边文件名做严格一致性校验。习惯做法是全部统一为小写、统一后缀。这个坑在Windows上开发、Linux上训练的工作流里尤其常见本地跑通、上服务器翻车。5.2 训练到第60轮mAP突然掉到0.6以下BN统计量崩溃现象前50轮val mAP稳定在0.82第55轮之后开始掉掉到0.6以下再训不回去。原因小数据集里batch size很小BN层在每轮更新时统计的均值方差波动很大加上最后一个epoch里patience不高、模型还在持续更新BN统计量被少数几条难样本带偏。这和热词里经常提到的「yolo训练中bn崩溃」是同一个问题。解决训练结束后不直接用最后的权重而是用训练过程中在验证集上表现最好的那一轮权重重新跑一次「测试模式」推理。YOLO命令行里可以指定modelruns/police/stage2_finetune/weights/best.pt它会用eval模式和固定BN参数推理指标会回升一部分。如果还不行训练完用全部train数据做一次短迭代重估BN统计量这个操作在Ultralytics框架里可以通过训练时带上--eval-final相关参数控制。5.3 验证集指标高但实测视频里疯狂漏检小目标密集场景失效现象mAP50有0.84验证集抽查也都框得准但真实监控画面里一米开外的警察目标直接漏掉。原因357张训练图里目标占比基本都在10%以上近景大头照模型没见过「远景小目标」这种尺度的特征表达。mAP50这个指标本身就比较宽容它允许预测框和真实框有50%的IOU就算命中而真实场景的mAP50高并不能说明检测框足够精准。解决有两种路径一是给训练集做尺度增强——把缩放到一半尺寸的图也混进训练集让模型在训练阶段见过小尺度目标二是降低推理置信度阈值从0.25降到0.15如果随之而来的误检率不高说明模型对远景目标只是不自信而不是完全没识别。这个选择的判断依据是看置信度分布大量漏检目标的置信度集中在0.2-0.35区间说明阈值卡太严如果集中在0.05以下说明模型根本没学会到这个特征只能补数据。5.4 混淆矩阵输出总和对不上val标注文件里存在不合规数据现象打印混淆矩阵时横纵坐标的数值加总后不等于验证集真实目标数差了几个。原因验证集标签文件里存在坐标归一化后超出0-1范围的框或者类别的class_id超过了nc定义范围。YOLO的评估流程遇到超界坐标会报warning然后跳过遇到超范围class_id会直接排除该条标注。这几个被「悄悄扔掉」的框正好是模型预测失败的那几个混淆矩阵右上角的漏检格就会比预期少。解决评估前把训练和验证的标签文件全部重新过一遍检查脚本把越界坐标和非法类别ids打印出来并修正。这个坑的隐蔽性在于它不影响训练——训练时标签错误只是当作噪声学习了但评估时的数据入口有严格校验两边行为不一致导致了「训练正常但评估数据异常」。5.5 两类别样本严重失衡civilian类目标数量是police的6倍现象只标注了警察和两名路人某张图里路人15个、警察1个训练时loss完全被civilian主导police类mAP很低而civilian类mAP很高。原因标注时把画面里所有非警察的人都标成了civilian而police只标了少数中央目标。这在「警察/非警察」二分类任务里是常见做法但会导致类别失衡——如果不调整模型对civilian类习得的特征足够丰富对police类的特征则匮乏。解决训练时配置cls1.5或更高把分类损失权重加大强迫模型更重视police类。更根本的做法是数据清洗时做目标数量统计把civilian目标数控制在police的2-3倍以内超出部分通过主动放弃部分标注框或者只保留画面中显著的civilian目标来调节。注意这不是为了「作弊」而是让模型能在有限样本里学到两个类的决策边界而不是被高频类带偏。6. 用小数据集把模型做到能验证和落地的最后一步数据集只有357张训练流程走完后理性的做法不是追求把mAP刷到0.95而是把这个模型的「可信边界」摸清楚。我给自己的工作流是用验证集上效果最好的best.pt权重挑20到30张没参与训练的真实场景图可以是自己手机拍的、同事提供的监控截图、或网上搜到的现场照片统一缩放到训练时的imgsz尺寸逐张人工检查预测结果——重点不是看框准不准而是看两类错误的分布哪些是警察没框出来、哪些是路人被误框成警察。这两个错误在生产里性质完全不同。警察漏检是「漏报」可能引发责任事故路人误检是「误报」最多增加复核成本。如果你的验证集里漏报占比超过20%这个模型还不适合独立运行至少要加一道人工复核环节如果误报偏多可以通过调高置信度阈值到0.4再测一轮误报会明显减少漏报增加幅度不大。我个人的习惯是给这类小数据集模型做一个「固定场景冒烟测试」挑一个模型表现最好的日间场景录30秒视频跑一遍推理把每一帧的置信度、检测框数量、类别分布全部打点记录下来。然后对比这30秒里出现过的警察姿态变化——走路、背对镜头、低头、骑车。如果背对镜头和骑车这两种姿态下检测框置信度明显低于平均水平那这就是下一步采集数据的方向专门补背部和骑行的正样本。357张起点不高但把难例分清楚再采集新数据只要50张就能把短板补上一大截。这个习惯帮我避免过很多次「实验室指标好看现场一上就露馅」的翻车。最后还有一句如果你打算把这个方向做深建议不要把标注工作一次性花完留30张左右的图作为未知测试集永远不要进训练流程。这是你检验后续任何一次优化是否真的有进步的唯一裁判。希望帮到你。本文还有配套的精品资源点击获取