- 逆向:Ghidra/IDA 全量反编译 CoreSDKLib.dll/ThermalSDK.dll/libthermalSDK.so/ libcoresdk.so(ARM64)/libmagcore.so,导出 analysis/ida/export/ - 解码官方渲染管线:DDT 校准表加载->快门端点选择->Q12 插值->ref(4x type1 帧 均值)->NUC 查表->盲元补偿->窗口->LUT1024 重建->2x 升采样->调色板 - 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致 - demo3 v5:完整复刻官方管线(含 DDT 解析、FFC 状态机、快门温度驱动), 修复 load_ddt 表错位导致的零像素问题 - 鬼影根因分析写入 analysis/reverse_20260813_full.md - 心跳/恢复机制:analysis/session_state.md + tools/resume_rev.ps1 - 新增 tsdk_pair3 增强采集工具;历史工具归档 csdk/tools/legacy/; 根目录抓帧残留删除,历史文档归档 analysis/history/ - csdk/README.md 完整使用文档;.gitignore/.gitattributes 补 LFS 规则
493 lines
20 KiB
C
493 lines
20 KiB
C
/* MAG160C Windows Demo - live thermal imaging viewer.
|
|
*
|
|
* Features:
|
|
* - real-time pseudo-color thermal image (160x120 upscaled to 640x480)
|
|
* - estimated temperature readout (calibrated to a start-up background
|
|
* reference; ~147 counts/C measured on the reference unit)
|
|
* - mouse probe temperature, max-temp hotspot marker, center temp
|
|
* - manual FFC trigger, frame-difference display mode, BMP snapshot
|
|
*
|
|
* Build (MinGW):
|
|
* gcc -std=c11 -mwindows -I<libusb-include> mag160c_demo.c <libusb-1.0.x64.a> \
|
|
* -o mag160c_demo.exe -lgdi32 -luser32 -lpthread
|
|
* copy libusb-1.0.dll next to the exe.
|
|
*/
|
|
#define _WIN32_WINNT 0x0601
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
#include <libusb.h>
|
|
|
|
static FILE *g_log;
|
|
|
|
/* ---- protocol (verified on hardware) ------------------------------------ */
|
|
#define MAG_CMD_PREPARE1 0x6bb6b66b
|
|
#define MAG_CMD_PREPARE2 0x6bb6b66c
|
|
#define MAG_CMD_GET_INFO 0x6bb6b66f
|
|
#define MAG_CMD_FFC 0x6bb6b672
|
|
#define MAG_CMD_START 0x6bb6b673
|
|
#define MAG_CMD_STOP 0x6bb6b674
|
|
|
|
#define IR_W 160
|
|
#define IR_H 120
|
|
#define FRAME_LEN (IR_W * IR_H * 2)
|
|
|
|
/* ---- globals ------------------------------------------------------------ */
|
|
static libusb_context *g_usb;
|
|
static libusb_device_handle *g_dev;
|
|
static unsigned char g_frame[FRAME_LEN];
|
|
static unsigned char g_bmp[54 + IR_W * IR_H * 3];
|
|
static char g_status[512];
|
|
static volatile int g_new_frame;
|
|
static volatile unsigned g_frame_count;
|
|
static volatile unsigned g_frame_type;
|
|
static int g_diff_mode = 1; /* diff mode by default (best hand contrast) */
|
|
static unsigned short g_reference[IR_W * IR_H];
|
|
static int g_has_reference;
|
|
static int g_running;
|
|
static double g_fps;
|
|
|
|
/* temperature estimate: counts -> deg C, linear about the startup reference */
|
|
static double g_counts_per_c = 147.0; /* measured: hand vs background 1322 cts / ~9 C */
|
|
static int g_probe_x = -1, g_probe_y = -1;
|
|
static int g_max_x = -1, g_max_y = -1;
|
|
static double g_max_temp = -100.0;
|
|
static double g_center_temp = -100.0;
|
|
|
|
/* ---- helpers ------------------------------------------------------------ */
|
|
static int sendcmd(unsigned magic, unsigned param, int len) {
|
|
if (g_log) { fprintf(g_log, "CMD %08x len=%d param=%u\n", magic, len, param); fflush(g_log); }
|
|
unsigned char cmd[8] = {0};
|
|
cmd[0] = (unsigned char)(magic);
|
|
cmd[1] = (unsigned char)(magic >> 8);
|
|
cmd[2] = (unsigned char)(magic >> 16);
|
|
cmd[3] = (unsigned char)(magic >> 24);
|
|
if (len >= 8) {
|
|
cmd[4] = (unsigned char)(param);
|
|
cmd[5] = (unsigned char)(param >> 8);
|
|
cmd[6] = (unsigned char)(param >> 16);
|
|
cmd[7] = (unsigned char)(param >> 24);
|
|
}
|
|
int xfer = 0;
|
|
if (libusb_bulk_transfer(g_dev, 0x03, cmd, len, &xfer, 2000)) return -1;
|
|
unsigned char resp[0x1000];
|
|
if (libusb_bulk_transfer(g_dev, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static double counts_to_c(unsigned v) {
|
|
return 25.0 + (v - (int)g_reference[g_probe_x >= 0 ? (g_probe_y * IR_W + g_probe_x) : 0]) / g_counts_per_c;
|
|
}
|
|
|
|
static void render(const unsigned char *frame) {
|
|
unsigned hist[65536] = {0};
|
|
unsigned nz = 0;
|
|
for (int i = 0; i < FRAME_LEN; i += 2) {
|
|
unsigned v = (unsigned)frame[i] | ((unsigned)frame[i + 1] << 8);
|
|
if (v == 0) continue;
|
|
hist[v]++;
|
|
nz++;
|
|
}
|
|
unsigned mn = 0, mx = 0, acc = 0;
|
|
unsigned lo = (unsigned)(nz * 2 / 100);
|
|
unsigned hi = (unsigned)(nz * 98 / 100);
|
|
for (unsigned k = 0; k < 65536; ++k) {
|
|
acc += hist[k];
|
|
if (acc >= lo && mn == 0) mn = k;
|
|
if (acc >= hi && mx == 0) { mx = k; break; }
|
|
}
|
|
if (mx <= mn + 50) { mn = 0; mx = 65535; }
|
|
|
|
int hot = 0;
|
|
unsigned hotv = 0;
|
|
unsigned center_sum = 0;
|
|
unsigned char *px = g_bmp + 54;
|
|
for (int y = 0; y < IR_H; ++y) {
|
|
for (int x = 0; x < IR_W; ++x) {
|
|
int o = (y * IR_W + x) * 2;
|
|
unsigned v = (unsigned)frame[o] | ((unsigned)frame[o + 1] << 8);
|
|
int diffv = 0;
|
|
if (g_diff_mode && g_has_reference) {
|
|
diffv = (int)v - (int)g_reference[y * IR_W + x];
|
|
}
|
|
if (x >= 70 && x < 90 && y >= 50 && y < 70) center_sum += v;
|
|
if (v > hotv && v != 0) { hotv = v; hot = y * IR_W + x; }
|
|
unsigned char r, g, b;
|
|
if (g_diff_mode && g_has_reference) {
|
|
/* neutral gray background, hand = bright red/orange, cold = blue
|
|
fixed span 2000: hand occlusion measures ~+1200 counts */
|
|
int span = 2000;
|
|
if (diffv > 0) {
|
|
unsigned idx = (unsigned)(diffv * 255 / span);
|
|
if (idx > 255) idx = 255;
|
|
r = (unsigned char)(128 + idx / 2);
|
|
g = (unsigned char)(128 - idx / 2);
|
|
b = (unsigned char)(128 - idx / 2);
|
|
} else {
|
|
unsigned idx = (unsigned)(-diffv * 255 / span);
|
|
if (idx > 255) idx = 255;
|
|
r = (unsigned char)(128 - idx / 2);
|
|
g = (unsigned char)(128 - idx / 2);
|
|
b = (unsigned char)(128 + idx / 2);
|
|
}
|
|
} else {
|
|
unsigned idx = (v - mn) * 255 / (mx - mn + 1);
|
|
if (idx > 255) idx = 255;
|
|
if (idx < 64) { r = 0; g = (unsigned char)(idx * 4); b = 255; }
|
|
else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255 - (idx - 64) * 4); }
|
|
else if (idx < 192) { r = (unsigned char)((idx - 128) * 4); g = 255; b = 0; }
|
|
else { r = 255; g = (unsigned char)(255 - (idx - 192) * 4); b = 0; }
|
|
}
|
|
int dst = (IR_H - 1 - y) * IR_W * 3 + x * 3;
|
|
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
|
|
}
|
|
}
|
|
if (hot >= 0) {
|
|
g_max_x = hot % IR_W;
|
|
g_max_y = hot / IR_W;
|
|
g_max_temp = 25.0 + ((int)hotv - (int)g_reference[hot]) / g_counts_per_c;
|
|
/* draw a marker: white cross on the hottest pixel */
|
|
int mx2 = g_max_x, my2 = IR_H - 1 - g_max_y;
|
|
for (int k = -2; k <= 2; ++k) {
|
|
if (mx2 + k >= 0 && mx2 + k < IR_W) {
|
|
int d2 = my2 * IR_W * 3 + (mx2 + k) * 3;
|
|
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
|
}
|
|
if (my2 + k >= 0 && my2 + k < IR_H) {
|
|
int d2 = (my2 + k) * IR_W * 3 + mx2 * 3;
|
|
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
|
}
|
|
}
|
|
}
|
|
g_center_temp = 25.0 + ((int)(center_sum / 400) - (int)g_reference[50 * IR_W + 70]) / g_counts_per_c;
|
|
}
|
|
|
|
/* ---- window ------------------------------------------------------------- */
|
|
static HWND g_hwnd;
|
|
static HFONT g_font;
|
|
|
|
static void set_status(const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(g_status, sizeof(g_status), fmt, ap);
|
|
va_end(ap);
|
|
InvalidateRect(g_hwnd, NULL, TRUE);
|
|
}
|
|
|
|
static void save_bmp(void) {
|
|
char path[MAX_PATH];
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st);
|
|
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
|
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
|
FILE *f = fopen(path, "wb");
|
|
if (f) {
|
|
fwrite(g_bmp, 1, sizeof(g_bmp), f);
|
|
fclose(f);
|
|
set_status("saved %s", path);
|
|
}
|
|
}
|
|
|
|
static void do_ffc(void) {
|
|
sendcmd(MAG_CMD_FFC, 1, 8);
|
|
Sleep(300);
|
|
sendcmd(MAG_CMD_FFC, 0, 8);
|
|
set_status("FFC triggered");
|
|
}
|
|
|
|
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
|
switch (msg) {
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps;
|
|
HDC dc = BeginPaint(hw, &ps);
|
|
/* image */
|
|
HDC mem = CreateCompatibleDC(dc);
|
|
HBITMAP bm = CreateCompatibleBitmap(dc, IR_W, IR_H);
|
|
HGDIOBJ old = SelectObject(mem, bm);
|
|
SetDIBitsToDevice(mem, 0, 0, IR_W, IR_H, 0, 0, 0, IR_H, g_bmp + 54,
|
|
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
|
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, IR_W, IR_H, SRCCOPY);
|
|
SelectObject(mem, old);
|
|
DeleteObject(bm);
|
|
DeleteDC(mem);
|
|
|
|
/* text panel */
|
|
SelectObject(dc, g_font);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextColor(dc, RGB(220, 220, 220));
|
|
int y = 20;
|
|
char line[256];
|
|
snprintf(line, sizeof(line), "frame : %u (type %u)", g_frame_count, g_frame_type);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
if (g_probe_x >= 0) {
|
|
double t = counts_to_c((unsigned)(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2] |
|
|
(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2 + 1] << 8)));
|
|
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
}
|
|
if (g_max_x >= 0) {
|
|
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
}
|
|
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF (reference)" : "absolute");
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
snprintf(line, sizeof(line), "scale : %.0f counts/C", g_counts_per_c);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
SetTextColor(dc, RGB(120, 220, 120));
|
|
TextOutA(dc, 10, 500, g_status, (int)strlen(g_status));
|
|
EndPaint(hw, &ps);
|
|
break;
|
|
}
|
|
case WM_LBUTTONDOWN: {
|
|
int x = LOWORD(lp), y = HIWORD(lp);
|
|
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
|
|
g_probe_x = (x - 10) * IR_W / 640;
|
|
g_probe_y = (y - 10) * IR_H / 480;
|
|
g_probe_y = IR_H - 1 - g_probe_y;
|
|
InvalidateRect(hw, NULL, TRUE);
|
|
}
|
|
break;
|
|
}
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wp)) {
|
|
case 1001: do_ffc(); break; /* FFC */
|
|
case 1002: save_bmp(); break; /* save */
|
|
case 1003: g_diff_mode = !g_diff_mode; break; /* diff mode */
|
|
case 1004: { /* re-reference */
|
|
for (int i = 0; i < FRAME_LEN; i += 2)
|
|
g_reference[i / 2] = (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
|
|
g_has_reference = 1;
|
|
g_diff_mode = 1;
|
|
set_status("reference captured (press FFC then re-capture for best result)");
|
|
break;
|
|
}
|
|
case 1005: { /* reset probe */
|
|
g_probe_x = -1;
|
|
InvalidateRect(hw, NULL, TRUE);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
return DefWindowProc(hw, msg, wp, lp);
|
|
}
|
|
|
|
static int open_camera(void) {
|
|
libusb_device **list = NULL;
|
|
ssize_t cnt = libusb_get_device_list(g_usb, &list);
|
|
for (ssize_t i = 0; i < cnt && !g_dev; ++i) {
|
|
struct libusb_device_descriptor d;
|
|
libusb_get_device_descriptor(list[i], &d);
|
|
if (d.idVendor == 0x833c) libusb_open(list[i], &g_dev);
|
|
}
|
|
libusb_free_device_list(list, 1);
|
|
if (!g_dev) return -1;
|
|
libusb_set_configuration(g_dev, 2);
|
|
libusb_set_configuration(g_dev, 1);
|
|
return libusb_claim_interface(g_dev, 0);
|
|
}
|
|
|
|
static int init_camera(void) {
|
|
for (int attempt = 0; attempt < 4; ++attempt) {
|
|
if (g_log) { fprintf(g_log, "init attempt %d\n", attempt + 1); fflush(g_log); }
|
|
{
|
|
/* hard reset before every attempt: the unit needs a fresh
|
|
session or the FFC/type switch stalls */
|
|
if (g_dev) libusb_reset_device(g_dev);
|
|
if (g_dev) { libusb_close(g_dev); g_dev = NULL; }
|
|
Sleep(2500);
|
|
}
|
|
if (open_camera() != 0) continue;
|
|
if (sendcmd(MAG_CMD_PREPARE1, 0, 4)) continue;
|
|
if (sendcmd(MAG_CMD_PREPARE2, 0, 4)) continue;
|
|
if (sendcmd(MAG_CMD_GET_INFO, 0, 4)) continue;
|
|
if (sendcmd(MAG_CMD_FFC, 0, 8)) continue;
|
|
Sleep(100);
|
|
if (sendcmd(MAG_CMD_FFC, 0, 8)) continue;
|
|
Sleep(300);
|
|
if (sendcmd(MAG_CMD_START, 0, 4)) continue;
|
|
Sleep(700);
|
|
unsigned char probe[64];
|
|
int xfer = 0, got = 0;
|
|
for (int k = 0; k < 10; ++k) {
|
|
if (libusb_bulk_transfer(g_dev, 0x81, probe, sizeof(probe), &xfer, 300) == 0 && xfer >= 28) {
|
|
unsigned m = (unsigned)probe[0] | ((unsigned)probe[1] << 8) |
|
|
((unsigned)probe[2] << 16) | ((unsigned)probe[3] << 24);
|
|
if (m == 0x1bb1b11b) { got = 1; break; }
|
|
}
|
|
}
|
|
if (got) {
|
|
if (g_log) { fprintf(g_log, "init ok (attempt %d)\n", attempt + 1); fflush(g_log); }
|
|
return 0;
|
|
}
|
|
if (g_log) { fprintf(g_log, "init no frames, retry\n"); fflush(g_log); }
|
|
libusb_close(g_dev);
|
|
g_dev = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int WINAPI WinMain(HINSTANCE inst, HINSTANCE hprev, LPSTR cmd, int show) {
|
|
(void)hprev; (void)cmd; (void)show;
|
|
|
|
g_log = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/demo_log.txt", "w");
|
|
if (g_log) { fprintf(g_log, "demo start\n"); fflush(g_log); }
|
|
|
|
libusb_init(&g_usb);
|
|
if (init_camera() != 0) {
|
|
if (g_log) { fprintf(g_log, "init FAILED\n"); fflush(g_log); }
|
|
MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR);
|
|
return 1;
|
|
}
|
|
|
|
WNDCLASSA wc = {0};
|
|
wc.lpfnWndProc = wndproc;
|
|
wc.hInstance = inst;
|
|
wc.lpszClassName = "Mag160cDemo";
|
|
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
|
|
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
RegisterClassA(&wc);
|
|
g_hwnd = CreateWindowA("Mag160cDemo", "MAG160C Thermal Demo",
|
|
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 600,
|
|
NULL, NULL, inst, NULL);
|
|
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
|
|
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
|
|
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
|
|
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
|
|
CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
|
|
670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL);
|
|
CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL);
|
|
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
|
|
|
|
/* bmp header */
|
|
unsigned sz = 54 + IR_W * IR_H * 3;
|
|
g_bmp[0] = 'B'; g_bmp[1] = 'M';
|
|
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[10] = 54; g_bmp[14] = 40;
|
|
g_bmp[18] = IR_W; g_bmp[19] = 0; g_bmp[22] = IR_H; g_bmp[23] = 0;
|
|
g_bmp[26] = 1; g_bmp[28] = 24;
|
|
|
|
g_running = 1;
|
|
ShowWindow(g_hwnd, SW_SHOW);
|
|
set_status("starting...");
|
|
|
|
/* capture startup background reference */
|
|
unsigned prevcnt = 0xffffffff;
|
|
int frames = 0, ffc_done = 0;
|
|
unsigned long long ref_sum[IR_W * IR_H] = {0};
|
|
int ref_n = 0;
|
|
DWORD t0 = GetTickCount();
|
|
DWORD last_fps_t = t0;
|
|
unsigned last_fps_cnt = 0;
|
|
|
|
MSG msg;
|
|
for (;;) {
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
if (msg.message == WM_QUIT) goto done;
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
unsigned char hdr[64];
|
|
int xfer = 0;
|
|
if (libusb_bulk_transfer(g_dev, 0x81, hdr, sizeof(hdr), &xfer, 500) || xfer < 28) continue;
|
|
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
|
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
|
if (m != 0x1bb1b11b) continue;
|
|
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
|
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
|
if (c == prevcnt) continue;
|
|
prevcnt = c;
|
|
if (libusb_bulk_transfer(g_dev, 0x81, g_frame, sizeof(g_frame), &xfer, 500) || xfer < FRAME_LEN) continue;
|
|
frames++;
|
|
if (g_log && (frames % 100) == 0) { fprintf(g_log, "frames=%d cnt=%u\n", frames, c); fflush(g_log); }
|
|
|
|
g_frame_count++;
|
|
g_frame_type = (unsigned)hdr[12];
|
|
/* accumulate reference over the first 30 frames */
|
|
/* wait for the stream to stabilize before capturing the reference */
|
|
if (!g_has_reference && frames >= 150 && ref_n < 30) {
|
|
for (int i = 0; i < FRAME_LEN; i += 2)
|
|
ref_sum[i / 2] += (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
|
|
ref_n++;
|
|
if (ref_n == 30) {
|
|
unsigned long long rs = 0;
|
|
for (int i = 0; i < IR_W * IR_H; ++i) {
|
|
g_reference[i] = (unsigned short)(ref_sum[i] / 30);
|
|
rs += g_reference[i];
|
|
}
|
|
g_has_reference = 1;
|
|
if (g_log) { fprintf(g_log, "reference captured: avg=%llu\n", rs / 19200); fflush(g_log); }
|
|
set_status("reference captured (frame 150+) - hand shows red");
|
|
}
|
|
} else if (g_has_reference) {
|
|
/* slow background tracking: reference follows slow drift so that a
|
|
hand (fast change) stays visible; 0.5%/frame */
|
|
for (int i = 0; i < FRAME_LEN; i += 2) {
|
|
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
|
|
unsigned r = g_reference[i / 2];
|
|
g_reference[i / 2] = (unsigned short)((r * 199 + v) / 200);
|
|
}
|
|
}
|
|
if (!ffc_done && frames == 3) {
|
|
sendcmd(MAG_CMD_FFC, 1, 8); /* switch to type=0 (temperature);
|
|
same as verified thermal_viewer.c */
|
|
ffc_done = 1;
|
|
Sleep(400);
|
|
continue;
|
|
}
|
|
if (g_log && (g_frame_count % 50) == 0) {
|
|
unsigned long long sv = 0, sd = 0;
|
|
int dmax = 0, dmin = 0;
|
|
for (int i = 0; i < FRAME_LEN; i += 2) {
|
|
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
|
|
sv += v;
|
|
int d = (int)v - (int)g_reference[i / 2];
|
|
sd += (unsigned long long)(d < 0 ? -d : d);
|
|
if (d > dmax) dmax = d;
|
|
if (d < dmin) dmin = d;
|
|
}
|
|
fprintf(g_log, "frame %u: type=%u avg=%llu ref=%u dmean=%llu dmin=%d dmax=%d\n",
|
|
g_frame_count, g_frame_type, sv / 19200, (unsigned)g_reference[0],
|
|
sd / 19200, dmin, dmax);
|
|
fflush(g_log);
|
|
}
|
|
render(g_frame);
|
|
if ((g_frame_count % 200) == 0) {
|
|
FILE *tf = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/render_test.bmp", "wb");
|
|
if (tf) { fwrite(g_bmp, 1, sizeof(g_bmp), tf); fclose(tf); }
|
|
}
|
|
DWORD now = GetTickCount();
|
|
if (now - last_fps_t >= 1000) {
|
|
g_fps = (g_frame_count - last_fps_cnt) * 1000.0 / (now - last_fps_t);
|
|
last_fps_t = now;
|
|
last_fps_cnt = g_frame_count;
|
|
}
|
|
InvalidateRect(g_hwnd, NULL, FALSE);
|
|
UpdateWindow(g_hwnd);
|
|
}
|
|
done:
|
|
g_running = 0;
|
|
libusb_clear_halt(g_dev, 0x03);
|
|
libusb_clear_halt(g_dev, 0x82);
|
|
Sleep(100);
|
|
sendcmd(MAG_CMD_STOP, 0, 4);
|
|
libusb_close(g_dev);
|
|
libusb_exit(g_usb);
|
|
return 0;
|
|
}
|