android: 修正增强档位说明(写反了)+ 分析页测温点沿用照片数值
上一提交把档位含义写成了「越高纳入越多弱对比区域」,方向是反的。按公式 amplitude = strength*(center-mean)/divisor, divisor = max(max-mean, mean-min, mapStrength), mapStrength = strength*gain*2>>8 重新推导: - 弱对比区域 divisor 被 mapStrength 兜住 → 幅度 = 128/gain,与档位无关; - 强边缘 divisor 由对比度主导 → 幅度随档位线性增长; - 门槛 mapStrength <= range*32 → 档位越高弱对比区域被整个排除(正是噪声所在处)。 即高档位是「更锐但更挑」。设置页文案与 DetailEnhance 文档已改正, 并加 theLevelRaisesTheContrastGateAndDropsWeakDetail 锁定 (range=60 窗口:8 档 mapStrength 1500 通过、16 档 3000 被拒)。 真机 HF 实测 0.365(关) / 0.438(6档) / 0.433(8档),与推导一致。 分析页测温点原先前先重新测量 NUC,于是照片上烧录的 Pt2 26.1℃ 与面板 25.9℃ 不一致(与极值同一类漂移)。现在优先用文件记录的 tempMc——照片上印的就是它, 仅在记录缺失时才测量。真机确认照片/面板/极值三方数值完全一致。 真机(小米 22041211AC/Android12/MIUI,重启后冷启动):15.1fps 稳定、单帧 6.1~6.6ms、 6/8 档 9.9~10.3ms、拍照 extremes=2、分析页只有一个 min/max 且与照片重合、 底部面板可见(36.0/20.4/24.5)。101 项测试全绿。
This commit is contained in:
@@ -44,6 +44,18 @@ package com.mag160c.thermal.core
|
||||
* near-silent on flat areas). The divisor floor is what keeps flat, noisy regions
|
||||
* quiet while edges still get pushed.
|
||||
*
|
||||
* ## What the strength level actually controls
|
||||
*
|
||||
* Raising the level does two things at once:
|
||||
* 1. it RAISES the contrast gate — a window is enhanced only when its 7x7 range
|
||||
* clears `mapStrength / 32`, so low-contrast areas (where sensor noise lives)
|
||||
* are excluded entirely at high levels; and
|
||||
* 2. it RAISES the amplitude on strong edges, where the local contrast itself
|
||||
* dominates the divisor.
|
||||
* Where the contrast is small enough that the divisor floor applies, the amplitude
|
||||
* works out to `strength / mapStrength` = `128 / gain` — independent of the level.
|
||||
* That is why turning the level up sharpens the picture instead of boiling it.
|
||||
*
|
||||
* ## Validation status — READ THIS
|
||||
*
|
||||
* The arithmetic above is a faithful transcription, and the structural properties
|
||||
|
||||
@@ -102,8 +102,15 @@ class AnalyzeViewModel(
|
||||
// which showed up on device as two "min" markers a few pixels apart.
|
||||
if (recorded.minPos >= 0) mnPos = recorded.minPos
|
||||
if (recorded.maxPos >= 0) mxPos = recorded.maxPos
|
||||
// Each probe keeps the temperature the CAPTURE recorded, because that is
|
||||
// the number burned into the photo next to the marker. Re-measuring the
|
||||
// NUC block gives a slightly different value (the sensor drifts between
|
||||
// capture and reload), which showed on device as the photo saying
|
||||
// "Pt2 26.1" while the panel said 25.9 for the same point. Only probes
|
||||
// that somehow lack a recorded value are measured.
|
||||
val loaded = (parsed?.probes.orEmpty()).map { p ->
|
||||
Probe(p.x, p.y, p.label, measureSensor(p.x, p.y) ?: (p.tempMc / 1000f))
|
||||
Probe(p.x, p.y, p.label, (p.tempMc / 1000f).takeIf { p.tempMc != 0 }
|
||||
?: measureSensor(p.x, p.y) ?: 0f)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
_render.value = bmp
|
||||
|
||||
@@ -247,17 +247,21 @@ fun SettingsScreen(
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
"加强局部细节(官方同款 7×7 局部映射)。档位沿用官方 SDK 自己的" +
|
||||
"定义:档位越高,纳入增强的弱对比区域越多(边缘强度本身不变)," +
|
||||
"画面更通透,弱细节里的噪声也会一起显现。",
|
||||
"加强局部细节(官方同款 7×7 局部映射)。档位越高:①对比度门槛越高," +
|
||||
"只有更强的细节会被增强,弱对比区域(噪声所在处)被排除;" +
|
||||
"②强边缘的增强幅度更大。所以高档位是「更锐但更挑」," +
|
||||
"而不是「连噪声一起放大」。",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
// Vendor levels: MAG_SetDetailEnhancement accepts 0..32 and the
|
||||
// pipeline uses `level << 3`. Extra headroom is safe because the
|
||||
// per-edge amplitude is level-independent (128/gain); the level
|
||||
// only decides which windows clear the contrast threshold.
|
||||
// pipeline uses `level << 3`. The level raises BOTH the contrast
|
||||
// gate (a window qualifies only when its 7x7 range clears
|
||||
// mapStrength/32) and the amplitude on strong edges (where the
|
||||
// local contrast dominates the divisor). On flat, noisy areas the
|
||||
// divisor floor holds the amplitude at 128/gain regardless of
|
||||
// level, which is why cranking it up sharpens instead of boiling.
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.heightIn(max = 320.dp)
|
||||
|
||||
@@ -184,6 +184,53 @@ class DetailEnhanceTest {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* What the level knob really does (this is the sentence the settings dialog
|
||||
* shows the user, so it is pinned here):
|
||||
* - the level raises the contrast GATE: a window is enhanced only when its 7x7
|
||||
* range clears `mapStrength / 32` (= level * gain * 2 / 256 / 32), so weak
|
||||
* detail — where sensor noise lives — is dropped at high levels; and
|
||||
* - where a window IS gated in, the divisor floor keeps its amplitude near
|
||||
* `128/gain` of the deviation, so it does not grow with the level.
|
||||
*
|
||||
* Window used below: 15 samples at 7200 and one at 7260 (range 60), centre 7260,
|
||||
* gain 3000.
|
||||
* level 8 (strength 64 -> mapStrength 1500): gate 1500 <= 60*32 = 1920 passes
|
||||
* level 16 (strength 128 -> mapStrength 3000): gate 3000 <= 1920 FAILS -> dropped
|
||||
*/
|
||||
@Test
|
||||
fun theLevelRaisesTheContrastGateAndDropsWeakDetail() {
|
||||
val size = 16
|
||||
fun window(): IntArray {
|
||||
val src = IntArray(size * size)
|
||||
for (dy in 0 until 4) for (dx in 0 until 4) src[(dy * 2) * size + dx * 2] = 7200
|
||||
src[0] = 7260
|
||||
src[3 * size + 3] = 7260
|
||||
return src
|
||||
}
|
||||
fun changeAt(strength: Int): Int {
|
||||
val gray = ByteArray(size * size) { 128.toByte() }
|
||||
DetailEnhance(size, size).enhance(window(), gray, strength = strength, gain = 3000)
|
||||
return (gray[3 * size + 3].toInt() and 0xFF) - 128
|
||||
}
|
||||
assertTrue(
|
||||
"level 8 (mapStrength 1500) must act on a range-60 window",
|
||||
changeAt(64) != 0,
|
||||
)
|
||||
assertEquals(
|
||||
"level 16 (mapStrength 3000) must drop the same window: its contrast " +
|
||||
"is below the gate",
|
||||
0,
|
||||
changeAt(128),
|
||||
)
|
||||
// and the amplitude is small wherever it does apply: the divisor floor caps
|
||||
// it near 128/gain, so higher levels cannot make flat areas boil
|
||||
assertTrue(
|
||||
"amplitude must stay small, was ${changeAt(64)}",
|
||||
kotlin.math.abs(changeAt(64)) <= 4,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doesNotChangeTheOverallLevelOfASmoothImage() {
|
||||
// on a smooth ramp the mean and the centre are equal, so no delta should be
|
||||
|
||||
Reference in New Issue
Block a user