2026.05.08征图日记9(后端GroundingDINO、TensorRT管线)

今天上午主要就是把国际化和分割掩码轮廓渲染做了一下,用cc改代码的时候,我发现我根本不需要全部看懂代码,只需要明白ai在做什么就够了,当然ai肯定有出bug的时候,你带着它再走几次一般也就改好了。并且我还在这个过程中学到了很多知识,大模型相关的,图像编码,国际化组件,归一化坐标等等。我觉得以后代码可能就不重要了,重要的是经验,你对于一个问题,基于以前的方法总结出来的经验如何应用到当前项目。当然在未来这也有可能被ai替代,那个时候,最重要的又是什么呢?我觉得是人与人之间的连接,维护客户关系,维护人际关系,情感。只要还没有到硅基生命的阶段,其实本质上所有东西还是人在推动,所以人与人之间的纽带才是ai不可替代的。销售是一个好的职业

作出决策本身不难,难得点在你要已经知道所有作出这个决策的前置知识,掌握这个领域的所有技能。这个时候才能作出比较正确的决策。
实习中,可能就只是改了几个参数,就可以让大模型的推理速度提高,但是前提是要知道整个项目的框架、数据流向,大模型推理的底层逻辑,这些参数都代表什么,改大或者改小对结果有什么影响
许多时候,我们都只是在为了改那几个参数(很小的行动)做长时间的准备和积累,这里面的试错成本是很高的。

原图要经过划窗切分成800x800,步长720,重叠80px,采用归一化坐标确定图像或者缺陷在图中整体位置,之后模型再去推理这些小图,推理完之后,nms_small窗口内去重(按置信度排序)进行合并,去掉置信度低的缺陷框,merge_detections跨窗口合并,加窗口偏移 → 原图坐标系,nms_large 去重(按面积排序,IoU(交并比)>0.5 判为重复),边界裁剪(切掉 padding 区误检),前端对返回的mask掩码进行解析并绘制


今日工作内容

国际化 i18n

  • 安装 i18next + react-i18next
  • 新建 src/i18n/locales/zh.ts(约 80 个 key)和 en.ts
  • 新建 src/i18n/index.ts,从 settingsStore 读取初始语言,默认中文
  • settingsStore 新增 language 字段,MMKV 持久化
  • App.tsx 新增 LanguageSync 组件,监听 store 语言变化调用 i18n.changeLanguage()
  • 修改 9 个 screen、3 个 component、3 个 utils/service 文件,所有硬编码文本替换为 t()
  • SettingsScreen 新增语言切换控件(SegmentedControl:中文/English)
  • API 返回的标签(label_cn / label)根据 i18n.language 选择显示
  • 修改 HomeScreen 描述文字为”世界级的精准检测机器视觉装备商”(英文:Machine Vision + AI / World-Class Precision Inspection),英文用 \n 拆两行

分割掩码轮廓渲染

  • 需求:在检测结果的图片上,用细线围出缺陷像素区域

  • 方案演进:

    1. SvgImage 渲染 base64 掩码 → 不显示(SvgImage 不支持 data URI)
    2. RN Image 叠加掩码 → 背景变暗(掩码黑色背景降低原图亮度)
    3. SVG 滤镜边缘检测 → 报错(react-native-svg 不支持 feGaussianBlur 等滤镜)
    4. JavaScript 纯算法方案(最终采用):
      • 安装 upng-js(含 pako)解码 PNG 掩码
      • 新建 src/utils/segmentation.ts:4-邻域边缘检测 + 检测框过滤
      • 边缘像素判定:白色且四邻至少一个黑色 → 青色轮廓
      • 检测框过滤:仅保留落在 bbox 范围内的轮廓(过滤无检测框的噪声区域)
      • 关键 bug 修复:bbox 是像素坐标,非归一化坐标,直接比较无需除宽高
      • 输出透明背景 RGBA PNG,叠加在 Svg 边框层下方
  • 最终渲染架构:

    1
    2
    3
    4
    View
    Image 原图 (RN Image, absolute)
    Image 轮廓图 (透明背景 + 青色细线, absolute)
    Svg 边框 + 标签 (absolute, 透明背景)

思考模型信息展示界面实现方案

  • 模型信息文字+图片放在前端or后端服务器?
  • 使用什么组件进行渲染?如何渲染?
  • 如何处理国际化的问题?

最终决定:文字图片放在前端app的assert的静态目录下,创建两个md文件分别对应中英文,图片目录共享,安装react-native-markdown-display包支持markdown渲染

关键技术认知

原图要经过划窗切分成800x800,步长720,重叠80px,采用归一化坐标确定图像或者缺陷在图中整体位置,之后模型再去推理这些小图,推理完之后,nms_small窗口内去重(按置信度排序)进行合并,去掉置信度低的缺陷框,merge_detections跨窗口合并,加窗口偏移 → 原图坐标系,nms_large 去重(按面积排序,IoU(交并比)>0.5 判为重复),边界裁剪(切掉 padding 区误检),前端对返回的mask掩码进行解析并绘制

下阶段计划

1、实现app模型信息展示
2、继续梳理大模型推理管线


操作日志

2026-05-08 — 移动端国际化 + 分割掩码轮廓渲染

国际化 i18n

  • 安装 i18next + react-i18next
  • 新建 src/i18n/locales/zh.ts(约 80 个 key)和 en.ts
  • 新建 src/i18n/index.ts,从 settingsStore 读取初始语言,默认中文
  • settingsStore 新增 language 字段,MMKV 持久化
  • App.tsx 新增 LanguageSync 组件,监听 store 语言变化调用 i18n.changeLanguage()
  • 修改 9 个 screen、3 个 component、3 个 utils/service 文件,所有硬编码文本替换为 t()
  • SettingsScreen 新增语言切换控件(SegmentedControl:中文/English)
  • API 返回的标签(label_cn / label)根据 i18n.language 选择显示
  • 修改 HomeScreen 描述文字为”世界级的精准检测机器视觉装备商”(英文:Machine Vision + AI / World-Class Precision Inspection),英文用 \n 拆两行

分割掩码轮廓渲染

  • 需求:在检测结果的图片上,用细线围出缺陷像素区域

  • 方案演进:

    1. SvgImage 渲染 base64 掩码 → 不显示(SvgImage 不支持 data URI)
    2. RN Image 叠加掩码 → 背景变暗(掩码黑色背景降低原图亮度)
    3. SVG 滤镜边缘检测 → 报错(react-native-svg 不支持 feGaussianBlur 等滤镜)
    4. JavaScript 纯算法方案(最终采用):
      • 安装 upng-js(含 pako)解码 PNG 掩码
      • 新建 src/utils/segmentation.ts:4-邻域边缘检测 + 检测框过滤
      • 边缘像素判定:白色且四邻至少一个黑色 → 青色轮廓
      • 检测框过滤:仅保留落在 bbox 范围内的轮廓(过滤无检测框的噪声区域)
      • 关键 bug 修复:bbox 是像素坐标,非归一化坐标,直接比较无需除宽高
      • 输出透明背景 RGBA PNG,叠加在 Svg 边框层下方
  • 最终渲染架构:

    1
    2
    3
    4
    View
    Image 原图 (RN Image, absolute)
    Image 轮廓图 (透明背景 + 青色细线, absolute)
    Svg 边框 + 标签 (absolute, 透明背景)

关键技术认知

  • GroundingDINO 是单模型一次前向传播,输出 boxes + logits + masks 三组结果
  • box_threshold 和 seg_threshold 独立过滤,可能导致有掩码无检测框的情况
  • 掩码(mask)是单通道灰度图,与原图同尺寸,白色=缺陷像素,黑色=背景
  • 边缘像素 = 白色像素且四邻至少有一个黑色 → 构成缺陷轮廓线

征图推理管线学习总结 — 2026-05-08

项目定位

  • 名称:征图 (Zhengtu Vision)
  • 类型:多模态缺陷检测系统
  • 核心技术:计算机视觉(CV),非大语言模型(LLM)
  • 核心模型:GroundingDINO(视觉-语言模型,开放词汇目标检测)
  • 推理引擎:TensorRT
  • 所需知识:GroundingDINO 原理、TensorRT 推理部署、经典 CV 基础(滑窗、NMS、IoU)、BERT 分词

GroundingDINO 基本概念

  • 模型类型:视觉-语言模型(VLM),开放词汇目标检测
  • 输入:图片 + 文本描述(如 “scratch”、”black broken line”)
  • 输出:检测框 (bbox)、置信度 (score)、可选分割掩码 (mask)
  • 与 LLM 的本质区别:LLM 是 text→text,GroundingDINO 是 image+text→boxes+masks
  • 模型结构(工程视角):
    • Text Encoder:BERT tokenizer + embedding,将 prompt 转为 token 向量
    • Image Encoder:backbone 提取图像特征图
    • Cross Attention:文本向量引导视觉特征注意力
    • Box Decoder:输出 bbox + confidence

推理管线全景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
原始大图 (如 4000×3000)

├─ 1. 可选缩放(downscale,LANCZOS 算法保边缘)

├─ 2. 滑窗切图 sliding_window_crop
│ 800×800 窗口,步长 720,重叠 80px
│ padding 补零保证右下角也能取到完整窗口
│ 输出:crops(小图列表)+ positions(位置列表)

├─ 3. 构建 token spans build_token_spans
│ 按 . 分割 prompt 字符串,记录每个类别的字符区间
│ "scratch.black line.?" → [[[0,7]], [[8,17]], ...]

├─ 4. 图像预处理 ToTensor + Normalize
│ ImageNet 均值/标准差,与训练时完全一致

├─ 5. 逐窗口推理(核心循环)
│ ├─ 5a. transform 当前窗口
│ ├─ 5b. _get_grounding_output:TRT 推理 + 按类别拆分
│ │ engine.caption_tokenize() → BERT 分词 → GPU
│ │ engine.infer() → TRT 前向传播 → GPU
│ │ 输出 boxes(900,4) + logits(256,900) + masks
│ ├─ 5c. 坐标反归一化:cx,cy,w,h → x1,y1,x2,y2 像素坐标
│ └─ 5d. nms_small:窗口内去重(按置信度排序)

├─ 6. merge_detections:跨窗口合并
│ 加窗口偏移 → 原图坐标系
│ nms_large 去重(按面积排序,IoU>0.5 判为重复)
│ 边界裁剪(切掉 padding 区误检)

├─ 7. merge_masks:掩码拼合(可选)

└─ 8. visualize_results:画检测框 + 分割轮廓
紫色矩形框 + 黄色标签 + 绿色轮廓线

核心函数详解

pipeline.py

函数 核心要点
sliding_window_crop 大图切 800×800 小块,重叠 80px 防漏检,padding 补零
_get_grounding_output TRT 推理 + 按 token_spans 把 900 个候选框分配到各缺陷类别
merge_masks 用 PIL.paste 把各窗口掩码贴回原图位置
process_large_image 总调度函数,把上述所有步骤串起来

postprocess.py

函数 核心要点
_iou_small / _iou_large 计算两框重叠度(交集÷并集),还算了交集/小框面积的变体
nms_small / nms_large 去重算法,分别按置信度和面积排序保留
merge_detections 坐标还原 + 跨窗口 NMS + 边界裁剪 + 生成摘要

visualizer.py

函数 核心要点
visualize_results 在原图上画紫色检测框 + 黄色标签 + 绿色分割轮廓

核心概念深入

IoU(交并比)

  • 公式:交集面积 ÷ 并集面积,值 0~1
  • 代码实现:取两框重叠区域的宽×高为交集,两框面积之和减交集为并集
  • 额外变体 iou2 = 交集 / min(框A面积, 框B面积):用于捕获大框套小框的情况
  • 阈值 0.5 的含义:两个框重叠超过一半,认为说的是同一个目标
    • 太低(如 0.2)→ 误杀相邻的真实缺陷
    • 太高(如 0.9)→ 同一个缺陷报多个框

NMS(非极大值抑制)

用 IoU 做去重的算法流程:

  1. 把所有框按优先级排序(置信度 或 面积)
  2. 取出最好的框 → 保留
  3. 计算它与其余框的 IoU,删除 IoU > 阈值的
  4. 重复直到没有剩余

nms_small(窗口内)按置信度排序:同一窗口多报了同一目标,置信度高的更可信。

nms_large(跨窗口)按面积排序:大框通常是完整缺陷,小框可能是窗口边缘截断的残片。

归一化坐标

  • 目的:训练时图片尺寸不统一,模型学相对位置才能泛化
  • 格式(cx, cy, w, h) — 中心点 + 宽高,值域 [0,1]
  • 反归一化cx * 800 → 像素中心xcx - w/2 → x1

分割掩码

一张和原图等大的灰度图,每个像素值代表该像素属于缺陷的概率:

  • 0(黑色)= 正常区域
  • 255(白色)= 缺陷像素,置信度最高
  • 中间值 = 可能是缺陷边缘

比检测框更精确,能刻画缺陷的真实形状。

Overlap(重叠区)

overlap = window_size - stride = 800 - 720 = 80px

保证任何小于 80px 的缺陷至少在一个完整窗口内被检测到。没有重叠的话,跨边界的缺陷会被切碎导致漏检。

TensorRT 推理三部曲

1
2
3
① memcpy_host_to_device  →  数据从 CPU 内存拷到 GPU 显存
② context.execute_v2 → GPU 执行推理
③ memcpy_device_to_host → 结果从 GPU 显存拷回 CPU 内存

关键对象:

  • Engine:训练好的模型编译成的 .engine 文件(PyTorch → ONNX → Engine)
  • Context:推理执行环境,非线程安全(需要 asyncio.Lock 串行化)
  • Buffer:GPU 显存上分配的输入/输出缓冲区

代码中的数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
TRT 引擎输出(raw):
outputs[0] → boxes (1, 900, 4) 900 个候选框,归一化 cx,cy,w,h
outputs[1] → logits (1, 256, 900) 每个 token × 每个候选框的匹配得分
outputs[2] → masks (1, H, W) 分割掩码

处理后(per window):
boxes_filt (N, 4) 筛选后的框(可能有 0~几个)
pred_phrases ["scratch(0.31)", ...] 标签+得分

合并后(最终输出):
merged_detections [x1,y1,x2,y2,conf,label] 原图像素坐标
merged_mask 单通道 PIL Image 全图掩码
summary "目标位置:..., 置信度:..., 类别:..." 可读摘要

学到的工程思维

  1. 滑窗重叠的设计权衡:重叠多→安全但慢,重叠少→快但可能漏检,0.9 比例是经验平衡点
  2. 两次 NMS 的不同策略:同一场景的去重算法,因为上下文不同(窗口内 vs 跨窗口),优先保留的依据不同(置信度 vs 面积)
  3. 逐层回退机制:CSV 缺失→内置 demo;TRT 失败→MockEngine;prompt 未找到→直接用 key
  4. 可视化是调试用的中间产物visualize_results 画的图当前代码并没有返回给前端,只取了它的尺寸
  5. 工业场景的阈值选择:低阈值保召回(宁可多报),高阈值保精度(宁可不报),根据产线容忍度调节

相关文件索引

文件 路径
推理引擎接口 backend/app/core/interface.py
TRT 引擎封装 backend/app/core/trt_infer.py
TRT 底层运行时 backend/app/core/trt_runtime.py
推理管线 backend/app/core/pipeline.py
后处理 backend/app/core/postprocess.py
可视化 backend/app/core/visualizer.py
Token 工具 backend/app/core/tokens.py