
简介这份数据集面向计算机视觉目标检测学习者和研究者聚焦狗狗常见行为识别包含8种行为类别吠叫、进食、趴卧、侧躺、坐下、睡眠、站立等共1551张标注图片对应1613个真实标注框。图片均为清晰原始图像未做增强处理适合用于YOLO系列与Faster R-CNN等主流检测模型的训练与验证。压缩包约57.77MB内含2000个文件主要类型为1551个JPEG图片、1551个XML标注文件和449个TXT说明/标签文件三个文件夹分别存放图片、VOC格式xml与YOLO格式txt便于直接组织数据集路径。全部标签已按VOC和YOLO两种格式对齐输出省去自行转换的步骤。该数据集可作为行为识别算法入门练习、模型对比实验或课程设计的数据支撑目前已有180人学习使用。推荐搭配YOLOv5/v8等框架快速开展训练或利用清晰标注制作可视化分析案例。1. 狗狗行为检测数据集1551张图能做什么不能做什么刚拿到这个狗狗行为检测数据集的时候我第一反应是打开labels数类别8种行为、总框数1613JPEGImages、Annotations、labels三个文件夹各1551个文件VOC和YOLO双格式都齐了。这种解压就能直接喂给YOLO的包比我之前从LabelImg导出的半成品省事太多至少不用现场写一套XML转TXT的脚本再去处理路径乱码。作为一个经常和标注数据打交道的开发者我看到这种“双格式齐备、标注数量明确”的资源心里其实是有底气的。不过先泼一盆冷水这个数据集解决的是“行为检测”不是“犬种识别”也不是姿态估计。它标的是bark、default、eat、lyingDown、lyingProne、sit、sleep、stand这8类行为状态每张图里可能有多只狗也可能只有一只。如果你想训练一个“狗在叫、在吃、在睡”的监控预警模型那它正合适但如果你打算拿它做单只狗的行为分类或者希望模型能区分“前腿弯曲和后腿弯曲”这种精细尺度的姿势差异那1551张图就不太够看而且类别之间有语义重叠比如lyingDown和lyingProne都是趴着区别主要在于身体是否贴地、腿的伸展方向。原数据没有做过增强分辨率清晰但视角相对单一我一般把它当成一份“基础标注包”训练时自己再补随机翻转、亮度扰动和Mosaic增强。适合的人群也很明确正在做宠物监护、动物行为分析、目标检测入门需要一份带标准标注的数据集练手或跑通流程的人。接下来我会从格式校验、训练配置、踩坑记录到行为频率分析把这套流程完整走一遍。2. 动手前先盘数据VOC与YOLO两种标注格式怎么对应拿到数据集先别急着训练第一步是把文件结构盘清楚。很多人在这一步翻车直接把labels文件夹拷进YOLO工程结果训练时报“no labels found”或者mAP一直低得离谱最后发现是坐标系搞错了。VOC标注和YOLO标注是两套完全不同的存储逻辑前者存的是左上角和右下角的像素坐标后者存的是归一化后的中心点坐标和宽高。双格式的好处是可以互相校验前提是你得知道它们是怎么对应的。2.1 压缩包里的三个文件夹JPEGImages、Annotations、labels解压后目录结构大致是这样的数据集根目录/ ├── JPEGImages/ # 1551张jpg图片 ├── Annotations/ # 1551个xml标注文件VOC格式 ├── labels/ # 1551个txt标注文件YOLO格式 ├── 说明.txt └── xyxr_image1366.txt # 若干图片名列表三个核心文件夹是配套的JPEGImages里的图片名和Annotations里的xml文件名、labels里的txt文件名一一对应比如xyxr_image1366.jpg对应xyxr_image1366.xml和xyxr_image1366.txt。如果你在Windows下解压建议全程用英文路径不然OpenCV读中文路径时偶尔会给你“玄学报错”这种报错在C和Python混用场景下尤其隐蔽。根目录下那些以xyxr_image开头的txt文件我看了下内容形式里面应该是对应图片名的清单常见用途是做数据集划分的索引。比如xyxr_image1366.txt里写着若干图片文件名那你就可以直接拿它作为训练列表。这个组织方式和Pascal VOC官方的ImageSets/Main/*.txt很像VOC就是靠这种列表文件来定义train和val集合的。所以这份数据集的作者在目录结构上还是用了心的没有把一堆图片和标注随便散在根目录里。很多YOLO初学者容易误以为labels里的txt就是最终格式不需要再看xml。实际不是这样labels里是归一化坐标xml里是真实像素坐标两者可以互相转换。标准标注流程通常是用LabelImg导出VOC格式xml再转成YOLO所需的txt。这份数据集把两种格式都给你了可以省掉转换这一步但为了验证标注质量我建议你至少自己写一遍转换脚本把xml和txt对照检查一遍既能理解坐标系换算又能揪出批处理生成的隐藏错误。2.2 从XML到TXT转换脚本与归一化坐标计算下面这个脚本是我处理VOC标注时的标准做法可以直接用来把Annotations里的xml转成labels里的txt。注意我用PIL从真实图片读取尺寸而不是从xml的size节点读因为有些标注工具写入的尺寸和实际图片不一致那样转换出来的框会整体偏移视觉上就是框“漂移”到狗身体之外。import os import xml.etree.ElementTree as ET from PIL import Image CLASSES [bark, default, eat, lyingDown, lyingProne, sit, sleep, stand] def convert_voc_to_yolo(xml_path, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) tree ET.parse(xml_path) root tree.getroot() # 以实际图片尺寸为准而不是xml里的size节点 img_file os.path.splitext(os.path.basename(xml_path))[0] .jpg with Image.open(os.path.join(img_dir, img_file)) as im: w, h im.size lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in CLASSES: print(f跳过未知类别: {cls}) continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 防止越界和非法框 xmin max(0, min(xmin, w - 1)) xmax max(0, min(xmax, w - 1)) ymin max(0, min(ymin, h - 1)) ymax max(0, min(ymax, h - 1)) if xmax xmin or ymax ymin: print(f跳过宽高为0的框: {img_file}) continue # VOC: (xmin, ymin, xmax, ymax) - YOLO: (cx, cy, bw, bh) cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / float(w) bh (ymax - ymin) / float(h) cls_id CLASSES.index(cls) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines))这段脚本的逻辑是先用ET.parse解析XML遍历所有object节点再从图片实际尺寸得到w和h然后把每个目标框的左上角和右下角坐标换算成归一化后的中心点坐标与宽高。注意我在转换前做了坐标越界clamp这样如果原标注里有坐标超出图片边界也不至于在训练时直接因为负数坐标而报错。如果你在labels里看到的txt坐标值有个别大于1或者小于0那就是生成时没有做clamp这种框YOLO虽然能读但损失计算时会变成异常值导致loss振荡。参数上唯一需要改的就是CLASSES的顺序它必须和你后续data.yaml里的类别顺序完全一致。如果你发现labels里已经有txt文件建议随机挑几十张用这个脚本重新生成一遍再diff一下看原始标注和转换脚本是否有出入。很多下载的数据集里VOC格式是准的YOLO格式是后面批处理生成时写错了类别索引这种坑我踩过不止一次尤其是类别名带大小写或空格的时候。2.3 快速校验把检测框画回去转换脚本跑完后不要只看文本输出要把框画回图片上看一看。这一步花不了几分钟但能提前暴露坐标错位、类别错乱、框画到背景上等问题。下面这段代码可以把xml里的框直接画到图上import cv2 import xml.etree.ElementTree as ET CLASSES [bark, default, eat, lyingDown, lyingProne, sit, sleep, stand] def draw_xml_boxes(img_path, xml_path): img cv2.imread(img_path) h, w img.shape[:2] tree ET.parse(xml_path) root tree.getroot() colors [(0, 255, 0), (255, 0, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255), (128, 128, 255), (255, 128, 128)] for obj in root.findall(object): cls obj.find(name).text box obj.find(bndbox) xmin int(float(box.find(xmin).text)) ymin int(float(box.find(ymin).text)) xmax int(float(box.find(xmax).text)) ymax int(float(box.find(ymax).text)) if xmin 0 or ymin 0 or xmax w or ymax h: print(f坐标越界: {img_path}, {cls}, {xmin},{ymin},{xmax},{ymax}) cls_id CLASSES.index(cls) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), colors[cls_id % len(colors)], 2) cv2.putText(img, cls, (xmin, max(0, ymin - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls_id % len(colors)], 1) cv2.imshow(check, img) cv2.waitKey(0) draw_xml_boxes(JPEGImages/xyxr_image1366.jpg, Annotations/xyxr_image1366.xml)画出来之后重点看三个点框是否紧贴狗的身体边缘、类别名是否和实际行为一致、有没有框把相邻的狗或者背景物体也圈进去。像sleep和lyingDown这两个类别如果框的大小和位置都很接近那在训练时就要考虑是不是要合并语义否则模型会一直困惑。这个校验脚本也可以改成遍历目录批量保存成图片用图片查看器快速过一遍比一张张弹窗更高效。3. 用YOLO训练狗狗行为检测从划分数据集到跑通训练数据格式确认没问题之后就可以正式进入训练环节了。很多朋友在网上下载YOLO工程后直接把整个数据集丢进去训练结果要么是类别数对不上要么是验证集和训练集有重叠导致mAP虚高。这里我用的方案是先写脚本划分数据集再写data.yaml然后开始训练最后用验证集评估各类别的表现。3.1 划分数据集固定随机种子避免图片泄漏训练集和验证集的划分必须保证图片不重叠。有些数据集根目录会提供xyxr_image*.txt列表如果有直接拿来当索引最省事如果没有就自己按8:2切分。下面这段脚本会把所有图片名随机分成train和val两份并生成两个列表文件。注意这里的随机种子必须固定否则每次运行结果都不一样复现实验时会对不上。import os import random random.seed(42) # 固定随机种子保证结果可复现 img_dir JPEGImages files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(files) val_ratio 0.2 val_count int(len(files) * val_ratio) val_files files[:val_count] train_files files[val_count:] with open(train.txt, w) as f: for name in train_files: f.write(os.path.join(img_dir, name) \n) with open(val.txt, w) as f: for name in val_files: f.write(os.path.join(img_dir, name) \n) print(ftrain: {len(train_files)}, val: {len(val_files)})随机种子固定为42这样每次跑出来的划分结果完全一致别人复现你的实验也更容易。val_ratio我一般取0.21551张图的话验证集约310张足够评估mAP了。如果你后续要做模型消融实验建议再加一个test.txt从训练集里再抽一部分出来但这份数据集的规模不算大直接train/val两分就够。这里写出的train.txt和val.txt是图片路径列表不是YOLO直接读取的标签路径。YOLO训练时会根据图片路径自动去找同名的txt标签文件所以只要图片名和labels里的txt文件名一致就没问题。3.2 写data.yaml并与YOLOv8/v5对接YOLOv5和YOLOv8都用data.yaml描述数据集路径和类别。这份狗狗行为数据集的8个类别是固定的所以data.yaml长这样train: ./train.txt val: ./val.txt nc: 8 names: [bark, default, eat, lyingDown, lyingProne, sit, sleep, stand]这里的train和val可以直接指向上面生成的txt文件YOLO会读取里面的图片路径。如果你用的是新版ultralytics也可以改成直接指向图片文件夹写法是train: ./JPEGImages但那样就无法精确控制验证集划分所以我更推荐指向txt列表。nc必须写8names的顺序必须和转换脚本里的CLASSES一致否则标签就全部错位了。这是YOLO训练里最常见也最致命的错误之一类别多的时候极难发现因为训练loss可能依然正常下降但实际推理时类别名全乱套了。3.3 训练命令与观测项以YOLOv8为例训练命令如下yolo train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0如果你的显存不大可以把模型换成yolov8n或者yolov8sbatch降到8imgsz降到416。狗的行为检测对分辨率要求不是特别高因为行为状态是全局性的不像车牌识别需要看清小字符640像素已经够用。训练过程中重点看两个指标train/box_loss在epoch 20之后是否还在明显下降以及metrics/mAP50有没有在后期出现震荡。如果mAP50一直卡在0.3以下且损失不掉就要回头检查标注里有没有大量小目标框或者类别语义是否重叠严重。对于这个数据集我建议额外关注sleep和lyingDown这两个类别因为它们在视觉上高度相似模型很容易把“侧躺睡觉”和“趴着不动”混在一起。如果二分类混淆矩阵里这两个类的互相误检率特别高可以在推理阶段用时序平滑来消除单帧误判。这也是一份行为检测数据集和普通物体检测数据集的本质区别行为检测的标签在时间上不独立单帧的检测结果往往带噪声需要后续做时序处理。3.4 训练后如何评估结果训练结束后别只盯着mAP。对于行为检测单帧mAP高不一定实用。比如一张图里狗坐着标成sit但模型在下一帧误检成stand虽然mAP计算时可能因为置信度阈值变化而掩盖这个问题。所以要在验证集上跑一遍把每类的precision和recall拉出来对比yolo val datadata.yaml modelruns/detect/train/weights/best.pt然后看results.csv里的P和R列重点关注default、eat这类框数不算很多的类别。default有211框eat有208框虽然数量不算少但它们的框面积可能差别很大一个远处的小狗在嗅地面可能被标成default一个近处的大狗在咀嚼可能被标成eat。如果某个类别的AP特别低可以用训练目录下的confusion_matrix.png确认它被误判成了哪个类别再决定是加样本还是后处理修正。评估通过后再进入下一步的避坑排查。4. 避坑记录狗狗行为检测数据集的五个常见问题训练行为检测数据集前三次跑通几乎都会被文件路径、坐标顺序、类别映射这些默认错误折磨一遍。我把这份数据集的踩坑记录整理成下面五条每一条都按“现象→原因→解决”的顺序来写你排查的时候直接照着做就行。启动训练前建议先跑一遍“最小验证”随便挑一张图确认jpg、xml、txt三者的文件名完全一致标签类别ID落在0到7之间坐标归一化值在0到1之间。这一步几分钟就能避免后文八成以上的翻车现场。4.1 训练时报错File not found: labels/xxx.txt现象YOLO训练刚开始日志里报找不到对应图片的label文件或者图片和标签路径错位。具体报错一般是RuntimeError: File not found: labels/train/xyxr_image1366.txt。原因大多数下载的数据集里labels文件夹中的txt文件名是规范化过的比如去掉空格或改了后缀但JPEGImages里的jpg文件名并没有跟着改。另一种常见情况是Windows下解压后图片名带中文或不可见字符导致匹配失败。解决写一个脚本按图片文件名为基准检查同级txt是否存在不存在就根据xml重新生成。我一般会在训练前强制走一遍“文件名一致性检查”把缺失列表打印出来而不是让YOLO在训练中途才报错。import os img_dir JPEGImages label_dir labels missing [] for img_name in os.listdir(img_dir): base os.path.splitext(img_name)[0] if not os.path.exists(os.path.join(label_dir, base .txt)): missing.append(img_name) print(fmissing txt count: {len(missing)}) if missing: print(\n.join(missing[:20]))这段脚本的逻辑很简单遍历所有图片名去labels目录找同名txt找不到就记录下来。如果缺失文件很多看图片名里有没有空格、括号、中文有的话统一重命名成纯英文和下划线组合。这也是Pascal VOC工程里常见的目录规范。等你发现缺失文件全部补上之后再启动YOLO训练这类报错基本就不会再出现了。4.2 框的坐标全部正常画出来却不在狗身上现象用labels里的txt画框坐标看起来正常比如0.45 0.30 0.20 0.35但画到图上偏移到角落去了。原因YOLO的归一化坐标是相对原图尺寸的但有些批处理脚本转格式时用了xml里的size节点而那个尺寸和实际图片尺寸不一致。比如xml里写的是800x600实际图片是1920x1080那么所有框都会因为缩放系数不对而错位。这种情况在你自己用爬虫下载图片再批量标注时特别常见因为图片没有被统一重压缩。解决永远以实际图片尺寸为准。按2.2节的脚本用PIL读取im.size不要信任xml里的size字段。如果已经训练了一半才发现这个问题不要抱有“模型可能自己适应了”的幻想数据错了就是错了坐标整体缩放错了模型学到的是错误的位置分布修正之后必须重新训练。4.3 mAP很高但实际视频里行为切换检测不到现象验证集mAP50有0.9但拿到真实监控视频里狗的姿势一变检测框就乱跳同一段视频里行为标签每秒换三次完全没法用。原因这是行为检测数据集最常见的坑单帧标注模式。每张图片是独立采样的标注者只根据静态图判断行为不考虑行为在时间上的连续性。于是sleep和lyingDown这种静态视觉几乎一样的类别在单帧上标注一致性很低模型学到的边界是模糊的单帧推理自然不稳定。解决不要只看单帧。把连续帧的检测结果做滑窗投票窗口长度通常取0.5到1秒以视频帧率25fps计算就是12到25帧。投票结果用出现次数最多的类别覆盖当前帧。这比在模型层硬调阈值有效得多而且代码量不大。我在第六章会给出完整的实现这里先记住思路。4.4 labels里的txt行数与xml里的object数量对不上现象用脚本统计某个txt文件发现里面有5行但xml里明明有6个object。训练时YOLO读取的标签和可视化标注对不上导致mAP评估有偏差。原因转换脚本里通常有类别过滤或者空框跳过逻辑。比如碰到不在CLASSES里的类别脚本会直接continue这样就少了行数。还有一种可能是标注里存在宽高为0的无效框比如标注人员鼠标拖到一半松手产生了xminxmax的笔误。解决转换脚本里加计数器把每个文件跳过的框数输出。统计整个数据集后如果跳过比例超过1%就要仔细看了。下面这段代码可以在转换时统计skipped 0 for obj in root.findall(object): cls obj.find(name).text if cls not in CLASSES: skipped 1 continue ... print(f{xml_path}: skipped {skipped})跑完整个Annotations目录后看看哪些文件跳过次数最多把它们单独拉出来用图片查看器确认。如果是无效框直接删掉如果是类别不在表里要么补充类别要么合并到相近类别。对于行为检测无效框通常意味着标注质量不稳定宁可少一个框也不能把错误框喂给模型。4.5 训练时loss正常下降但个别类别完全不预测现象训练到后期precision曲线里bark、sit这类类别几乎没有预测框但default、lyingDown这类类别表现正常模型看起来整体mAP还行但单独看某类AP几乎是0。原因类别不均衡和框面积分布差异共同导致的。bark只有168框而且嘴部张开这个特征比较局部模型在特征提取时容易被更大的狗身体框主导。另一个原因是标注框可能把整只狗都框进去了而“叫”这个行为更多体现在头部区域特征混在全身上下被大面积的背景和毛发特征稀释。解决先统计每类的框数和平均面积如果某类框数少且平均面积小可以单独做裁剪增强或者提高该类别的损失权重。YOLOv8里可以自己给类别加权或者用复制粘贴增强把这类目标贴到不同的背景上。还有一种更实用的做法训练两个模型第一个模型先检测狗的整体位置第二个模型对狗的头部区域做行为分类这样能把“在哪”和“在干嘛”解耦尤其适合bark、eat这种需要看局部细节的行为。这五条是我在行为检测项目里反复遇到的坑。如果你用的是这份数据集训练前把4.1和4.4两个检查跑一遍能省掉大半天的排障时间。4.3则是从实验到落地的关键一步直接关系到最终效果能不能用建议在项目一开始就把时序方案考虑进去而不是等模型训完再补。5. 类别不均衡与质量验证借VOC工具检查标注的边界训练跑通只是开始想让它真正落地还得回到“标注质量”这件事上。VOC格式最大的好处是可以直接用Pascal VOC社区的工具链做检查也能自己写统计脚本。这一章我把类别分布、框质量、增强策略串起来讲正好解决前面避坑章里提到的类别不均衡和标注一致性问题。5.1 统计每个标签的框数计算权重拿到数据集第一步是统计每个类别的框数。摘要里已经给出了一组数字bark 168、default 211、eat 208、lyingDown 240、lyingProne 148、sit 154、sleep 253、stand 231。除了bark和lyingProne略少整体分布不算极端。但框数和图片数不是一回事一张图可能有多个框也可能某个类别只出现在少数图片里那就意味着模型见过该类别的背景多样性很差。下面这段代码可以统计每个类别的“框数”和“出现图片数”import os from collections import Counter label_dir labels class_names [bark, default, eat, lyingDown, lyingProne, sit, sleep, stand] box_counter Counter() img_counter Counter() for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue path os.path.join(label_dir, txt_file) seen set() with open(path) as f: for line in f: parts line.strip().split() if not parts: continue cls_id int(parts[0]) box_counter[cls_id] 1 seen.add(cls_id) for cls_id in seen: img_counter[cls_id] 1 for i, name in enumerate(class_names): print(f{name}: 框数 {box_counter[i]}, 图片数 {img_counter[i]})这段脚本的逻辑是遍历labels下所有txt每行第一个数字是类别ID分别统计总框数和出现该类的图片数。输出结果后你会发现有些类别框数差不多但图片数差异很大。比如sit 154框如果分布在120张图里那平均每张图1.28个框如果分布在80张图里平均每张图1.9个框后者说明模型需要对同一张图里多只狗同时坐着的场景更敏感。基于这个统计可以决定是否要做类别重加权。常见做法是给样本少的类别更大的损失权重公式是w_i max(N) / N_i也就是用最多的框数除以每类框数。比如sleep有253框bark只有168框那bark的权重可以设为253/168约等于1.5。不过权重不要设太大否则模型会过拟合到少数类上导致验证集mAP反而下降。5.2 用Pascal VOC工具或自行编写检查脚本验证XML合法性Pascal VOC的官方工具链里有check_xml.py这类脚本但下载到的数据集不一定带。最常见做法是自己写一个轻量检查脚本重点检查四件事XML能否正常解析、每个object是否都有name和bndbox、bndbox坐标是否在图片范围内、同一文件的xml框数和txt行数是否一致。import os import xml.etree.ElementTree as ET xml_dir Annotations label_dir labels def check_xml_txt_pair(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() xml_count 0 for obj in root.findall(object): box obj.find(bndbox) if box is None: print(f{os.path.basename(xml_path)}: 缺少 bndbox) continue xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) if xmax xmin or ymax ymin: print(f{os.path.basename(xml_path)}: 无效框 {xmin},{ymin},{xmax},{ymax}) continue xml_count 1 txt_count 0 if os.path.exists(txt_path): with open(txt_path) as f: txt_count sum(1 for line in f if line.strip()) if xml_count ! txt_count: print(f{os.path.basename(xml_path)}: xml框数 {xml_count} ! txt行数 {txt_count}) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue base os.path.splitext(xml_name)[0] txt_name os.path.join(label_dir, base .txt) check_xml_txt_pair(os.path.join(xml_dir, xml_name), txt_name)这个脚本不会自动修复问题但会把所有不匹配的文件名打印出来。拿到不匹配列表后以xml为准重新生成txt这是最稳妥的修复方式。另外你也可以用voc2yolo这类第三方库做二次校验但我不建议把第三方库当作唯一依据因为它的类别映射表可能和你的CLASSES顺序不同。还有一点容易忽视有些标注工具在写VOC格式时坐标是从0开始还是从1开始会有差异检查时要注意xmin、ymin最小是否为0或1如果整个数据集统一从1开始转换脚本里减不减1会导致所有框发生一个像素级别的偏移对行为检测影响不大但对小目标检测可能会拉低几个百分点的mAP。5.3 简单的数据增强策略与注意事项这份数据集标注了“图片是否增强否”所以训练阶段必须自己补增强。YOLO默认的Mosaic增强、随机翻转、颜色扰动都能直接开但对行为检测有三条注意事项。第一水平和垂直翻转要慎用。垂直翻转会让“站立”的狗看起来像“倒立”模型会学到错误的方向特征水平翻转倒是问题不大因为狗的左右对称性不影响行为语义。第二随机裁剪不能太狠否则只裁到狗的腿部模型会误把“站立”学成“腿部纹理”。第三亮度对比度扰动要控制幅度夜间监控场景如果原图是白天过强的亮度扰动会让模型对光照变化更鲁棒但如果扰动太强会把本来就微弱的“张嘴”特征洗掉。一个可行的增强配置表如下增强项参数建议是否推荐Mosaic概率0.5到1.0推荐水平翻转p0.5推荐垂直翻转关闭不推荐HSV-H0.015小幅调整HSV-S0.7适度HSV-V0.4适度我曾经在一个宠物行为识别项目里把垂直翻转打开了结果模型对“倒立的狗”预测出极高的坐姿置信度因为训练数据里很多坐姿狗的腿部形态被翻转后和倒立姿态重合了。从那以后行为检测数据集的增强策略我会先问自己一句这个增强操作是否保持了行为语义的不变性语义不变的增强才能加否则就是噪声。这一点和普通物体检测有本质区别车还是那辆车但“坐”变成“倒立”就是完全不同的语义了。6. 进阶把行为检测结果接进时间序列分析算行为频率模型训练完成并验证mAP够用之后真正让行为检测产生价值的动作是把单帧检测结果转成可统计的行为时序数据。这里分享一个我在宠物监护项目里经常用的做法用YOLO逐帧推理记录时间戳和类别然后滑窗统计行为频率。6.1 将检测结果导出为带时间戳的CSV假设你的输入是一段25fps的监控视频先用YOLO跑一遍把每帧的检测结果写成CSV。下面的代码适用于ultralytics YOLOv8如果你的脚本跑的是YOLOv5把results[0].boxes换成results.xyxy[0]即可。import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) cap cv2.VideoCapture(dog_video.mp4) fps cap.get(cv2.CAP_PROP_FPS) frame_idx 0 with open(detections.csv, w, encodingutf-8) as f: f.write(frame,ts,class,xmin,ymin,xmax,ymax,conf\n) while True: ret, frame cap.read() if not ret: break ts frame_idx / fps results model.predict(frame, imgsz640, conf0.25, verboseFalse) for box in results[0].boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xmin, ymin, xmax, ymax [round(float(v), 2) for v in box.xyxy[0].tolist()] f.write(f{frame_idx},{ts:.3f},{cls_id},{xmin},{ymin},{xmax},{ymax},{conf:.3f}\n) frame_idx 1这段代码的逻辑是按帧推理每检测到一个目标就写一行CSV时间戳直接用帧号除以帧率。注意我设置了conf0.25这个阈值需要根据你的验证集precision/recall曲线来定通常选precision和recall的交点。对于行为检测我倾向把阈值调到0.3以上减少误报因为后续滑窗投票还能把漏检补回来比单纯追求单帧recall更符合实际。6.2 按固定窗口聚合行为频率得到detections.csv后就可以用pandas做滑窗统计。假设窗口是10秒每2秒滑动一次统计每个窗口里每类行为出现的帧数占比然后输出主要的持续行为。import pandas as pd df pd.read_csv(detections.csv) df[ts] df[ts].round(3) window 10.0 step 2.0 max_ts df[ts].max() rows [] start 0.0 while start window max_ts: end start window seg df[(df[ts] start) (df[ts] end)] counts seg.groupby(class).size() total len(seg) if total 0: dominant -1 dom_ratio 0 else: dominant counts.idxmax() dom_ratio counts.max() / total rows.append({start: round(start, 2), end: round(end, 2), dominant_class: int(dominant), ratio: round(dom_ratio, 3)}) start step summary pd.DataFrame(rows) print(summary.head(30))这段脚本把每个窗口里的检测结果按类别聚合找出出现帧数最多的行为作为该窗口的主导行为同时计算它占该窗口总检测帧数的比例。比如窗口内检测到800帧其中600帧是sleep那主导行为就是sleep占比0.75。这个主导行为序列才是你最终能拿去做报表、告警、长期趋势分析的东西而不是单帧的框。如果某个窗口里dominant_class频繁在邻近类别之间跳变说明该行为本身在时间上不连贯需要检查是不是模型误判或者是窗口步长设得太小。回头说一句数据集的单帧标注和真实视频推理之间永远隔着一条“时间连续性”的鸿沟。正因为如此我从那次宠物监护项目之后每次拿到行为检测数据集都会强制走一遍“数据质量检查—训练—滑窗投票—时序聚合”的完整流程哪怕只是1551张图的练手包。这套流程跑通后你换到其他行为检测数据集也能很快上手。希望帮到你。本文还有配套的精品资源点击获取