读研日记-RAP-SAM笔记

第一部分:训练数据流的精密解剖 (The Training Flow)

这是整个项目最复杂、最核心的部分。我们将以 configs/_base_/datasets/coco_panoptic_video_yt19_sam.py 中定义的 sam_dataset 为例,追踪一个数据样本的旅程。

旅程起点:MMDetWoker (数据加载子进程)

当训练开始时,PyTorch 的 DataLoader 会创建多个子进程(num_workers=2),每个子进程独立地执行数据加载和预处理。

Step 1: __getitem__ - 获取原始数据样本

在一个子进程中,CocoPanopticOVDataset 的 __getitem__ 方法被调用。

  • 动作:根据传入的索引 idx,从 self.data_list 中获取一个样本的信息字典。这个信息字典包含了 img_path (如 'data/coco/train2017/00000012345.jpg') 和 seg_map_path (如 'data/coco/annotations/panoptic_train2017/00000012345.png') 以及 segments_info (一个列表,包含这张图里每个物体的id, category_id, isthing 等元数据)。
  • 产出:一个包含了文件路径和元数据的 Python 字典,我们称之为 results。
    results = {'img_path': '...','seg_map_path': '...','segments_info': [...],# ... 其他元数据}

Step 2: sam_train_pipeline - 数据增强与转换流水线

results 字典被依次传递给 sam_train_pipeline 中的每一个转换操作。

  1. LoadImageFromFile:
  • 动作:根据 results['img_path'] 读取图片文件,解码成一个 H x W x 3 的 NumPy 数组,并添加到字典中。
  • results 字典更新:新增 img (NumPy 数组), img_shape, ori_shape 等键。
  1. LoadPanopticAnnotationsAll:
  • 动作:根据 results['seg_map_path'] 和 results['segments_info'],读取全景分割的 png 标注图。它会解析这张图,为每个物体实例生成一个二值的 gt_masks (掩码),并从元数据中提取 gt_bboxes (边界框) 和 gt_labels (类别标签)。
  • results 字典更新:新增 gt_masks, gt_bboxes, gt_labels, gt_seg_map 等键。这些都是“标准答案”。
  1. RandomFlip, RandomResize, RandomCrop:
  • 动作:这是数据增强的核心。
  • RandomFlip: 以50%的概率对 results['img'] 进行水平翻转。关键点:它不仅翻转图像,还会同步翻转 results['gt_masks'] 和 results['gt_bboxes'],确保数据和标注的一致性。
  • RandomResize & RandomCrop: 随机缩放和裁剪图像,同样,gt_masks 和 gt_bboxes 也会被相应地调整。
  • results 字典内容变化:img, gt_masks, gt_bboxes 的值和维度都发生了改变。
  1. FilterAnnotationsHB:
  • 动作:一个过滤器。它会遍历 gt_masks,如果某个物体的掩码面积小于32个像素 (min_gt_mask_area=32),它就会被认为是一个无效的、太小的物体,从而被从 gt_masks, gt_bboxes, gt_labels 中剔除。
  • 目的:防止模型学习由数据标注错误或数据增强产生的微小物体。
  1. GeneratePoint:
  • 动作:这是针对 SAM 的一个关键步骤。它会遍历 results['gt_masks'] 中的每一个真实物体掩码,并在每个掩码内部随机采样 N 个点(这里是30个点),模拟用户的交互式点击。
  • results 字典更新:新增 gt_points 键,其值为一个包含了所有模拟点击点坐标的列表。
  1. PackDetInputs:
  • 动作:这是 MMDetection 框架中的一个“打包工。它负责将 results 字典中的数据从 NumPy 数组和列表,转换成 PyTorch 的 Tensor。然后,它将这些 Tensor 组织成一个标准化的数据结构 DetDataSample。
  • 产出:一个 DetDataSample 对象。这个对象内部包含:
  • .gt_instances: 一个 InstanceData 对象,里面存放着 bboxes, labels, masks, points 的 Tensor。
  • .gt_sem_seg: 一个 PixelData 对象,存放着 gt_seg_map 的 Tensor。
  • 同时,原始的图像 Tensor 被单独提出来。
  • __getitem__ 的最终返回值: {'inputs': img_tensor, 'data_samples': data_sample_object}。

Step 3: collate_fn - 从样本到批次

DataLoader 从每个子进程收集到处理好的样本字典 {'inputs': ..., 'data_samples': ...},然后使用 collate_fn 函数将它们合并成一个批次 batch。

  • 动作:
  • 它将所有样本的 inputs (图像 Tensor)堆叠(stack)成一个 (N, 3, H, W) 的大 Tensor,其中 N 是 batch_size。
  • 它将所有的 data_samples (DetDataSample 对象) 收集到一个 Python 列表中。
  • 产出: 一个字典,batch = {'inputs': stacked_img_tensor, 'data_samples': [sample1, sample2, ...]}。这个批次数据被从 CPU 转移到 GPU。

Step 4: VideoPromptDataPreprocessor - 模型入口的最后一道工序

批次数据 batch 被送入模型 RapSAM 的 forward 方法,首先迎接它的是 data_preprocessor。

  • 动作:
  • 对 batch['inputs'] (图像 Tensor) 进行归一化 (减均值、除标准差)。
  • 执行 BatchFixedSizePad:由于数据增强中的随机裁剪,一个批次内的图像尺寸可能不同。这个操作会将批次内所有的图像和 data_samples 中的掩码,全部填充(pad)到批次中最大的尺寸,或者一个固定的尺寸 (image_size)。填充值分别为 0 和 255。
  • 产出: 一个尺寸规整、数值归一化的批次字典,准备进入模型的核心。

Step 5: RapSAM.forward() - 模型内的计算

为了理解这一步,我需要查看 seg/models/detectors/rapsam.py 的代码。

Read rapsam.py

从 rapsam.py 的代码看,RapSAM 继承自 Mask2formerVideo。Mask2formerVideo 的 forward 方法(在基类中)会定义核心的训练逻辑。结合 MMDetection 的标准实践,我们可以推断出接下来的步骤:

  • 动作: RapSAM.forward(inputs, data_samples, mode='loss') 被调用。
  1. 提取特征 (Backbone -> Neck): inputs (批次图像 Tensor) 首先流经 self.backbone (ResNet-50) 和 self.neck (YOSONeck),输出多层级的特征图 feats。
 feats = self.neck(self.backbone(inputs))

2.计算损失 (Head): 接着,forward 方法会调用 panoptic_head 的 loss 方法,并将特征图 feats 和标准答案 data_samples 传进去。

feats = self.neck(self.backbone(inputs))
  • 在 RapSAMVideoHead.loss() 内部:
  1. 预测: panoptic_head 会根据 feats 生成一系列的预测,包括 pred_logits (预测类别) 和 pred_masks (预测掩码)。
  2. 匹配 (Assigner): HungarianAssigner (匈牙利匹配器) 会上场。它会将模型的 N 个预测结果,与 data_samples 中的 M 个真实物体(Ground Truth)进行一对一的最佳匹配。匹配的依据是 match_costs(分类损失、Dice损失等的加权和)。
  3. 计算损失: 匹配完成后,对于每一对匹配上的(预测,真实物体),代码会使用 CrossEntropyLoss 计算分类损失,使用 DiceLoss 和 CrossEntropyLoss 计算掩码损失。
  • 产出: forward 方法最终返回一个包含所有损失项的字典。
losses = self.panoptic_head.loss(feats, data_samples)

Step 6: 反向传播与优化

  • 动作: MMDetection 的训练引擎(Runner)接收到这个 losses 字典,将所有损失值相加得到 total_loss。然后执行 total_loss.backward() 计算梯度,再执行 optimizer.step() 更新模型 RapSAM 的所有权重。

至此,一个数据样本的训练旅程,从磁盘上的文件,到最终驱动模型参数更新,就完整地结束了。这个旅程会在整个数据集上重复进行多个 epoch。


第二部分:推理数据流的精密解剖 (The Inference Flow)

推理流程要简单直接得多,因为它不需要标准答案,也没有反向传播。我们以 demo/demo.py 为例。

Step 1: 初始化 DetInferencer

  • 动作: inferencer = DetInferencer(model=config_file, weights=pth_file)。
  1. 构建模型: 根据 eval_rap_sam_coco.py 配置文件在 CPU 上构建出 RapSAM 的模型骨架。
  2. 加载权重: 读取 rapsam_r50_12e.pth 权重文件,并将参数加载到模型骨架中。
  3. 移动到设备: 将加载好参数的模型移动到指定的 device (如 'cuda:0'),并设置为评估模式 (model.eval())。这会关闭 Dropout 等只在训练时使用的层。

Step 2: inferencer(inputs) - 执行调用

  • 动作: 当你调用 inferencer('demo/demo.jpg') 时,DetInferencer 内部会执行一个简化的数据处理流水线。这个流水线由配置文件中的 test_pipeline 决定。我需要看一下 eval_rap_sam_coco.py 来确定这个流水线。

Read eval_rap_sam_coco.py

好的,eval_rap_sam_coco.py 文件导入了 .._base_.datasets.coco_panoptic_lsj,推理时的数据处理流程(test_pipeline)定义在那里。这个流程通常会是这样的:

  1. LoadImageFromFile: 读取 demo/demo.jpg。
  2. Resize: 将图片缩放到一个固定的尺寸,比如 (1280, 736),但保持原始图像的宽高比,多余部分进行填充。
  3. PackDetInputs: 和训练时一样,打包数据。但这次 data_samples 里是空的,没有 gt_instances 等真值信息。

Step 3: model.forward() - 模型推理

  • 动作: 预处理后的数据被送入 model.forward(inputs, data_samples, mode='predict')。
  1. 提取特征: 和训练时一样,inputs 图像 Tensor 流经 backbone 和 neck,得到特征图 feats。
  2. 执行预测: forward 方法会调用 panoptic_head 的 predict 方法。
 results = self.panoptic_head.predict(feats, data_samples)
  1. 在 predict 内部,模型会生成 pred_logits 和 pred_masks。因为它没有真值可以匹配,所以它会直接输出所有它认为存在的物体。
  • 产出: predict 方法返回一个 DetDataSample 对象的列表,每个对象代表一张输入图片的结果。这个对象内部的 .pred_panoptic_seg 属性包含了预测的全景分割图,而 .pred_instances 属性则包含了每个被检测到物体的 masks, labels, 和 scores。

Step 4: 后处理与可视化

  • 动作: DetInferencer 接收到模型返回的结果列表。
  1. 过滤: 它会遍历 pred_instances 中的每个物体,如果一个物体的 scores 低于 pred_score_thr (比如0.3),这个物体就会被丢弃。
  2. 可视化: DetInferencer 内置的 Visualizer 会接手。它会在原始的、未经缩放的输入图片上,根据留下的 masks 绘制分割区域,并根据 labels 为不同物体涂上不同的颜色,还可以在旁边写上类别名称和分数。

Step 5: 输出

  • 动作: DetInferencer 将这张被画上了分割结果的图片,保存到 --out-dir 指定的目录中,比如 outputs/vis/demo.jpg。