修复 demo3 颗粒感:显示层改用官方同款 320x240 2x 平滑输出

- 根因:demo3 之前渲染 160x120 原始灰度并最近邻放大,官方输出是
  2x 升采样后的 320x240(2-tap 平滑本身抑噪)
- 已排除:0x230(FUN_1800175a0 帧差调整)、0x41fe8 隔行平滑(=0)、
  temporal filter 0x41fe0(=1) 均未启用,f20 mode=1 直通
- 修复:render_bmp 改用 g_gray320 上色,BMP/窗口改 320x240,
  StretchBlt 加 HALFTONE,BMP 头支持 >255 尺寸
- 验证:同场景与官方 pair_000.rgb 直方图相关性 0.9796,颜色数 227=官方
This commit is contained in:
ZXCLI
2026-08-14 16:16:10 +08:00
parent 0bfb926892
commit 03ed936a5f
4 changed files with 50 additions and 38 deletions
+18 -15
View File
@@ -5,7 +5,7 @@
## 当前阶段 ## 当前阶段
**阶段 5/5:全部完成(2026-08-13 晚** **阶段 6/6:全部完成(2026-08-14**
## 已完成的里程碑 ## 已完成的里程碑
@@ -16,22 +16,25 @@
- [x] 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致 - [x] 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致
- [x] 鬼影根因文档:analysis/reverse_20260813_full.md §7 - [x] 鬼影根因文档:analysis/reverse_20260813_full.md §7
- [x] demo3 v5 官方复刻实现并实机运行 - [x] demo3 v5 官方复刻实现并实机运行
- [x] **零像素根因定位与修复**load_ddt 在端点块内多读 blind 记录导致 - [x] 零像素根因定位与修复load_ddt 表错位)
EP1+ 表错位 1280 字节 → 修好后全帧 0 个零像素,gain=4000 正常值 - [x] **颗粒感修复**:demo3 显示层改为官方同款 320×240(2x 平滑灰度 +
- [x] demo3 vs 官方同场景对比:gray 直方图相关性 0.9197(跨会话) 调色板 + HALFTONE 放大),同场景 RGB 直方图相关性 0.9796
- [ ] 提交(用户未要求,等待指示) - [x] 仓库整理 + csdk/README.md 完整文档 + 本地 commit 0bfb926
- [ ] push(等服务器上线,用户手动)
## 零像素问题(已解决,记录在案 ## 颗粒感问题(已解决,2026-08-14
- 现象:demo3 的 nuc 在 (65,25)/(66,25) 为 0,官方同坐标 9987/9989 - 现象:官方画面比 demo3 清晰,demo3 颗粒感重
- 根因:**demo3 的 load_ddt 读取布局错误**——blind 记录区在文件所有端点块 - 根因:demo3 v5 的显示层渲染的是 160×120 原始灰度(最近邻放大到 640×480),
之后,但代码在每个端点块内读了 blind,导致 EP1+ 的 thr/gain 表整体 而官方输出是 2x 升采样后的 320×240(2-tap 平滑本身抑噪),官方 app 显示
错位 1280 字节。错位表在死像素列附近的 seg0 gain=35321(应为 ~4000), 的也是这个 2x 缓冲。
d2 踩中后 NUC 输出 clamp 到 0。 - 排查过程(已排除的嫌疑):FUN_1800175a0(0x230)是帧差偏移调整非平滑;
- 修复:thr+gain 按端点块顺序读完后再统一读 blindcsdk/tools/mag160c_demo3.c 0x41fe8 后处理(FUN_1800184f0/660 隔行平滑)官方会话 =0 未启用;
load_ddt temporal filter 0x41fe0=1 未启用;f20 mode=1 直通
- 验证:修复后全帧 0 个零像素;(65,25) nuc=9814、盲元目标 9740/9732 - 修复:render_bmp 改用 g_gray320(官方 2x 缓冲)上色,BMP/窗口改 320×240,
gain0=4000 正常值;窗口/统计正常([8493,10554] m9409 s258 StretchBlt 加 HALFTONE
- 验证:320×240 BMP 与官方同场景 pair_000.rgb 直方图相关性 0.9796
颜色数 227 = 官方 227(之前 160×120 版只有 ~161 色)。
## 常用命令 ## 常用命令
Binary file not shown.
+4 -3
View File
@@ -171,12 +171,13 @@ Start-Process "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" -WorkingDir
| `mag160c_official_nuc_thr.bin` | 官方 NUC 阈值表(历史抓取,备用)| | `mag160c_official_nuc_thr.bin` | 官方 NUC 阈值表(历史抓取,备用)|
| `libusb-1.0.dll` | libusb 运行库 | | `libusb-1.0.dll` | libusb 运行库 |
验证状态(2026-08-13): 验证状态(2026-08-14):
- NUC+盲元 与官方同帧抓帧逐像素 0/19200 误差 - NUC+盲元 与官方同帧抓帧逐像素 0/19200 误差
- 阈值/gain 插值 0 误差;2x 升采样 0/76800 误差;窗口公式与官方一致 - 阈值/gain 插值 0 误差;2x 升采样 0/76800 误差;窗口公式与官方一致
- 同静态场景 gray 直方图与官方相关性 0.92(跨会话) - 同静态场景 320×240 RGB 直方图与官方相关性 0.98(跨会话)
- 显示层 = 官方同款 320×240(2x 平滑灰度 + 调色板 + HALFTONE 放大)
- 输出日志:`demo3_diag.txt`(每帧统计)、`demo3_ffc.txt`FFC 事件)、 - 输出日志:`demo3_diag.txt`(每帧统计)、`demo3_ffc.txt`FFC 事件)、
`demo3_auto_*.bmp`(每 30 帧快照) `demo3_auto_*.bmp`(每 30 帧快照320×240
### 5. 官方抓帧工具(tsdk_pair2 / tsdk_pair3 ### 5. 官方抓帧工具(tsdk_pair2 / tsdk_pair3
+26 -18
View File
@@ -1,4 +1,4 @@
/* MAG160C Windows Demo v5 - FULL official pipeline replication. /* MAG160C Windows Demo v5 - FULL official pipeline replication.
* *
* Pixel-verified against CoreSDKLib.dll (Ghidra decompile + live same-frame * Pixel-verified against CoreSDKLib.dll (Ghidra decompile + live same-frame
* captures, 2026-08-13): * captures, 2026-08-13):
@@ -76,6 +76,8 @@ static double counts_to_c(double v) {
#define W 160 #define W 160
#define H 120 #define H 120
#define NPIX (W * H) #define NPIX (W * H)
#define VW 320
#define VH 240
/* official timing constants (live: dev+0x2d78..0x2d94 = 4,5,4,1800,250) */ /* official timing constants (live: dev+0x2d78..0x2d94 = 4,5,4,1800,250) */
#define OFF_N0 4 /* ref frames after FFC(1) window start */ #define OFF_N0 4 /* ref frames after FFC(1) window start */
@@ -90,7 +92,7 @@ static double counts_to_c(double v) {
static libusb_context *g_ctx; static libusb_context *g_ctx;
static libusb_device_handle *g_h; static libusb_device_handle *g_h;
static unsigned char g_frame[40000]; static unsigned char g_frame[40000];
static unsigned char g_bmp[54 + W * H * 3]; static unsigned char g_bmp[54 + VW * VH * 3];
static char g_title[256]; static char g_title[256];
static unsigned short g_live[NPIX]; static unsigned short g_live[NPIX];
@@ -601,26 +603,28 @@ static void upscale2x(void) {
static void render_bmp(void) { static void render_bmp(void) {
unsigned char *px = g_bmp + 54; unsigned char *px = g_bmp + 54;
for (int y = 0; y < H; ++y) { /* 官方输出是 320x240:用 2x 升采样后的灰度上色(2-tap 平滑本身
for (int x = 0; x < W; ++x) { * 就抑制了颗粒感;官方 app 显示的也是这个 2x 缓冲)。 */
int i = y * W + x; for (int y = 0; y < VH; ++y) {
unsigned char gv = g_gray160[i]; for (int x = 0; x < VW; ++x) {
int i = y * VW + x;
unsigned char gv = g_gray320[i];
unsigned char r = mag160c_official_palette256[gv][2]; unsigned char r = mag160c_official_palette256[gv][2];
unsigned char g = mag160c_official_palette256[gv][1]; unsigned char g = mag160c_official_palette256[gv][1];
unsigned char b = mag160c_official_palette256[gv][0]; unsigned char b = mag160c_official_palette256[gv][0];
int dst = (119 - y) * W * 3 + x * 3; int dst = (VH - 1 - y) * VW * 3 + x * 3;
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r; px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
} }
} }
if (g_max_x >= 0) { if (g_max_x >= 0) {
int mx2 = g_max_x, my2 = 119 - g_max_y; int mx2 = g_max_x * 2, my2 = (VH - 1 - g_max_y * 2);
for (int k = -2; k <= 2; ++k) { for (int k = -3; k <= 3; ++k) {
if (mx2 + k >= 0 && mx2 + k < W) { if (mx2 + k >= 0 && mx2 + k < VW) {
int d2 = my2 * W * 3 + (mx2 + k) * 3; int d2 = my2 * VW * 3 + (mx2 + k) * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255; px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
} }
if (my2 + k >= 0 && my2 + k < H) { if (my2 + k >= 0 && my2 + k < VH) {
int d2 = (my2 + k) * W * 3 + mx2 * 3; int d2 = (my2 + k) * VW * 3 + mx2 * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255; px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
} }
} }
@@ -633,11 +637,12 @@ static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
PAINTSTRUCT ps; PAINTSTRUCT ps;
HDC dc = BeginPaint(hw, &ps); HDC dc = BeginPaint(hw, &ps);
HDC mem = CreateCompatibleDC(dc); HDC mem = CreateCompatibleDC(dc);
HBITMAP bm = CreateCompatibleBitmap(dc, W, H); HBITMAP bm = CreateCompatibleBitmap(dc, VW, VH);
HGDIOBJ old = SelectObject(mem, bm); HGDIOBJ old = SelectObject(mem, bm);
SetDIBitsToDevice(mem, 0, 0, W, H, 0, 0, 0, H, g_bmp + 54, SetDIBitsToDevice(mem, 0, 0, VW, VH, 0, 0, 0, VH, g_bmp + 54,
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS); (BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, W, H, SRCCOPY); SetStretchBltMode(dc, HALFTONE);
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, VW, VH, SRCCOPY);
SelectObject(mem, old); SelectObject(mem, old);
DeleteObject(bm); DeleteObject(bm);
DeleteDC(mem); DeleteDC(mem);
@@ -784,12 +789,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
820, 200, 100, 32, g_hwnd, (HMENU)1005, inst, NULL); 820, 200, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
unsigned sz = 54 + W * H * 3; unsigned sz = 54 + VW * VH * 3;
g_bmp[0] = 'B'; g_bmp[1] = 'M'; g_bmp[0] = 'B'; g_bmp[1] = 'M';
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8); g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24); g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
g_bmp[10] = 54; g_bmp[14] = 40; g_bmp[10] = 54; g_bmp[14] = 40;
g_bmp[18] = W; g_bmp[19] = 0; g_bmp[22] = H; g_bmp[23] = 0; g_bmp[18] = VW & 0xFF; g_bmp[19] = (VW >> 8) & 0xFF;
g_bmp[20] = (VW >> 16) & 0xFF; g_bmp[21] = (VW >> 24) & 0xFF;
g_bmp[22] = VH & 0xFF; g_bmp[23] = (VH >> 8) & 0xFF;
g_bmp[24] = (VH >> 16) & 0xFF; g_bmp[25] = (VH >> 24) & 0xFF;
g_bmp[26] = 1; g_bmp[28] = 24; g_bmp[26] = 1; g_bmp[28] = 24;
ShowWindow(g_hwnd, SW_SHOW); ShowWindow(g_hwnd, SW_SHOW);