- 数据集
- 计算机视觉
- 3D视觉
【免费下载链接】ScanNet
本文基于 ScanNet 仓库中的 ScannerApp/README.md 展开,系统讲解 ScanNet iOS 扫描采集 App 的编译构建流程、五种扫描数据文件(*.txt、*.depth、*.h264、*.cam、*.imu)的格式细节,并结合仓库源码深入剖析深度帧压缩编码、H.264 硬件编码、IMU 二进制布局以及“上传 + 校验”的数据回传链路,帮助读者完整理解 ScanNet 数据集从 iPad 端采集到数据服务器落盘的整条采集链路。
一、ScannerApp 的定位:ScanNet 数据采集链路的起点
ScanNet 是一套包含超过 1500 个场景、250 万帧视图的 RGB-D 视频数据集(见仓库根 README.md)。其原始数据并非凭空产生,而是由配套 iPad 应用 ScanNet Scanner App 采集而来:
The scanner app acquires RGB-D scans using a structure.io sensor, stores color, depth and IMU data on local memory and then uploads to the data staging server for processing.
也就是说,ScannerApp 承担三件事:采集(通过 Structure.io 传感器获取深度帧、通过 iPad 摄像头获取彩色帧、通过 CoreMotion 获取 IMU 数据)、本地存储(把数据写入 iPad Documents 目录)、回传(PUT 上传至数据暂存服务器,由 Server 目录下的上传服务接收并触发后续处理)。仓库根 README 也明确了这一分工:ScannerApp负责用 iPad + Structure.io 传感器采集 RGB-D 序列,Server负责接收这些序列。
从源码结构看,App 的核心状态机定义在 ViewController.h 的ScannerState枚举中,依次为ScannerStateSceneLabelling(填写场景标签)→ScannerStateCubePlacement(放置立方体初始化位姿)→ScannerStateScanning(正式扫描)→ScannerStateViewing(查看本地已有扫描)。扫描开始前用户必须填写 userName、sceneLabel 并选择场景类型,否则 ViewController.mm 中的scanButtonPressed:会弹出 “Incomplete metadata” 提示并阻止进入扫描状态——这保证了每个序列的元数据完整性。
二、编译与构建
2.1 环境前提
README 给出的编译前提(Prerequisites)如下:
- iPad Air 2(其他 iPad 或 iPhone 也可以运行,但官方未做测试);源码 ViewController.mm 中通过
sysctlbyname("hw.machine")识别iPad5,3(Wi-Fi)/iPad5,4(LTE)机型,并对 iPad Air 2 默认开启 30 FPS 高分辨率深度流,其他设备默认关闭以避免帧率过低; - Xcode 7.2(可通过 App Store 或 Apple 开发者下载页获取,更新版本可用但可能需要修改工程文件);
- 仓库内置的外部依赖:Structure.io 传感器 SDK与GPUImage图像滤波库,分别位于 ScannerApp/Frameworks/Structure.framework 和 ScannerApp/Frameworks/GPUImage。
2.2 构建步骤
README 给出的构建流程共 5 步:
- 用 Xcode 打开 Scanner.xcodeproj;
- 在 Scanner/Config.h 顶部设置数据上传服务器 URL;
- 连接 iOS 设备并授权开发机向其安装;
- 在顶部工具栏选择 “Scanner” 目标和所连设备,点击 “play” 图标构建到设备;
- 断开开发机、把 Structure 传感器接到 iPad 上,运行 Scanner App。
2.3 上传服务器配置(Config.h)
Config.h 是整个 App 唯一的网络配置入口,全部为宏定义:
// Upload server configuration #define SERVER "http://dovahkiin.stanford.edu" #define UPLOAD ":8000/upload" #define VERIFY ":8000/verify" // Upload stream checksum chunking size in bytes #define READ_CHUNK_SIZE 4096 #define DEBUG_MODESERVER/UPLOAD/VERIFY拼接出上传端点SERVER+UPLOAD(PUT 接收文件)与校验端点SERVER+VERIFY(GET 确认文件完整性);READ_CHUNK_SIZE为 4096 字节,是本地计算 MD5 校验和时的分块读取大小(见 ViewController.mm 的calculateChecksum,按块调用CC_MD5_Update);DEBUG_MODE控制调试期 UI 行为,例如仅在 DEBUG 模式下才在扫描列表行上显示 “Upload”/“Delete” 操作按钮。
对应服务端实现在 Server/upload.py,提供PUT/POST /upload与GET /verify?filename=<name>&checksum=<md5>两个路由,文件写入 staging 目录,扫描全部到齐(scan_done_uploading)后触发后续处理。upload.py头部注释还给出了模拟 iPad 上传的 curl 命令,可用于自行联调:
curl -v -X PUT -H "FILE_NAME: test.h264" --data-binary "@<path_to_some_file>" \ -H "Content-Type: application/ipad_scanner_data" "http://localhost:8000/upload"三、一次扫描产生的文件组织
当用户按下 Scan 按钮进入扫描状态后,ViewController+Sensor.mm 的startScanningAndOpen会先确认已收到相机内参和deviceId(否则拒绝启动并提示No intrinsics received),然后以时间戳加设备 ID 作为文件前缀:
[dateFormatter setDateFormat:@"yyyy-MM-dd_hh-mm-ss"]; std::string dateString = [[dateFormatter stringFromDate:[NSDate date]] UTF8String]; ... [self openFile:(dateString + "__" + _options.deviceId)];随后openFile:一次性打开五个输出文件,全部位于 iPad 的 Documents 目录:
| 文件 | 内容 |
|---|---|
<prefix>.h264 | iPad 彩色相机 H.264 码流 |
<prefix>.depth | Structure 传感器压缩深度帧流 |
<prefix>.txt | 元数据(分辨率、内外参、场景标签等) |
<prefix>.imu | IMU 原始二进制数据 |
<prefix>.camera | 每帧相机曝光参数 |
此外,第一帧还会额外导出一张<prefix>.jpg缩略图(ViewController+Sensor.mm 中g_frameCount == 1时写入),供后续 “查看历史扫描” 列表使用。扫描结束时closeFile补齐元数据帧数,并把彩色/深度时间戳数组追加写入.depth文件末尾,再逐一把五个文件关闭。
四、五种数据格式详解(README 核心内容)
4.1 元数据文件(*.txt)
README 给出的元数据文件完整样例:
colorWidth = 1296 colorHeight = 968 depthWidth = 640 depthHeight = 480 fx_color = 1170.187988 fy_color = 1170.187988 mx_color = 647.750000 my_color = 483.750000 fx_depth = 571.623718 fy_depth = 571.623718 mx_depth = 319.500000 my_depth = 239.500000 colorToDepthExtrinsics = 0.999977 0.004401 0.005230 -0.037931 -0.004314 0.999852 -0.016630 -0.003321 -0.005303 0.016607 0.999848 -0.021860 -0.000000 0.000000 -0.000000 1.000000 deviceId = AA408AE6-80BB-4E45-B6BA-5ECC8C17FB2F deviceName = iPad One sceneLabel = 0001 sceneType = Bedroom / Hotel numDepthFrames = 1912 numColorFrames = 1912 numIMUmeasurements = 4185各字段含义可从 ViewController+Sensor.mm 的writeIntrinsics逐一对应:
colorWidth/colorHeight、depthWidth/depthHeight:彩色/深度帧分辨率。注意若启用半分辨率彩色(useHalfResColor),写入的彩色分辨率会减半;样例中 1296×968 说明该场景采集时未减半(iPad 后置相机原分辨率);fx_color/fy_color/mx_color/my_color与fx_depth/...:两组相机内参(焦距与主点),在扫描前的前几帧由 SDK 的glProjectionMatrix反解得到(getIntrinsicsFromGlProj);colorToDepthExtrinsics:4×4 彩色相机到深度相机坐标系的刚体变换(行主序展开为 16 个值)。从源码看,若使用硬件配准深度(useHardwareRegisteredDepth)则写单位阵,否则调用 SDK 的colorCameraPoseInSensorCoordinateFrame:获取真实外参;deviceId/deviceName:iPad 设备 UUID 与名称(用于服务端区分采集设备);sceneLabel/sceneType:用户填写的场景编号与类别,类别候选列表硬编码在 ViewController.mm,包括 Dining Room、Office、Classroom、Bedroom / Hotel、Living room / Lounge、Kitchen、Bookstore / Library、Bathroom、Conference Room、Misc. 共 9 类;numDepthFrames/numColorFrames/numIMUmeasurements:扫描结束时由closeFile统计写入的三流帧数,是下游校验三流时间戳对齐的基础。
源码中还额外写入了 README 样例未列出的userName、appVersionId(形如Scanner.v1.<build>)以及closeFile追加的scanSavedAt(ISO 风格时间戳)字段,实际文件以当前 App 代码输出为准。
4.2 深度数据(*.depth)
README 描述*.depth是 Structure.io 传感器的压缩深度帧流,并指明参考 depth2pgm 代码解析。结合 depth2pgm.cpp 的实现,其文件结构可以完整还原:
- 逐帧组织:每帧前是一个 4 字节小端
uint32_t的压缩数据长度byteSize,紧随其后是byteSize字节的压缩负载; - 压缩算法:采用 Occipital 的 uplinksimple 图像编解码器(uplinksimple_image-codecs.h,基于 DCT 的位流压缩)。采集端在 ViewController+Sensor.mm 中用
uplinksimple::encode压缩,压缩缓冲上限按每像素 2 字节分配((2*width+1)*height),即压缩后体积通常不高于原始数据的 2 倍; - shift → depth 查表:传感器输出的是 16 位 “shift” 值,经 uplinksimple_shift2depth.h 中的 1024 项查找表换算为毫米深度(表值从 0/264mm 起单调递增至 9729mm);
0xffff为无效值哨兵,解析时凡大于等于该换算结果的像素一律置 0; - 时间戳尾巴:所有深度帧之后,先写入
numDepthFrames个 double(深度帧时间戳,秒),再写入numColorFrames个 double(彩色帧时间戳);depth2pgm的process函数会读出深度时间戳并换算为微秒。
解析示例程序的使用方式(见 depth2pgm/README.md):
# 在 ScannerApp/depth2pgm 目录下编译 make # 用法: depth2pgm input.depth output_basename numDepthFramesToExtract输出为二进制 PGM 格式(P5,16 位,深度单位 mm)。depth2pgm.cpp 的saveAsPGM注意了一个关键细节:写出前对每个 16 位像素做字节交换(std::swap),把小端数据转成 PGM 规范要求的网络字节序,并注释 “data values are 16-bit each”。默认按 640×480 解析,与 App 中使用的STStreamConfigDepth640x480流配置一致。
4.3 彩色数据(*.h264)
README 描述*.h264是 iPad 相机的 H.264 编码彩色帧流,可用 ffmpeg 转成图片序列:
ffmpeg -i id.h264 %6d.color.png从 Encoder.mm 与 EncoderConfig.h 看,编码链路的实现事实包括:
- 编码器基于 VideoToolbox 的
VTCompressionSession,源像素格式为kCVPixelFormatType_420YpCbCr8BiPlanarFullRange(与 ViewController+Camera.mm 中AVCaptureVideoDataOutput的采样格式一致),格式类型在 JPEG 与 H.264 间由EncoderConfig::MediaFormat选择,扫描时固定走 H.264; - 编码参数在 ViewController+Sensor.mm 组装:
EncoderConfig(colorWidth, colorHeight, EncoderConfig::H264, 30, _options.colorEncodeBitrate),即30 fps、码率默认 5000 kbps(Options::colorEncodeBitrate的默认值,见 ViewController.h); - 彩色帧在进入编码器前会取
colorFrame.halfResolutionColorFrame半分辨率版本,因此元数据里的彩色分辨率与传感器原始分辨率存在 2 倍关系时需注意useHalfResColor的实际状态; - 输出为Annex B格式:
writeToFile会遍历 AVCC 长度前缀的 NALU,逐个加上 4 字节起始码{0,0,0,1}写出;关键帧时先从CMFormatDescription中取出 SPS/PPS 各加起始码写入,保证文件可独立解码。
深度帧与彩色帧的同步由 SDK 的帧同步机制完成:相机回调captureOutput:didOutputSampleBuffer:把彩色 buffer 直接喂给[_sensorController frameSyncNewColorBuffer:],SDK 随后回调sensorDidOutputSynchronizedDepthFrame:andColorFrame:产出配对的深度/彩色帧(见 ViewController+Camera.mm 与 ViewController+Sensor.mm)。startStructureSensorStreaming会根据是否使用彩色相机选择STFrameSyncDepthAndRgb或STFrameSyncOff。
4.4 相机数据(*.cam)
README 说明*.cam是 iPad 相机设置流,包含曝光信息(README 中格式细节标注为 TODO),但源码给出了确切布局:
- 文件头先写入 3 字节版本字符串
"v1"(ViewController+Sensor.mm); - 之后每帧彩色数据写 5 个 32 位 float:白平衡红增益
redGain、绿增益greenGain、蓝增益blueGain、曝光时长exposureDuration(秒)、ISO 值(ViewController+Sensor.mm)。
这些参数用于后续重建与标注时的曝光校正参考。App 还提供 UI 支持:扫描前可通过 AWB 开关在自动/锁定曝光白平衡间切换,或在曝光表(auto (low light)、2/5/10/15/20 ms)中手动指定曝光档位(ViewController.mm)。
4.5 IMU 数据(*.imu)
README 给出*.imu的原始二进制布局,约每秒 53 个采样,每个采样为 15 个 8 字节 double(小端、IEEE 754):
--- Repeats approx. 53 times per second --- [8-byte capture device up-time sec. represented in double precision IEEE 754 little-endian] [8-byte x rotation rate (Radians/s, double, RHR)] [8-byte y rotation rate (Radians/s, double, RHR)] [8-byte z rotation rate (Radians/s, double, RHR)] [8-byte x user-caused acceleration vector (G, double, device's reference frame)] [8-byte y user-caused acceleration vector (G, double, device's reference frame)] [8-byte z user-caused acceleration vector (G, double, device's reference frame)] [8-byte x magnetic field (microtesla, double)] [8-byte y magnetic field (microtesla, double)] [8-byte z magnetic field (microtesla, double)] [8-byte roll (Radians, double)] [8-byte pitch (Radians, double)] [8-byte yaw (Radians, double)] [8-byte x gravity vector (G, double, device's reference frame)] [8-byte y gravity vector (G, double, device's reference frame)] [8-byte z gravity vector (G, double, device's reference frame)] ----- REPEAT ------源码侧与该布局完全吻合:ViewController.mm 的setupIMU通过CMMotionManager以 60 FPS 请求 device motion 事件(代码注释说明实际回调约为 53 FPS),processDeviceMotion:withError:按 “systemUptime → rotationRate ×3 → userAcceleration ×3 → magneticField ×3 → roll/pitch/yaw → gravity ×3” 的顺序逐字段fwrite8 字节并立即fflush,字段顺序与 README 描述一一对应;时间戳字段使用的是系统运行时间(NSProcessInfo.systemUptime),源码注释也坦诚说明这依赖主机小端浮点表示。README 同时提示 IMU 数据语义可参考 Apple CMDeviceMotion API 文档。元数据中的numIMUmeasurements即该回调中维护的计数器g_numIMUmeasurements,样例值 4185 除以 53 Hz 可反推出约 79 秒的扫描时长,与numDepthFrames = 1912(约 30 fps × 64 秒)量级吻合。
五、上传与校验:从 iPad 到数据服务器
README 开篇提到的 “uploads to the data staging server” 在源码中是一条完整的两段式链路:
- 上传(PUT):
uploadButtonPressed:扫描 Documents 目录(排除.jpg缩略图),为每个文件构造 PUT 请求——FILE_NAME头携带文件名、Content-Type: application/ipad_scanner_data、Content-Length为文件字节数,正文为文件输入流(ViewController.mm)。uploadQueue的并发被限制为 1,即串行上传;进度通过didSendBodyData回调按已传字节/总字节驱动进度条; - 校验(GET verify):单个文件返回 HTTP 200 后,App 本地按 4096 字节块(
READ_CHUNK_SIZE)计算 MD5,请求VERIFY?filename=<name>&checksum=<md5>;服务端比对暂存文件的校验和,若返回 200,App 才删除本地副本(ViewController.mm)。verifyQueue并发为 3,允许校验与下一轮上传并行。
服务端对应实现在 Server/upload.py:/upload路由接收 PUT/POST 并把文件写入 staging 目录,判断该扫描的全部文件到齐(scan_done_uploading)后触发处理流程;/verify路由执行校验和比对。这套 “上传成功但校验失败则保留本地文件重试” 的设计,是采集数据在弱网环境下仍能最终一致落盘的关键。
此外,App 会检测 WiFi 连通性(isConnectedToWIFI,实际以能否通过 WiFi 到达服务器主机判断),未连网时隐藏上传按钮——即上传能力与网络状态直接联动。
六、适用前提与注意事项
- 硬件强依赖:采集必须使用 Structure.io(Occipital)传感器 + iPad,SDK 以闭源 framework 形式内置在 ScannerApp/Frameworks/Structure.framework;传感器断连或低电量时 App 会中止扫描并复位状态(
sensorDidDisconnect、sensorBatteryNeedsCharging)。 - 工具链年代:README 明确以 Xcode 7.2 为基准,工程文件为旧版格式;新版 Xcode 编译前可能需要修改工程设置。
- 数据解析起点:拿到
*.depth数据后,推荐从 ScannerApp/depth2pgm 的示例程序入手理解帧组织与 shift→mm 换算;ScanNet 正式数据发布使用的是统一的*.sens容器格式(见 SensReader 与根 README 的 Data Organization 一节),其字段语义与本文所述的元数据、深度、IMU 布局一脉相承。 - 服务器端:若要自行搭建接收端,可参考 Server/upload.py 及 Server/start_upload_server.sh 的启动方式,并用文件头部注释中的 curl 命令模拟 iPad 端上传做端到端验证。
读完本文,读者应能独立复现 ScannerApp 的构建配置(Config.h服务器端点)、按二进制布局解析*.depth/*.imu/*.cam五类文件、用 ffmpeg 或 depth2pgm 转换彩色与深度数据,并理解 PUT 上传 + MD5 verify 的可靠回传机制——这正是 ScanNet 数据集采集链路的完整技术全貌。
- 数据集
- 计算机视觉
- 3D视觉
【免费下载链接】ScanNet
相关推荐
ScanNet 数据集详解:RGB-D 室内场景数据组织、`.sens`/`.segs.json` 数据格式与配套工具链
ScanNet 数据集详解:RGB D 室内场景数据组织、 .sens / .segs.json 数据格式与配套工具链 ScanNet 是一个包含 250 万帧
数据集计算机视觉3D视觉ScanNet Converter 详解:将 ScannerApp 原始采集数据转换为 .sens 传感器流的完整流程
ScanNet Converter 详解:将 ScannerApp 原始采集数据转换为 .sens 传感器流的完整流程 本文以 ScanNet 仓库中的 Con
数据集计算机视觉3D视觉WeiboSpider终极指南:7种微博数据采集模式详解与实战
WeiboSpider终极指南:7种微博数据采集模式详解与实战 WeiboSpider是一款持续维护的新浪微博采集工具,能够帮助用户轻松获取各类微博数据。无论是
网页爬虫数据采集
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考