深入解析CARPK数据集:技术原理与实战应用指南

1次阅读
没有评论

共计 4307 个字符,预计需要花费 11 分钟才能阅读完成。

image.webp

CARPK 数据集技术解析与实战指南

背景痛点:高空俯视车辆检测的挑战

高空俯视视角下的车辆检测是智慧交通、停车场管理等场景的核心需求,但存在三大技术难点:

深入解析 CARPK 数据集:技术原理与实战应用指南

  1. 小目标密集:车辆在航拍图像中仅占 50×50 像素左右(约占图像的 0.1%),YOLOv5 等检测器的默认 anchor 尺寸难以匹配
  2. 透视畸变严重:镜头边缘的车辆会发生几何形变,导致标注框与实际轮廓偏移
  3. 遮挡频繁:停车场场景中车辆间距小,平均每图有 30+ 辆车的重叠遮挡

CARPK 数据集的价值在于:

  • 包含 1,500 张 4K 分辨率航拍图像(是 UAVDT 数据集的 3 倍)
  • 精确标注了 89,777 辆车的旋转边界框(Rotated BBox)
  • 覆盖昼夜、晴雨等多种光照条件

数据特性对比分析

特性 CARPK VisDrone UA-DETRAC
视角 俯视 90° 俯视 45° 平视
标注格式 PASCAL VOC COCO XML
车辆尺寸 8-120 像素 20-300 像素 50-500 像素
遮挡样本占比 68% 42% 15%

标注文件结构解析

CARPK 采用 PASCAL VOC 标准的 XML 标注,关键字段如下:

<annotation>
  <size>
    <width>4000</width>
    <height>3000</height>
  </size>
  <object>
    <name>car</name>
    <bndbox>
      <xmin>1254</xmin>
      <ymin>876</ymin>
      <xmax>1312</xmax>
      <ymax>932</ymax>
    </bndbox>
    <difficult>0</difficult>
  </object>
  <!-- 更多 object 节点 -->
</annotation>

PyTorch 实战代码

1. 数据加载器实现

from lxml import etree
from typing import List, Dict, Tuple
import cv2
import albumentations as A

class CARPKDataset(torch.utils.data.Dataset):
    """CARPK 数据集加载器,支持 Mosaic 增强"""

    def __init__(self, 
                 img_dir: str, 
                 annot_dir: str, 
                 transforms=None, 
                 use_mosaic: bool = False):
        self.img_dir = img_dir
        self.annot_dir = annot_dir
        self.transforms = transforms
        self.use_mosaic = use_mosaic
        self.img_ids = [f.stem for f in Path(img_dir).glob('*.jpg')]

        # 小目标专用增强
        self.small_obj_aug = A.Compose([A.RandomResizedCrop(1024, 1024, scale=(0.3, 1.0)),
            A.HueSaturationValue(hue_shift_limit=20),
            A.RandomBrightnessContrast(p=0.5),
        ], bbox_params=A.BboxParams(format='pascal_voc'))

    def __getitem__(self, idx):
        if self.use_mosaic and idx > 3:  # 跳过前 4 张用于 Mosaic
            return self._load_mosaic(idx)

        img, target = self._load_single(idx)

        if self.transforms:
            transformed = self.transforms(
                image=img,
                bboxes=target['boxes'],
                labels=target['labels'])
            img = transformed['image']
            target['boxes'] = torch.as_tensor(transformed['bboxes'])

        return img, target

    def _load_single(self, idx) -> Tuple[np.ndarray, Dict]:
        """加载单张图像及其标注"""
        img_id = self.img_ids[idx]
        img_path = f"{self.img_dir}/{img_id}.jpg"
        annot_path = f"{self.annot_dir}/{img_id}.xml"

        # 解析 XML
        tree = etree.parse(annot_path)
        size = tree.find('size')
        boxes, labels = [], []

        for obj in tree.xpath('//object'):
            bndbox = obj.find('bndbox')
            boxes.append([float(bndbox.find('xmin').text),
                float(bndbox.find('ymin').text),
                float(bndbox.find('xmax').text),
                float(bndbox.find('ymax').text)
            ])
            labels.append(1)  # CARPK 只有 car 类别

        img = cv2.imread(img_path)
        target = {
            'boxes': boxes,
            'labels': torch.as_tensor(labels, dtype=torch.int64),
            'image_id': torch.tensor([idx])
        }

        # 小目标增强
        if len(boxes) > 30:  # 只在密集场景应用
            augmented = self.small_obj_aug(image=img, bboxes=boxes, labels=labels)
            img = augmented['image']
            target['boxes'] = augmented['bboxes']

        return img, target

2. Mosaic 增强实现

def _load_mosaic(self, idx):
    """加载 4 张图像拼接成 Mosaic"""
    indices = [idx] + [random.randint(0, len(self)-1) for _ in range(3)]
    imgs, targets = [], []

    # 加载四角图像
    for i, index in enumerate(indices):
        img, target = self._load_single(index)
        imgs.append(img)
        targets.append(target)

    # 拼接逻辑
    mosaic_img = np.zeros((2048, 2048, 3), dtype=np.uint8)
    mosaic_boxes = []

    # 左上角
    x1a, y1a, x2a, y2a = 0, 0, 1024, 1024
    mosaic_img[y1a:y2a, x1a:x2a] = cv2.resize(imgs[0], (1024, 1024))
    for box in targets[0]['boxes']:
        mosaic_boxes.append([box[0]/4 + x1a, box[1]/4 + y1a,
            box[2]/4 + x1a, box[3]/4 + y1a
        ])

    # 其他三个区域类似处理...

    # 随机裁剪最终输出
    crop = A.RandomCrop(1024, 1024)(image=mosaic_img, bboxes=mosaic_boxes)
    return crop['image'], {'boxes': crop['bboxes'], 'labels': torch.cat([t['labels'] for t in targets])}

避坑指南

1. 畸变矫正问题

现象:模型在图像边缘的检测 AP 显著低于中心区域

解决方案

  • 在数据加载时添加镜头畸变校正
  • 使用 OpenCV 的 cv2.undistort 函数:
    camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
    dist_coeffs = np.array([k1, k2, p1, p2, k3])
    img = cv2.undistort(img, camera_matrix, dist_coeffs)

2. 空标注处理

现象:部分图像因天气原因无车辆,直接加载会报错

解决方案

# 在__getitem__中添加检查
if len(target['boxes']) == 0:
    # 返回虚拟标注
    target['boxes'] = torch.zeros((0, 4), dtype=torch.float32)
    target['labels'] = torch.zeros((0,), dtype=torch.int64)

3. 内存泄漏问题

现象:训练时内存持续增长直至 OOM

排查方法

  1. 检查数据加载器是否在 __getitem__ 中意外保留引用
  2. 使用 tracemalloc 定位泄漏点:
    import tracemalloc
    tracemalloc.start()
    # ... 运行训练代码...
    snapshot = tracemalloc.take_snapshot()
    for stat in snapshot.statistics('lineno')[:10]:
        print(stat)

性能优化建议

8GB 显存配置下的训练技巧

  1. Batch Size 选择
  2. YOLOv5s 模型:batch_size=8
  3. Faster R-CNN:batch_size=2 + 梯度累积 4 次

  4. 混合精度训练

    scaler = torch.cuda.amp.GradScaler()
    
    with torch.cuda.amp.autocast():
        outputs = model(inputs)
        loss = criterion(outputs, targets)
    
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

  5. 数据加载优化

  6. 使用 torchvision.ops.drop_duplicate_boxes 过滤重复标注
  7. 启用 pin_memory=Truenum_workers=4

延伸思考

  1. 标注质量评估:当前 mAP 指标是否足以反映小目标检测质量?是否需要引入:
  2. 像素级 IoU(对 50px 以下目标更敏感)
  3. 边缘重合度指标

  4. 跨摄像头适应:当部署到新停车场时,如何解决:

  5. 摄像头参数差异导致的域偏移
  6. 不同光照条件下的颜色分布变化

结语

CARPK 数据集为高空车辆检测研究提供了宝贵的基准数据,但其小目标、高密度的特性仍需特殊处理。通过本文介绍的数据加载、增强和优化技巧,开发者可以快速构建 baseline 模型。建议在实际项目中结合具体业务场景(如停车场车位统计、交通流量监控等)进一步优化模型架构。

正文完
 0
评论(没有评论)