2026.05.20征图日记18(说好的open house呢?)
今天先改了App的名字,之前拼错了是Focusight不是Foucsight,之后就和mentor在进行最后的调整
改完了之后就回去自己位置刷力扣了
感觉这个App不是要用来展示的,现在也没有说去展示现场调试,或者通知我们啥的,玩我呢!?
还有一件事就是公司在马来西亚有办事处,所以打算用马来的同事订阅claudecode,再想办法搭一个中转站到国内,听说实习生好像也能用,酥胡~
LiDAR理论上是依靠飞行时间差来计算距离的,所以距离越近误差越大,所以在10-20cm这个距离的误差是很大的,但恰巧因为缺陷很小,app的拍摄距离也落到了这个区间,所以用iphone来测量微小缺陷面积本来就不合适,硬件层面的误差就能把你耗死
和Ai合作的时候要特别注意Ai的谄媚性,是指你在修改方案的时候,无论你说什么,Ai都会找到你这个方案的亮点,从而对你进行附和。从而让你忽略掉一些方案设计上的缺陷,这些缺陷在Ai的上下文不全的情况下,Ai是无法判断的,这个时候是需要你自己注意并进行判断
字符串解码
1 | class Solution { |
今日工作内容
将 App 内所有品牌文案从 征图/ZhengtuVision 改为 Focusight Vision,修改 app.json、Info.plist、LaunchScreen.storyboard、AppDelegate.mm。
修复 Swift 原生相机模块距离显示硬编码中文,改为通过 React props 传入 i18n 字符串。修复历史记录和结果页缺陷数量摘要未国际化问题。
从设置页移除标定法和距离法选项,开启缺陷面积测量后直接走 LiDAR。核心计算代码和 CalibrationOverlay 组件保留不动。
ImageCropPicker 默认 width/height 为 200×200,导致所有裁切/未裁切图片都被缩放到 200×150,缺陷 mask 像素数偏差 10 倍,面积测量严重不准。修复:openCropper 和 openPicker 中显式设置 width: 400, height: 300。同时将推理提示文案统一为’正在推理中…’。(这个修改之后还是不准后续还需修改)
将缺陷测量方法从根据掩膜像素积分改为根据深度图像素积分,具体方案在 “上下文摘要:缺陷面积测量深度像素积分方案” 中
下阶段计划
- 继续优化App交互与测量
上下文摘要:缺陷面积测量深度像素积分方案
生成时间:2026-05-20
一、涉及的关键文件
| 层 | 文件 | 职责 |
|---|---|---|
| Swift | mobile/ios/UnifiedCameraModule.swift |
统一相机:单 AVCaptureSession 双输出(RGB+LiDAR) |
| Swift | mobile/ios/DepthCaptureModule.swift |
独立 LiDAR 模块:预热、单帧快照、连续流(legacy) |
| ObjC | mobile/ios/UnifiedCameraModule.m |
RN bridge |
| ObjC | mobile/ios/DepthCaptureModule.m |
RN bridge |
| TS 封装 | mobile/src/native/UnifiedCamera.ts |
capturePhotoWithDepth() 零时差采集 |
| TS 封装 | mobile/src/native/DepthCapture.ts |
独立 LiDAR TS 封装(legacy) |
| TS 工具 | mobile/src/utils/depthTransform.ts |
深度图裁剪:RGB cropRect → 深度坐标系映射 |
| TS 工具 | mobile/src/utils/image.ts |
图片压缩(仅长边 >4096 时生效) |
| TS 核心 | mobile/src/utils/measurement.ts |
面积计算引擎:三种测量方法 + 逐缺陷面积(已改动) |
| TS 类型 | mobile/src/types/measurement.ts |
AreaResult、CalculationContext、MeasurementSession 等 |
| 页面 | mobile/src/screens/CaptureScreen.tsx |
拍照→裁剪→压缩→存图+深度到 store |
| 页面 | mobile/src/screens/ResultScreen.tsx |
调用面积计算,传给 AreaDisplay 和 DetectionList |
| 页面 | mobile/src/screens/ConfigScreen.tsx |
downscale 参数(1-4)设置界面 |
| 组件 | mobile/src/components/result/AreaDisplay.tsx |
总面积展示面板 |
| 组件 | mobile/src/components/result/DetectionList.tsx |
逐缺陷面积列表 |
| Store | mobile/src/store/inferenceStore.ts |
fx/fy、depthSnapshot、measurementSession 存储 |
| Store | mobile/src/store/settingsStore.ts |
measureAreaEnabled 开关 |
| 后端 | backend/app/core/pipeline.py |
downscale 参数生效位置(width//=downscale) |
| 后端 | backend/app/utils/image_io.py |
downscale_if_needed(仅长边 >4096 时自动缩放) |
| 后端 | backend/app/services/inference_service.py |
推理服务编排,返回 image_size(已 downscale 后) |
二、完整数据流
1 | 1. 拍照 unifiedCamera.capturePhotoWithDepth() |
三、之前的方案是什么
3.1 积分框架:掩膜像素为单位
旧 perPixelIntegrate 的核心逻辑(measurement.ts 旧版):
1 | for (let my = 0; my < maskH; my++) { |
循环以掩膜像素为粒度,每个缺陷掩膜像素累加一次 Z²/(fx_depth × fy_depth)。
3.2 像素映射:最近邻 + 回退搜索
depthLookupWithFallback:先查目标深度像素的 Z 值,无效时向外扩展最多 3 像素搜索有效深度值(曼哈顿环状搜索),跨过深度不连续面时可能取到错误表面的深度。
3.3 内参:深度摄像头的 fx_depth/fy_depth
fx/fy 来自 DepthFrameProcessor(Swift),从 depthData.cameraCalibrationData.intrinsicMatrix 提取并按深度图实际分辨率缩放。
四、旧方案有什么缺点
4.1 核心 Bug:漏乘分辨率缩放因子
Z²/(fx_depth × fy_depth) 是一个深度像素的物理面积,而循环遍历的是掩膜像素。两者覆盖相同 FOV 但分辨率不同:
| 场景 | 掩膜分辨率 | 深度图分辨率 | 每个掩膜像素面积是深度像素面积的 |
|---|---|---|---|
| downscale=1 | 400×300 | 152×114 | 0.144 倍 |
| downscale=2 | 200×150 | 152×114 | 0.578 倍 |
| downscale=4 | 100×75 | 152×114 | 2.31 倍 |
每个掩膜像素应该累加的是 Z²/(fx_mask × fy_mask),而非 Z²/(fx_depth × fy_depth)。两者关系:fx_mask = fx_depth × (maskW/depthW)。
缺失的修正因子:(depthW × depthH) / (maskW × maskH)。当前漏乘,导致面积结果随 downscale 参数变化——同一块物理缺陷,换个参数面积就变。
| downscale | 面积偏差 |
|---|---|
| 1 | 高估约 6.9 倍 |
| 2 | 高估约 1.7 倍 |
| 4 | 低估约 57% |
4.2 回退搜索可能取错深度
depthLookupWithFallback 在目标深度像素无效时向外扩展 3 像素。缺陷位于物体边缘时,回退可能跨越深度不连续面,取到背景的深度而非缺陷所在表面的深度。
4.3 深度噪声被平方放大但无抵消机制
逐像素 Z² 累加,每个像素的独立测量噪声被平方放大后累加(而不是通过平均来抵消)。改用深度像素方案后,多个掩膜像素共享一个 Z 值,等效于对噪声做了局部分组平均。
4.4 其他误差源(方案改进不涉及的)
以下误差源属于 LiDAR 硬件的根本性限制,本文讨论的方案改动不直接解决,但深度像素方案的覆盖率诚实反映了数据质量:
| 因素 | 影响程度 | 说明 |
|---|---|---|
| 深度图分辨率低(~256×192) | 高 | 空间精度受限于 LiDAR 传感器 |
| LiDAR 深度噪声 | 中 | ~1cm 精度,Z² 放大误差 |
| Float32→Uint16 量化 | 中低 | 亚毫米精度丢失 |
| 表面倾斜未补偿 | 中低 | 公式假设表面垂直于光轴 |
| RGB-LiDAR 基线视差 | 中低 | 两个传感器有物理位置差 |
五、如何改进(两种可选方案)
方案 A:掩膜像素方案(加缩放因子)
保持现有循环结构,在面积累加后乘修正因子:
1 | area = Σ z²/(fx_depth × fy_depth) × (depthW × depthH) / (maskW × maskH) |
优点:改动量最小,一行代码。
缺点:
- 公式不直观,修正因子容易在后续维护中被遗漏
- 仍需推导 fx_mask(多一环变换)
- 回退搜索问题依旧
- 广角主摄的 RGB 相机没有工厂标定内参可用(见第九节)
方案 B:深度像素方案(推荐,已采用)
改变积分框架:以深度像素为积分单位,掩膜像素”投票”到深度像素桶中。
优点:
- 内参直接用深度摄像头的工厂标定值,无需推导
- 分辨率比例内嵌在 coverage 中,不会遗漏
- 无效深度直接跳过,覆盖率真实,不猜测
- 推导链短,维护风险低
缺点:改动量较大(需重写积分循环),需要额外的深度像素桶数组(~17KB 内存)。
两种方案数学等价,差异在工程属性而非计算结果。
六、改进后的方案是什么
6.1 核心算法:双线性投票 + 深度像素积分
1 | 第一步:初始化深度像素权重桶 |
6.2 双线性权重的作用
掩膜像素在深度图坐标系中是一个 sub-pixel 矩形(宽 scaleX,高 scaleY),该矩形可能与最多 4 个深度像素相交。双线性分配按几何占地比例划分权重,权重之和恒为 1。相比最近邻(整颗分配给一个深度像素),双线性在边缘处平滑过渡,不同 downscale 下面积更稳定。
1 | dx=76 dx=77 |
6.3 为什么面积不随 downscale 变化
假设某个深度像素对应物理区域内有 10 处缺陷:
| downscale | maskW×maskH | 该区域掩膜像素数 N | N/总掩膜像素/pixelPerDepth | fraction |
|---|---|---|---|---|
| 1 | 120000 | 10 | 10/6.93 | 1.44 |
| 2 | 30000 | ~2.5 | 2.5/1.73 | 1.44 |
| 4 | 7500 | ~0.625 | 0.625/0.43 | 1.44 |
N 和 pixelsPerDepthPixel 随 downscale 同比例缩放,fraction 恒定,面积不变。
七、为什么要这么改(深度像素 > 掩膜像素的七个理由)
| # | 理由 | 掩膜像素方案 | 深度像素方案 |
|---|---|---|---|
| 1 | 内参来源 | 需推导 fx_mask = fx_depth × (maskW/depthW) | 直接用工厂标定 fx_depth |
| 2 | 公式自洽性 | z²/(fx×fy) × 修正因子 不直观 |
coverage × Z²/(fx×fy) 匹配物理直觉 |
| 3 | 分辨率比例 | 显式乘因子,容易遗漏 | 内嵌在 bucket 的 fraction 中 |
| 4 | 回退搜索 | 需要外扩 3 像素,可能取错深度 | 无效深度直接跳过,不猜测 |
| 5 | 覆盖率语义 | 被回退搜索高估 | 真实反映深度数据可用性 |
| 6 | 推导链 | fx_depth → fx_mask → 修正因子 → 积分(3 步) | fx_depth → 积分(1 步) |
| 7 | 维护性 | 高(当前 bug 就是证据) | 低 |
八、具体是如何改动的
改动文件:mobile/src/utils/measurement.ts,三处变更,调用方接口不变。
8.1 删除:depthLookupWithFallback 函数(原 128-152 行)
不再需要回退搜索,深度无效像素直接跳过。该函数无其他调用方。
8.2 重写:perPixelIntegrate(原 159-228 行)
| 旧 | 新 | |
|---|---|---|
| 循环单位 | 掩膜像素 | 先掩膜像素(投票),再深度像素(积分) |
| 像素映射 | 最近邻 Math.round(mx*scaleX) |
双线性分配到四个邻近深度像素 |
| Z 获取 | depthLookupWithFallback 回退搜索 |
直接从深度数组索引 |
| 面积累加 | z²/(fx×fy) 裸值 |
weight × z²/(fx×fy),weight 隐含分辨率修正 |
| 深度无效处理 | 回退搜索取邻近值 | 跳过不累加,覆盖率降低反映真实数据质量 |
8.3 重写:perPixelIntegrateBbox(原 233-273 行)
同理改动,但 bucket 范围限定在 bbox 对应的深度像素区域(局部桶,减少内存分配)。
8.4 不改的部分
calculateArea— 调用接口不变,内部仍调用perPixelIntegratecalculatePerDetectionAreasLidar— 调用接口不变,内部仍调用perPixelIntegrateBbox- 标定法(
calibrateArea、calculateCalibratedArea)、距离法(distanceArea) - 掩膜解码(
countDefectPixels、getMaskData、countPixelsInBbox) - 深度解码(
decodeDepthUint16) - ResultScreen、AreaDisplay、DetectionList 等 TSX/组件
8.5 验证
1 | cd mobile && npm run lint # 通过,零错误 |
九、硬件背景
9.1 使用的摄像头
builtInLiDARDepthCamera,对应后置广角主摄(Wide),超广角和长焦完全不参与。
9.2 分辨率
广角主摄照片:4032×3024(约 12MP)。LiDAR 深度图:约 256×192。
9.3 时空对齐
RGB 和 LiDAR 封装在同一逻辑设备中(builtInLiDARDepthCamera),共享时钟源。单个 AVCaptureSession 同时输出 AVCapturePhotoOutput 和 AVCaptureDepthDataOutput,硬件层面同步触发,出厂做过 RGB-LiDAR 联合几何标定。深度图返回时已 warp 对齐到 RGB 坐标系。
9.4 RGB 内参为什么拿不到
Apple 只为深度数据流提供 cameraCalibrationData(AVDepthData 属性),内含 intrinsicMatrix(工厂逐台标定的深度摄像头内参)。广角主摄的 RGB 照片流没有等价接口,只能反推(从 sensor 尺寸或 FOV 计算设计值,精度低于工厂标定)或硬编码(按机型维护型号表)。深度像素方案绕开了这个限制,直接使用深度摄像头的出厂标定内参。
十、相关文档
.trellis/tasks/05-20-depth-pixel-integration/prd.md— PRD 需求文档.trellis/tasks/05-20-depth-pixel-integration/research/why-depth-pixel.md— 深度像素方案详解