docs: 官方 PC 端同帧逐像素对比(NUC 逐位一致、调色板一致、温度曲线系统差 +0.1/-0.7C、窗口规则差异、PC 采集链路与驱动坑)

This commit is contained in:
ZXCLI
2026-10-06 02:14:26 +08:00
parent 948c9d5d3d
commit ce27a1bb82
3 changed files with 168 additions and 0 deletions
@@ -0,0 +1,111 @@
# 官方 PC 端 vs 逆向 SDK:同一帧逐像素对比(2026-10-06)
> 目的:热像仪接 PC 后,用**官方 Windows 软件**采图,与**逆向出来的 SDK/App 管线**
> 渲染同一帧做逐像素对比,找出"照片到底差在哪些细节上"。
> 证据图:`docs/android_app/pc_vs_ours_20261006.png`(官方 / 我们 / 差异×8 / 灰度 ×16)。
## 结论速览
| 环节 | 一致性 | 证据 |
|---|---|---|
| NUC(端点插值 + 增益/阈值表 + 盲元补偿 + ref) | **逐像素完全一致** | 抽检 4 帧:19200/19200 像素相等,max diff = 0 |
| 调色板 | **完全一致** | 官方 `.pal`、官方彩色图颜色集合、我们的 `PALETTE256_ARGB` 三者 256/256 相同 |
| 灰度映射(窗口 + 直方图 LUT + 2× 上采样) | 平均 **+1.3 级**、最大 10 级(/255) | 320×240 灰度逐像素;本轮场景计数 9600–11000 |
| 彩色输出 | 60.9–72 % 字节完全相同,平均 \|Δ\| 1.4–1.9/255,最大 20 | 官方 `.rgb`(BGR24)vs 我们导出的 PNG |
| 细节增强(7×7 DDE) | 官方这份渲染**没开**;我们默认等级 0 也关 | 官方彩色 = palette(官方灰度),逐像素 max diff = 0 |
| 显示窗口 | **下界规则不同** | 官方 20/20 帧 `winHi = 数据max`;12/20 帧 `winLo = 数据min`,其余仅低 1–7 计数。我们 `winLo = mean−500`,本例比数据 min 低 **188** 计数 |
| 温度换算 | **系统差:+0.1 ℃ @27 ℃ → −0.7 ℃ @38 ℃** | 官方 `t32` 是 NUC 计数的精确直线(k=8.539 mC/count,残差 ≤0.5 mC),我们的固定 T2E 表斜率 8.26→7.67 递减 |
**一句话**:渲染管线(NUC/调色板/灰度)已经和官方 PC SDK 对得上,肉眼几乎无差别;
真正还差的是**温度曲线**(最高到 0.7 ℃)和**窗口下界规则**(造成约 1 级亮度偏差)。
## 1. 官方 Windows 侧用谁
| 候选 | 判定 |
|---|---|
| `app/【PC分析软件】…exe`(内部名 **ThermoX 2.5.3.6**,巨哥电子) | 是**网络码流**软件:面板是"指定IP / 启用DHCP Server / 传输混合/纯视频/纯温度码流",`刷新`扫不到 USB 相机,运行期不加载任何 USB 库 → 不吃本机 USB 机芯 |
| `IR_Camera_SDK-1.0.1/windows/windows/app/EloThermal.exe`(官方 SDK 演示,C# 源码在仓库) | 能连 USB 出图(CameraSDK/ThermalSDK + libusb0),但**没有任何存图功能**(全源码无 Encoder/Save)→ 只能截屏 |
| `build-artifacts/tsdk_pair3.exe`(本仓库工具,调用官方 `ThermalSDK.dll` + `CoreSDKLib.dll`) | ✅ **采用**:一次采集给出官方渲染图、原始帧、温度、调色板、LUT、DDT 全套 |
## 2. 方法(可复现)
```
# 1) 设备驱动(本机原来没装,CM_PROB_FAILED_INSTALL)
# 厂商 INF:IR_Camera_SDK-1.0.1/windows/windows/drivers/amd64/ThermoCube_stream.inf
# (libusb-win32 1.2.6.0,cat 是 libwdi 自签证书) → 需先把该证书导入
# "受信任的根证书颁发机构" + "受信任的发布者",再:
pnputil /add-driver ThermoCube_stream.inf /install # 设备变 OK,libusb0 服务 RUNNING
# 2) 官方基准采集(输出目录用第 1 个参数,勿落在仓库里)
cd <temp>
tsdk_pair3.exe <temp>\off20 20 6000 > off20_stdout.txt
# 每帧产出 19 个文件:gray / rgb / cbrgb(BGR24) / raw / f20 / ref / t32 / pal /
# lut / h1024 / cdf / hist / thr0 / thr1 / thrw / g0 / g1 / gw / blind (+official.ddt)
# 3) 用我们的管线离线渲染同一批帧(临时 JVM 单测,用完已删)
# RenderPipeline.loadDdt(official.ddt) 后:
# repeat(4){ frameRemote(pair.ref, phase=2, shutter) } // ref = 官方的参考帧
# frameRemote(pair.f20, phase=0, shutter) // 渲染
# 产出 our_XXX.png / .nuc / .t32,再用 Python 与官方逐像素比
```
**新解码出的官方内部缓存语义**(供后续逆向用):
| 缓冲 | 地址 | 含义 |
|---|---|---|
| 帧输入 | dev+0x41f20 | 官方喂给 NUC 的帧数据(= USB 帧 payload 域,均值 ~12000) |
| 参考帧 | dev+0x41ef0 | NUC 用的 ref |
| 传感器温度 | dev+0x54 | 端点插值用的 FPA 温度(与 DDT 锚点同量纲,m℃) |
| NUC 输出 | dev+0x270 | 校正后计数(均值 ~9600–11000);`MAG_GetTemperatureData_Raw` 的输入 |
| 端点选择 | dev+0x41840 | 官方的 `sel`,与我们的 `endpoint()` 取值完全一致 |
> 关键验证:把 `dev54` 当帧尾 FPA 温度喂进我们的管线,NUC 输出与官方
> `pair_*.raw` **19200/19200 像素逐位相等**——这同时锁定了帧尾 FPA 字段的语义。
## 3. 温度:官方是"每设备线性响应",我们用的是固定曲线
官方 `t32`(19200 像素 × int32 毫度)与 NUC 计数之间是**精确直线**:
```
t_mC = 8.539 * counts - 55688 # 残差 ≤ 0.5 mC;逐帧斜率 8.5358–8.5424(≈常数)
```
同一计数下不同像素的温度离散为 **0 mC**(无逐像素差异)→ 说明官方对"已校正计数"
只做一次全局线性换算(对应 `ConvertResponse2Temperature` 一族,csdk 里已有实现
`mag160c_temp_convert_response`,但安卓 App 未使用)。
我们的 `TempMath.countsToTempMc` 是固定 T2E 反查表,局部斜率随计数**递减**:
| 计数区间 | 官方斜率 | 我们的斜率 | 比值 |
|---|---|---|---|
| 9600–9900 | 8.5390 | 8.2581 | 1.034 |
| 9900–10200 | 8.5391 | 8.1993 | 1.041 |
| 10200–10600 | 8.5390 | 7.9505 | 1.074 |
| 10600–11000 | 8.5390 | 7.6706 | 1.113 |
→ 温度读数偏差:**≈27 ℃ 处 +0.1 ℃,≈38 ℃ 处 −0.62 ℃**(累计最大 −0.70 ℃)。
## 4. 建议的下一步
1. **温度**(优先):安卓端改用设备响应换算。官方 PC 侧对已校正计数是线性映射,
系数随设备(本机实测 k=8.539 mC/count、b≈−55.7 k mC,随 FPA 温度微动 ~±50 mC)。
需要先定位系数的来源(DDT / BasePara 里的标定字段),再按官方公式实现;
`csdk/src/mag160c_temp.c::mag160c_temp_convert_response` 已是同款算法,可作参照。
2. **窗口**:把 `statsWindow()` 从"`mean ± (dev24*1000>>dev4cShift)/2` 向外扩"改成官方行为
(上界 = 数据 max、下界 = 数据 min,20 帧实测 20/20 与 12/20 如此,其余差 ≤7 计数)。
这一项解释了灰度上 ~1.3 级的系统性偏差。
3. **手机侧对照照(未完成)**:本轮只覆盖"渲染",没覆盖官方**手机 App 的拍照路径**。
建议:同一静止场景,官方手机 App 与我们的 App 各拍一张同调色板照片;
官方 MDT 里存的就是 NUC 后帧,可以直接喂我们的显示阶段逐像素比
(需要给管线加一个"只跑显示阶段"的离线入口)。
4. **PC 端 C 工具链**:本机 libusb-1.0 1.0.19 对 libusb0.sys 绑定**不可用**
(`claim_interface` → Win32 错误 87;官方走 libusb0.dll API 正常)。
若要在这台机器跑 csdk 的 demo3/render_offline,需要换 libusb-1.0 版本,
或把设备改绑 WinUSB/libusbK。
## 5. 复现清单(临时文件不在仓库)
- 官方基准数据(20 帧全套,35 MB)与差异图在
`%TEMP%\mag160c_wincmp\off20\`(`compare_visual.png` / `compare_tempdiff.png` / `compare.txt`);
仓库内只保留 `docs/android_app/pc_vs_ours_20261006.png` 一张证据图。
- 驱动安装脚本痕迹:`%TEMP%\mag160c_wincmp\pnputil*.log`、`cert.log`。
Binary file not shown.
+54
View File
@@ -489,6 +489,60 @@
"未连接"分支显示,正常出图时用户看不到任何反馈)。
- [x] 单测 66 → **76 项全绿**;debug + release(R8) 双构建通过;APK 已更新。
## 用户反馈修复 第二十六轮(2026-10-06,官方 PC 软件同帧对比:渲染逐像素一致,温度曲线有系统差)
用户把热像仪接上 PC,要求"调用项目里官方给的 win 软件拍图,看和逆向出来的 sdk 的图
差在哪些细节上"。详细报告:`docs/android_app/pc_official_compare_20261006.md`,
证据图 `docs/android_app/pc_vs_ours_20261006.png`。
### 1) 官方 Win 侧选谁(侦察结论)
- `app/【PC分析软件】…exe` = 内部名 **ThermoX 2.5.3.6**(巨哥电子),是**网络码流**软件
(指定IP/DHCP Server/传输混合·纯视频·纯温度码流),扫不到 USB 机芯 → 不适用于本机芯。
- `IR_Camera_SDK-1.0.1/.../EloThermal.exe`(官方 SDK 演示)能出图但**无存图功能**(全源码核实)。
- 采用 `tsdk_pair3.exe`(调用官方 `ThermalSDK.dll`/`CoreSDKLib.dll`)做基准采集:
每帧 19 个文件(官方渲染 rgb/cbrgb、灰度、NUC 输出、输入帧、ref、t32 温度、pal、
LUT、盲元、DDT),官方 DDT 一并拿到。
### 2) 同帧逐像素对比结果(官方 vs 我们的 Kotlin 管线)
- **NUC 域逐位相等**:抽检 4 帧 19200/19200 像素完全一致(max diff = 0)→ 端点插值、
增益/阈值表、盲元补偿、ref 处理全部正确。
- **调色板 256/256 完全一致**;官方彩色图 = palette(官方灰度)(max diff 0)→ 官方这份
渲染**没开** 7×7 细节增强,与我们默认增强等级 0 对齐。
- **灰度平均差 +1.3 级、最大 10 级**;彩色 61–72 % 字节相同(平均 |Δ| 1.4–1.9/255)。
- **窗口下界规则不同**:官方 20/20 帧 `winHi = 数据max`、12/20 帧 `winLo = 数据min`
(其余低 1–7 计数);我们 `winLo = mean−500`,本例比数据 min 低 188 计数
→ 就是那 1.3 级偏差的来源。
- **温度曲线系统差**:官方 t32 是 NUC 计数的精确直线(k = 8.539 mC/count,残差 ≤0.5 mC,
无逐像素差异,即 `ConvertResponse2Temperature` 一类);我们固定 T2E 表斜率 8.26→7.67
递减 → **27 ℃ 处 +0.1 ℃,38 ℃ 处 −0.62 ℃**(最大 −0.70 ℃)。
### 3) 新解码的官方内部语义(供后续复用)
帧输入 = `dev+0x41f20`、ref = `dev+0x41ef0`、**FPA 温度 = `dev+0x54`**(喂进我们管线后
NUC 与官方逐位相等,据此锁定帧尾 FPA 字段)、NUC 输出 = `dev+0x270`(`raw` 缓冲)。
### 4) 环境坑(重要)
- 本机原本没装相机驱动(`CM_PROB_FAILED_INSTALL`)。厂商 INF `ThermoCube_stream.inf`
是 libusb-win32 1.2.6.0 + libwdi 自签 cat → 需先把证书导入"受信任的根/发布者"才能
`pnputil /add-driver /install`;装好后设备 OK、`libusb0` 服务 RUNNING。
- **libusb-1.0 1.0.19 在该绑定下不可用**:`claim_interface` 内部 Win32 错误 87
(`LIBUSB_ERROR_ACCESS`),故 csdk 的 `demo3.exe`/`render_offline` 在本机跑不起来;
官方 SDK(libusb0.dll API)正常。要用 C 工具需换 libusb-1.0 版本或把设备改绑
WinUSB/libusbK。
### 5) 待办(下一轮)
1. 温度改用设备响应换算(线性系数;先定位 DDT/BasePara 中的标定字段)。
2. `statsWindow()` 下界对齐官方行为(≈数据 min)。
3. 手机侧受控对照(官方手机 App 的 MDT 里存的就是 NUC 后帧,可喂我们的显示阶段;
需要给管线加"只跑显示阶段"的离线入口)。
临时文件(官方 20 帧基准数据、差异图、harness)都在 `%TEMP%\mag160c_wincmp\`,仓库内
只保留结论文档 + 一张证据图;本轮临时单测已删除。
## 用户反馈修复 第二十五轮(2026-09-12,关闭 debug 日志 + 带图操作手册)
用户三点要求:把 App 的 debug 日志关掉;做一个带图的手册;push 上去。