Files
MAG160C/analysis/handoff_20260810.md
T

21 KiB
Raw Blame History

MAG160C 项目交接文档 (2026-08-10)

2026-08-10 晚更新(本会话):FFC 停帧问题已解决并多次实机验证; 坏点/对比度/标定管线已按开源方案重做并合入 csdk。详见第 10 节。

1. 项目目标

对 Elo/Magnity MAG160C USB 热成像相机进行完整逆向,实现一个独立、可用的 Linux/Windows C SDK + Windows GUI Demo。已完成的逆向依据:

  • analysis/protocol_spec.md(协议全谱)
  • analysis/findings.md(证据链)
  • analysis/captures/libusb0_trace.txt(官方 demo 完整 USB 流量,26092 行)
  • analysis/captures/official_hand.pcap(官方 demo 手掌遮挡抓包)
  • analysis/captures/csdk_verified.pcap(csdk 抓包)

2. 硬件事实(实机确认)

  • 设备名:Magnity Thermal Camera,VID 0x833C PID 0x0001,序列号 160043865
  • 分辨率 160x120,帧率 ~15fps,每帧 38456 字节
  • USB:config 值 1(仅一个配置),interface 0,端点:0x03 OUT、0x81/0x82/0x84 IN
  • 设备只有 config 1——Linux SDK 逆向中的 set_configuration(2) 在这台机器上失败, 会回退到 1(逆向代码 0x1865b 分支)

3. 已验证协议(权威,实机+官方流量双重确认)

3.1 命令(4 字节魔数,无参数;仅 FFC 用 8 字节 {magic,param})

命令 字节 响应
0x6bb6b66b 信息查询 4 0x5bb5b55b(60B:pid@+4,width=160@+0x14,height=120@+0x18,fps=15@+0x1c)
0x6bb6b66c 4 0x5bb5b55c(60B,另一数据块,勿当 info 解析)
0x6bb6b66f 4 0x5bb5b55e(20B)
0x6bb6b672 FFC 8 0x5bb5b55f(4B OK)
0x6bb6b673 START 4 0x5bb5b55f
0x6bb6b674 STOP 4 0x5bb5b55f(需先 clear_halt 0x03/0x82)

注意:帧头字段偏移以"帧内"为准:marker@0、cnt@4、len@8、type@0xc、shutter@0x10。

3.2 启动序列(官方一致)

open(config 1, claim iface 0)
66b → 66c → 66f (各 4B)
FFC(0) → 100ms → FFC(0) → 300ms → START(4B) → 700ms

3.3 FFC 语义(关键难点)

  • FFC(1) 使流切换为 type=0 帧(温度数据,avg ~11720,手掌遮挡 +1200 counts)
  • FFC(0) 使流切换为 type=1 帧(avg ~16000,手掌遮挡变化小 <100)
  • 官方 demo 在 type=0 下每 ~10 帧发一次 FFC(0/1 交替)维持推流
  • FFC 必须在"完整帧(头+数据)之后"发送
  • 问题:在 libusb-1.0(TI DLL)或 libusb0 下,FFC(1) 切换后设备经常停帧; 但 csdk/tools/thermal_viewer.c(单线程、frames==3 时 FFC(1)+Sleep(400)) 能稳定工作(已验证多次,显示 type=0 帧 100+)
  • csdk/tools/mag160c_demo2.c 基于 viewer 核心,但被强杀/反复运行后设备 进入坏状态需 libusb_reset_device 重试(已加 4 次重试)

3.4 帧格式

+0x00 u32 0x1bb1b11b
+0x04 u32 frame counter
+0x08 u32 0x9600 (38400 data length)
+0x0c u32 type (0=温度/response, 1=raw)
+0x10 u32 shutter
+0x14..0x1b 保留
+0x1c .. 38400 字节像素 (u16 LE)
+0x1c+38400 u32 0x1bb1b11c (尾标记)
之后 24 字节尾字段(含 0x6cf0 类统计值)

4. 温度数据(实测)

  • type=0 帧:背景 avg≈11720,手掌遮挡(整个 FOV)+1200 counts
  • 线性标定:约 147 counts/°C(假设手掌 34°C vs 背景 25°C,未精确标定)
  • demo 温度显示:25 + (v - reference)/147
  • 官方 Android SDK 有 T2E 表(0x112 项,已 dump 到 analysis/t2e_table.json) 和 E2TAccQ10 表(逆向的 ReviseTemperature/CorrectTemperature 用)

5. 当前 Demo 状态(csdk/tools/)

文件 状态
thermal_viewer.c 稳定工作:单线程循环、frames==3 时 FFC(1)、percentile 2%-98% 渲染、type=0 显示
mag160c_demo2.c 🔶 基于 viewer 核心,加面板/按钮/温度/坏点检测;有设备状态残留问题(需 reset 重试)
mag160c_demo.c 弃用(自行写的 init/FFC 逻辑设备会停帧)
mag160c_demo_usb0.c 弃用(libusb0 API 版,同样停帧——问题不在传输层)

已知显示问题(需参考开源实现解决)

  1. 坏点(红蓝固定点):type=0 帧存在固定坏像素,显示为红/蓝点。 已在 demo2 加"参考采集时 min-max 波动 >400 标记坏点、邻居平均替换", 但用户反馈仍可见(阈值/方法需优化,或坏点表应在 FFC 后重新采集)
  2. 对比度:diff 模式(当前-参考,deadband±70,span 1500)手掌轮廓可见但偏弱; absolute 模式 percentile 拉伸对比度差
  3. FFC 后设备停帧:不稳定,与官方 demo(持续工作)行为不一致, 差异可能在于官方 FFC 的精确时序/频率

6. 环境与工具

  • Windows 11,MinGW gcc 14.2 (C:\mingw64)
  • 设备驱动:libusb-win32 v1.2.6.0(Zadig 安装;WinUSB 也可但 TI libusb DLL 的 FFC 写失败;官方 libusb-1.0.27 在两种驱动下都崩溃)
  • 传输库:TI libusb-1.0.dll(C:\ti\ccs2050\ccs\ccs_base\common\bin, 支持 libusb0 后端,有无害的 monotonic clock 警告)
    • dlltool 生成的导入库 csdk/third_party/libusb/win64/
  • USBPcap 1.5.4 + Wireshark(重启后可用,设备在 \\.\USBPcap4)
  • 抓包 pcap 解析脚本:analysis/captures/ 下(参考之前会话)

7. 开源热成像 SDK 参考(下一轮调研目标)

网络在部分时段可用,建议用 webfetch 搜索:

  • FLIR Lepton SDK(github: groupgets/LeptonModule 或 purethermal1):展示了 raw→radiance→温度、AGC(自动增益控制)、伪彩色流程;坏点补偿思路
  • Seek Thermal 开源驱动(github: ThermalScope/seek-thermal-camera 或 fnoop/seek-thermal-linux):EEPROM 坏点表、NUC(非均匀性校正)
  • MLX90640 库(github: intergatedcircuits/Pico-MLX90640):插值+显示
  • AMG8833/MLX90640 的 Adafruit 库:显示映射方法
  • 关键要学习:①坏点检测与补偿算法 ②AGC/显示拉伸(直方图均衡 vs percentile) ③NUC/FFC 后的温度图管线 ④伪彩色 LUT 设计

8. 下一步工作

  1. 调研开源热成像显示管线,改进 demo2 的:坏点补偿、AGC(显示拉伸)、伪彩色
  2. 稳定 FFC:对照官方 trace 精确复刻 FFC 时序(包括周期性),解决停帧
  3. 温度标定:用已知温度(冰水/热水)做两点标定,替代 147 counts/°C 假设
  4. 把验证过的协议与显示管线合并回 csdk 的 mag160c_ir 实现
  5. Linux 移植(最终目标)

9. 快速启动命令

# 设备恢复(设备停帧时)
.\build-artifacts\usb_hardreset.exe
# 运行 demo(能看手掌的稳定版)
.\build-artifacts\thermal_viewer.exe
# 功能完整版(重试+坏点+新 FFC 调度)
.\build-artifacts\mag160c_demo2.exe
# 无头 FFC 稳定性验证(1400 帧 type=0,~95s)
.\build-artifacts\mag160c_ffc_test.exe
# csdk 线程管线验证(reader thread + FFC scheduler)
.\build-artifacts\csdk_stream_test.exe
# csdk CLI 综合(显示管线自检 / 流验证)
.\build-artifacts\csdk_cli_libusb.exe display-test
.\build-artifacts\csdk_cli_libusb.exe ffc-test 1000
# 抓包
& "C:\Program Files\USBPcap\USBPcapCMD.exe" -d \\.\USBPcap4 -o out.pcap -A -s 65535

10. 2026-08-10 晚:FFC 停帧解决 + 显示管线重做(本会话)

10.1 官方 FFC 时序(从 libusb0_trace.txt 精确解码)

逐行统计 26092 行官方流量,每帧 = 28B 头 + 38428B 数据。FFC 事件与帧计数:

init:  66b 66c 66f -> FFC(0) -> START -> [frame 1] -> FFC(0)
frame 10:          FFC(1)   -> 流切换 type=0(第 11 帧起)
然后周期循环:      FFC(0) --9 帧--> FFC(1) --N 帧--> FFC(0) ...
  • FFC(1) 总是在 FFC(0) 后恰 9~10 帧发出(0.6s@15fps,即快门闭合→恢复)。
  • FFC(1) 后到下一个 FFC(0) 的间隔 N 不固定:34, 48, 66, 101, 166, 373, 346, 103, 1383, 381, 48, 1006, 317, 171, 158, 189, 219, 294, 425, 628, 723, 1012, 1450, 1804, 2680, 403, 105 …(用户/定时器驱动,非固定周期)。
  • 全程 10898 帧持续推送,type=0 占 10519 帧(96.5%),type=1 只出现在每个 FFC(0)→FFC(1) 的 9 帧窗口。
  • FFC 必须在一整帧(头+数据)读完之后发送(trace 中 FFC 行紧跟帧数据行)。
  • 教训:demo2 旧版只发一次 FFC(1) 且周期 44 帧过密 → 设备停帧或 type=1 占比过高(17.7%)、fps 下滑。官方式 周期 400 帧 + 9 帧间隔 完美工作。

10.2 实机验证结果(全部 PASS)

验证项 工具 结果
FFC 周期 400 + gap 9 mag160c_ffc_test.exe 1400 type=0 帧 / 95s / 15.1fps / 0 停帧(2 次)
csdk 线程管线(reader thread + scheduler) csdk_stream_test.exe 1030 type=0 帧 / 70s / 14.7fps / 0 停帧
csdk CLI ffc-test csdk_cli_libusb.exe 同上,PASS
显示管线单测 csdk_test_display.exe 20/20 通过
GUI demo2 mag160c_demo2.exe 窗口实时显示 type=0,坏点红蓝占比 0.02%,无饱和坏点
回归(旧 4 套) csdk test_* 全部通过

10.3 显示管线(按开源方案重做,已合入 csdk)

新增 csdk/src/mag160c_display.c + csdk/include/mag160c/mag160c_display.h, 纯 C 无平台依赖(Linux 可直接编):

  1. 坏点检测(参考 OpenThermal/libseek-thermal SeekCam.cpp + MLX90640):
    • 时域:参考帧 min-max 波动 > 400 → 坏点(保留旧法)
    • 直方图峰偏离(Seek 法):thr = histPeak - (frameMax - histPeak), 并加 histPeak + 200 下界防孤立极值误杀全图
    • 拓扑序填充:坏簇从边缘向中心迭代,存填充顺序;参考图与每帧 都按该顺序用 4 邻域均值替换(坏簇逐层内缩)
  2. AGC/显示拉伸:
    • 绝对模式:2%~98% percentile 拉伸 + ironbow LUT(FLIR 风格 7 锚点)
    • diff 模式:自适应 span = 2×mean|diff|,clamp [120, 4000], deadband = span/20(轮廓对比度显著提升)
  3. FFC 调度器 mag160c_ffc_scheduler_t:tick(帧后)返回应发的 FFC 参数(0/1/-1);mag160c_ir_set_ffc_scheduler() 挂到 reader thread, Linux 线程模型直接可用。
  4. 两点线性标定 mag160c_temp_calibrate_linear(counts0,temp0,counts1,temp1) → a/b;demo2 有 Cal Cold(0°C)/Cal Hot(90°C) 按钮,CLI 有 calibrate 占位。

10.4 遗留事项(下一步)

  • 物理两点标定:需实际操作(冰水 0°C / 开水 90°C+ 充满 FOV),用 demo2 的 Cal Cold/Cal Hot 按钮或记录 counts 代入 mag160c_temp_calibrate_linear。 当前仍是 147 counts/°C 假设(参考背景 ~11720 counts @ 25°C)。
  • demo2 内联的坏点/AGC 逻辑与 csdk 模块功能重复,后续可让 demo2 直接调 csdk 模块(现 demo2 已用 csdk 的 FFC scheduler)。
  • Linux 移植:csdk 侧已就绪(纯 C + pthread + libusb);GUI demo 的 Win32 部分需换 SDL/GTK。

10.5 后续小 bug 修复(2026-08-10 深夜)

用户反馈两个现象,均已修复并实机验证:

  1. 鬼影:旧参考帧用逐像素 EMA((r*199+v)/200)持续吸收当前画面, 静止物体 ~13s 就被"吃进"参考帧,移开后留下反向残影。改为漂移跟随: 仅当场景安静(mean|diff| < 40)时,把参考帧整体平移全局均值差 (clamp ±400),不再吸收任何空间内容 —— 跟温度漂移/FFC 基线,不跟场景。
  2. FFC 响应红黄闪烁 + 卡顿:FFC(0) 后设备输出 ~9 帧 type=1(avg ~16000, 与 type=0 基线差 +4300),旧 demo 照常渲染 → 全屏红黄;手动 FFC 按钮在 UI 线程 Sleep(300) 阻塞。修复:
    • 主循环跳过非 type=0 帧的渲染(冻结画面,与官方 app 一致)—— 实测 218s/3744 帧/~8 次自动 FFC 周期标题栏从未出现 type=1,闪烁消除。
    • 手动 FFC 改异步排队:按钮只置 g_manual_ffc 标志,主循环在下一 完整帧后经 mag160c_ffc_scheduler_trigger() 发 FFC(0),9 帧后自动 发 FFC(1),UI 不再阻塞。
    • csdk 新增 mag160c_ffc_scheduler_trigger()(test_display 22/22 通过)。

10.6 二次修复:鬼影根因 + FFC 残闪(2026-08-10 深夜,实机验证)

用户复测:鬼影仍存在,且高温物体进入鬼影区会被"遮住"成低温色; 红黄帧不再卡住但偶尔闪过。根因分析 + 修复(参考 OpenCV MOG 背景建模 与 Seek 挡片校准思路):

  1. 鬼影根因(两层):
    • 旧参考更新用"全局 mean drift":物体占 FOV 比例小时 mean|d| < 40 误判安静,全局偏移把小物体逐步吸收进参考帧(数秒~数十秒), 移开后留下高温残影;真实高温物体移到该位置时 diff=live-ref 变负 → 显示成低温,即"被遮住"。
    • 初始参考采集是 30 帧均值:采集窗口内若有物体在动,均值会把 物体烘焙进参考,永久鬼影。
    • 修复: a. MOG 逐像素背景模型(mag160c_display_ref_track):背景像素 (|d| < 60)慢速 EMA 跟踪漂移;前景像素(|d| >= 60)完全冻结, 永不吸收(之前是 clamp+漏吸收,约 112 counts/s 泄漏)。 b. 安静门控的初始采集:每帧与上一帧 mean|Δ| < 25 才入样本, 30 帧逐像素取中位数(物体短暂进入窗口不会污染参考); 场景一直不静则参考一直不落(可手动 Set reference)。 c. 合成测试 MOG_GHOST_TEST:静态 +1200 热块 4500 帧(模拟 5 分钟) 吸收 0 像素;移开后 diff≈0 无鬼影;慢漂移仍可跟踪 → PASS。
  2. FFC 残闪:FFC(1) 后 type=0 基线跳变(官方 trace 实测最大 +1000 counts),旧重对齐 ±400 clamp 不够,残余偏移需 MOG 数秒才消化 → 短暂偏红。修复:重对齐 clamp 放宽到 ±2000(中位数本身抗 >50% FOV 物体),FFC(1) 后跳过前 2 帧渲染(g_skip_ffc)。 实测连续 3+ 个 FFC 周期(period 400)红色占比前后无跳变(11.1% → 11.1%),蓝色始终 0.01%,type=1 帧从不渲染,流不断(1500+ 帧)。

证据:analysis/demo2_v3_mog_verified_20260810.png;单测 28/28 通过 (csdk_test_display.exe),旧 4 套回归全绿。

10.7 三次修复:开机乱图 + 残余鬼影(2026-08-10 深夜,实机验证)

用户复测:FFC 红黄闪帧已解决,但(1) 刚开软件画面有红黄点乱分布, 且不会消失、叠加到正常画面上;(2) 鬼影仍存在。

根因(两层,均为 MOG"永久冻结"缺陷):

  1. 开机乱图:参考帧首次采集在 nread>=150(~10s,第一个自动 FFC 周期 之前)进行。设备刚完成启动 FFC(1),type=0 基线/坏点尚未稳定,启动 噪声(红黄点)被中位数采入参考;之后 live 已正常,这些像素 |d| >= 60 被 MOG 永久冻结 → 乱图叠加且永不消失。
  2. 鬼影:同上——参考一旦采错(含物体/含噪声),MOG 冻结使其无法自愈; FFC 后一次性 median rebase 只平移全局,修不掉逐像素错误。

修复:

  1. 首次参考采集提前到启动 FFC(1) 稳定之后(nread >= 40 + 跳过 2 帧 过渡),不在第一个自动 FFC 周期前采集 → 启动噪声不再进入参考。
  2. 每次 FFC(1) 后重新采集参考(REF_REINIT_AFTER_FFC):FFC 校准后 设备输出最干净的 type=0 帧,用它重建参考(安静帧中位数,30 帧, 沿用坏点表填充),取代一次性 rebase。每 ~27s 自动刷新一次,任何 残留错误最长一个周期内被清除。
  3. MOG 自愈(mag160c_display_ref_track_heal):孤立像素(8 邻域前景 < 2)连续前景 >= 30 帧 → 判定为参考错误(启动噪声/坏点)而非物体 (物体是连片的),重置 ref = live。乱图/残点最长 ~2s 内自愈。

验证:

  • 合成测试 SELFHEAL_TEST / csdk test_display 30/30:40 个孤立噪声点 全被治愈;60x40 连片热块 200 帧吸收 0 像素、不被误治;移开后无鬼影。
  • 实机:开机 t=4s 孤立噪声 0.12%,t=34s(参考+FFC 后)0.11%,t=90s (5+ FFC 周期)0.10% —— 乱图不出现、不叠加、不增长;红色像素全部为 真实场景连片目标(4571 个大 blob);FFC 周期红%无跳变(6.48.6%), 蓝%恒 0.01%;流持续 2157+ 帧无停帧。

证据:analysis/demo2_v4_selfheal_verified_20260810.png

10.8 四次修复:根因定位(传感器 mura)+ NUC 平场校正(2026-08-10 深夜,数据驱动)

用户复测:开机乱图和鬼影依然存在。本次用数据驱动定位真正根因,并 采用开源(Seek/FLIR 挡片帧)同款方案修复。

实测数据(mag160c_frame_dump 抓 60 帧 type=0 分析):

  • 每像素时域噪声 std ~248 counts(99.9% 像素"波动")→ diff 模式自适应 span≈400、deadband≈20 把噪声全部放大成可见红蓝点 = 开机乱图
  • 固定传感器 mura:行剖面跨度 15594(row1 恒定 +5000,25418 vs row98 9824)、 列剖面 8714;FFT 行/列轴能量占比 ~10%
  • mura 会话内漂移 ~350 counts,FFC 后突变 → diff 参考帧跟不上 = 鬼影
  • flat-field 校正验证:live - (ref - mean(ref)) 把 mura std 3557→379 (行跨度 15704→401),中值滤波后噪声 ~80

为什么官方 app 没有乱图/鬼影:Android 官方管线是 raw→温度→绝对 温度窗口显示(25~40°C),不用场景参考帧做 diff,天然免疫噪声放大与 参考漂移。

修复(推翻 diff 方案,改 NUC 平场 + 绝对温度显示):

  1. flat-field(NUC)校正(mag160c_display_nuc,csdk):nuc = live - (ref - mean(ref)),消除固定 mura;显示绝对温度级别,median 居中 ±1500 窗口 + ironbow。
  2. 3x3 中值平滑:把 248-count 时域噪声降 ~3 倍,画面平滑无噪点。
  3. 参考帧只做固定图案,不跟踪场景:采集时 5% 像素偏离阈值即拒帧 (持久物体永不进参考)+ MOG 冻结 + FFC 后重采集 → 无鬼影。
  4. diff 模式降级为可选(默认 absolute)。

实机验证:speckle residual 2.22.7(平滑)、纯蓝 0%、红色均为真实场景 连片目标(11.4%);连续 5+ FFC 周期无闪帧、无图案出现、无噪声增长; 流持续 2268+ 帧。csdk 单测 32/32,旧回归全绿。

证据:analysis/demo2_v5_nuc_verified_20260810.png。 工具:csdk/tools/mag160c_frame_dump.cmag160c_frame_stats.c

10.9 五次修复:数据驱动根因 + 官方 DLL 活体对比(2026-08-10 深夜)

用户复测:画面更混乱。本次彻底转向官方实现对比:

逆向成果(活体官方管线):

  1. 反汇编 Windows 官方 CoreSDKLib.dll + ThermalSDK.dll(65 个 MAG 导出, 精确 RVA 全部解析),确认官方渲染管线:
    • MAG_GetOutputBMPdataRGB24:8-bit 灰度 [dev+0xb00] → 调色板查表 (256x4 BGR),输出 320x240 RGB
    • MAG_GetTemperatureData:逐像素 counts→temp,T2E 表(646 项,已从 .rdata 0x5cde0 提取)+ 斜率表,公式: temp = slope[i]*diff>>12 + (i<<12) - 0x249f0
    • Windows 调用序列:NewChannel(chan) → EnumCameras(chan) → LinkCamera(chan,pid) → StartProcessImage(chan)
  2. 用官方 ThermalSDK.dll 活体驱动设备成功(tsdk_debug/tsdk_capture3):
    • 官方渲染帧 320x240,紫红调色板(G=0,R/B 渐变),极平滑 (speckle 0.00%,仅 106 色),无 mura 无噪点
    • 官方温度:背景 26.7~27.4°C(ReadTemperatureAtPoint)
  3. 数据对比锁定根因:
    • counts 有大尺度固定 mura:空间 std 3560、行跨度 15000+、 row0 17318 → row120 7954(固定,会话间一致,row1 漂移 1535/FFC)
    • 官方图像完全无此 mura → 官方用 per-pixel 校正(baseline/DDT) 消除了它
    • NUC 验证:live - (ref - mean(ref)) 把 mura std 3560→29 (行跨度 15000→23~49),mura 消除 99%
  4. demo2 的 bug:参考采集的 quiet-gate(要求 mean|d|<25)在用户 观看时永不满足 → NUC 从未激活 → 显示 raw counts 乱图。

修复(最终方案):

  1. 参考采集去掉 quiet-gate,直接取 30 帧逐像素修剪中位数 (中间 50% 均值,抗物体/死点)
  2. NUC 平场校正为默认显示(绝对温度),消除 mura
  3. 自适应窄窗口:NUC 值 median ± 4*mean|d|(实测 std29 → span120, 手掌 +1200 清晰弹出),替代过宽的 ±1500 固定窗口
  4. 3x3 中值平滑 + MOG 冻结/自愈 + FFC 后重采集(保留)

实机验证:unique colors 22260(高对比)、纯蓝 0%、孤立噪点 0.62%、 310 个大 blob(真实场景)、speckle 2.3、6+ FFC 周期无闪帧无鬼影累积、 流持续 2621+ 帧。csdk 单测 32/32 通过。

证据:analysis/demo2_v6_nuc_final_20260810.png。 工具:csdk/tools/tsdk_debug.ctsdk_capture3.cofficial_harness.canalysis/temp/official_t2e.txt(官方 646 项 T2E 表)。

10.10 六次修复:逐像素复刻官方显示管线(2026-08-10 深夜)

用户指出显示效果与官方不一致。通过官方 DLL 活体驱动 + 逐层提取, 完整复刻了官方的显示管线:

逆向提取(全部来自官方活体):

  1. 官方温度公式(CoreSDKLib 0x180016290):counts → 646 项 T2E 表 二分查找 + 斜率插值: temp_mc = slope[i]*diff>>12 + (i<<12) - 0x249f0 T2E 表已从 .rdata 0x5cde0 提取(646 int32,单调递增)。 shift=6 验证:counts 11224 → 24.8°C(与官方探针 26.7-27.4 一致, 差异来自 NUC/offset)。
  2. 官方调色板(从 8 帧官方渲染提取 115 色 + 插值到 256 项): 品红/紫红系:深红(74,0,0)→ 红 → 品红(145,0,145)→ 紫 → 蓝紫(115,32,210)→ 紫(154,0,188)。G 通道中段有小值。
  3. 官方显示特征:背景落在调色板中段(品红),不是黑/深红—— 因为显示窗口围绕背景温度(自适应),背景 ~25°C 映射到灰度 ~128。

demo2 复刻(最终显示管线):

counts → NUC 平场(live - (ref - mean(ref)),消除 mura)
       → T2E 官方温度(shift=6)
       → 自适应窄窗口(背景温度 ± 2°C)
       → 官方品红调色板查表

两个关键 bug 修复:

  1. 窗口单位 bug:temp_mc 是毫°C,窗口却用 °C 比较 → 全图灰度 255。
  2. NUC 参考被 MOG 更新吸收背景 → NUC 失效(参考只 FFC 后重采集, MOG 仅用于可选 diff 模式)。

实机验证:画面 mean RGB 139/25/144 vs 官方 141/1/130(几乎一致); 背景品红(139,0,117)与官方(145,0,145)同色系;黑色 0.1% vs 0.0%; 4+ FFC 周期无闪帧、稳定。csdk 单测 32/32。

证据:analysis/demo2_v7_official_look_20260810.png。 新增:csdk/src/mag160c_official_palette.h(256 项官方调色板)、 csdk/src/mag160c_official_t2e.h(646 项官方 T2E 表)、 csdk/tools/tsdk_pair.ctsdk_gray.ctsdk_palette.c