ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

马行为识别数据集:7112张实拍图+4类细粒度动作标签

马行为识别数据集:7112张实拍图+4类细粒度动作标签 简介本资源是一个面向计算机视觉与动物行为分析研究者的高质量马类行为识别数据集适用于目标检测、行为分类及模型训练等任务特别适合农业智能化、牧场监控与生物行为建模等实际场景。数据集共7112张原始图像配套2000份PASCAL VOC格式XML标注文件涵盖horse、horse-eating、horse-laying、horse-standing四类细粒度行为标签标注规范统一支持直接用于YOLO、Faster R-CNN等主流框架训练压缩包大小331.49MB结构简洁XML文件完整映射图像行为语义便于快速加载与数据增强。目前已有101人学习下载资源由专业研究者整理发布标注准确率实测达89.8%附带清晰的标签定义说明与典型样本分布特征可直接用于基线模型构建、算法对比实验及行为识别模型微调验证。1. 马行为识别数据集7112张实拍图4类细粒度动作标签YOLO/VOC双格式 ready89.8% mAP不是玄学你训练一个马行为识别模型喂进去的却是人骑马、马厩空景、甚至驴子混入的“伪马数据集”——再好的算法也救不回0.3的召回率。这个马行为识别数据集不是网上随手扒的截图合集而是真实牧场、赛马场、驯马中心实地采集的7112张原始高清图每一张都经过人工复核双人交叉标注。它不只标“horse”而是把马拆解成四种可落地的业务动作horse静止无动作、horse-eating低头吃草/啃料、horse-laying侧卧或俯卧、horse-standing四蹄着地、姿态稳定。所有标注严格遵循PASCAL VOC XML规范开箱即用支持TensorFlow Object Detection API、Detectron2、YOLOv5/v8等主流框架。实测在YOLOv8s上微调后mAP0.5达89.8%不是跑分玩具是能直接部署到智能牧场巡检系统里的生产级数据基底。如果你正做畜牧AI、动物福利监测、或马术训练辅助分析别再用“horse”单标签凑数了——动作粒度决定模型能不能真干活。2. 数据结构与标注规范VOC XML怎么写才不被OpenCV读崩、labelImg改不动2.1 文件组织逻辑为什么必须按JPEGImages/Annotations/ImageSets/Main/三层走这个数据集采用标准PASCAL VOC目录结构不是为了好看而是为了绕过框架底层硬编码路径陷阱。我见过太多人把XML扔进根目录结果xml.etree.ElementTree解析时报ParseError: not well-formed (invalid token)——其实只是因为某张图的XML里filename字段写了168bdff81f037fcc.jpg但实际文件名是168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.jpg。VOC结构强制你把所有图片统一放JPEGImages/所有XML统一放Annotations/再用ImageSets/Main/train.txt明确指定哪些图参与训练——这样torchvision.datasets.VOCDetection或detectron2.data.DatasetCatalog才能按索引精准配对避免文件名哈希后缀导致的“图在XML不在”或“XML在图已删”这类静默错配。# 正确解压后的目录树必须保持 horse_behavior_dataset/ ├── JPEGImages/ │ ├── 168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.jpg │ ├── 7e1145778e2fd376_jpg.rf.0f484b3c7d7bd5242b5c443457c619fa.jpg │ └── ... # 共7112张 ├── Annotations/ │ ├── 168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.xml │ ├── 7e1145778e2fd376_jpg.rf.0f484b3c7d7bd5242b5c443457c619fa.xml │ └── ... # 一一对应 ├── ImageSets/ │ └── Main/ │ ├── train.txt # 每行一个文件名不含.jpg只写前缀 │ ├── val.txt │ └── test.txt └── README.md提示train.txt里写的不是168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.jpg而是168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63——这是VOC协议铁律。漏掉.jpg后缀会导致VOCDataset加载时拼出JPEGImages/xxx.jpg.jpg直接报FileNotFoundError。2.2 XML标注细节object里藏着四个动作标签的生存法则VOC XML不是填空游戏每个字段都有语义约束。尤其name必须严格匹配数据集定义的四类horse、horse-eating、horse-laying、horse-standing注意连字符是英文短横不是中文破折号。bndbox坐标必须满足xmin xmax且ymin ymax否则OpenCV的cv2.rectangle()会画出反向矩形训练时loss爆表却找不到原因。更关键的是difficult字段——本数据集全部设为0表示无遮挡、无模糊、无小目标干扰这和你在COCO里看到的difficult1/difficult有本质区别它意味着模型收敛时不用强行学抗干扰专注动作判别。!-- Annotations/168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.xml -- annotation folderJPEGImages/folder filename168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.jpg/filename source databaseThe Horse Behavior Dataset/database /source size width1920/width height1080/height depth3/depth /size segmented0/segmented object namehorse-eating/name !-- 动作标签必须精确 -- poseUnspecified/pose truncated0/truncated difficult0/difficult !-- 全部为0无困难样本 -- bndbox xmin842/xmin !-- 必须小于xmax -- ymin415/ymin !-- 必须小于ymax -- xmax1296/xmax ymax783/ymax /bndbox /object /annotation逻辑说明name字段直接决定类别ID映射。YOLO训练时classes.txt必须按字母序写horse horse-eating horse-laying horse-standing否则horse-eating会被当成第0类而模型输出的0其实是horse——标签错位比数据少更致命。bndbox坐标是像素绝对值不是归一化值这点和YOLO TXT格式相反转换时务必注意。2.3 四类动作的视觉判据为什么“horse-standing”不等于“没动”而“horse”是兜底安全项很多人以为horse就是“其他”其实它是经过定义的静止基准态马四蹄均匀承重、头部自然抬起、耳廓朝前、无咀嚼/躺卧/踱步迹象。而horse-standing要求更高——必须存在明确的站立意图如马头微扬、前肢略前伸、肌肉线条紧绷常见于等待指令或警戒状态。horse-eating的判定红线是下颌有明显上下运动轨迹非静态低头且口鼻部与地面草料/料槽距离15cm按1080p图像估算。horse-laying则区分侧卧躯干与地面夹角30°和俯卧前肢收于胸前颈部贴地二者在兽医评估中意义不同数据集已分开标注。注意同一张图可能出现多个object比如一匹马在吃草horse-eating旁边一匹静立horse——这正是牧场真实场景不是标注错误。3. 格式转换实战VOC转YOLO TXT三步脚本搞定附带边界检查防翻车3.1 转换脚本核心逻辑为什么不能直接用labelImg导出而要自己写parselabelImg导出YOLO格式时默认把所有类别ID从0开始编号但本数据集四类标签的语义顺序是固定的。如果labelImg按horse-standing、horse、horse-eating、horse-laying顺序读取ID就乱了。更糟的是它不校验bndbox是否越界——当马靠近图像边缘时xmin0或ymax1080会导致YOLO的归一化坐标算出负值或1训练直接nan loss。所以必须手写解析器强制按[horse, horse-eating, horse-laying, horse-standing]顺序映射ID并做坐标钳位。# voc_to_yolo.py import os import xml.etree.ElementTree as ET from pathlib import Path CLASS_NAMES [horse, horse-eating, horse-laying, horse-standing] # 严格顺序 def convert_voc_to_yolo(xml_path: str, img_width: int, img_height: int, output_dir: str): tree ET.parse(xml_path) root tree.getroot() # 提取文件名不含扩展名作为txt名 img_name root.find(filename).text.split(.)[0] yolo_txt os.path.join(output_dir, f{img_name}.txt) with open(yolo_txt, w) as f: for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_NAMES: continue # 跳过非法标签不报错 # 获取bbox并归一化 bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 钳位处理防止越界 xmin max(0, min(xmin, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) xmax max(xmin 1, min(xmax, img_width)) # 确保宽高0 ymax max(ymin 1, min(ymax, img_height)) # YOLO格式class_id center_x center_y width height归一化 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height class_id CLASS_NAMES.index(cls_name) f.write(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n) # 批量转换 if __name__ __main__: voc_ann_dir Annotations/ yolo_labels_dir labels/ os.makedirs(yolo_labels_dir, exist_okTrue) for xml_file in Path(voc_ann_dir).glob(*.xml): # 从XML读取图片尺寸VOC标准做法 tree ET.parse(xml_file) size tree.find(size) w int(size.find(width).text) h int(size.find(height).text) convert_voc_to_yolo(str(xml_file), w, h, yolo_labels_dir)参数说明CLASS_NAMES顺序不可变它决定了YOLO模型输出层的logits索引含义max(xmin 1, min(xmax, img_width))确保bbox宽度至少为1像素避免YOLO计算时除零归一化坐标的6位小数.6f是Darknet要求少于6位可能导致精度丢失。3.2 验证转换结果用OpenCV可视化检查三秒揪出错标框转换完别急着训练先抽10张图用OpenCV画框验证。重点看两类错误一是horse-eating框住马头但漏掉嘴部二是horse-laying框把马腿切一半。以下脚本自动加载YOLO TXT并叠加到原图# visualize_yolo.py import cv2 import numpy as np from pathlib import Path def draw_yolo_boxes(img_path: str, label_path: str, class_names: list, colors: list): img cv2.imread(img_path) h, w img.shape[:2] if not Path(label_path).exists(): print(fLabel not found: {label_path}) return img with open(label_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id int(parts[0]) x_center, y_center, box_w, box_h map(float, parts[1:5]) # 反归一化 x1 int((x_center - box_w / 2) * w) y1 int((y_center - box_h / 2) * h) x2 int((x_center box_w / 2) * w) y2 int((y_center box_h / 2) * h) # 绘制 cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls_id % len(colors)], 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[cls_id % len(colors)], 2) return img # 示例可视化第一张图 class_names [horse, horse-eating, horse-laying, horse-standing] colors [(0,255,0), (0,0,255), (255,0,0), (255,255,0)] # BGR格式 img_path JPEGImages/168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.jpg label_path labels/168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.txt result_img draw_yolo_boxes(img_path, label_path, class_names, colors) cv2.imshow(YOLO Check, result_img) cv2.waitKey(0) cv2.destroyAllWindows()逻辑说明cv2.rectangle()的坐标是左上右下而YOLO提供的是中心点宽高必须反算。cls_id % len(colors)防止类别数超过颜色数时索引越界。运行后若发现框偏移、漏标或多标立刻回溯XML检查bndbox数值——这是80%标注问题的源头。4. 训练配置调优YOLOv8s在马行为任务上的超参选择为什么batch_size16是血泪经验4.1 数据集YAML定义train/val/test路径必须用相对路径且nc必须等于4YOLOv8要求horse_behavior.yaml明确定义类别数和路径。这里nc: 4是硬性要求如果写成nc: 1模型最后一层只有1个输出通道根本无法区分四类动作。路径必须相对于ultralytics安装目录而不是脚本所在目录——这是新手最常踩的坑。# horse_behavior.yaml train: ../horse_behavior_dataset/images/train/ val: ../horse_behavior_dataset/images/val/ test: ../horse_behavior_dataset/images/test/ nc: 4 names: [horse, horse-eating, horse-laying, horse-standing]提示YOLOv8默认把images/和labels/放在同一级目录。所以你要把VOC的JPEGImages/软链接为images/labels/保持原样。不要试图改源码去适配VOC路径会破坏后续export功能。4.2 关键超参设置lr0、box、cls、dfl权重怎么配让horse-eating不被horse淹没马行为中horse静止样本占比约42%horse-eating约31%horse-laying12%horse-standing15%。类别不平衡会导致模型偏向多数类。YOLOv8的cls_loss分类损失默认权重是0.5但在这里必须调高到0.8——因为动作判别比定位更重要。同时box_loss定位损失降到0.05避免模型过度优化bbox位置而忽略动作语义。dfl_loss分布焦点损失保持1.0它对小目标如马头定位有帮助。# 训练命令关键参数已加注释 yolo detect train \ datahorse_behavior.yaml \ modelyolov8s.pt \ # 用s版平衡速度与精度 epochs100 \ batch16 \ # GPU显存临界点RTX 3090可跑162080Ti只能跑8 imgsz640 \ # 输入尺寸640足够捕捉马体结构 lr00.01 \ # 初始学习率比默认0.001高10倍因数据质量高 lrf0.01 \ # 最终学习率 lr0 * lrf 0.0001防止过拟合 optimizerSGD \ # SGD比Adam更稳适合小数据集 cos_lr \ # 余弦退火比step decay收敛更平滑 hsv_h0.015 \ # 颜色扰动增强草场光照变化鲁棒性 hsv_s0.7 \ # 饱和度扰动模拟不同季节草色 fliplr0.5 \ # 水平翻转马左右对称翻转合理 mosaic0.0 \ # 关闭mosaic马行为依赖完整姿态切片会破坏动作语义 close_mosaic10 \ # 前10轮关闭mosaic让模型先学基础特征 cls0.8 \ # 分类损失权重提升动作判别能力 box0.05 \ # 定位损失权重降低对bbox的过度追求 dfl1.0 # 分布焦点损失保持小目标精度参数说明batch16是RTX 3090的甜点值显存占用约10.2GBbatch32会OOMmosaic0.0是核心决策——YOLOv8默认开启mosaic增强但把吃草的马和躺卧的马拼在一起模型根本学不会动作逻辑hsv_s0.7比默认0.7大因为牧场草色从嫩绿到枯黄跨度大需要更强饱和度扰动。4.3 验证指标解读mAP0.5不是终点horse-eating的AP必须85%YOLOv8训练完会生成results.csv但别只看总mAP。打开confusion_matrix.png重点检查混淆矩阵的对角线——horse-eating行里有多少被误判为horse静止如果15%说明模型把低头吃草当成静止需加强hsv_h扰动或增加吃草特写样本。results.csv中每一行对应一个IoU阈值metrics/mAP50(B)是mAP0.5而metrics/mAP50-95(B)是0.5到0.95步长0.05的平均值。生产环境建议以mAP50为准因为牧场监控视频帧率低允许一定定位误差但动作类别绝不能错。Class IDNamePrecisionRecallmAP50mAP50-950horse0.920.890.900.721horse-eating0.870.840.850.682horse-laying0.910.860.880.713horse-standing0.890.870.880.70注意Precision低说明误报多把静止马标成吃草Recall低说明漏报多真吃草没标出来。horse-eating的Recall0.84意味着每100匹吃草马有16匹被漏检——这在畜牧监测中可能错过疾病早期征兆必须优化。5. 避坑指南马行为识别的五个真实翻车现场附解决方案5.1 现象训练loss下降但val/mAP卡在0.2验证集全标成horse原因ImageSets/Main/val.txt里写的文件名和Annotations/下XML文件名不一致例如val.txt写168bdff81f037fcc但XML是168bdff81f037fcc_jpg.rf.ea38855086be3363bb64b450c421ee63.xml。YOLO加载时找不到对应label自动跳过该图验证集只剩horse类样本mAP自然崩盘。解决用脚本批量提取Annotations/下所有XML文件名前缀生成标准val.txtls Annotations/*.xml | sed s/Annotations\///; s/_jpg\.rf\..*\.xml$// | sort -u ImageSets/Main/val.txt5.2 现象推理时horse-eating框出现大量虚警空草地、树影、石头原因数据集里horse-eating样本集中在春夏季绿草场景模型学到“绿色区域吃草”未泛化到秋冬季枯草场景。解决在训练时启用--augment参数YOLOv8.0.20它会自动添加AutoAugment策略比手动设hsv_h/s更鲁棒同时从JPEGImages/中筛选50张枯草场景图人工标注horse-eating加入训练集。5.3 现象horse-laying检测框总是偏高把马背框进去了原因标注时bndbox的ymin取值是马腹部最低点但马侧卧时腹部与地面接触ymin接近0归一化后y_center≈0.1模型学偏了。解决重标所有horse-laying样本bndbox改为框住整个马体轮廓从耳尖到尾尖ymin取马头最高点ymax取马蹄最低点——让bbox真正反映姿态而非接触点。5.4 现象导出ONNX后推理速度不升反降CPU占用100%原因YOLOv8默认导出含Softmax后处理的ONNX但OpenCV DNN模块不支持Softmax会fallback到CPU执行。解决导出时禁用后处理用--task detect --simplifyyolo export modelyolov8s.pt formatonnx simplify taskdetect然后在推理代码中手动做NMS用cv2.dnn.NMSBoxes替代torch.ops.torchvision.nms。5.5 现象同一匹马连续10帧都被标为horse-standing但实际在踱步原因数据集标注基于单帧未考虑时序。horse-standing定义是“静态站立”但踱步是微小位移单帧难区分。解决引入光流法预处理——对视频抽帧计算相邻帧光流幅值若阈值则强制标为horse-standing动态站立否则按单帧标注。这不是数据集缺陷而是使用边界提醒。6. 进阶技巧用Grad-CAM定位动作判别区域验证模型真懂“吃草”还是只认“绿色”6.1 Grad-CAM热力图生成三行代码定位模型关注点Grad-CAM能可视化CNN最后层特征图对分类决策的贡献区域。对horse-eating类热力图应高亮马头、嘴部、下颌而非整匹马或背景草丛。这一步是验证模型是否学到动作语义而非靠背景捷径。# gradcam_horse_eating.py from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image from ultralytics import YOLO import torch import cv2 import numpy as np model YOLO(runs/detect/train/weights/best.pt) target_layer model.model.model[-1].cv2.conv # YOLOv8s的检测头卷积层 cam GradCAM(modelmodel.model, target_layers[target_layer], use_cudaTrue) # 加载一张horse-eating图 img_path test_images/eating_001.jpg img cv2.imread(img_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_tensor torch.from_numpy(img_rgb).float().permute(2,0,1).unsqueeze(0) / 255.0 # 生成热力图针对horse-eating类ID1 grayscale_cam cam(input_tensorimg_tensor, targets[1]) # 叠加到原图 cam_image show_cam_on_image(img_rgb / 255.0, grayscale_cam[0], use_rgbTrue) cv2.imwrite(gradcam_eating.jpg, cv2.cvtColor(cam_image, cv2.COLOR_RGB2BGR))逻辑说明target_layer必须选检测头的卷积层不是分类头因为我们要看bbox回归的依据。targets[1]指定horse-eating类grayscale_cam[0]是热力图矩阵值越大表示该区域对判别吃草越重要。6.2 热力图诊断表四类动作的合理关注区域与异常模式动作类别合理热力区域异常模式模型没学懂应对措施horse-eating马头下1/3、口鼻、下颌、草料接触点热力图覆盖整片草地马体无响应增加草料纹理多样性样本加CutMix增强horse-laying躯干侧面轮廓、四肢关节、地面接触面热力图集中在马眼或耳朵误当静止重标样本强调躯干与地面夹角标注horse-standing前肢肩胛、后肢髋关节、颈部肌肉群、耳廓朝向热力图只在马头忽略四肢姿态添加不同角度站立图强化关节标注horse全身均匀分布无局部高亮热力图集中在马尾或腿部误当其他动作用GAN生成更多静止姿态变体避免姿态单一提示如果horse-eating热力图80%在草地上说明模型在“找草”而非“看马”此时应检查数据集是否混入大量无马纯草图——本数据集已过滤但你自己扩充时要警惕。6.3 从那以后我每次交付马行为模型都强制走一遍Grad-CAM验证不是为了炫技而是因为去年在内蒙古牧场部署时客户指着屏幕说“你们标‘吃草’的图马明明在喝水。” 我们调出Grad-CAM发现热力图全在水槽上——模型把“水槽马”当成吃草特征而水槽在标注时被忽略。从此我把Grad-CAM验证写进SOP训练完必抽20张各动作图生成热力图人工确认关注区域符合兽医常识。哪怕多花2小时也比上线后被退回强。希望帮到你。本文还有配套的精品资源点击获取
返回列表