简介面向狗狗行为识别与目标检测任务数据集提供1551张原始图片并同时附带VOC格式的XML标注和YOLO格式的TXT标注覆盖bark吠叫、default默认、eat进食、lyingDown躺卧等8种常见行为矩形框总计1613个。压缩包共2000个文件核心为1551个xml标注文件与449个txt文件整体约57.77MBJPEGImages、Annotations、labels三个目录分别存放图片、XML和TXT其中labels下的txt文件按YOLO格式记录类别与归一化框坐标Annotations下的xml遵循VOC标注规范便于直接接入YOLO、Faster R-CNN等主流检测框架。图片清晰度良好未经增强处理适合行为分类、姿态估计等视觉任务的算法验证与模型训练。目前已有180人学习下载适合计算机视觉学习者或研究人员快速获取带标注数据节省采集与人工标注时间。数据集标注规范、类别覆盖较均衡可直接用于训练、验证及不同检测算法的对比实验也可作为课程设计或毕业设计的基础数据。1. 狗狗行为检测数据集拆包1551张图、8种行为YOLOVOC双格式到底能省多少事做狗狗行为检测项目时第一道坎通常不是模型而是数据。自己从视频里抽帧、清洗、画框、打行为标签攒出1551张有效图至少得折腾两周而且行为类别的标注比普通目标检测更容易标错因为姿态相近的“坐下”和“趴下”连人都容易看走眼。这个数据集把8种常见狗狗行为整理成YOLO和PASCAL VOC两套标注格式解压后可以直接进入YOLOv8训练流程老项目如果只认VOC XML也能无缝接入。对做宠物摄像头、狗狗陪伴机器人、宠物行为分析平台的工程师来说1551张不算大样本但足够把训练流程跑通也足够提前暴露格式转换、类别不均衡、过拟合这些真实问题。这篇文章按我实际动手的顺序讲从解压目录讲到训练参数再把最容易翻车的地方单独列出来。2. 狗狗行为检测数据集的目录、类别映射与YOLO/VOC标注换算2.1 解压后的第一件事确认目录结构和train/val划分拿到压缩包我建议别急着把图片拖进标注工具里看效果先把整体目录拉出来。多数带YOLO格式的数据集会按下面的结构组织图片放images、标签放labels、VOC的XML放annotationstrain和val预先切好dogs-behavior/ ├── images/ │ ├── train/ │ │ └── dog_0001.jpg │ └── val/ │ └── dog_0001.jpg ├── labels/ │ ├── train/ │ │ └── dog_0001.txt │ └── val/ │ └── dog_0001.txt ├── annotations/ │ ├── train/ │ │ └── dog_0001.xml │ └── val/ │ └── dog_0001.xml ├── classes.txt └── data.yaml具体文件名不一定叫dog_0001但结构逻辑基本一致labels目录下的txt文件和images目录下的jpg文件同名只是后缀不同。这一步要确认三件事train和val图片数量比例是否合理、labels里有没有明显缺文件、classes.txt在不在。我一般直接在终端里跑一条命令看统计unzip 狗狗行为检测数据集.zip -d ./dog_dataset find ./dog_dataset -type f | awk -F. {print $NF} | sort | uniq -c这条命令把文件后缀统计出来jpg、txt、xml、yaml各有多少个一目了然。如果txt数量远小于jpg数量说明标签文件缺失还没训练就要先补数据如果xml数量和txt数量差不多说明VOC标注和YOLO标注双份齐全后续做格式转换时就有对账依据。先打开classes.txt看类别名和排列顺序这是整个数据集最容易被忽略的关键。YOLO标签里的类别ID就是classes.txt的行号从0开始第一行是0第二行是1以此类推。比如常见的行为划分是进食、坐下、趴下、站立、行走、奔跑、吠叫、玩耍那么classes.txt第1行如果写的是eatingID 0就代表eating后面所有标注里的0都指它。顺序一旦和data.yaml里的names对不上训练不会报错但推理出来的行为类别名会张冠李戴。2.2 YOLO和VOC的坐标换算XML像素坐标转成txt归一化坐标VOC格式的标注存放在XML文件里用xmin、ymin、xmax、ymax表示目标框的绝对像素坐标而YOLO的txt每行格式是“类别ID、中心点x、中心点y、框宽、框高”而且后四个值都必须除以图片宽高做归一化。把两者对应起来的关键公式就是中心点取最小最大坐标的均值宽高取差值再分别除以图片宽高import os import xml.etree.ElementTree as ET def voc_to_yolo_annotation(xml_path, out_dir, class_names): 把单张VOC XML转成YOLO txt。 class_names的顺序决定了YOLO标签里每个框的类型ID。 tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) basename os.path.splitext(os.path.basename(xml_path))[0] lines [] for obj in root.findall(object): cat obj.find(name).text if cat not in class_names: continue cls_id class_names.index(cat) 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) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, basename .txt), w) as f_out: f_out.write(\n.join(lines)) class_names [ eating, sitting, lying, standing, walking, running, barking, playing, ] os.makedirs(labels/train, exist_okTrue) for xml_name in os.listdir(annotations/train): if xml_name.endswith(.xml): voc_to_yolo_annotation( os.path.join(annotations/train, xml_name), labels/train, class_names, )这段脚本里class_names的顺序和classes.txt保持一致通过class_names.index(cat)把VOC里的类别名称转成YOLO的类别ID而不是手写数字映射能少踩一个错位坑。中心点用(xmin xmax) / 2计算除以图片宽高后得到0到1之间的归一化值框宽用xmax - xmin算绝对宽度再除以图片宽度。输出精度保留6位小数已经足够匹配主流训练框架的读取精度。这里要说明一点这个数据集既然同时给了YOLO和VOC两种格式训练时直接用现成的labels目录就行不需要做转换。给你这个脚本的真正用途是两个一是拿来验证包内VOC标注和YOLO标签能否对上二是以后你拿LabelImg标了一批新图存成VOC格式时能顺手转进现有训练集。实际跑的时候如果遇到某个XML里name写的是中文并且不在class_names里脚本会直接跳过这个目标所以扩展类别时记得同步改class_names。2.3 训练前做一次类别分布统计8个行为里谁多谁少必须心里有数在正式训练前花五分钟统计一下标签分布能避免后面训练半天发现个别行为类别完全没有样本量支撑。我用两个口径统计按目标框数量算一个数按图片数量再算一个数。因为有的图里会同时出现多只狗、多个框行为检测落地时更关心的是“包含这个行为的图片有多少张”。from collections import Counter import glob by_box Counter() by_image Counter() for txt_path in glob.glob(labels/**/*.txt, recursiveTrue): with open(txt_path, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] seen_classes set() for line in lines: cls_id int(line.split()[0]) by_box[cls_id] 1 seen_classes.add(cls_id) for cls_id in seen_classes: by_image[cls_id] 1 print(按目标框统计, dict(by_box)) print(包含该行为的图片数, dict(by_image))这段脚本不涉及训练框架纯Python就能跑递归遍历labels目录下所有txt。如果某个行为只有几十张图比如奔跑和吠叫这类瞬间动作本来就难抓拍在1551张的规模里很容易变成冷门类后面的训练策略就要针对性调整要么给这些类适当做离线增强要么接受它们的AP值偏低而不是盲目追求八个类平均。还有一种情况值得警惕如果按目标框统计和按图片统计两个结果差距很大说明很多图片里有多只狗并且行为各不相同。此时要先确认项目要的是图像级行为标签还是目标级行为标签两种语义在训练和评估时的指标含义完全不同做产品需求调研时经常在这里出现信息差。3. 用YOLOv8把狗狗行为检测跑起来data.yaml、训练命令与结果判读3.1 用YOLOv8训练自己的数据集路径整理和data.yaml配置YOLOv8也就是Ultralytics框架读取数据集靠的是data.yaml不直接索引classes.txt所以先把数据描述文件写对。这个数据集如果包内自带data.yaml也要打开检查一下path和names是否与当前路径一致因为很多打包好的数据集默认路径是标注者电脑上的绝对路径直接复用到你的机器上一定会报找不到图片。# data.yaml path: /home/user/datasets/dogs-behavior train: images/train val: images/val nc: 8 names: 0: eating 1: sitting 2: lying 3: standing 4: walking 5: running 6: barking 7: playingpath是数据集根目录的绝对路径train和val的值是相对根目录的图片子目录路径。这里要特别强调train指向的是图片目录而不是标签目录YOLOv8会自己根据images这个名字找到同级labels目录下的txt标签它的约定是图片在images/train标签就在labels/train。如果你把目录结构改成别的名字就得自己在代码里改路径匹配逻辑所以不要轻易动目录名。nc必须等于names里列出的类别数写错会在训练初始化阶段报错或者静默错位。names可以写成这种字典形式也可以直接写成列表两种写法Ultralytics都支持但类别顺序必须和标签文件里的ID一一对应。最稳妥的做法是把classes.txt的内容原封不动搬过来保持顺序不变。3.2 最小训练命令imgsz、batch、epochs三个参数怎么定环境装好之后最精简的训练命令是下面这条。我用的是coco预训练权重v8s起步1551张的数据规模不建议从零随机初始化训练也不建议一上来就上v8l这种大模型s尺寸在这个数据量级属于平衡点。pip install ultralytics yolo detect train \ modelyolov8s.pt \ data/home/user/datasets/dogs-behavior/data.yaml \ epochs100 \ imgsz640 \ batch8 \ patience20 \ device0 \ project./runs \ namedog_behavior几个关键参数的考量参数默认值我建议怎么看imgsz640行为检测的框不算极端小640够用显存紧张再降到480但小行为如抬头、摇尾的细节会受影响batch8以显存为第一约束8不够就调到4batch太大在小数据集上反而加速过拟合epochs1001551张图通常50轮以内就到平台期100轮配合早停足够patience20验证集指标连续20轮没提升就停省时间yolov8n虽然更快但在这种小数据集上容易出现欠拟合尤其区分趴下和坐下这种细节姿态时模型容量不够学不细。yolov8l则容易把小样本行为直接当成噪声丢掉。v8s的推理速度和精度折中先用它跑通一轮拿到baseline再决定要不要往上加容量。device参数如果只有CPU就写devicecpuepochs建议先跑一轮看看loss曲线形态确认数据和标签没大问题后再正式跑100轮。命令行跑完会在./runs/detect/dog_behavior下生成训练产物包括weights目录、results.csv和一组可视化图。3.3 训练完看什么loss曲线、mAP50和混淆矩阵训练结束后别只盯着终端里最后一行mAP我会按固定顺序看四样东西results.csv里的loss曲线、验证集mAP50、混淆矩阵图、还有几张带预测框的验证图。import pandas as pd df pd.read_csv(runs/detect/dog_behavior/results.csv) print(df[[epoch, train/cls_loss, val/cls_loss, metrics/mAP50(B)]].tail(5))这条命令读出最后5轮的指标。正常情况是train/cls_loss和val/cls_loss同步下降然后同时走平如果train loss还在降但val loss掉头往上说明模型开始背训练集了小数据集上这是典型信号。mAP50主要看0.5这个IoU阈值下的表现行为检测的框画得粗一点问题不大更关心的是行为类别判断对不对所以mAP50比mAP50-95更有参考价值。混淆矩阵要重点看相邻行为之间的误检比如lying和sitting互相混、standing和walking互相混这类错误靠加大epochs解决不了要从数据层面想办法比如补拍姿态更标准的样本或者干脆合并容易混淆的行为类别。最后再打开验证集预测图确认边界框确实框在狗身上而不是落在背景物体上这一步是真正判断标签质量的时候指标再漂亮框偏了也是白搭。4. 避坑清单1551张数据集训练中常踩的5个坑与排查方法4.1 坑一标签和图片对不上号训练时一多半图片是背景现象训练过程不报错loss也在下降但验证时的边界框大量漂移mAP50低到离谱打开预测图发现模型在背景区域乱框。原因很常见的坑。图片目录和标签目录里的文件名对不上可能某个jpg的对应txt缺失也可能一个图片名出现了多个后缀版本jpg和jpeg混在一起导致YOLOv8在找同名txt时漏掉一部分标注直接把没标签的图当背景图训练。解决训练前先做一次严格的对应关系检查把缺失标签的图片全部列出来import glob import os for phase in (train, val): img_files glob.glob(fimages/{phase}/*.jpg) missing [] for img in img_files: stem os.path.splitext(os.path.basename(img))[0] txt_path flabels/{phase}/{stem}.txt if not os.path.exists(txt_path): missing.append(img) print(f{phase} 缺失标签{len(missing)} / {len(img_files)}) for m in missing[:5]: print( , m)如果图片有jpg和png混合的情况上面这个脚本要把后缀都匹配一遍最稳的办法是只取文件名主体做比对不写死后缀。找到缺失图片后用2.2节给的VOC转YOLO脚本重新从xml生成缺失的txt因为双格式数据集里VOC标注通常是全的转一下就能补回来。4.2 坑二VOC转YOLO时坐标算错验证AP全白现象训练过程一切正常生成的混淆矩阵也有内容但验证集的precision和recall全是0或者全类别AP都一样。原因典型算错位置是中心点公式。有人把x_center直接写成xmin / img_w漏掉了(xmin xmax) / 2这一步还有人转完忘记除以图片宽高直接存了绝对像素坐标。这两种错误的结果都是框的位置完全错乱模型学不到有效特征。解决转完不做可视化验证等于白转。写一个小函数把YOLO标签画回图上肉眼对比原图和框是否贴合import cv2 def show_yolo_box(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: cid, xc, yc, bw, bh map(float, line.strip().split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) return img check_img show_yolo_box(images/train/dog_0001.jpg, labels/train/dog_0001.txt) cv2.imwrite(check.jpg, check_img)这段代码把txt里的归一化坐标还原成像素坐标时用的公式是(xc - bw/2) * w和转换时刚好互逆。随机挑5到10张图画出来看一遍框能贴合再进训练这一步花十分钟能省后面一整天的瞎调参。4.3 坑三8个行为类别样本量差距悬殊个别行为AP被打穿现象整体mAP50看着还挺好但拆开看单类AP发现barking或者walking这类行为AP是0或者低到不可用。原因还是那句老话在1551张图里少数行为类别可能只有三五十个目标框。这些小类别样本在训练时被mosaic、翻转等增强一搅和模型更容易把它们当作背景噪声忽略。解决我一般分三步走。第一步用2.3的统计脚本算出每个行为类别的图片数低于100张的单独处理。第二步给这些少样本类别做离线增强水平翻转、亮度抖动、轻微旋转把几十张扩到一百多张而不是简单在代码里重复同一张图。第三步评估时单独看每个类别的AP不要只看总mAP总指标容易掩盖单类失效。注意YOLOv8不像分类模型那样有一个现成的class_weight参数真正可持续的解法还是从数据侧把少样本类补齐。4.4 坑四train/val按帧随机切同源视频帧泄漏导致结果虚高现象训练集mAP50到了0.9以上拿训练时没见过的真实视频去测漏检反而严重差距非常明显。原因如果这个数据集是从视频抽帧来的同一个视频的相邻帧相似度极高。随机按帧划分train和val时同一段视频的相似帧会同时出现在两边模型等于提前见过答案val指标虚高一到真实场景就现原形。解决按视频片段划分而不是按帧划分。假设文件名前缀是视频标识比如dog0011_0123.jpg里的dog0011就是视频ID那就要整组划分import glob import os import random import shutil random.seed(0) def video_key(path): return os.path.basename(path).split(_)[0] imgs glob.glob(images/*.jpg) video_ids list({video_key(p) for p in imgs}) random.shuffle(video_ids) split int(len(video_ids) * 0.8) train_videos set(video_ids[:split]) val_videos set(video_ids[split:]) for img in imgs: vid video_key(img) dst images/train if vid in train_videos else images/val shutil.copy(img, os.path.join(dst, os.path.basename(img)))标签文件按同样逻辑复制到labels下对应目录确保train和val里不出现同一个视频ID。按视频划分后val集合里的场景和train区别更明显获得的指标才接近真实部署水平。小样本数据集上这种泄漏问题特别隐蔽因为总mAP不降但泛化能力已经出名地差。4.5 坑五类别ID从1开始写训练正常但推理时行为名称错位现象训练能正常跑完预测框的位置也对但输出的行为类别名和实际行为对不上比如明明检测到狗躺着打印出来的类别却是坐着。原因标注工具或转换脚本里类别ID从1开始编号而classes.txt和YOLO框架都要求从0开始。更常见的是有人中途往classes.txt中间插了一行类别导致后面所有标签ID往后移了一位数据本身没变只是ID对不上了。解决训练前先扫描一遍所有标签文件检查最大类别ID是否等于类别总数减1import glob min_id, max_id 999, -1 for txt in glob.glob(labels/**/*.txt, recursiveTrue): with open(txt) as f: for line in f: cls_id int(line.split()[0]) min_id min(min_id, cls_id) max_id max(max_id, cls_id) print(最小类别ID, min_id, 最大类别ID, max_id) assert min_id 0 and max_id 7, 类别ID范围异常请检查classes.txt如果max_id等于8说明确有ID从1开始的情况把所有标签文件里的class_id统一减1就行。这个坑最容易出现在用VOC数据拼了YOLO数据的场景里因为VOC里的name是字符串转到YOLO时容易以1为基准从头编号。做一次全量扫描确认比等推理阶段返工划算太多。5. 部署前验证视频推理和ONNX导出时的三个细节训练完不要直接拿去部署先拿一段真实场景视频跑一遍推理。这里有个容易被忽视的细节训练时用的验证集图片通常是整理过的现场视频的光照、角度、遮挡完全不一样跑视频能发现很多静态图上不存在的漏检问题。yolo detect predict \ modelruns/detect/dog_behavior/weights/best.pt \ sourcedemo.mp4 \ conf0.25 \ save_txtTrue \ save_confTrueconf0.25是常规建议值但行为检测场景我建议专门试一次0.1和一次0.1和0.1不对是0.1和0.5两种阈值观察漏检和误检的平衡点。行为本身有歧义性比如站立和行走在单帧上本来就难分置信度阈值调低能保留更多候选框后续再靠时序平滑做判断这是行为检测和通用目标检测用法上最明显的区别。确认视频推理效果没问题后再做ONNX导出方便后续用TensorRT或者OpenVINO在边缘设备上部署yolo export modelruns/detect/dog_behavior/weights/best.pt \ formatonnx \ imgsz640 \ opset12 \ simplifyTrue导出后要检查两件事第一输入尺寸是否是固定的640如果部署端想用动态分辨率需要重新导出并确认动态轴第二用onnxruntime跑一遍推理对比一下和PyTorch原模型的结果差距。ONNX推理通常有微小浮点差异但框和类别不应有大变化如果出现明显的框偏移多数情况是导出时预处理设置和训练时不一致。我现在的习惯是训练完先跑一次val再拿一段没见过的竖屏视频按真实使用场景过一遍最后才导出ONNX并且会刻意记录CPU上的推理耗时。1551张的小数据集本身参数上限就摆在那里与其纠结调参不如在验证流程上多下功夫。希望这些踩过的坑能帮你少走一段弯路。本文还有配套的精品资源点击获取