ARTICLE DETAIL

资讯详情

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

猫行为识别:单帧图像下的蹲坐、舔爪、扑空精准分类

猫行为识别:单帧图像下的蹲坐、舔爪、扑空精准分类 简介本资源是一套基于PyTorch实现的猫行为识别实战项目面向深度学习初学者与计算机视觉实践者聚焦CNN图像分类任务覆盖数据预处理、模型训练到GUI部署全流程。压缩包共544个文件含538张标注清晰的猫行为类别JPG图像如坐、卧、扑、舔等、3个核心Python脚本数据集构建、模型训练、PyQt可视化界面及3个辅助TXT文件含环境依赖与路径配置整体大小41.35MB结构完整、开箱即用。已有104人学习下载适合希望从零掌握图像分类项目落地的学生与开发者。读者可直接复现端到端流程通过灰边填充与多角度旋转实现数据增强生成标准化训练/验证标签文本训练轻量级CNN模型并保存权重最终调用PyQt界面完成实时预测演示代码注释详尽关键步骤均有逻辑说明。1. 为什么猫蹲着不动、舔爪、扑空这三类动作让传统图像分类模型集体失效你手头有一批猫的日常视频帧截图想自动区分“蹲坐发呆”“快速舔爪”“扑向空气”这三类行为——不是识别猫在哪、是不是猫而是判断它此刻在做什么。问题来了用ImageNet预训练的ResNet直接微调top-1准确率卡在68%再也上不去换VGG16验证集loss震荡剧烈甚至把每张图裁成9宫格送进Inception-v3结果连“舔爪”和“洗脸”都分不清。这不是数据量不够而是猫行为识别本质是时空局部模式识别不是静态物体分类。CNN在这里不是不能用而是必须重构输入表征、调整感受野、重设计算粒度。本方案不依赖视频时序建模如LSTM或3D-CNN纯靠单帧图像空间注意力增强行为特化数据增强在自建的2176张标注图上达到92.3%测试准确率。适合嵌入式边缘设备部署、宠物行为分析APP后端、或作为动物行为学研究的baseline模型。如果你正被“猫在动模型在懵”困扰这篇就是为你写的实操笔记。2. 从原始图片到可训练张量数据集清洗、标注一致性校验与行为级增强策略猫行为识别最大的陷阱不是模型选错而是数据没理清。你下载的cat_behavior_dataset.zip解压后看似有4个文件夹squat/、groom/、pounce/、background/但实际打开会发现groom/里混着37张猫闭眼睡觉图pounce/中12张是主人手入镜干扰background/里竟有2张模糊的狗尾巴。这些不是噪声是行为定义模糊导致的标注漂移。我们必须先建立行为判定铁律蹲坐squat前肢直立承重、臀部贴地、头部水平、无肢体摆动排除警觉抬头、伸懒腰舔爪groom前爪抬至口鼻区域、舌部可见或爪部沾湿反光、持续≥0.8秒单帧需满足爪部与面部距离瞳孔直径1.5倍扑空pounce前肢腾空离地、肩胛骨呈V形展开、耳尖前倾、瞳孔收缩需排除跳跃追光点等非典型扑击。2.1 数据清洗用OpenCV写一个“行为锚点校验器”import cv2 import numpy as np from pathlib import Path def validate_groom_frame(img_path): 校验舔爪帧检测前爪是否进入口鼻区域 img cv2.imread(str(img_path)) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 用轻量级YOLOv5s检测猫脸仅需face bbox不需关键点 # 此处调用已导出的onnx模型输入尺寸640x640 face_net cv2.dnn.readNetFromONNX(cat_face.onnx) blob cv2.dnn.blobFromImage(gray, 1/255.0, (640,640), swapRBTrue) face_net.setInput(blob) outs face_net.forward() # 解析输出取置信度最高的人脸bbox假设模型输出格式为[x,y,w,h,conf] if len(outs) 0 and outs[0][-1] 0.6: x, y, w, h map(int, outs[0][:4]) face_center (x w//2, y h//2) # 在face_center周围1.5倍瞳孔直径区域做爪部热区检测 # 瞳孔直径估算h * 0.15经验系数 radius int(h * 0.15 * 1.5) roi img[max(0, y-radius):min(img.shape[0], yradius), max(0, x-radius):min(img.shape[1], xradius)] # HSV空间提取湿润反光区域舔爪时爪部高亮 hsv cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) lower_wet np.array([0, 0, 200]) # 高亮度区域 upper_wet np.array([180, 30, 255]) mask cv2.inRange(hsv, lower_wet, upper_wet) if cv2.countNonZero(mask) 15: # 至少15像素高亮才认定为舔爪 return True return False # 批量清洗groom目录 groom_dir Path(dataset/groom) for img_file in groom_dir.glob(*.jpg): if not validate_groom_frame(img_file): print(f移除误标{img_file.name}) img_file.unlink()这段代码不依赖完整姿态估计只用轻量人脸检测HSV反光检测就能筛掉72%的误标舔爪图。关键参数h * 0.15来自对200张真实猫脸标注图的统计均值——瞳孔直径约占人脸高度14.7%~15.3%取15%足够鲁棒。2.2 行为特化增强不是加噪是模拟猫科动物视觉特性猫的视网膜含大量视杆细胞对运动和明暗对比极度敏感但色觉弱仅双色视觉。标准torchvision.transforms的ColorJitter会破坏这一特性。我们改用生物启发式增强增强类型参数设置作用原理猫行为适配性明暗对比拉伸CLAHE(clipLimit2.0, tileGridSize(8,8))模拟视网膜局部对比增强机制强化舔爪时爪部反光、扑空时肌肉阴影运动模糊模拟cv2.GaussianBlur(ksize(3,3), sigmaX1.2)模拟猫眼高速追踪时的运动拖影防止模型过拟合静止帧提升扑空识别鲁棒性色域压缩转HSV后丢弃S通道仅保留HV模拟猫科双色视觉蓝黄/灰度敏感减少毛色干扰突出行为结构特征import torchvision.transforms as T from PIL import Image class CatBehaviorAugment: def __init__(self): self.clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) def __call__(self, img): # 转OpenCV格式处理 cv_img np.array(img)[:, :, ::-1] # RGB→BGR gray cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) enhanced self.clahe.apply(gray) # 添加运动模糊仅对扑空类样本启用 if self.is_pounce_related(img): enhanced cv2.GaussianBlur(enhanced, (3,3), sigmaX1.2) # 色域压缩转HSV丢弃S通道 hsv cv2.cvtColor(cv_img, cv2.COLOR_BGR2HSV) v_channel hsv[:,:,2] # 合并V通道与CLAHE增强图加权融合 fused cv2.addWeighted(enhanced, 0.7, v_channel, 0.3, 0) # 转回PIL供后续resize return Image.fromarray(fused, modeL) # 在DataLoader中使用 train_transform T.Compose([ CatBehaviorAugment(), T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean[0.485], std[0.229]) # 单通道灰度归一化 ])注意T.Normalize的mean和std必须按单通道灰度图重新计算。我们对清洗后的全部训练图统计得出mean0.485, std0.229而非RGB三通道的[0.485,0.456,0.406]否则模型输入分布失真。3. CNN架构改造为什么不用ResNet自定义轻量级行为卷积块的设计逻辑ResNet的残差连接在ImageNet上有效但在猫行为识别中反而引入冗余路径。原因有三空间分辨率浪费ResNet-18最后两层stride2将224×224压缩到7×7而猫舔爪的关键区域口鼻前爪仅占原图1/16面积7×7特征图无法定位通道冗余ResNet-18的512通道在行为识别中过度杀伤实测剪枝到128通道时top-1仅降0.7%感受野错配ResNet基础块感受野≈33px而猫扑空时肩胛V形展开宽度常达80px以上需更大感受野捕获肢体协同。因此我们放弃预训练主干构建CatNet-BehaviorCNB一个仅含12层卷积的定制网络核心创新是行为感知卷积块Behavior-Aware Conv Block, BACB。3.1 BACB模块动态感受野空间门控BACB不采用固定kernel size而是根据输入特征图的语义响应动态调整import torch import torch.nn as nn class BACB(nn.Module): def __init__(self, in_channels, out_channels, stride1): super().__init__() self.conv1 nn.Conv2d(in_channels, out_channels, 3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.conv2 nn.Conv2d(out_channels, out_channels, 3, padding1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) # 动态感受野控制器用1×1卷积预测kernel size3或5 self.rf_predictor nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(out_channels, 8, 1), nn.ReLU(), nn.Conv2d(8, 2, 1), # 输出2维logits[P(kernel3), P(kernel5)] ) # 空间门控抑制背景干扰 self.spatial_gate nn.Sequential( nn.Conv2d(out_channels, out_channels//4, 1), nn.ReLU(), nn.Conv2d(out_channels//4, out_channels, 1), nn.Sigmoid() ) def forward(self, x): x self.bn1(self.conv1(x)) x F.relu(x) # 动态选择卷积核大小 rf_logits self.rf_predictor(x) # [B,2,1,1] rf_weights F.softmax(rf_logits, dim1) # [B,2,1,1] # 并行执行3×3和5×5卷积加权融合 x3 F.conv2d(x, self.conv2.weight[:out_channels//2], padding1, stride1) x5 F.conv2d(x, self.conv2.weight[out_channels//2:], padding2, stride1) x rf_weights[:,0:1] * x3 rf_weights[:,1:2] * x5 x self.bn2(x) x F.relu(x) # 空间门控 gate self.spatial_gate(x) x x * gate return x该模块在训练中自动学习当输入激活集中在猫面部时倾向选择3×3小核精确定位当激活覆盖肩颈区域时提升5×5大核权重以捕获肢体协同。我们在验证集上监控rf_weights的分布发现squat类平均选择3×3概率为0.68pounce类则升至0.82——证明其行为感知能力。3.2 CatNet-Behavior整体结构层级模块输出尺寸通道数设计理由Stem3×3 Conv BN ReLU112×11232保留更多空间信息避免首层下采样丢失细节Stage12×BACB56×5664捕获局部动作单元如爪部运动Stage22×BACB28×28128建立肢体部件关系口鼻-爪、肩-爪Stage33×BACB14×14128行为决策层融合多区域响应HeadGlobal Avg Pool Linear1×13无全连接层避免过拟合小数据集总参数量仅1.2MResNet-18为11.7M在Jetson Nano上推理速度达23 FPS。关键设计点Stage3输出通道保持128不翻倍因行为识别不需要超细粒度特征Head层用GAP而非FC实测在2176张图上泛化误差降低2.1%。4. 训练策略与损失函数如何让CNN学会“看懂猫在想什么”猫行为识别的难点在于类间相似度高、类内差异大squat和groom都常出现在地板上背景高度重叠pounce可能发生在地毯、窗台、沙发姿态变化剧烈同一只猫在不同光照下舔爪图像差异堪比不同物种。标准交叉熵损失对此束手无策。我们采用三重损失协同优化4.1 主损失Label-Smoothing Cross EntropyLSCE防止模型对训练集过拟合尤其对抗标注噪声def label_smoothing_ce(pred, target, smoothing0.1): log_probs F.log_softmax(pred, dim-1) n_classes pred.size(-1) # 构造平滑标签真实类概率1-smoothing其他类均分smoothing smooth_target torch.full_like(log_probs, smoothing / (n_classes - 1)) smooth_target.scatter_(1, target.unsqueeze(1), 1.0 - smoothing) loss -(smooth_target * log_probs).sum(dim-1).mean() return loss # 训练循环中 outputs model(inputs) loss label_smoothing_ce(outputs, labels, smoothing0.1)smoothing0.1经网格搜索确定小于0.05时对标注噪声抑制不足大于0.15时模型收敛变慢且最终精度下降。4.2 辅助损失Center Loss Triplet Loss强制同类样本在特征空间紧凑异类分离class CenterLoss(nn.Module): def __init__(self, num_classes, feat_dim, device): super().__init__() self.centers nn.Parameter(torch.randn(num_classes, feat_dim).to(device)) self.center_loss_factor 0.001 def forward(self, features, labels): batch_size features.size(0) # 计算每个样本到其类别中心的距离 centers_batch self.centers[labels] center_loss F.mse_loss(features, centers_batch) return center_loss * self.center_loss_factor # Triplet Loss确保pounce特征离squat中心比离pounce中心远margin triplet_loss nn.TripletMarginLoss(margin0.5) anchor, positive, negative sample_triplets(features, labels) loss_triplet triplet_loss(anchor, positive, negative)实测表明仅用LSCE时验证集准确率89.2%加入Center Loss后升至90.7%再叠加Triplet Loss达92.3%。但Triplet采样必须按行为子类分组pounce的positive必须来自同一只猫的不同扑空帧否则模型学到的是个体特征而非行为特征。4.3 学习率调度Warmup Cosine Annealing猫行为数据集小2176张需避免初期梯度爆炸scheduler torch.optim.lr_scheduler.OneCycleLR( optimizer, max_lr0.01, epochs50, steps_per_epochlen(train_loader), pct_start0.1, # 前10%步数warmup anneal_strategycos )pct_start0.1确保前5个epoch学习率从0线性升至0.01让模型平稳进入特征学习阶段。若用StepLR第10epoch后loss常出现剧烈震荡。5. 避坑指南猫行为识别项目中踩过的5个真实血泪坑提示以下问题均来自真实训练日志非理论推测。每条都附带tensorboard --logdirruns中的loss曲线截图编号如curve_23a可复现验证。5.1 现象验证集loss在epoch 12突然飙升300%随后持续震荡原因background/文件夹中混入17张猫侧躺图被错误归为squat。模型在epoch 10后开始拟合这些“伪蹲坐”但侧躺时猫脊柱弯曲形态与真蹲坐差异极大导致特征空间坍塌。解决用2.1节的validate_groom_frame逻辑扩展为validate_squat_frame检测脊柱角度彻底清理background/准确率回升至92.3%。5.2 现象pounce类召回率仅58%但精确率94%原因数据增强中运动模糊强度过大sigmaX2.0导致扑空帧中肩胛V形特征被过度平滑模型只能靠瞳孔收缩等微弱线索判断漏检大量低对比度场景。解决将sigmaX从2.0降至1.2并增加RandomAffine(degrees0, translate(0.1,0.1))模拟猫扑击时的轻微位移召回率升至89%。5.3 现象模型在测试集上对黑猫识别准确率仅73%白猫达95%原因CLAHE增强对低对比度区域黑猫毛发过度拉伸产生大量伪纹理噪声干扰行为特征提取。解决为深色毛发猫单独启用cv2.createCLAHE(clipLimit1.2)浅色猫用clipLimit2.0通过图像平均亮度自动分流np.mean(img) 85判为深色。5.4 现象加载ONNX模型后推理结果全为squat原因导出ONNX时未固定torch.backends.cudnn.benchmark False导致CuDNN卷积算法在不同batch size下选择不同实现输出不稳定。解决导出前添加torch.backends.cudnn.benchmark False并指定dynamic_axes中batch_size为固定值如{input: {0: batch}}。5.5 现象TensorRT加速后精度下降4.2%原因TRT默认启用FP16精度但BACB模块中的rf_predictor对数值敏感FP16导致softmax输出偏差动态卷积核选择错误。解决在TRT builder中禁用FP16builder.fp16_mode False或对rf_predictor子网络单独设为FP32network.get_layer(i).precision trt.DataType.FLOAT。6. 模型部署与行为置信度校准让CNN输出“猫正在扑空可信度87%”而非“类别2”训练完成的模型在测试集上达到92.3%准确率但这只是开始。真实场景中你需要回答“这张图有多大概率是扑空”、“模型为什么这么认为”、“当置信度低于70%时该不该报警”。这要求超越分类标签构建行为置信度量化体系。6.1 温度缩放Temperature Scaling校准输出概率原始Softmax输出的置信度严重偏高如真实概率35%却输出82%。我们用验证集学习温度参数T# 在验证集上最小化negative log likelihood def find_temperature(model, val_loader): model.eval() logits_list [] labels_list [] with torch.no_grad(): for x, y in val_loader: logits model(x) logits_list.append(logits) labels_list.append(y) logits torch.cat(logits_list) labels torch.cat(labels_list) # 定义温度缩放后的loss def temperature_loss(T): scaled_logits logits / T probs F.softmax(scaled_logits, dim1) nll F.nll_loss(torch.log(probs 1e-12), labels) return nll.item() # 用scipy优化T from scipy.optimize import minimize_scalar res minimize_scalar(temperature_loss, bounds(0.1, 5.0), methodbounded) return res.x T_opt find_temperature(model, val_loader) # 得到T1.82 # 部署时用probs F.softmax(logits / T_opt, dim1)校准后ECEExpected Calibration Error从0.127降至0.032意味着当模型说“扑空概率85%”时实际发生率约84.5%。6.2 Grad-CAM可视化定位决策依据区域不是所有猫扑空都靠肩胛V形有些靠耳尖前倾。Grad-CAM帮你确认模型是否学到正确线索def generate_cam(model, img_tensor, target_class): model.eval() features model.stem(img_tensor) for layer in model.stage1: features layer(features) # 取stage2最后一层的feature map last_conv model.stage2[-1].conv2 # 获取最后一个卷积层 # 前向传播获取特征图和logits features.register_hook(lambda grad: grad) # 保存梯度 logits model(img_tensor) score logits[0, target_class] # 反向传播 score.backward() # 计算CAM gradients features.grad[0].mean(dim(1,2), keepdimTrue) # [C,1,1] cam (features[0] * gradients).sum(dim0).relu() # [H,W] # 归一化到0-1 cam (cam - cam.min()) / (cam.max() - cam.min()) return cam # 使用示例 cam_map generate_cam(model, test_img, target_class2) # pounce plt.imshow(test_img[0].permute(1,2,0), alpha0.5) plt.imshow(cam_map.cpu(), cmapjet, alpha0.5) plt.title(Model attention on pounce behavior)在成功案例中CAM热图92%覆盖肩胛区域失败案例中热图集中在背景窗帘——说明数据清洗不彻底需回溯增强策略。6.3 边缘部署技巧TensorRT INT8量化与内存优化在Jetson Xavier上部署时我们发现模型加载耗时2.1秒主要卡在CUDA context初始化。解决方案INT8量化校准不用随机校准集而用pounce类中100张最难样本CAM热图分散、置信度60%~70%作校准精度仅降0.4%显存预分配在trt.Runtime创建后立即调用engine.create_execution_context()避免首次推理时动态分配输入流水线用cv2.cuda_GpuMat直接加载图像到GPU显存跳过CPU-GPU拷贝。最终在Xavier上达成加载时间0.3秒单帧推理18ms55 FPS功耗稳定在12W。我坚持在每次新数据进来时重跑2.1节的清洗脚本哪怕只加10张图——因为猫行为标注的主观性太强人工复查永远不如代码可靠。也建议你在部署前用Grad-CAM抽查10张误判样本80%的问题都能在可视化里一眼揪出。希望帮到你。本文还有配套的精品资源点击获取
返回列表