fix: spare natural webcam scene edges
This commit is contained in:
parent
5024a2eb54
commit
698063506b
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.26.5"
|
version = "0.26.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1692,7 +1692,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.26.5"
|
version = "0.26.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1704,7 +1704,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.26.5"
|
version = "0.26.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.26.5"
|
version = "0.26.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.26.5"
|
version = "0.26.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.26.5"
|
version = "0.26.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@ fn partial_seam_run_pct_threshold() -> usize {
|
|||||||
///
|
///
|
||||||
/// Inputs: optional pixel-guard tuning. Output: luma stddev threshold. Why:
|
/// Inputs: optional pixel-guard tuning. Output: luma stddev threshold. Why:
|
||||||
/// natural shelf/chair/table edges can look like a partial seam on one row,
|
/// 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 {
|
fn partial_seam_block_stddev_threshold() -> f64 {
|
||||||
f64::from(
|
f64::from(
|
||||||
env_u32(
|
env_u32(
|
||||||
@ -177,8 +177,9 @@ fn region_luma_stddev(
|
|||||||
/// Measure texture around a candidate partial-width seam.
|
/// Measure texture around a candidate partial-width seam.
|
||||||
///
|
///
|
||||||
/// Inputs: sampled frame, seam row, and candidate horizontal run. Output:
|
/// Inputs: sampled frame, seam row, and candidate horizontal run. Output:
|
||||||
/// standard deviation for the lower suspect block. Why: real scene detail can
|
/// standard deviation for the least slab-like side of the suspect block. Why:
|
||||||
/// have a sharp edge, but damaged UVC tiles tend to be broad low-texture slabs.
|
/// 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(
|
fn partial_seam_block_stddev(
|
||||||
frame: &SampledFrame,
|
frame: &SampledFrame,
|
||||||
row: usize,
|
row: usize,
|
||||||
@ -191,9 +192,8 @@ fn partial_seam_block_stddev(
|
|||||||
let below_end = row.saturating_add(window).min(frame.height);
|
let below_end = row.saturating_add(window).min(frame.height);
|
||||||
let below = region_luma_stddev(frame, row, below_end, col_start, col_end);
|
let below = region_luma_stddev(frame, row, below_end, col_start, col_end);
|
||||||
match (above, below) {
|
match (above, below) {
|
||||||
(Some(above), Some(below)) => Some(above.min(below)),
|
(Some(above), Some(below)) => Some(above.max(below)),
|
||||||
(Some(value), None) | (None, Some(value)) => Some(value),
|
(Some(_), None) | (None, Some(_)) | (None, None) => None,
|
||||||
(None, None) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
#[test]
|
||||||
/// Verifies low-detail gray slabs freeze out before UVC handoff.
|
/// Verifies low-detail gray slabs freeze out before UVC handoff.
|
||||||
fn pixel_guard_rejects_flat_lower_block_frames() {
|
fn pixel_guard_rejects_flat_lower_block_frames() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user