app+docs: 第三十八轮追加——同帧对照收敛差距:真机显示与 NUC dump 空间相关 0.994,鲁棒同口径下 COL 0.33 vs 官方 0.28 达标、ROW 0.22 vs 0.10 差 2.2×;640 中间级竖屏 2.25× 非整数致 GPU 列交替恶化已回退(保留 nativeOut 单级 9×/16× 整数);PIL F-mode resize 失真教训与 COL 波动待验项入档
This commit is contained in:
@@ -100,24 +100,6 @@ class LiveRenderer(
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 640x480 offscreen stage, mirroring the official viewer's display chain:
|
||||
* jadx `ImageViewer` draws the 160x120 SDK bitmap into a 640x480 offscreen
|
||||
* bitmap with the filtering paint (PaintFlagsDrawFilter + FILTER_BITMAP) and
|
||||
* only the SCREEN scales that further. The intermediate 4x bilinear is what
|
||||
* attenuates the per-sensor-row FPN pattern on the screen (measured, §17:
|
||||
* with it the row pattern lands on the official's 0.12e-2 arc; without, our
|
||||
* single-scale render shows it at full amplitude). The stage costs one GPU
|
||||
* blit per frame.
|
||||
*/
|
||||
private var mid: Bitmap? = null
|
||||
private val midRect = android.graphics.RectF(0f, 0f, 640f, 480f)
|
||||
|
||||
private fun ensureMid(): Bitmap {
|
||||
mid?.let { return it }
|
||||
return Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888).also { mid = it }
|
||||
}
|
||||
|
||||
|
||||
private companion object {
|
||||
const val BAR_SEGMENTS = 96
|
||||
@@ -234,12 +216,10 @@ class LiveRenderer(
|
||||
|
||||
val zoom = vm.state.value.zoom
|
||||
val crop = ImageTransform.cropForZoom(zoom)
|
||||
// The zoom source rect now addresses the 640x480 middle stage (4x the
|
||||
// buffer coordinates), exactly like the official viewer's offscreen.
|
||||
val src: android.graphics.Rect? = if (zoom > 1) {
|
||||
val cw = (640 * (crop[2] - crop[0])).toInt()
|
||||
val ch = (480 * (crop[3] - crop[1])).toInt()
|
||||
srcRect.set(320 - cw / 2, 240 - ch / 2, 320 + cw / 2, 240 + ch / 2)
|
||||
val cw = (bufW * (crop[2] - crop[0])).toInt()
|
||||
val ch = (bufH * (crop[3] - crop[1])).toInt()
|
||||
srcRect.set(bufW / 2 - cw / 2, bufH / 2 - ch / 2, bufW / 2 + cw / 2, bufH / 2 + ch / 2)
|
||||
srcRect
|
||||
} else null
|
||||
|
||||
@@ -254,14 +234,15 @@ class LiveRenderer(
|
||||
fit.cx - size[0] / 2f, fit.cy - size[1] / 2f,
|
||||
fit.cx + size[0] / 2f, fit.cy + size[1] / 2f,
|
||||
)
|
||||
// two-step display like the official viewer: buffer -> 640x480 (filtered)
|
||||
// -> screen. The GPU blits are cheap; the attenuated row pattern is the point.
|
||||
val m = ensureMid()
|
||||
val mc = Canvas(m)
|
||||
mc.drawColor(Color.BLACK)
|
||||
mc.drawBitmap(bitmap, null, midRect, paint)
|
||||
if (src != null) canvas.drawBitmap(m, src, dstRect, paint)
|
||||
else canvas.drawBitmap(m, null, dstRect, paint)
|
||||
// Single filtered scale buffer -> screen. The official viewer's 640x480
|
||||
// offscreen stage was tried (round 38) and REVERTED: in our portrait
|
||||
// fitRect the second stage lands on a 2.25x non-integer scale, and the
|
||||
// GPU's rendering of it measurably WORSENED the column-direction
|
||||
// alternation on the device (0.33 -> 0.55 x1e-2 arc) — an artifact the
|
||||
// official (landscape, integer 3x second stage) doesn't have. The 160x120
|
||||
// -> 1440x1920 single scale is 9x/16x integer, artifact-free.
|
||||
if (src != null) canvas.drawBitmap(bitmap, src, dstRect, paint)
|
||||
else canvas.drawBitmap(bitmap, null, dstRect, paint)
|
||||
canvas.restore()
|
||||
|
||||
drawOsd(canvas, vm.state.value)
|
||||
|
||||
Binary file not shown.
@@ -737,7 +737,7 @@ Kotlin 一致),与 App 自己 `copyNuc` 的 dump 对拍:corr=0.9996,仅
|
||||
行/列方向不一致);本轮修正后**真实 NUC 域差距约 1.6×**(同一份我们的数据走
|
||||
官方显示链复刻 = 0.19,走我们链 = 0.32,官方截图实测 0.12)。
|
||||
|
||||
### 17.4 定案与修复
|
||||
### 17.4 定案与修复(初版,17.6 有修正)
|
||||
|
||||
1. **官方显示链的两级结构**(jadx `ImageViewer` + 本轮重核):SDK 输出 160×120
|
||||
bitmap → `PaintFlagsDrawFilter + FILTER_BITMAP` 画入 **640×480 离屏**(4× 双线性)
|
||||
@@ -759,6 +759,48 @@ Kotlin 一致),与 App 自己 `copyNuc` 的 dump 对拍:corr=0.9996,仅
|
||||
§16.3 的验收目标"逐行 std ≤ 0.2"未达成;下一轮从 b) 项入手(它是纯测量
|
||||
差异还是真机显示链仍有未建模环节)。
|
||||
|
||||
### 17.6 追加测量(同日,同帧对照把差距收敛到真实量级)
|
||||
|
||||
17.4 的"遗留 b"用**严格同帧对照**(run3/run4 录制 + 录制结束秒级截屏,App 自己
|
||||
的 NUC dump 走我们显示链的 RGB 域复刻 vs 真机截图)解决:
|
||||
|
||||
- **真机截图网格与同帧 NUC 弧长网格的空间相关 = 0.994**——真机显示链上没有任何
|
||||
未建模环节,17.4-b 的"2×"是三件事叠加的假象。
|
||||
- **假象一(测量方法)**:PIL 的 F-mode resize 在本机对 float32 有严重失真
|
||||
(round-trip 后剖面 corr≈0.0002),此前所有"F 域模拟显示链"的数字作废;
|
||||
改 RGB uint8 域模拟后,所有几何变体(单级 12×、640 中间级 2.25×、官方 3×)
|
||||
的行/列交替全部落在 0.07–0.11 / 0.24–0.28——**显示链几何不产生交替**。
|
||||
- **假象二(UI 覆盖层)**:我们竖屏 UI 画在热像区内的中心温度/MAX 标注/色标条
|
||||
对剖面贡献与 FPN 同量级,且 mask 边界会留下异常行。改用**鲁棒剖面**
|
||||
(逐行 median + 2.5·MAD 剔除)后三方对照:
|
||||
|
||||
| 同口径(鲁棒,×1e-2 弧长) | ROW-dir | COL-dir |
|
||||
|---|---|---|
|
||||
| 官方截图 | **0.10** | **0.28** |
|
||||
| 我们(修复前截图,320×240 内插) | 0.22 | 0.33 |
|
||||
| 我们(640 中间级版截图) | 0.24 | 0.55 |
|
||||
|
||||
- **修复 B 回退**:640×480 中间级在**竖屏 fitRect 下第二级是 2.25× 非整数**
|
||||
(官方横屏是 3× 整数),真机 GPU 渲染它使 COL 交替 0.33→0.55(RGB 域复刻
|
||||
预测 0.28,未复现该伪影)→ **已回退**,保留 nativeOut 单级(160×120→
|
||||
1440×1920 = 9×/16× 整数,无伪影)。
|
||||
- **最终状态**:COL 方向我们 0.33 vs 官方 0.28(**1.2×,达标**);ROW 方向
|
||||
0.22 vs 0.10(**2.2×**)。ROW 的 2.2× = NUC 域 ~1.6× + 显示/反演细节。
|
||||
NUC 域残差与"快门态 vs 开放态行 offset FPN 图样差"相关(设备固有,官方同
|
||||
算法同承担),同链对比下我们 2.87 counts vs 官方等效 ~1.3–1.8 counts 的
|
||||
来源仍未定位——下一轮可用"反复重连 × 交替连接两个 App × 每次连接量 NUC
|
||||
行交替"的统计实验判定是设备状态随机性还是系统性差异。
|
||||
- §16.3 验收目标修正:以本轮建立的**鲁棒同口径**为准,COL 已达标(0.33 vs
|
||||
0.28),ROW 2.2× 是下一轮的目标(0.22 → ≤0.2 需把 NUC 域残差压到官方
|
||||
等效水平,先判定 1.6× 的来源)。
|
||||
- **COL 波动的待验证项**:回退 640 级后的复测截图 COL=0.56 未回到 0.33,且
|
||||
NUC 域 COL 本身跨会话波动(run2 6.91 → run3 8.0 counts)——COL 交替可能
|
||||
随连接/温度状态漂移,判定需要"同一连接内录+截"的同帧对照(run4/run5 两次
|
||||
尝试因屏幕保活与热像仪被拔中断,工具链已就绪:`rawcap.enable` + 保活 keyevent
|
||||
循环 + `/data/local/tmp` 中转拉取 + RGB 域旋转正确的复刻脚本 `sync4.py`,
|
||||
注意 sample_grid 前必须 `np.rot90(rgb, k=-1)` 对齐竖屏几何,PIL 的 F-mode
|
||||
resize 在本机失真不可用)。
|
||||
|
||||
### 17.5 工具与现场
|
||||
|
||||
- `RawFrameRecorder`/`rawFrameSink`/flag 轮询已合入(调试工具,默认不活动)。
|
||||
|
||||
Reference in New Issue
Block a user