fix: spare natural webcam scene edges

This commit is contained in:
Brad Stein 2026-05-20 16:53:01 -03:00
parent 5024a2eb54
commit 698063506b
6 changed files with 72 additions and 12 deletions

6
Cargo.lock generated
View File

@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.26.5"
version = "0.26.6"
dependencies = [
"anyhow",
"async-stream",
@ -1692,7 +1692,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.26.5"
version = "0.26.6"
dependencies = [
"anyhow",
"base64",
@ -1704,7 +1704,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.26.5"
version = "0.26.6"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.26.5"
version = "0.26.6"
edition = "2024"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.26.5"
version = "0.26.6"
edition = "2024"
build = "build.rs"

View File

@ -16,7 +16,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.26.5"
version = "0.26.6"
edition = "2024"
autobins = false

View File

@ -78,7 +78,7 @@ fn partial_seam_run_pct_threshold() -> usize {
///
/// Inputs: optional pixel-guard tuning. Output: luma stddev threshold. Why:
/// natural shelf/chair/table edges can look like a partial seam on one row,
/// but damaged codec slabs usually have a low-texture block on one side.
/// but damaged codec slabs usually keep both sides of the tear low-texture.
fn partial_seam_block_stddev_threshold() -> f64 {
f64::from(
env_u32(
@ -177,8 +177,9 @@ fn region_luma_stddev(
/// Measure texture around a candidate partial-width seam.
///
/// Inputs: sampled frame, seam row, and candidate horizontal run. Output:
/// standard deviation for the lower suspect block. Why: real scene detail can
/// have a sharp edge, but damaged UVC tiles tend to be broad low-texture slabs.
/// standard deviation for the least slab-like side of the suspect block. Why:
/// real wall/furniture edges often have one smooth side, but damaged UVC tiles
/// tend to create broad low-texture slabs on both sides of the tear.
fn partial_seam_block_stddev(
frame: &SampledFrame,
row: usize,
@ -191,9 +192,8 @@ fn partial_seam_block_stddev(
let below_end = row.saturating_add(window).min(frame.height);
let below = region_luma_stddev(frame, row, below_end, col_start, col_end);
match (above, below) {
(Some(above), Some(below)) => Some(above.min(below)),
(Some(value), None) | (None, Some(value)) => Some(value),
(None, None) => None,
(Some(above), Some(below)) => Some(above.max(below)),
(Some(_), None) | (None, Some(_)) | (None, None) => None,
}
}

View File

@ -329,6 +329,66 @@ fn pixel_guard_accepts_textured_partial_scene_edges() {
);
}
#[test]
/// Verifies smooth wall plus textured furniture edges are not frozen as tears.
fn pixel_guard_accepts_wall_over_couch_scene_edges() {
temp_env::with_vars(
[
("LESAVKA_UVC_MJPEG_PIXEL_GUARD", Some("1")),
(
"LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_DELTA",
Some("28"),
),
(
"LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_COVERAGE_PCT",
Some("32"),
),
(
"LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_RUN_PCT",
Some("12"),
),
(
"LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_BLOCK_STDDEV",
Some("14"),
),
],
|| {
let width = 320;
let height = 180;
let mut frame = vec![0u8; width * height * 3];
let seam_y = height.saturating_mul(77) / 100;
let start_x = width.saturating_mul(61) / 100;
let end_x = width.saturating_mul(80) / 100;
for y in 0..height {
for x in 0..width {
let offset = (y * width + x) * 3;
if y < seam_y || !(start_x..end_x).contains(&x) {
// Keep the wall naturally smooth but not slab-flat; the
// regression is specifically about one smooth side plus
// one textured side, not about blank-frame detection.
let shade = 174u8.saturating_add(((x + y) % 28) as u8);
frame[offset] = shade;
frame[offset + 1] = shade.saturating_add(6);
frame[offset + 2] = shade.saturating_sub(5);
} else {
let texture = ((x * 23 + y * 17) % 74) as u8;
let base = 54u8.saturating_add(texture);
frame[offset] = base;
frame[offset + 1] = base.saturating_add((x % 27) as u8);
frame[offset + 2] = base.saturating_sub((y % 21) as u8);
}
}
}
assert_eq!(
super::mjpeg_visual_guard::sampled_rgb_frame_for_test(width, height, &frame),
None
);
},
);
}
#[test]
/// Verifies low-detail gray slabs freeze out before UVC handoff.
fn pixel_guard_rejects_flat_lower_block_frames() {