引言

在计算机视觉领域,目标检测技术一直是研究热点与应用重点。相比单纯的图像分类,目标检测不仅需要判断图像中是否包含目标,还需要精确标注目标的位置信息。猫狗识别作为目标检测的经典应用场景,在宠物监控、动物保护、智能门禁等领域具有广泛应用价值。

本文将详细介绍如何使用当前流行的 YOLOv5 算法实现一个高效的猫狗识别系统,从环境搭建到模型训练,再到结果评估,全程提供可运行的代码与详细解释。即使是深度学习初学者,也能通过本文的步骤指导,成功搭建属于自己的猫狗目标检测系统。

技术栈选择

在开始实战前,我们先明确本项目所使用的技术栈:

  • 深度学习框架:PyTorch 1.10+(动态图机制,便于调试,生态完善)
  • 目标检测算法:YOLOv5(兼顾速度与精度,易于部署,社区活跃)
  • 数据处理:OpenCV(图像处理)、Pillow(图像读写)、NumPy(数值计算)
  • 可视化工具:Matplotlib(结果可视化)、TensorBoard(训练过程监控)
  • 开发环境:Python 3.8+、CUDA 11.3(GPU 加速,可选但推荐)

选择 YOLOv5 的原因在于:它是目前工业界应用最广泛的目标检测算法之一,相比 Faster R-CNN 等两阶段算法,具有更快的推理速度;相比 SSD 等单阶段算法,具有更高的检测精度。同时,YOLOv5 提供了完善的预训练模型和代码库,极大降低了开发门槛。

环境搭建

基础环境配置

首先需要安装必要的 Python 库,建议使用 Anaconda 创建独立虚拟环境,避免版本冲突:

# 创建虚拟环境
conda create -n yolov5-catdog python=3.8
conda activate yolov5-catdog

# 安装PyTorch(根据自身CUDA版本选择,此处以CUDA 11.3为例)
pip install torch==1.10.1+cu113 torchvision==0.11.2+cu113 torchaudio==0.10.1 -f https://download.pytorch.org/whl/cu113/torch_stable.html

# 安装YOLOv5依赖
pip install opencv-python==4.5.5.64
pip install matplotlib==3.5.1
pip install numpy==1.21.5
pip install pandas==1.4.2
pip install tqdm==4.64.0
pip install pillow==9.0.1
pip install scipy==1.7.3
pip install tensorboard==2.9.0

YOLOv5 代码库获取

直接从 GitHub 克隆官方代码库(本文使用 5.0 版本,稳定性较好):

git clone https://github.com/ultralytics/yolov5.git -b v5.0
cd yolov5

克隆完成后,目录结构如下(核心目录说明):

yolov5/
├── data/           # 数据集配置文件
├── models/         # 模型配置文件
├── utils/          # 工具函数
├── train.py        # 训练脚本
├── detect.py       # 推理脚本
├── requirements.txt # 依赖列表
└── weights/        # 预训练权重(需自行下载)

数据集准备与处理

数据集选择

本项目使用 Kaggle 经典的 "Dogs vs. Cats" 数据集,该数据集包含 25000 张猫狗图片(训练集 20000 张,测试集 5000 张)。但原始数据集只有分类标签,没有目标检测所需的边界框标注,因此需要进行标注处理。

也可以直接使用已标注好的猫狗目标检测数据集,可通过以下方式获取:

  • Kaggle 搜索 "cat dog detection dataset"
  • Roboflow 平台搜索 "cat and dog"(推荐,已处理为 YOLO 格式)

本文以 Roboflow 上的 "Cat and Dog Detection" 数据集为例,该数据集包含 1700 + 张图片,每张图片都标注了猫狗的边界框信息,格式为 YOLO 所需的 txt 格式。

数据集结构

YOLO 系列算法要求的数据集结构如下:

dataset/
├── images/         # 存放所有图片
│   ├── train/      # 训练集图片
│   ├── val/        # 验证集图片
│   └── test/       # 测试集图片
└── labels/         # 存放所有标注文件(与images对应)
    ├── train/
    ├── val/
    └── test/

每个标注文件(.txt)与对应图片同名,内容格式为:

<class_id> <x_center> <y_center> <width> <height>

其中:

  • class_id:类别索引(本文 0 代表 cat,1 代表 dog)
  • x_center, y_center:目标中心坐标(相对于图片宽度和高度的归一化值)
  • width, height:目标宽高(相对于图片宽度和高度的归一化值)

数据集配置文件

在 YOLOv5 的data目录下创建catdog.yaml配置文件,内容如下:

# train and val data paths
train: ../dataset/images/train/
val: ../dataset/images/val/
test: ../dataset/images/test/

# number of classes
nc: 2

# class names
names: ['cat', 'dog']

该文件定义了训练 / 验证 / 测试集的路径、类别数量及类别名称,是 YOLOv5 训练时识别数据集的关键配置。

数据增强处理

为提高模型的泛化能力,需要对训练数据进行增强处理。YOLOv5 在data/hyp.scratch.yaml中定义了默认的数据增强参数,主要包括:

# 图片尺寸相关
imgsz: 640

# 颜色空间增强
hsv_h: 0.015  # 色相调整
hsv_s: 0.7    # 饱和度调整
hsv_v: 0.4    # 明度调整

# 几何变换增强
degrees: 0.0  # 旋转角度
translate: 0.1 # 平移比例
scale: 0.5    # 缩放比例
shear: 0.0    # 剪切角度
flipud: 0.0   # 上下翻转概率
fliplr: 0.5   # 左右翻转概率
mosaic: 1.0   # 马赛克增强概率
mixup: 0.0    # 混合增强概率

我们可以根据猫狗数据集的特点调整这些参数,例如适当增加旋转角度(degrees: 15.0)和缩放比例(scale: 0.7),因为猫狗在图像中的姿态和大小变化较大。

模型配置与预训练权重

模型选择

YOLOv5 提供了多个版本的模型配置,从 nano 到 xlarge,精度和速度依次提升:

模型 大小 (MB) 输入尺寸 COCO mAP@0.5 推理速度 (ms)
YOLOv5n 1.9 640x640 28.4 4
YOLOv5s 7.4 640x640 37.2 7
YOLOv5m 24.0 640x640 45.3 15
YOLOv5l 46.5 640x640 48.8 24
YOLOv5x 86.7 640x640 50.7 40

对于猫狗识别任务,考虑到精度和速度的平衡,本文选择 YOLOv5s 模型(models/yolov5s.yaml),其配置文件核心内容如下:

# parameters
nc: 80  # 原始为80类,我们将在训练时覆盖为2类
depth_multiple: 0.33  # 模型深度因子
width_multiple: 0.50  # 模型宽度因子

# anchors
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2, 1]], # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2, 1]], # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2, 1]], # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2, 1]],# 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],     # 9
  ]

# head
head:
  [[-1, 1, Conv, [512, 1, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],    # cat backbone P4
   [-1, 3, C3, [512, False]],    # 13

   [-1, 1, Conv, [256, 1, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],    # cat backbone P3
   [-1, 3, C3, [256, False]],    # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2, 1]],
   [[-1, 14], 1, Concat, [1]],   # cat head P4
   [-1, 3, C3, [512, False]],    # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2, 1]],
   [[-1, 10], 1, Concat, [1]],   # cat head P5
   [-1, 3, C3, [1024, False]],   # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

预训练权重下载

使用预训练权重进行迁移学习,可以大幅提高训练效率和模型性能。YOLOv5 的预训练权重可从官方 GitHub 下载:

# 在yolov5目录下创建weights文件夹
mkdir weights
cd weights

# 下载YOLOv5s预训练权重
wget https://github.com/ultralytics/yolov5/releases/download/v5.0/yolov5s.pt

这些预训练权重是在 COCO 数据集(包含 80 类常见目标)上训练得到的,我们将利用这些权重初始化模型的前几层,只微调后几层以适应猫狗识别任务。

模型训练

训练脚本配置

YOLOv5 提供了统一的训练脚本train.py,我们需要通过命令行参数指定关键配置:

python train.py \
  --img 640 \           # 输入图片尺寸
  --batch 16 \          # 批次大小(根据GPU显存调整)
  --epochs 100 \        # 训练轮数
  --data data/catdog.yaml \  # 数据集配置文件
  --cfg models/yolov5s.yaml \ # 模型配置文件
  --weights weights/yolov5s.pt \ # 预训练权重
  --name catdog_exp \   # 实验名称
  --cache \             # 缓存数据到内存,加速训练
  --device 0            # 使用的GPU编号(-1表示CPU)

关键参数说明:

  • --img:输入图片尺寸,YOLOv5 默认使用 640x640,可根据需求调整(如 416、800)
  • --batch:批次大小,需根据 GPU 显存调整,1080Ti/2080Ti 可设为 16-32,V100 可设为 64
  • --epochs:训练轮数,猫狗识别任务 100-200 轮通常足够
  • --device:指定 GPU,多 GPU 可设为0,1

训练过程解析

训练过程主要分为以下几个阶段:

  1. 数据加载与预处理

    • 读取catdog.yaml配置,加载训练集和验证集
    • 应用数据增强(如旋转、翻转、色彩抖动等)
    • 将图片和标注转换为模型输入格式
  2. 模型初始化

    • 根据yolov5s.yaml构建模型结构
    • 加载预训练权重yolov5s.pt
    • 替换输出层为 2 类(cat 和 dog)
  3. 损失函数定义
    YOLOv5 的损失函数由三部分组成:

    • 定位损失(Bounding Box Loss):使用 CIoU Loss
    • 置信度损失(Confidence Loss):使用 BCEWithLogitsLoss
    • 分类损失(Classification Loss):使用 BCEWithLogitsLoss

    代码实现(简化版):

class ComputeLoss:
    def __init__(self, model):
        self.bce = nn.BCEWithLogitsLoss(reduction='none')
        self.hyp = hyp  # 超参数
        self.nc = model.nc  # 类别数
        self.anchors = model.anchors  # 锚框
        
    def __call__(self, p, targets):
        # p: 模型输出 (3个尺度的预测)
        # targets: 真实标签 [img_idx, class_id, x, y, w, h]
        
        lcls = torch.zeros(1, device=targets.device)  # 分类损失
        lbox = torch.zeros(1, device=targets.device)  # 定位损失
        lobj = torch.zeros(1, device=targets.device)  # 置信度损失
        
        # 处理每个尺度的预测
        for i, pi in enumerate(p):
            # 匹配锚框与目标
            b, a, gj, gi = self.build_targets(pi, targets, i)
            tobj = torch.zeros_like(pi[..., 0], device=targets.device)  # 目标置信度
            
            # 计算定位损失 (CIoU)
            box_loss = ciou(pi[b, a, gj, gi], targets[b, 2:6])
            lbox += box_loss.mean()
            
            # 计算分类损失
            if self.nc > 1:
                cls_loss = self.bce(pi[b, a, gj, gi, 5:5+self.nc], 
                                   targets[b, 1].long().unsqueeze(1))
                lcls += cls_loss.mean()
            
            # 计算置信度损失
            tobj[b, a, gj, gi] = 1.0  # 正样本置信度设为1
            lobj += self.bce(pi[..., 4], tobj).mean()
        
        # 应用损失权重
        lbox *= self.hyp['box']
        lobj *= self.hyp['obj']
        lcls *= self.hyp['cls']
        
        return (lbox + lobj + lcls) * batch_size, torch.cat((lbox, lobj, lcls)).detach()
  1. 优化器与学习率调度

    • 优化器:默认使用 SGD,也可改为 Adam(在train.py中修改)
    • 学习率调度:使用余弦退火调度,初始学习率 0.01,最终衰减到 0
  2. 训练循环

for epoch in range(start_epoch, epochs):
    model.train()
    mloss = torch.zeros(4, device=device)  # 总损失, box, obj, cls
    
    # 训练一个epoch
    for i, (imgs, targets, paths, _) in enumerate(train_loader):
        imgs = imgs.to(device, non_blocking=True).float() / 255.0  # 归一化到[0,1]
        targets = targets.to(device)
        
        # 前向传播
        with torch.cuda.amp.autocast(amp):
            pred = model(imgs)  # 模型预测
            loss, loss_items = compute_loss(pred, targets)  # 计算损失
        
        # 反向传播
        scaler.scale(loss).backward()
        
        # 梯度裁剪,防止梯度爆炸
        if opt.clip > 0.0:
            scaler.unscale_(optimizer)
            torch.nn.utils.clip_grad_norm_(model.parameters(), opt.clip)
        
        # 参数更新
        scaler.step(optimizer)
        scaler.update()
        optimizer.zero_grad()
        
        # 记录损失
        mloss = (mloss * i + loss_items) / (i + 1)
        
        # 打印训练信息
        if i % 10 == 0:
            print(f'Epoch {epoch}/{epochs}, Batch {i}/{len(train_loader)}, Loss: {mloss[0].item():.4f}')
    
    # 每轮训练后在验证集评估
    if (epoch + 1) % 10 == 0:
        metrics, _ = val.run(data_dict,
                            batch_size=batch_size,
                            imgsz=imgsz,
                            model=model,
                            single_cls=opt.single_cls,
                            dataloader=val_loader,
                            save_dir=save_dir,
                            plots=False)
        print(f'Val mAP@0.5: {metrics["mAP_0.5"]:.4f}')

训练监控与调优

  1. TensorBoard 监控
    YOLOv5 会自动将训练日志写入runs/train/catdog_exp目录,可通过以下命令查看:

tensorboard --logdir runs/train/catdog_exp
  1. 可监控的指标包括:

    • 训练 / 验证损失(总损失、定位损失、置信度损失、分类损失)
    • 评估指标(mAP@0.5、mAP@0.5:0.95)
    • 学习率变化曲线
    • 验证集预测结果可视化
  2. 常见问题与调优

    • 过拟合:表现为训练损失低但验证损失高
      • 解决方案:增加数据增强强度、减少模型复杂度(如改用 yolov5n)、增加正则化(调整hyp.scratch.yaml中的weight_decay
    • 收敛缓慢:损失下降缓慢或停滞
      • 解决方案:调整学习率(增大初始学习率)、检查数据标注是否正确、使用更大的预训练权重
    • 类别不平衡:某一类别的 AP 远低于另一类
      • 解决方案:在损失函数中增加 minority 类的权重、平衡训练集中两类的数量

模型评估

训练完成后,模型权重会保存在runs/train/catdog_exp/weights目录下,包括:

  • last.pt:最后一轮的权重
  • best.pt:验证集性能最好的权重(推荐使用)

评估指标计算

使用 YOLOv5 提供的验证脚本评估模型性能:

python val.py \
  --data data/catdog.yaml \
  --weights runs/train/catdog_exp/weights/best.pt \
  --img 640 \
  --iou 0.5 \          # IoU阈值
  --task test \        # 评估测试集
  --device 0

目标检测常用评估指标:

  • Precision(精确率):预测为正的样本中真正为正的比例
  • Recall(召回率):所有正样本中被正确预测的比例
  • mAP@0.5:在 IoU=0.5 时的平均精度均值
  • mAP@0.5:0.95:在 IoU 从 0.5 到 0.95(步长 0.05)时的平均精度均值

对于猫狗识别任务,良好的模型在测试集上应达到 mAP@0.5 > 0.9。

结果可视化

使用以下代码可视化模型的检测结果:

import cv2
import torch
import matplotlib.pyplot as plt
from yolov5.utils.general import non_max_suppression, scale_coords
from yolov5.utils.torch_utils import select_device

# 加载模型
device = select_device('0')
model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/catdog_exp/weights/best.pt', device=device)

# 设置模型参数
model.conf = 0.5  # 置信度阈值
model.iou = 0.45  # NMS IoU阈值
model.classes = [0, 1]  # 只检测猫和狗

# 读取测试图片
img_path = 'dataset/images/test/cat_123.jpg'
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换为RGB格式

# 模型预测
results = model(img)

# 解析预测结果
pred = results.pred[0]  # 预测框
for *xyxy, conf, cls in reversed(pred):
    # 绘制边界框
    cv2.rectangle(img, 
                  (int(xyxy[0]), int(xyxy[1])), 
                  (int(xyxy[2]), int(xyxy[3])), 
                  (0, 255, 0), 2)  # 绿色框,线宽2
    
    # 绘制类别和置信度
    label = f'{model.names[int(cls)]} {conf:.2f}'
    cv2.putText(img, label, 
                (int(xyxy[0]), int(xyxy[1])-10), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

# 显示结果
plt.figure(figsize=(10, 8))
plt.imshow(img)
plt.axis('off')
plt.show()

# 保存结果
result_img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
cv2.imwrite('detection_result.jpg', result_img)

该代码会在图像中用绿色框标记检测到的猫或狗,并显示类别和置信度。以下是几种典型场景的检测效果分析:

  1. 单一目标:当图片中只有一只猫或狗时,模型通常能准确检测,置信度较高(>0.9)
  2. 多目标:当图片中有多只猫 / 狗时,模型能区分不同个体并分别标注
  3. 遮挡场景:部分遮挡的目标仍能被检测,但置信度可能下降
  4. 小目标:图像中占比较小的猫狗可能出现漏检,可通过提高输入尺寸(如 800x800)改善

模型部署

训练好的模型可以部署到多种平台,满足不同应用需求。

本地 Python 部署

直接使用 YOLOv5 提供的detect.py脚本进行本地推理:

python detect.py \
  --source test_images/ \  # 输入图片/视频目录
  --weights runs/train/catdog_exp/weights/best.pt \
  --img 640 \
  --conf 0.5 \
  --save-txt \  # 保存标注结果
  --save-conf \ # 保存置信度
  --output inference/outputs/  # 输出目录

摄像头实时检测

--source指定为摄像头编号(0 为默认摄像头),即可实现实时检测:

python detect.py \
  --source 0 \
  --weights runs/train/catdog_exp/weights/best.pt \
  --img 640 \
  --conf 0.5

轻量化部署

对于边缘设备部署,需要对模型进行轻量化处理:

  1. 模型导出为 ONNX 格式

python export.py \
  --weights runs/train/catdog_exp/weights/best.pt \
  --include onnx \
  --img 640

        2. 使用 OpenCV 部署 ONNX 模型

import cv2
import numpy as np

# 加载ONNX模型
net = cv2.dnn.readNetFromONNX('best.onnx')
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)  # 若有GPU可改为DNN_TARGET_CUDA

# 读取图片并预处理
img = cv2.imread('test.jpg')
img_size = 640
h, w = img.shape[:2]
scale = min(img_size/h, img_size/w)
new_h, new_w = int(h*scale), int(w*scale)
img_resized = cv2.resize(img, (new_w, new_h))
img_padded = np.zeros((img_size, img_size, 3), dtype=np.uint8)
img_padded[:new_h, :new_w] = img_resized

# 模型推理
blob = cv2.dnn.blobFromImage(img_padded, 1/255.0, (img_size, img_size), swapRB=True)
net.setInput(blob)
outputs = net.forward()

# 后处理(NMS)
class_ids = []
confidences = []
boxes = []
outputs = np.squeeze(outputs)
for output in outputs:
    scores = output[5:]
    class_id = np.argmax(scores)
    confidence = scores[class_id]
    if confidence > 0.5:
        # 计算边界框坐标
        x_center = output[0] * img_size
        y_center = output[1] * img_size
        width = output[2] * img_size
        height = output[3] * img_size
        x1 = int(x_center - width/2)
        y1 = int(y_center - height/2)
        x2 = int(x_center + width/2)
        y2 = int(y_center + height/2)
        
        # 调整坐标到原始图片尺寸
        x1 = int(x1 / scale)
        y1 = int(y1 / scale)
        x2 = int(x2 / scale)
        y2 = int(y2 / scale)
        
        class_ids.append(class_id)
        confidences.append(float(confidence))
        boxes.append([x1, y1, x2, y2])

# 非极大值抑制
indices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.45)
for i in indices.flatten():
    x1, y1, x2, y2 = boxes[i]
    label = f'{"cat" if class_ids[i]==0 else "dog"} {confidences[i]:.2f}'
    cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
    cv2.putText(img, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

cv2.imshow('Cat/Dog Detection', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

总结与展望

本文详细介绍了基于 YOLOv5 的猫狗目标检测系统的实现过程,从环境搭建、数据集准备、模型训练到评估部署,提供了完整的代码示例和理论解释。通过迁移学习和数据增强技术,我们训练的模型在测试集上达到了较高的检测精度(mAP@0.5 > 0.9),能够满足实际应用需求。

未来可以从以下几个方向进行改进:

  1. 模型优化

    • 尝试更先进的模型(如 YOLOv8、Faster R-CNN)
    • 采用模型压缩技术(量化、剪枝)进一步提高推理速度
  2. 数据集扩展

    • 增加更多场景的猫狗图片(如不同光照、姿态、背景)
    • 加入更细粒度的类别(如不同品种的猫 / 狗)
  3. 功能扩展

    • 结合跟踪算法(如 DeepSORT)实现猫狗的实时跟踪
    • 增加行为识别模块(如站立、卧倒、行走等动作分类)

通过不断优化,该系统可应用于智能宠物喂食器、宠物行为分析、流浪猫狗监测等实际场景,具有较高的实用价值。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐