2026.05.09征图日记10(模型信息展示&&LiDAR(激光雷达)测量缺陷面积)

今天上午来就在搞昨天的app模型信息展示,先尝试了用md原生渲染,发现图片渲不出来,后来就是md语法+html把图片渲染出来,通过imageMap映射本地图片到url,之后在html里面显示。
搞完这个之后就在研究之前leader说的,缺陷像素面积向真实物理面积的转换,这个无非就是两种方法:

  1. 使用标定板,板子上面的长度是已知的,根据板子的像素去推算缺陷的像素面积
  2. 得到摄像头与平面的距离,知道焦距,经过一系列的计算,就可以得到缺陷像素面积

下午就让ds给出方案,生成 App — 缺陷面积测量技术方案文档,之后在丢给ds去执行。执行完了之后还是有很多bug,对于越来越大的项目ds就显得力不从心了

下午leader和mentor又说只实现LiDAR(激光雷达)了,只好把之前实现的方法的UI入口删掉(后续要的时候随时恢复),之后就在做这个了。

难点:通过AVDepthData获取深度图后,LiDAR 深度图分辨率通常是 256×192,而 RGB 照片是 3024×4032(12MP),并且用户还有可能会对照片进行裁剪,思考如何在兼容之前的代码的情况下实现深度图与剪裁后的照片的像素映射

每一天都很痛苦,但每一天都很充实,能学到新的东西,见到新的事物,哦耶✌️


今日工作内容

1、app实现展示模型信息,双md文档支持国际化、支持长单词断行、支持图片表格
2、研究缺陷像素面积向真实物理面积转换问题
3、总结技术文档

下阶段计划
1、通过调用激光雷达实现app中的缺陷物理面积显示
2、优化app交互逻辑


模型信息展示页实现总结

架构方案

采用 本地 Markdown + WebView 渲染 方案,支持中英文双语内容。

文件结构

1
2
3
4
5
6
7
8
9
mobile/src/assets/model-info/
├── model_info_zh.md # 中文正文,直接编辑即可生效
├── model_info_en.md # 英文正文,结构与中文同步
├── content.ts # 导入 md 文件 + 注册图片映射
└── images/ # 图片目录,md 中通过相对路径引用
├── 1a1.png ~ 1a9.png # 图1 划伤样本 3×3 网格
├── 1b1.png ~ 1b9.png # 图1 缺口样本 3×3 网格
├── 2a1.png ~ 2d1.png # 图2 跨场景检测 2×2 网格
└── .gitkeep

关键实现

文件 用途
metro.transformer.js 自定义 Metro 转换器,将 .md 文件转为字符串模块导出
metro.config.js 注册 md 到 sourceExts,指定 transformer 路径
src/types/md.d.ts TypeScript 声明 .md 模块类型
src/screens/ModelInfoScreen.tsx 核心页面:marked 解析 MD → CSS 注入 → WebView 渲染

图片渲染流程

1
2
MD: ![示例](./images/1.png)  →  marked image renderer  →  imageMap[key]  →  本地 file:// URI
MD: <img src="./images/1.png"> → marked 放行原始 HTML → 后处理 replaceAll → 本地 file:// URI

两种写法都支持,imageMapcontent.ts 中维护,通过 Image.resolveAssetSource(require(...)).uri 获取图片路径。

演变过程

第一阶段:选型与基础搭建

初始需求:将一份约 1800 字的 Word 文档(含图片)在移动端展示,后续可方便修改。

方案评估

  • 后端 API 返回内容:需要额外的接口、数据库、后台编辑功能,过度设计
  • 本地 Markdown:编辑即生效,无需后端改动,Git 可追踪变更历史

选择本地 MD 方案,制定计划:安装依赖 → 创建内容文件 → 新建页面 → 注册路由 → 添加入口。

第二阶段:渲染方案试错

尝试 1:react-native-markdown-display

安装后遇到 isString is not a function 运行时错误。根因是该库依赖 markdown-it,其内部模块 markdown-it/lib/common/utils.js 在 Metro/Hermes 下无法正确解析。

最终选择:替换为 react-native-webview + marked。marked 是纯 JS 实现,无依赖冲突,在 Hermes 引擎下运行稳定。WebView 负责渲染 HTML,灵活度远高于原生组件。

第三阶段:内容导入机制演变

方案 A(被否决):在 content.ts 中用模板字符串硬编码 MD 内容。

用户明确反对:”如何能做到我只改变 md 文档,不同步”。这催生了自定义 Metro 转换器方案。

方案 B(最终采用):编写 metro.transformer.js,拦截 .md 文件,将文件内容包裹为 module.exports = "内容"。配合 metro.config.jsmd 加入 sourceExts。从此编辑 .md 文件即可,Metro HMR 自动热更新。

第四阶段:marked 渲染器踩坑

错误this.renderer.heading is not a function (it is undefined)

根因marked.parse(md, {renderer: {image: ...}})替换整个 renderer 对象,导致 heading、paragraph、table 等方法全部丢失。

修复:改用 marked.use({renderer: {image: ...}}) 放在模块顶层。marked.use()扩展而非替换,仅覆盖 image 方法,其余方法保留默认实现。注意:HMR 时 marked.use() 会累积调用,但仅影响开发环境,不影响功能。

第五阶段:图片渲染问题

问题 1:图片不显示。imageMap 是空对象,MD 中的相对路径未映射到真实文件 URI。

修复:在 content.ts 中用 require('./images/xxx.png') 导入图片,Image.resolveAssetSource() 解析为 file:// URI,填入 imageMap

问题 2:用户在全角字符 (U+FF5C)拼接的表格中放图片,marked 不识别。

修复:改为标准 ASCII | 分隔符。

问题 3:需要合并单元格 colspan。标准 markdown 表格不支持 colspan

修复:改用原始 HTML <table> 标签写在 md 中。但原始 <img> 标签不经过 marked 的 image renderer,路径不会被 imageMap 解析。解决方案:在 marked.parse() 之后增加后处理循环,对生成的 HTML 字符串做 replaceAll,将 src="./images/xxx.png" 替换为解析后的 URI。

第六阶段:样式迭代

图片宽度问题:最初在 md 中写 width="150",不同手机屏幕宽度不一致导致显示效果差异大。

修复:去掉所有硬编码宽度,CSS 中 table-layout: fixed 强制等宽列 + td img { width: 100% } 填满单元格,图片自动适配屏幕宽度。

文字居中:表头 th 和标注文字需要居中。最初在 md 中逐个 <td> 写 inline style,改为 CSS 全局 td { text-align: center; vertical-align: middle; }

背景优化:用户觉得 #0B1120 蓝黑背景不够优雅。将 body 背景改为 transparent,让 ScreenContainer 的渐变底色透过来。同时整体排版精调:增大行高到 1.75、拉开标题层级、去掉了表格的外框线、改用 border-spacing 留白。

英文断行:英文长单词在行尾整体跳到下一行,导致上一行右侧留下大片空白。

修复:body 加 overflow-wrap: break-word + word-break: break-word + hyphens: auto,HTML 加 lang 属性让浏览器按正确语言规则加连字符。

已完成功能

  1. 内容架构:双 .md 文件,编辑即生效,无需手动同步
  2. 国际化:根据 settingsStore.language 自动切换中英文内容
  3. 图片表格:支持合并单元格(colspan),3×3 和 2×2 网格布局
  4. 响应式图片table-layout: fixed + td img { width: 100% },不同手机等宽占满
  5. 文字居中:表格内容全局 text-align: center + vertical-align: middle
  6. 背景透明:WebView body 设为 transparent,融入 ScreenContainer 的渐变背景
  7. 长单词断行overflow-wrap: break-word + hyphens: auto + lang 属性,英文行尾不留白
  8. 中英文表格同步:HTML 表格结构完全一致,仅文字不同
  9. 页面入口:首页 “了解模型能力” 按钮 → ModelInfo 路由
  10. 导航注册RootStackParamList 新增 ModelInfoAppNavigator 注册 Screen

技术栈

  • 渲染react-native-webview + marked(v18)
  • 图片解析Image.resolveAssetSource(RN 内置)
  • 国际化react-i18next + zustand store
  • 样式:注入 CSS 字符串,暗色主题(透明底 + 浅色文字)

日常维护

  • 修改文字:直接编辑 model_info_zh.mdmodel_info_en.md
  • 添加图片:将图片放入 images/,在 content.tsrequire 并加入 imageMap
  • 调整样式:修改 ModelInfoScreen.tsx 中的 CSS 常量
  • 重启 Metro:每次修改配置或新增图片后执行 npx react-native start --reset-cache

iOS 缺陷检测 App — 缺陷面积测量技术方案文档

文档目的
本文档总结了 iOS 应用中,基于缺陷掩膜(mask)推算真实物理面积(mm²)的两种核心方案:参照物标定法LiDAR 深度法。内容涵盖原理、精度分析、所需参数、获取方式、计算过程、架构设计及实现细节,供开发团队参考。


1. 背景与需求

  • App 主要功能:拍照 → 上传 → 后端推理返回缺陷像素掩码图 → 前端渲染并推算缺陷真实物理面积。
  • 需要实现两种面积测量模式:
    • 标定法:高精度,需用户配合放置参照物。
    • LiDAR 深度法:自动化,利用设备 LiDAR 传感器快速估算。
  • 两种模式可在 App 设置中切换,历史数据可重算。

2. 整体架构设计

采用策略模式,统一面积计算接口,前端根据设置切换具体策略。

1
2
3
4
5
6
7
8
9
10
11
protocol AreaCalculator {
var method: MeasurementMethod { get }
func calculateArea(mask: CGImage, context: CalculationContext) throws -> AreaResult
}

struct CalculationContext {
let originalImage: UIImage
let depthData: AVDepthData? // LiDAR 模式使用
let calibrationData: AVCameraCalibrationData?
let referenceScale: Double? // 标定法使用 (mm/px)
}
  • 主控制器持有 currentCalculator: AreaCalculator
  • 切换设置后,使用已保存的 MeasurementSession 重新计算,无需重新拍照。

3. 标定法(参照物法)

3.1 原理

利用已知物理尺寸的参照物,计算图像像素与实际尺寸的比例关系,进而将 mask 像素数换算为面积。

3.2 参数

参数 含义 获取方式
L_mm 参照物真实长度(mm) 用户输入或预设(硬币 25mm 等)
L_px 参照物在图像中的像素长度 用户拖动线段,计算端点欧氏距离
N mask 白色像素总数 统计二值掩膜非零像素数

3.3 计算步骤

  1. 标定比例尺
    [
    k = \frac{L_{mm}}{L_{px}} \quad (\text{mm/px})
    ]
  2. 单像素面积
    [
    A_{pixel} = k^2 = \left( \frac{L_{mm}}{L_{px}} \right)^2 \quad (\text{mm}^2)
    ]
  3. 缺陷总面积
    [
    A_{defect} = N \times A_{pixel} = N \times \frac{L_{mm}^2}{L_{px}^2}
    ]

3.4 精度分析

  • 理想条件(正对拍摄、无畸变、标定精确):相对误差 0.5% ~ 2%
  • 实际使用:通常 2% ~ 5%,透视严重时可达 10%。
  • 提升精度手段:透视校正(OpenCV/Vision)、使用长参照物、引导用户正对拍摄。

4. LiDAR(激光雷达) 深度法

4.1 原理

利用 LiDAR 传感器提供的逐像素深度值 (Z) 和相机内参 (f_x, f_y),计算每个像素覆盖的真实物理面积,然后在 mask 区域内积分。

4.2 参数

参数 含义 获取来源
depthMap 逐像素深度值(米) AVDepthData.depthDataMap
fx, fy 像素焦距(px) AVCameraCalibrationData.intrinsicMatrix
mask 缺陷掩膜(需对齐到深度图尺寸) 缩放/裁剪后端返回的彩色图 mask
深度图尺寸 (W, H) 用于遍历 CVPixelBufferGetWidth/Height

4.3 计算步骤

单个像素的真实面积(深度 (Z),单位米):
[
A_{pixel}(u,v) = \frac{Z(u,v)}{f_x} \times \frac{Z(u,v)}{f_y} = \frac{Z(u,v)^2}{f_x \cdot f_y} \quad (\text{m}^2)
]
转为 mm²:乘以 (10^6),即:
[
A_{pixel,mm²}(u,v) = \frac{(Z_{mm})^2}{f_x \cdot f_y}
]

缺陷总面积两种求法:

  1. 逐像素积分(精确):
    [
    A_{total} = \sum_{(u,v) \in defect} \frac{Z(u,v)^2}{f_x \cdot f_y}
    ]
    跳过无效深度(0 或 NaN)。
  2. 平均深度近似(快速):
    [
    A_{total} \approx N \times \frac{\bar{Z}^2}{f_x \cdot f_y}
    ]
    其中 (\bar{Z}) 为 mask 区域内有效深度的平均值。

4.4 精度与局限

  • 理想平面、中近距离(30~50cm):相对误差 2% ~ 8%
  • 微小缺陷(<10mm²)因深度图分辨率低,基本不可测。
  • 受材质影响大(玻璃/金属/黑色塑料会产生空洞),需做无效值过滤和可靠性提示。

5. 参数获取方法对比

参数 标定法 LiDAR 深度法
图像 mask ✅ 与原图同尺寸 ✅ 需缩放到深度图尺寸
参照物真实长度 ✅ 用户输入
参照物像素长度 ✅ 用户交互量取
深度图 ✅ 由 LiDAR 相机采集
相机内参 fx, fy ✅ AVCameraCalibrationData
深度图尺寸 ✅ 从 CVPixelBuffer 获取

6. 深度图详解

  • 本质是距离图,每个像素存储该点到镜头的物理距离(单位:米),Float32 格式。
  • 在 LiDAR 机型上通过 AVCapturePhoto 输出 AVDepthData 获取。
  • 常见尺寸 256×192,与高分辨率彩色图分离,需要几何对齐。

7. 交互与设置流程

  1. 拍照时根据当前设置决定是否启用深度流。
  2. 后端返回 mask 后,打包 MeasurementSession(含原图、mask、深度数据、标尺比例等)。
  3. 用户在设置切换测量方式后,App 自动选用对应计算器重算面积。
  4. 标定法需额外提供画线 UI 和长度输入;LiDAR 法需处理不支持的设备自动回退。

8. 优缺点总结与选型建议

特性 标定法 LiDAR 深度法
精度 高(可 <1% 配合校正) 较低(2%~8%,受材质影响)
自动化 需用户标定 全自动
硬件要求 所有 iPhone 仅 Pro(LiDAR)机型
微小缺陷 可测 基本不可测
适用场景 质检、维修等需要可复现数值 快速估测、无参照物时

推荐实现:两种模式共存,允许用户自由切换;默认标定法,LiDAR 法在结果中标注“估算值仅供参考”。


9. 附录:核心计算公式汇总

方案 公式
标定法 (A = N \cdot \left( \frac{L_{mm}}{L_{px}} \right)^2)
LiDAR 逐像素 (A = \sum \frac{Z_{mm}^2}{f_x f_y})
LiDAR 平均近似 (A \approx N \cdot \frac{\bar{Z}_{mm}^2}{f_x f_y})

文档版本:v1.0
适用系统:iOS 14+(LiDAR 需 Pro 机型)
相关框架:AVFoundation, Vision, Accelerate, ARKit(可选)