
简介本资源是面向计算机视觉与三维目标检测研究者的实用工具包专为适配mmdetection3d框架而开发的SUN RGB-D数据集Python预处理方案解决了原版Matlab脚本在跨平台部署、环境依赖及可维护性方面的局限适用于深度学习初学者至进阶开发者开展室内场景3D检测实验与模型训练。压缩包共含2个核心Python脚本extract_split.py负责数据集划分train/val/testextract_data_v2.py完成RGB-D图像与3D标注包括边界框、类别、朝向等的结构化解析与COCO-style格式转换全部代码轻量简洁总大小仅3KB便于快速集成与二次开发。目前已有1341人学习下载资源由作者suiyingy持续维护代码逻辑清晰、注释完备可直接用于数据准备流程显著降低SUN RGB-D数据接入mmdetection3d的技术门槛并为后续模型调优与结果复现提供可靠输入基础。1. 为什么 SUN RGB-D 数据集在 mmdetection 中不能直接训练——预处理不是“复制粘贴”而是结构对齐SUN RGB-D 是一个面向室内场景理解的多模态数据集包含 RGB 图像、深度图、边界框标注2D bbox、3D box、语义分割掩码及场景分类标签。但 mmdetection 作为目标检测框架只消费标准 COCO 或 Pascal VOC 格式的 2D 检测标注即每张图像对应一个 JSON 文件COCO或 XML 文件VOC其中明确列出类别 ID、归一化/像素级 bbox 坐标、是否被截断等字段。而 SUN RGB-D 原始数据以.mat和.txt混合格式存储bbox 坐标分散在多个字段中如gt_bbox_2d、gt_class、gt_orientation且存在大量无效样本空标注、深度图缺失、类别映射不一致。直接将原始路径喂给 mmdetection 的CocoDataset类会触发KeyError: annotations或IndexError: list index out of range。这不是环境配置问题而是数据 schema 与框架契约之间的结构性错位。本文聚焦 Python 层面的可复现预处理流程从原始 SUN RGB-D 下载包出发用纯 Python OpenCV SciPy 解析.mat统一生成符合 mmdetection v3.xCocoDataset加载协议的train.json/val.json并保留深度图路径供后续多模态扩展。适合已部署好 mmdetection 环境、正卡在“数据进不来”环节的算法工程师和研究生。2. 解析 SUN RGB-D 原始 .mat 标注用 scipy.io.loadmat 提取 bbox 与类别避开 MATLAB 依赖SUN RGB-D 的标注核心存储在SUNRGBDv2/annotation/目录下的.mat文件中每个文件对应一张图像例如sunrgbd_train_000001.mat。这些文件是 MATLAB v7.3 格式HDF5 封装不能用scipy.io.loadmat直接读取会报NotImplementedError: Please use h5py to load this file。必须改用h5py显式打开并注意 MATLAB 的列优先索引与 Python 行优先索引的坐标翻转问题。2.1 安装必要依赖并验证数据结构pip install opencv-python h5py numpy scikit-image tqdm提示不要用scipy.io.loadmat处理 SUN RGB-D 的.mat文件。该数据集使用 MATLAB-v7.3保存loadmat仅支持-v7及更早版本。强行调用会导致ValueError: Unable to open object (bad object header version)。验证原始数据目录结构是否合规ls -l SUNRGBDv2/ # 应包含 # annotation/ # .mat 标注文件 # image/ # RGB 图像.jpg # depth/ # 深度图.png需转为 uint16 # train.txt # 训练集图像名列表无后缀 # val.txt # 验证集图像名列表2.2 用 h5py 解析单个 .mat 文件提取 2D bbox 和类别以下函数解析一个.mat文件返回(x1, y1, x2, y2)格式的 bbox 列表和对应的类别 ID 列表import h5py import numpy as np def parse_mat_annotation(mat_path): 解析 SUN RGB-D .mat 标注文件返回 bbox 和类别列表 with h5py.File(mat_path, r) as f: # MATLAB 中 gt_bbox_2d 是 shape(4, N) 的数组列优先存储 [x1,y1,x2,y2] # h5py 读出为 (4, N)需转置并转换为 (N, 4) try: bbox_2d f[gt_bbox_2d][:] # shape: (4, N) if bbox_2d.size 0: return [], [] bbox_2d bbox_2d.T # - (N, 4) # MATLAB 索引从 1 开始Python 从 0 开始需减 1 bbox_2d bbox_2d - 1.0 # 转为 int确保坐标为像素整数 bbox_2d np.round(bbox_2d).astype(int) # 过滤掉无效 bboxx1x2 or y1y2 valid_mask (bbox_2d[:, 0] bbox_2d[:, 2]) (bbox_2d[:, 1] bbox_2d[:, 3]) bbox_2d bbox_2d[valid_mask] # 类别gt_class 是字符串数组需解码 class_names [] gt_class_ref f[gt_class] for i in range(gt_class_ref.shape[0]): ref gt_class_ref[i, 0] if isinstance(f[ref], h5py.Dataset): name_bytes f[ref][()].tobytes() class_name name_bytes.decode(utf-8).strip() class_names.append(class_name) else: class_names.append() class_names [n for n, m in zip(class_names, valid_mask) if m] except KeyError as e: print(fWarning: {mat_path} missing key {e}, skipping) return [], [] return bbox_2d.tolist(), class_names2.2.1 关键参数说明f[gt_bbox_2d][:]读取所有 bbox 坐标。MATLAB 存储为[x1; y1; x2; y2]即 4 行 × N 列因此.T后为 N 行 × 4 列。bbox_2d - 1.0MATLAB 坐标系左上角为 (1,1)OpenCV/PIL 为 (0,0)必须减 1 对齐。np.round(...).astype(int)避免浮点误差导致负坐标或越界强制转为整数。valid_mask过滤掉x1 x2或y1 y2的退化框常见于标注错误或遮挡。2.3 构建全局类别映射字典解决 SUN RGB-D 类别冗余问题SUN RGB-D 原始标注含 37 个类别但部分类别语义重叠如bed和double_bed、部分极少出现如lamp仅 12 张图。mmdetection 训练要求类别 ID 从 1 开始连续编号且需与 config 中num_classes严格一致。常见做法是合并相似类、剔除低频类最终保留 19 类参考 SUN RGB-D 官方 benchmark 设置SUNRGBD_CATEGORIES [ bed, bookshelf, sofa, table, chair, desk, dresser, night_stand, sink, lamp, computer, person, door, window, picture, mirror, rug, pillow, floor ] # 构建映射原始类名 → 新类别 ID1-based category_mapping {name: i1 for i, name in enumerate(SUNRGBD_CATEGORIES)}注意category_mapping必须与后续 mmdetection config 中classes字段完全一致否则Class names are not matched错误将导致训练中断。3. 生成 mmdetection 兼容的 COCO 格式 JSON从图像路径到 annotations 字段的完整构造mmdetection 的CocoDataset要求输入 JSON 必须包含images、annotations、categories三个顶级字段且annotations[i][image_id]必须等于images[j][id]。SUN RGB-D 原始数据无全局唯一 ID需用文件名哈希或序号生成image_id并确保annotations中的bbox为[x, y, w, h]COCO 格式而非[x1, y1, x2, y2]。3.1 构造 images 列表关联 RGB 图像、深度图路径与尺寸import cv2 import json from pathlib import Path def build_images_list(image_dir, depth_dir, split_file): 构建 COCO images 列表包含 RGB 和深度图路径 images [] with open(split_file, r) as f: image_names [line.strip() for line in f.readlines()] for idx, name in enumerate(image_names): rgb_path Path(image_dir) / f{name}.jpg depth_path Path(depth_dir) / f{name}.png # 读取 RGB 图像获取宽高 img cv2.imread(str(rgb_path)) if img is None: print(fWarning: {rgb_path} not found, skipping) continue height, width img.shape[:2] # 深度图路径存入 image info供后续多模态加载器使用 images.append({ id: idx 1, # COCO 要求 id 从 1 开始 file_name: f{name}.jpg, depth_file_name: f{name}.png, # 自定义字段不破坏 COCO schema width: width, height: height, license: 1 }) return images3.1.1 关键设计点depth_file_name是自定义字段mmdetection 默认忽略未知字段但可在自定义 Dataset 类中读取用于__getitem__时加载深度图。id从 1 开始连续编号与annotations[i][image_id]严格对应避免Image id does not exist报错。3.2 构造 annotations 列表将 bbox 转换为 COCO 格式并绑定类别def build_annotations_list(annotation_dir, image_list, category_mapping): 构建 COCO annotations 列表 annotations [] ann_id 1 # COCO annotation id 从 1 开始 for img_info in image_list: name Path(img_info[file_name]).stem mat_path Path(annotation_dir) / f{name}.mat bboxes, class_names parse_mat_annotation(str(mat_path)) if not bboxes: continue for bbox, class_name in zip(bboxes, class_names): if class_name not in category_mapping: continue # 跳过未映射类别如 low-frequency 类 x1, y1, x2, y2 bbox # COCO bbox format: [x, y, width, height] x, y, w, h x1, y1, x2 - x1, y2 - y1 # 边界检查确保 bbox 在图像内 w max(1, min(w, img_info[width] - x)) h max(1, min(h, img_info[height] - y)) x max(0, min(x, img_info[width] - w)) y max(0, min(y, img_info[height] - h)) annotations.append({ id: ann_id, image_id: img_info[id], category_id: category_mapping[class_name], bbox: [float(x), float(y), float(w), float(h)], area: float(w * h), iscrowd: 0 }) ann_id 1 return annotations3.2.1 参数校验逻辑max(1, ...)强制 bbox 宽高 ≥1 像素避免area0导致 mmdetection 内部ZeroDivisionError。min(..., img_info[width] - x)防止 bbox 越右边界cv2.rectangle绘图时会崩溃。iscrowd0SUN RGB-D 所有标注均为单实例 bounding box非 crowd 场景。3.3 生成最终 JSON 文件整合 images、annotations、categoriesdef generate_coco_json(image_dir, depth_dir, annotation_dir, split_file, output_json, category_mapping): 生成完整 COCO 格式 JSON images build_images_list(image_dir, depth_dir, split_file) annotations build_annotations_list(annotation_dir, images, category_mapping) categories [ {id: cat_id, name: name, supercategory: none} for name, cat_id in category_mapping.items() ] coco_data { images: images, annotations: annotations, categories: categories, licenses: [{id: 1, name: SUN RGB-D License, url: }] } with open(output_json, w) as f: json.dump(coco_data, f, indent2) print(fGenerated {output_json} with {len(images)} images and {len(annotations)} annotations) # 使用示例 generate_coco_json( image_dirSUNRGBDv2/image, depth_dirSUNRGBDv2/depth, annotation_dirSUNRGBDv2/annotation, split_fileSUNRGBDv2/train.txt, output_jsondata/sunrgbd/coco_format/train.json, category_mappingcategory_mapping )提示输出路径data/sunrgbd/coco_format/需与 mmdetection config 中data.train.ann_file路径一致。建议按data/{dataset_name}/coco_format/结构组织便于复用 configs。4. 深度图预处理将 16-bit PNG 深度图归一化为 float32并验证与 RGB 尺寸对齐SUN RGB-D 的深度图以 16-bit PNG 存储像素值代表毫米级距离如65535表示 65.535 米。mmdetection 默认只处理 RGB但多模态检测需将深度图作为第二通道输入。关键挑战是深度图与 RGB 图分辨率不完全一致部分图像深度图被裁剪或插值且数值范围过大直接float32加载会溢出。4.1 批量校验 RGB 与深度图尺寸一致性def validate_image_depth_alignment(image_dir, depth_dir, split_file): 检查 RGB 与深度图尺寸是否匹配 mismatches [] with open(split_file, r) as f: image_names [line.strip() for line in f.readlines()] for name in image_names: rgb_path Path(image_dir) / f{name}.jpg depth_path Path(depth_dir) / f{name}.png if not rgb_path.exists() or not depth_path.exists(): continue rgb_img cv2.imread(str(rgb_path)) depth_img cv2.imread(str(depth_path), cv2.IMREAD_UNCHANGED) # 保持 16-bit if rgb_img.shape[:2] ! depth_img.shape[:2]: mismatches.append((name, rgb_img.shape[:2], depth_img.shape[:2])) if mismatches: print(Mismatched dimensions (name, rgb_shape, depth_shape):) for m in mismatches[:10]: # 仅打印前 10 个 print(m) print(f... and {len(mismatches)-10} more) else: print(All RGB and depth images have matching dimensions.) return len(mismatches) 0 # 运行校验 validate_image_depth_alignment(SUNRGBDv2/image, SUNRGBDv2/depth, SUNRGBDv2/train.txt)4.1.1 不一致的典型原因与修复原因SUN RGB-D 原始数据中部分深度图经双线性插值缩放至与 RGB 匹配但插值引入浮点坐标导致shape差异如480x640vs480x639。修复方案对深度图做cv2.resize重采样强制对齐# 对单张深度图重采样 depth_resized cv2.resize(depth_img, (rgb_img.shape[1], rgb_img.shape[0]), interpolationcv2.INTER_NEAREST) # 用最近邻插值保精度4.2 深度图归一化从毫米到 0~1 范围的 float32 张量深度值范围为0~65535 mm但有效深度通常在0~10000 mm10 米内。直接除以65535.0会压缩有效信号。更合理的做法是统计训练集深度图的 1st 和 99th 百分位数以此为归一化区间。def compute_depth_stats(depth_dir, split_file, percentile(1, 99)): 计算深度图全局归一化参数 depths [] with open(split_file, r) as f: image_names [line.strip() for line in f.readlines()] for name in image_names[:1000]: # 采样前 1000 张估算 depth_path Path(depth_dir) / f{name}.png if not depth_path.exists(): continue depth_img cv2.imread(str(depth_path), cv2.IMREAD_UNCHANGED) # 过滤无效深度0 和 65535 常表示缺失 valid_depth depth_img[(depth_img 0) (depth_img 65535)] if len(valid_depth) 0: depths.extend(valid_depth.tolist()) if not depths: raise ValueError(No valid depth values found) p1, p99 np.percentile(depths, percentile) print(fDepth range: [{p1:.0f}, {p99:.0f}] mm (percentile {percentile})) return p1, p99 # 计算并保存参数 min_depth, max_depth compute_depth_stats(SUNRGBDv2/depth, SUNRGBDv2/train.txt) # 输出Depth range: [123, 5872] mm (percentile (1, 99))4.2.1 归一化函数实现def normalize_depth(depth_img, min_val, max_val): 将 16-bit 深度图归一化为 [0,1] float32 depth_norm np.clip(depth_img.astype(np.float32), min_val, max_val) depth_norm (depth_norm - min_val) / (max_val - min_val) return depth_norm # shape: (H, W), dtype: float32 # 示例加载并归一化一张图 depth_img cv2.imread(SUNRGBDv2/depth/sunrgbd_train_000001.png, cv2.IMREAD_UNCHANGED) depth_float normalize_depth(depth_img, min_depth, max_depth) print(fDepth normalized: {depth_float.min():.3f} ~ {depth_float.max():.3f})注意归一化参数min_depth/max_depth必须在训练前固定并在推理时复用。若在CustomDataset的__getitem__中动态计算会导致 batch 内归一化不一致。5. 集成到 mmdetection 训练流程修改 config 并验证数据加载器输出生成train.json和val.json后需配置 mmdetection 使用该数据集。核心是继承CocoDataset并重写load_annotations以支持读取depth_file_name字段同时修改数据流水线pipeline加载深度图。5.1 创建自定义 Dataset 类支持 RGBDepth 双通道输入# custom_dataset.py from mmdet.datasets import CocoDataset from mmdet.datasets.builder import DATASETS from mmdet.datasets.pipelines import Compose import cv2 import numpy as np DATASETS.register_module() class SUNRGBDCocoDataset(CocoDataset): def __init__(self, ann_file, pipeline, depth_prefixNone, **kwargs): super().__init__(ann_file, pipeline, **kwargs) self.depth_prefix depth_prefix # 深度图根目录路径 def get_ann_info(self, idx): 重写以支持 depth_file_name 字段 ann_info super().get_ann_info(idx) img_info self.data_infos[idx] if depth_file_name in img_info: ann_info[depth_file_name] img_info[depth_file_name] return ann_info def pre_pipeline(self, results): 在 pipeline 前注入 depth 路径 super().pre_pipeline(results) if depth_file_name in results.get(ann_info, {}): depth_path self.depth_prefix / results[ann_info][depth_file_name] results[depth_file] str(depth_path)5.2 修改 config 文件启用双模态 pipeline在 mmdetection config如configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py中修改# 数据集配置 data dict( samples_per_gpu2, workers_per_gpu2, traindict( typeSUNRGBDCocoDataset, ann_filedata/sunrgbd/coco_format/train.json, img_prefixdata/sunrgbd/image/, depth_prefixdata/sunrgbd/depth/, # 新增 pipeline[ dict(typeLoadImageFromFile), # 加载 RGB dict(typeLoadDepthFromFile), # 新增加载深度图 dict(typeLoadAnnotations, with_bboxTrue), dict(typeResize, img_scale(1333, 800), keep_ratioTrue), dict(typeRandomFlip, flip_ratio0.5), dict(typeNormalize, # 注意需同时归一化 RGB 和 Depth mean[123.675, 116.28, 103.53, 0], # R,G,B,D 均值D 均值设为 0因 depth 已归一化 std[58.395, 57.12, 57.375, 1.0]), # D 标准差设为 1.0 dict(typePad, size_divisor32), dict(typeDefaultFormatBundle), dict(typeCollect, keys[img, depth, gt_bboxes, gt_labels]) # 新增 depth ]), valdict( typeSUNRGBDCocoDataset, ann_filedata/sunrgbd/coco_format/val.json, img_prefixdata/sunrgbd/image/, depth_prefixdata/sunrgbd/depth/, pipeline[ dict(typeLoadImageFromFile), dict(typeLoadDepthFromFile), dict(typeResize, img_scale(1333, 800), keep_ratioTrue), dict(typeRandomFlip, flip_ratio0.0), dict(typeNormalize, mean[123.675, 116.28, 103.53, 0], std[58.395, 57.12, 57.375, 1.0]), dict(typePad, size_divisor32), dict(typeImageToTensor, keys[img, depth]), # 新增 depth dict(typeCollect, keys[img, depth, gt_bboxes, gt_labels]) ]), testdict( typeSUNRGBDCocoDataset, ann_filedata/sunrgbd/coco_format/val.json, img_prefixdata/sunrgbd/image/, depth_prefixdata/sunrgbd/depth/, pipeline[ dict(typeLoadImageFromFile), dict(typeLoadDepthFromFile), dict(typeResize, img_scale(1333, 800), keep_ratioTrue), dict(typeRandomFlip, flip_ratio0.0), dict(typeNormalize, mean[123.675, 116.28, 103.53, 0], std[58.395, 57.12, 57.375, 1.0]), dict(typePad, size_divisor32), dict(typeImageToTensor, keys[img, depth]), dict(typeCollect, keys[img, depth]) ]) ) # 模型 backbone 输入通道数改为 4R,G,B,D model dict( backbonedict( typeResNet, depth50, num_stages4, out_indices(0, 1, 2, 3), frozen_stages1, norm_cfgdict(typeBN, requires_gradTrue), norm_evalTrue, stylepytorch, init_cfgdict(typePretrained, checkpointtorchvision://resnet50), in_channels4 # 关键修改从 3 改为 4 ), )5.3 验证数据加载器用 tools/misc/browse_dataset.py 可视化 RGBDepth运行以下命令检查数据加载是否成功python tools/misc/browse_dataset.py configs/custom/sunrgbd_faster_rcnn.py --output-dir ./browse_sunrgbd --show-number 5成功标志生成的可视化图中左半部分为 RGB 图右半部分为归一化后的深度图灰度图近处亮、远处暗且 bbox 准确覆盖目标。常见失败原因FileNotFoundError: [Errno 2] No such file or directory: data/sunrgbd/depth/sunrgbd_train_000001.png检查depth_prefix路径是否正确.png文件是否存在。KeyError: depth_file_name确认train.json中images[i]是否包含该字段且SUNRGBDCocoDataset正确继承。RuntimeError: Given groups1, weight of size [64, 4, 7, 7], expected input[2, 3, 800, 1216] to have 4 channelsbackbonein_channels4未设置或LoadDepthFromFilepipeline 未生效。提示browse_dataset.py是 mmdetection 内置工具无需额外安装。它会自动调用 dataset 的__getitem__是最直接的端到端验证方式。本文还有配套的精品资源点击获取