2026.05.12征图日记12(调通激光雷达)
依旧是昨天剩下的拿不到深度数据问题。让ai排查了昨天剩下的日志,也没排查出什么东西。先是格式不兼容,之后是缺失视频参数拿不到彩色视频帧,最后返回了深度数据之后又是内存访问错误。我套了
今天中午快要吃饭的时候leader来看我们这个软件做到什么程度了,当时的情况是可以获取到激光雷达的数据了,但是全程都是他们在讨论,我说话直接被无视,感觉和这个公司的团队不合,打算待3个月就溜。并且感觉这个部门也不会有hc,最重要的是太远了。不如在重庆成都找一个小公司。当然还是要准备秋招争取校招进大厂!!!
最后经过一系列的纠错,终于搞好了,但是采到的深度数据存疑,又让我设计一个界面专门测试激光雷达的准确性。(好累,感觉学不到什么东西,一天都是在和ai说话)
LiDAR 测试页面做基本验证(对准已知距离的物体看读数),确认深度值本身是否合理。如果距离读数正确但面积计算不对,问题就在 FOV 映射和分辨率上。
进入设置改为平均深度,再次拍照测量观察数据是否存疑
app实装到iphone 16 pro max上现在有这么几个问题:1、在测试激光雷达界面,用户点击开始测试,提示当前平台不支持 LiDAR。2、第一次点击拍照,推理结束之后点击测定缺陷面积,提示未采集到深度数据,请重新拍照,第二次和之后不会发生这样的情况。
我们可以先简单一点,就假设用户拍的照片没有透视,都是水平拍摄,这样只需要测量一个点或者一个部分的平均深度带入计算即可
拿给mentor测,因为每次测试都要在iphone手机上才能build之后测激光雷达,所以mentor都被搞得有点不耐烦了。现在测试界面调好了,能拿到深度数据,缺陷检测也ok,接下来就是给一个是否需要缺陷检测的选项就行
按钮添加完了之后又让我搞一个标定法,一次标定+测距,后续就可以不用标定了。
卧槽,我现在脑子一团乱麻,mentor的方案我大致了解了,但是感觉有很多细节需要去思考。这个要慢慢整理
以后不能搞这么快了,脑容量是有限的,需求是无限的。珍惜生命,适度摸鱼
冷静下来,拆分问题,理清逻辑。技术文档
今日工作内容:
1、解决app实装在手机上调用摄像头和激光雷达报错的问题
2、测试激光雷达测距的准确性
3、测试app缺陷面积计算的准确性
下阶段计划
1、研究标定法测量缺陷面积
2、研究一次标定修正LiDAR测量缺陷面积方案
BugFix: iPhone 16 Pro Max H16 ISP LiDAR 深度采集失败 — failed preProcessJasper
- 日期: 2026-05-12
- 设备: iPhone 16 Pro Max (H16 ISP, iOS 18.x)
- 影响范围: 当前仅在 iPhone 16 Pro Max 上复现;iPhone 12 Pro~15 Pro 未报告此问题
- 状态: 待真机验证(
.inputPriority改动尚未部署)
现象
- 用户点击拍照按钮,触发
DepthCapture.capture() - 原生端
AVCaptureSession启动成功,isRunning = true depthDataOutput(didOutput:)delegate 回调从未触发 — 无深度帧产出- 3 秒超时后 JS 端调用
stopCapture()释放资源 - App 弹出”未采集到深度数据,请重新拍照”提示
- 注意:系统自带测距仪 app 正常工作,证明 LiDAR 硬件本身无故障
日志关键信息
日志文件:.claude/app.log、.claude/app1.log、.claude/app2.log
H16 ISP 特征
设备日志中反复出现 appleh16camerad 进程报错:
1 | failed preProcessJasper jasper |
Jasper 是 Apple 对 LiDAR ToF 传感器系统的内部代号。preProcessJasper 是 AppleDepth.framework 中 ADJasperColorV2Executor 执行的深度预处理步骤。此步骤失败意味着深度数据管线在 ISP 层就已中断,后续的 AVCaptureDepthDataOutput 回调永远不会触发。
ADJasperColorInFieldCalibration 失败
在 preProcessJasper 之前,系统还报了实时联合标定错误:
1 | ADJasperColorInFieldCalibration: number of valid spots is too low (error code -22964) |
这是 ToF-Color 实时联合标定(ADJasperColorInFieldCalibration),负责校准 LiDAR 深度传感器和 RGB 摄像头之间的空间对应关系。标定失败导致 Jasper 预处理无法正常工作。
sessionPreset 覆盖显式格式(关键发现)
默认情况(无 sessionPreset):系统隐式使用 AVCaptureSessionPresetHigh(1920×1080),该 preset 在 startRunning() 阶段静默覆盖代码中显式设置的格式:
| 属性 | 代码显式设置 | .High preset 实际生效值 |
|---|---|---|
activeFormat |
YUV BiPlanar, 高分辨率 | 1920×1080 |
activeDepthDataFormat |
kCVPixelFormatType_DepthFloat16(hdep) |
kCVPixelFormatType_DisparityFloat16(hdis, 320×180) |
格式被覆盖后,深度数据的类型(Depth vs Disparity)和分辨率都发生变化,可能直接影响 Jasper 标定所需的帧数据质量。
根因分析
因果链
1 | sessionPreset 未设置 → 系统隐式使用 .High |
为什么测距仪正常
系统自带测距仪使用 Apple 私有框架(ARKit/私有 AVCapture 配置),可直接与 ISP 通信绕过 AVCaptureSession 的 preset 覆盖逻辑。第三方 app 通过 AVFoundation 公开 API 时受 preset 约束。
修复尝试记录(时间顺序)
尝试 1:移除 .vga640x480 preset + 显式格式选择 + 直接 delegate
改动:
- 移除
sessionPreset = .vga640x480 - 手动枚举 camera.formats 选取 YUV BiPlanar + DepthFloat16
- 从
AVCaptureDataOutputSynchronizer(多路同步)改为直接使用AVCaptureDepthDataOutputDelegate
结果:❌ 失败。由于未设置 sessionPreset,系统默认 .High,格式被静默覆盖。
尝试 2:添加 AVCaptureVideoDataOutput 提供 RGB 帧
假设:ADJasperColorInFieldCalibration 需要 RGB 视频帧来执行联合标定。
改动:同时添加 AVCaptureVideoDataOutput 和 AVCaptureDepthDataOutput。
结果:❌ SIGSEGV(Signal 11 段错误),app 在 startRunning() 后约 409ms 崩溃。
分析:H16 ISP 上同时配置 depth + video 输出可能触发底层竞态或不兼容。
尝试 3:sessionPreset = .photo
假设:.photo preset 比 .High 更适合深度采集,且 Apple 文档推荐用于高质量深度数据。
结果:❌ 未测试(用户在此前已报告问题依旧)。但 .photo 同样可能覆盖显式格式,风险与 .High 相同。
尝试 4(当前):sessionPreset = .inputPriority
假设:.inputPriority 是 Apple 明确设计用于”开发者完全控制格式选择”的 preset,系统保证不做任何覆盖。
改动:
- 将
s.sessionPreset = .photo改为s.sessionPreset = .inputPriority - 在
startRunning()之后添加.error级别日志,验证实际生效的视频格式、深度格式、深度子类型和 preset 名称
结果:⏳ 待真机验证
代码变更
文件:mobile/ios/DepthCaptureModule.swift
变更 1:sessionPreset 改为 inputPriority
1 | // 改动前(line ~167) |
变更 2:startRunning() 后增加格式验证日志
1 | // 在 startRunning() 和 isRunning 检查之后新增 |
后续排查方向(若 .inputPriority 仍失败)
如果 .inputPriority 成功保留了显式格式但深度帧仍无产出,则说明格式覆盖不是(唯一)原因,需排查:
- 确认格式验证日志:
startRunning 后验证行是否显示DepthFloat16(0x68646570)和高分辨率 - Jasper 标定是否仍失败:设备日志中
failed preProcessJasper和-22964错误是否消失 - ARKit 替代方案:使用
ARSession+.sceneDepth配置,通过 ARKit 获取深度数据,绕过 AVCaptureSession 的限制 - 设备级重置:怀疑设备 LiDAR 传感器的出厂标定数据损坏(极少见但可能),尝试重置所有设置
日志导出方法
1 | # 实时流式查看 |
BugFix: LiDAR 深度采集 Float16/Float32 格式不匹配导致崩溃
- 日期: 2026-05-12
- 设备: iPhone 16 Pro Max
- 影响范围: 所有 LiDAR iPhone(12 Pro 起)
现象
- 用户第一次点击拍照 → 弹出”暂无深度图数据”提示
- 用户第二次点击拍照 → app 闪退
日志分析
日志文件位于 .claude/app2.log,包含两次独立崩溃。
第一次崩溃(pid 13109, 10:53:11)
1 | Swift/arm64e-apple-ios.swiftinterface:39402: Fatal error: Float value cannot be converted to Int because it is either infinite or NaN |
- 信号: SIGABRT(Swift fatal error)
- 原因:
Int(Infinity)调用触发 Swift 运行时陷阱
第二次崩溃(pid 13385, 11:30:07)
1 | SpringBoard: Process exited: domain:signal(2) code:SIGSEGV(11) |
- 信号: SIGSEGV(段错误)
- 原因: 第一次崩溃后原生 AVCaptureSession 未正常清理,残留状态导致内存访问越界
时间线
| 时间 | 事件 |
|---|---|
| 10:53:01 | 用户第一次点击拍照,LiDAR 开始采集 |
| 10:53:06 | LiDAR 会话启动,深度图 320x240,子类型 0x68646570(hdep = DepthFloat16) |
| 10:53:07 | 首帧深度数据到达 |
| 10:53:08 | 弹出 RCTAlertController(3 秒超时,”暂无深度图数据”) |
| 10:53:11 | 第一次崩溃: Float value cannot be converted to Int |
| 11:30:06 | 用户重新打开 app,第二次尝试拍照 |
| 11:30:07 | LiDAR 会话再次启动 |
| 11:30:07 | 第二次崩溃: SIGSEGV |
根因
文件: mobile/ios/DepthCaptureModule.swift
核心问题:Float16 数据被当作 Float32 读取
代码在选择深度格式时明确指定了 kCVPixelFormatType_DepthFloat16(line 218-219):
1 | guard let depthFormat = (videoFormat.supportedDepthDataFormats.reversed().first { df in |
但在处理深度像素数据时,使用 Float32 指针读取(line 423):
1 | let rawPointer = baseAddr.assumingMemoryBound(to: Float32.self) |
Float16 每像素 2 字节,Float32 每像素 4 字节。用 Float32 指针遍历 Float16 内存时:
- 每次读取 4 字节 = 两个相邻 Float16 值的位拼接
- 拼接后的 32 位模式被解释为 Float32 → 大量产生 inf / NaN
- 后续
Int(minDepth * 1000)中minDepth为 inf →Int(Infinity)→ Swift fatal error
崩溃链
1 | Float16 内存 → Float32 指针错误读取 → inf/NaN 垃圾值 |
为什么之前没发现
- 代码注释中提到之前使用
.highpreset 时系统会静默切换深度格式为hdis(DisparityFloat16),说明格式选择一直有问题 - 换用
.inputPrioritypreset 后成功选中了hdep(DepthFloat16),但后续处理代码没有同步适配 - iPhone 12 Pro~15 Pro 可能因硬件差异,Float16 拼接出的位模式碰巧未触发崩溃
修复方案
改动 1:核心修复 — 统一转为 Float32 再处理
1 | // 修复前 |
使用 Apple 提供的 converting(toDepthDataType:) API,在像素处理前将任意格式的深度数据统一转为 Float32。无论输入是 Float16 还是其他格式,都能正确转换。
改动 2:防御加固 — Int() 转换增加 isFinite 检查
1 | // 修复前 |
作为安全网,即使上游出现意外的非有限值也不会崩溃。
改动 3:日志优化
日志中增加原始类型和转换说明,方便后续排查:
1 | os_log(.info, log: log, "收到深度数据帧 ... 原始类型=%{public}@,将转为Float32处理", rawTypeName) |
验证方法
- 在 iPhone 16 Pro Max 上重新构建并安装 app
- 进入拍照页面,选择 LiDAR 测量模式
- 第一次点击拍照 → 应正常采集深度数据,不再弹出”暂无深度图数据”
- 第二次点击拍照 → 应正常工作,不再闪退
- 检查 Xcode Console 日志确认:
原始类型=Float16,将转为Float32处理深度采集完成: 有效像素=76800/76800 范围=xxx-xxxmm
修复 LiDAR 测试界面 + 首次采集失败 + 简化面积计算
背景
App 部署到 iPhone 16 Pro Max 后发现三个问题:
- LiDAR 测试界面点击”开始测试”报错
- 首次拍照推理后测定缺陷面积提示”未采集到深度数据”,第二次正常
- 面积计算过于复杂,用户希望简化为单点深度
问题根因
问题 1:LiDAR 测试界面不工作
根因:LiDARStreamModule.swift 和 LiDARStreamModule.m 创建了文件但未注册到 Xcode 项目(pbxproj)。Xcode 不会编译这两个文件,导致 NativeModules.LiDARStreamModule 为 undefined。LiDARStream.start() 因 !MODULE 抛出异常。
修复方案:将流式推送功能合并到已有的 DepthCaptureModule 中(已在 pbxproj 注册),而不是使用独立模块。具体做法是让 DepthCaptureModule 继承 RCTEventEmitter,添加 startStream/stopStream 方法。这样无需修改 pbxproj。
问题 2:首次拍照深度采集失败
根因:当前流程是”先深度后拍照”——DepthCapture.capture() 启动 LiDAR 会话 → 等 3 秒超时 → 释放硬件 → 300ms 延迟 → 打开相机 UI。首次冷启动 startRunning() 慢(LiDAR 硬件初始化 + H16 ISP Jasper 标定),3 秒超时不够。第二次硬件已预热所以成功。
修复方案:改为”先拍照后深度”——先打开相机 UI 让用户拍照 → 拍照完成后相机 UI 关闭 → 硬件释放 → 再采集深度。这样:
- 避免了硬件资源冲突
- LiDAR 采集不再受拍照前的时间约束
- 用户拍照后仍会持握设备对准物体,深度数据仍有意义
问题 3:面积计算简化
当前实现:lidarIntegralArea 逐像素遍历掩膜,映射到深度图,双线性插值获取每个像素深度,累加 Z²/(fx×fy)。复杂度高,对深度图空间对齐精度要求高。
简化方案:假设水平拍摄无透视,只需一个代表性深度值 Z,公式 A = N × Z² / (fx × fy)。Z 取深度图中心区域的平均值。
实现方案
1. 将流式推送合并到 DepthCaptureModule
修改文件:mobile/ios/DepthCaptureModule.swift
改动:
class DepthCaptureModule: NSObject→class DepthCaptureModule: RCTEventEmitter- 添加
supportedEvents()返回["LiDARDepthFrame", "LiDARStreamError"] - 添加
startObserving()/stopObserving()管理isObserving标志 - 添加
@objc func startStream(_ fps: Int, resolve:, rejecter:)方法 - 添加
@objc func stopStream(_ resolve:, rejecter:)方法 - 流式会话使用独立的
streamSession: AVCaptureSession?属性 captureDepthSnapshot中增加前置检查:如有流式会话先停止- 复用
LiDARDetector的设备检测和格式选择逻辑 - delegate 回调中:若
isObserving为 true 则sendEvent;否则走原有的 completion 路径
修改文件:mobile/ios/DepthCaptureModule.m
改动:
@interface RCT_EXTERN_MODULE(DepthCaptureModule, NSObject)→@interface RCT_EXTERN_MODULE(DepthCaptureModule, RCTEventEmitter)- 添加
RCT_EXTERN_METHOD(startStream:(NSInteger)fps resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - 添加
RCT_EXTERN_METHOD(stopStream:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
修改文件:mobile/ios/ZhengtuVision/ZhengtuVision-Bridging-Header.h
改动:
- 添加
#import <React/RCTEventEmitter.h>(Swift 代码需要继承 ObjC 的 RCTEventEmitter)
2. 修改 LiDARStream.ts 指向合并后的模块
修改文件:mobile/src/native/LiDARStream.ts
改动:
NativeModules.LiDARStreamModule→NativeModules.DepthCaptureModule- 其余接口不变(start/stop/onFrame/onError)
3. 删除独立的 LiDARStreamModule 文件
删除文件:
mobile/ios/LiDARStreamModule.swiftmobile/ios/LiDARStreamModule.m
这些文件不在 pbxproj 中,删除不影响编译。
4. 改为”先拍照后深度”流程
修改文件:mobile/src/screens/CaptureScreen.tsx
改动要点:
pick()函数中:移除拍照前的深度采集逻辑handleResult()函数中:拍照完成后,如果measurementMethod === 'lidar',启动深度采集- 采集深度时使用
withTimeout(capture(), 5000)— 增加超时到 5 秒 - 采集成功后存入
pendingDepth.current,再执行现有的深度裁剪逻辑 - 采集失败时确保
depthSnapshot被清空(防止残留旧数据)
新的 pick() 流程:
1 | 1. pendingDepth.current = null |
5. 简化面积计算
修改文件:mobile/src/utils/measurement.ts
改动:
- 删除
lidarIntegralArea函数(逐像素积分法) - 删除
adaptiveBilinearInterpolate函数(仅在积分法中使用) - 新增
lidarCenterDepthArea函数:
1 | function lidarCenterDepthArea( |
- 修改
calculateArea()入口函数:LiDAR 分支直接调用lidarCenterDepthArea - 简化
ResultScreen.tsx中的 LiDAR 面积计算调用:移除lidarIntegrationMode分支判断,统一使用 centerDepth 方式
修改文件:mobile/src/screens/ResultScreen.tsx
改动:
- LiDAR 计算分支:不再区分
integral和average模式 - 统一调用
calculateArea(ctx)或lidarCenterDepthArea - 移除
lidarIntegrationMode相关的条件判断
修改文件:mobile/src/store/settingsStore.ts
改动:
- 移除
lidarIntegrationMode状态和相关 setter(不再需要)
修改文件:mobile/src/screens/SettingsScreen.tsx
改动:
- 移除”LiDAR 深度计算模式”的 SegmentedControl UI 区块
6. 清理 CaptureScreen 中的 LiDARStream 引用
修改文件:mobile/src/screens/CaptureScreen.tsx
改动:
- 移除
import {LiDARStream} from '@/native/LiDARStream'(拍照前不再需要停止测试流) - 或保留但在
handleResult的深度采集前调用LiDARStream.stop()
文件清单
修改文件(8 个):
mobile/ios/DepthCaptureModule.swift— 合并流式推送功能,改为继承 RCTEventEmittermobile/ios/DepthCaptureModule.m— 更新模块声明和导出方法mobile/ios/ZhengtuVision/ZhengtuVision-Bridging-Header.h— 添加 RCTEventEmitter 导入mobile/src/native/LiDARStream.ts— 指向 DepthCaptureModule 而非独立模块mobile/src/screens/CaptureScreen.tsx— 改为”先拍照后深度”流程mobile/src/utils/measurement.ts— 简化 LiDAR 面积计算为 centerDepth 方式mobile/src/screens/ResultScreen.tsx— 移除 integral/average 模式分支mobile/src/screens/SettingsScreen.tsx— 移除 LiDAR 计算模式设置项
删除文件(2 个):
9. mobile/ios/LiDARStreamModule.swift — 不再需要的独立模块
10. mobile/ios/LiDARStreamModule.m — 不再需要的独立桥接头
可选修改:
11. mobile/src/store/settingsStore.ts — 移除 lidarIntegrationMode 状态
验证方案
问题 1 验证:LiDAR 测试界面
- 重新构建 app:
cd mobile && npm run ios - 打开 app → 首页右上角 ◉ 按钮 → 进入 LiDAR 测试页面
- 点击”开始测试” → 应显示深度热力图和实时距离
- 点击”停止测试” → 流停止
- 返回首页,进入拍照流程 → 应正常工作
问题 2 验证:首次拍照深度采集
- 杀掉 app 重新启动(确保 LiDAR 硬件冷启动)
- 进入拍照 → 拍摄照片 → 裁剪确认
- 推理完成后 → 点击”测定缺陷面积”
- 应正常显示面积结果(不再提示”未采集到深度数据”)
问题 3 验证:简化面积计算
- 完成一次带 LiDAR 的检测流程
- 在结果页点击”测定缺陷面积”
- 面积数值应合理(与距离法对比验证)
- 设置页面不再显示”LiDAR 计算模式”选项
边界条件
- 模拟器上测试界面应显示友好提示
- 非 LiDAR 设备上拍照流程不受影响
- 深度采集超时时不应阻断正常流程(仅影响面积计算)