192 lines
10 KiB
Markdown
192 lines
10 KiB
Markdown
# MAG160C 逆向交接文档 v2(2026-08-11)
|
||
|
||
> **本文件是当前最新、最完整的上下文快照。** 覆盖硬件/协议/官方SDK逆向/
|
||
> 显示管线演进/全部失败教训。下一轮请从第 1 节开始读,严格按第 7 节
|
||
> 的方法论执行。
|
||
|
||
## 1. 项目目标
|
||
|
||
对 Elo/Magnity MAG160C USB 热成像相机(160x120, 15fps)完整逆向:
|
||
1. 官方从"USB 读取 raw → 显示到屏幕"的**完整管线**逐层复原
|
||
2. 自研 C SDK(csdk/)+ Windows demo 的**最终屏幕画面必须与官方 app 一致**
|
||
3. Linux 移植(最终目标)
|
||
|
||
## 2. 硬件与协议(已验证,权威)
|
||
|
||
### 2.1 USB 拓扑
|
||
- VID 0x833C PID 0x0001,序列号 160043865,config 1,interface 0
|
||
- 端点:0x03 OUT(命令)、0x82 IN(命令响应)、0x81 IN(帧流)、0x84 IN(大读取,未用)
|
||
- 驱动:libusb-win32 v1.2.6.0;传输库:TI libusb-1.0.dll + dlltool 导入库
|
||
|
||
### 2.2 命令(已验证,与官方流量一致)
|
||
| 命令 | 字节 | 响应 |
|
||
|---|---|---|
|
||
| 0x6bb6b66b | 4 | 0x5bb5b55b 60B(pid@0, w=160@0x10, h=120@0x14, fps=15@0x18) |
|
||
| 0x6bb6b66c | 4 | 0x5bb5b55c 60B(另一块,勿当 info) |
|
||
| 0x6bb6b66f | 4 | 0x5bb5b55e 20B |
|
||
| 0x6bb6b672 FFC | **8**(magic+param) | 0x5bb5b55f 4B |
|
||
| 0x6bb6b673 START | 4 | 0x5bb5b55f |
|
||
| 0x6bb6b674 STOP | 4 | 0x5bb5b55f(需先 clear_halt 0x03/0x82) |
|
||
|
||
启动序列:`66b → 66c → 66f → FFC(0)×2 → 300ms → START → 700ms`
|
||
|
||
### 2.3 FFC 语义与时序(已精确解码,稳定方案已验证)
|
||
- FFC(1):流切换为 **type=0**(温度数据,手掌 +1200 counts)
|
||
- FFC(0):流切换为 type=1(raw)
|
||
- 官方 cadence:FFC(0)→ 恰 9 帧 → FFC(1)→ 间隔 34~2680 帧 → 循环
|
||
- **已验证稳定方案**:period=400 帧 + gap=9 帧(FFC 必须在完整帧后发)
|
||
- 实测:1400+ type=0 帧 / 15.1fps / 0 停帧(多次)
|
||
|
||
### 2.4 帧格式(重要:像素偏移!)
|
||
```
|
||
28B 头: 0x1bb1b11b | cnt | len=0x9600(38400) | type | shutter | 保留
|
||
38400B: 像素 u16 LE
|
||
28B 尾: 0x1bb1b11c @ offset 38400 | 统计值(如 0x7839)
|
||
```
|
||
**关键:第二次 bulk read 返回 38428 字节 = 38400 像素(从偏移 0 开始)+ 28 尾。
|
||
demo 曾用偏移 +28 读像素是错的(错位 14 u16)。** 正确:像素从 data[0] 开始。
|
||
|
||
## 3. 实机数据(决定性事实)
|
||
|
||
### 3.1 counts 数据特征
|
||
- 背景 counts 均值 ~11224(会话间 11224~13431 漂移)
|
||
- **巨大固定 mura**:空间 std 3560,行跨度 15000+
|
||
(row0 ~17318 → row120 ~7954,row119 回升;两个会话剖面几乎相同)
|
||
- row1 会话内恒定(+5000),漂移 62~1535(FFC 状态不同)
|
||
- **每像素时域噪声 std ~248 counts**(99.9% 像素波动,非坏点)
|
||
- 坏点:min=0/max=45340 恒定出现的像素(少数)
|
||
|
||
### 3.2 NUC 验证(关键结论)
|
||
- `live - (ref - mean(ref))`:mura std 3560 → **29**,行跨度 15000 → 23~49
|
||
- NUC 后温度(官方 T2E shift=6):**24.9°C 均匀(std 0.19)**,与官方探针
|
||
26.7~27.4°C 接近
|
||
- **结论:NUC 平场校正是消除 mura 的正确方法**
|
||
|
||
## 4. 官方 SDK 逆向成果(本会话最大收获)
|
||
|
||
### 4.1 可活体驱动的官方管线
|
||
Windows 官方 `CoreSDKLib.dll`(490KB, 65 导出,精确 RVA 已解析)+
|
||
`ThermalSDK.dll`(90KB, 19 导出)。**ThermalSDK 高层 API 可直接驱动设备**:
|
||
|
||
```c
|
||
// 已验证可用的调用(tsdk_debug/tsdk_capture3/tsdk_gray/tsdk_palette):
|
||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||
LoadLibraryA("ThermalSDK.dll"); // 依赖同目录 CoreSDKLib.dll 等
|
||
SetUnitMode(0); SetTempBoundary(30,44,37); SetNewIRFrameDelegate(onIR);
|
||
Start(); // 内部:NewChannel(0) -> EnumCameras -> LinkCamera(chan,pid) -> StartProcessImage
|
||
// 回调:onIR(buf, 320, 240, 3) 拿到官方渲染 RGB 帧(2x 放大)
|
||
// ReadTemperatureAtPoint(x, y, res) -> 16B 结构, tempRaw=double @16, tempArm=double @24
|
||
```
|
||
|
||
### 4.2 已提取的官方数据(analysis/ 目录)
|
||
| 数据 | 位置 | 说明 |
|
||
|---|---|---|
|
||
| 官方 T2E 表(646 int32) | `analysis/official_t2e_table.txt` | 从 .rdata 0x5cde0 提取,单调递增 51→4200129 |
|
||
| 官方渲染帧 ×8 | `build-artifacts/official_ir_00..07.rgb` | 320x240 RGB,品红系 |
|
||
| 官方灰度图 | `build-artifacts/official_gray.bin` | 160x120,93% 像素=0,顶部14行坏点带 |
|
||
| 官方调色板(错误读取) | `build-artifacts/official_palette_live.bin` | Jet 色系 BGR,疑似偏移错误,勿用 |
|
||
| 官方调色板(帧反推 115色) | `analysis/temp/official_palette_full.npy` | 品红系,可信 |
|
||
| 官方探针温度 | 记录在 handoff | 背景 26.7~27.4°C |
|
||
| 官方 T2E 表(已转C头) | `csdk/src/mag160c_official_t2e.h` | 646 项 |
|
||
| 官方调色板(已转C头,插值) | `csdk/src/mag160c_official_palette.h` | 256 项,品红系(可能需修正) |
|
||
|
||
### 4.3 官方温度公式(0x180016290 反汇编,权威)
|
||
```
|
||
x = counts << (7 - shift) ; shift 参数运行时确定(本机验证=6)
|
||
i = 二分查找 T2E[i] <= x < T2E[i+1]
|
||
diff = x - T2E[i]
|
||
slope[i] = (0x1000000 + (T2E[i+1]-T2E[i])/2) / (T2E[i+1]-T2E[i])
|
||
temp_mc = (slope[i]*diff >> 12) + (i<<12) - 0x249f0 ; 毫°C
|
||
```
|
||
验证:counts 11224 → shift=6 → 24.82°C;12500 → 31.83°C;13000 → 34.48°C
|
||
NUC 后背景 → 24.9°C(均匀)。
|
||
|
||
### 4.4 官方渲染机制(部分逆向)
|
||
- `MAG_GetOutputBMPdataRGB24(chan, buf, size, order)`:8-bit 灰度 `[dev+0xb00]`
|
||
→ 调色板查表(256×4 BGR)→ 输出 320x240 RGB
|
||
- `MAG_GetOutputBMPdata(chan, w, buf)`:返回灰度缓冲指针 `dev+0xaf0`(19200B)
|
||
- **未逆向:counts → 灰度(dev+0xaf0)的生成逻辑**(AGC/温度窗口在哪)
|
||
- **未确认:调色板确切偏移**(dev+0xb00 vs graybuf+0x10 读到的数据不符)
|
||
|
||
### 4.5 官方显示效果(用户标准)
|
||
- 官方 app 画面:背景**品红(145,0,145)**,热物体紫红渐变,顶部坏点带白色
|
||
- 官方灰度:93% 像素=0(说明显示窗口下限 > 背景温度,背景灰度≈0?
|
||
但渲染背景是品红 → 矛盾,需重新验证灰度与渲染的对应!)
|
||
- **注意:official_gray.bin 与 official_ir_03.rgb 是不同会话抓的,
|
||
不能直接配对!必须同一时刻抓灰度+RGB+温度**
|
||
|
||
## 5. 显示管线演进与失败教训(重要)
|
||
|
||
| 版本 | 方案 | 结果 | 教训 |
|
||
|---|---|---|---|
|
||
| v1 原始 | 直接显示 counts + percentile | 乱图/坏点 | raw 不可直接显示 |
|
||
| v2 | EMA 参考 + diff | 鬼影 | EMA 吸收静态场景 |
|
||
| v3 | 全局漂移 + 坏点拓扑填充 | 鬼影仍在 | 参考吸收小物体 |
|
||
| v4 | MOG 冻结 + 自愈 | 乱图叠加 | 参考采集被 quiet-gate 卡住 |
|
||
| v5 | MOG 冻结 + FFC 重采集 | 画面更混乱 | 参考从未建立(NUC 未激活) |
|
||
| v6 | NUC 平场 + 自适应窗口 | 平滑但对比度差、颜色不对 | 找到了 NUC,但窗口/调色板错 |
|
||
| v7 | NUC + T2E + 官方调色板 | 品红背景与官方一致但**什么都分辨不出** | 窗口/灰度映射仍不对,对比度丢失 |
|
||
|
||
**用户最新反馈(2026-08-11)**:v7 画面"什么都分辨不出来"(对比度丢失,
|
||
物体与背景无法区分),且"还是有鬼影"。结论:即使色系对了,灰度映射/
|
||
窗口机制仍然错误,参考帧方案仍有残留问题。**必须回到官方完整管线**。
|
||
|
||
### 5.1 核心失败教训
|
||
1. **不能边猜边试**:v1-v7 都是"猜显示算法→实机看效果→再猜"。
|
||
用户明确要求:**先把官方"读取→显示"全流程逆向完毕,再改代码**。
|
||
2. **最终屏幕画面必须与官方一致**:用户以此为标准,不是"看起来平滑"。
|
||
3. **参考帧/NUC 的副作用(鬼影)未解决**:NUC 参考冻结后无更新,
|
||
温度漂移会导致残留。
|
||
4. **官方灰度图与渲染帧未配对**:不同会话数据不可用于像素级对比。
|
||
5. **调色板偏移未确认**:graybuf+0x10 读到的 Jet 色系与渲染帧品红系矛盾,
|
||
说明读错位置或存在多个调色板。
|
||
|
||
## 6. 当前代码状态
|
||
|
||
- `csdk/tools/mag160c_demo2.c`:最新版(官方管线尝试,v7)
|
||
- 显示:counts → NUC → T2E temp(shift=6)→ 自适应窗口(背景±2°C)→
|
||
品红调色板(插值版)
|
||
- 已知问题:对比度差(分辨不出物体)、可能有鬼影
|
||
- `csdk/tools/mag160c_ffc_test.c` / `csdk_stream_test.exe`:FFC 稳定性验证(已 PASS)
|
||
- `csdk/tools/tsdk_*.c`:官方 ThermalSDK 活体 harness(tsdk_debug 最稳定)
|
||
- `csdk/src/mag160c_display.c`:纯 C 显示模块(NUC/坏点/AGC/FFC 调度)
|
||
- `csdk/src/mag160c_official_t2e.h` / `mag160c_official_palette.h`:官方表
|
||
- `csdk/tests/test_display.c`:32/32 通过
|
||
- 证据图:`analysis/demo2_v2..v7_*.png`
|
||
|
||
## 7. 下一轮方法论(必须遵守)
|
||
|
||
### 7.1 总原则
|
||
**先把官方"读取→显示"全流程逆向完毕并像素级对比一致,再动 demo 代码。**
|
||
|
||
### 7.2 具体步骤(建议顺序)
|
||
1. **同帧抓取**:写一个 harness(基于 tsdk_debug),在**同一帧**内抓:
|
||
- 官方灰度(MAG_GetOutputBMPdata → dev+0xaf0)
|
||
- 官方渲染 RGB24(MAG_GetOutputBMPdataRGB24)
|
||
- 官方温度(MAG_GetTemperatureData 或 ReadTemperatureAtPoint 多点多帧)
|
||
- 官方调色板(需先确认正确偏移)
|
||
存成配对文件(如 official_pair_%03d.gray/.rgb/.bin)。
|
||
2. **逆向 counts→灰度 生成代码**:在 CoreSDKLib.dll 找写 dev+0xaf0 的
|
||
函数(帧处理线程),确认:AGC 算法(线性?直方图?)、温度窗口、
|
||
是否含 NUC/坏点。这是"读取→显示"缺失的核心环节。
|
||
3. **确认调色板**:用同帧灰度+RGB 精确提取 256 项调色板
|
||
(gray→color 逐项),替换现在的插值版。
|
||
4. **像素级对比**:用我们的 counts + 逆向的完整管线重建画面,与
|
||
官方同帧渲染逐像素对比(相关系数、色差),迭代到一致。
|
||
5. **温度标定**:用官方温度数据校准 shift 和 offset,替换 147 counts/C。
|
||
6. **鬼影**:参考帧方案在官方管线确定后重新设计(官方是否有参考帧?
|
||
还是纯逐帧处理?)。
|
||
|
||
### 7.3 工具与入口
|
||
- 设备空闲检查:`Get-PnpDevice | ? InstanceId -match 833C`
|
||
- 官方 harness:`build-artifacts\tsdk_debug.exe`(最稳定,先跑这个验证设备)
|
||
- 我们的帧 dump:`build-artifacts\mag160c_frame_dump.exe <n> <dir>`
|
||
- 分析脚本:`analysis/temp/`(Python,numpy/scipy/PIL 可用)
|
||
- 官方 DLL 反汇编:objdump -d -Mintel(RVA 见 `analysis/temp/exports2.py`)
|
||
|
||
### 7.4 环境
|
||
- Windows 11 + MinGW gcc 14.2(`C:\mingw64\bin`),无 cmake
|
||
- libusb:`csdk/third_party/libusb/win64/`
|
||
- 官方 SDK:`IR_Camera_SDK-1.0.1/windows/windows/app/`
|
||
- Python 3.10 + numpy/scipy/PIL
|