Add segmentation post-processing: dark water fix + wetland reclassify

Two heuristic rules applied after SegEarth-OV3 inference:

1. Dark water: if background pixels have mean_rgb < 0.24 and std < 0.08,
   reclassify as water. Fixes GTA-UAV satellite dark ocean (57% → ~15% bg).

2. Wetland reclassify (GTA-UAV only): split false-positive wetland pixels
   by color — green-dominant → vegetation, else → bare soil. Fixes 14.3%
   muddy/wetland false positives on GTA-V hillside terrain.

Config flags: seg_fix_dark_water (default True), seg_reclassify_wetland
(default False, enabled in run_gta_uav.py).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
pikaliov
2026-04-18 02:37:43 +03:00
parent d1ddb40036
commit f0c876dfc7
5 changed files with 142 additions and 1 deletions

View File

@@ -13,6 +13,7 @@ from src.augmentor.inference import (
infer_chmv2_batch,
infer_depth_batch,
infer_segmentation_batch,
postprocess_segmentation,
_SOBEL_X,
_SOBEL_Y,
)
@@ -220,3 +221,59 @@ class TestInferSegmentationBatch:
result = infer_segmentation_batch(pipeline, seg_config, sample_batch, torch.device("cpu"))
assert result.shape == (B, 1, H, W)
assert (result == 0).all()
# ---------------------------------------------------------------------------
# Post-processing heuristics
# ---------------------------------------------------------------------------
class TestPostprocessSegmentation:
def test_dark_water_reclassified(self) -> None:
"""Dark uniform background → water."""
# Dark image (mean ~0.15, std ~0.02)
rgb = torch.full((1, 3, 32, 32), 0.15)
rgb += torch.randn_like(rgb) * 0.02
rgb.clamp_(0, 1)
seg = torch.zeros(1, 1, 32, 32, dtype=torch.uint8) # all background
result = postprocess_segmentation(seg, rgb)
assert (result == 4).all() # water
def test_bright_background_unchanged(self) -> None:
"""Bright background should NOT become water."""
rgb = torch.full((1, 3, 32, 32), 0.6)
seg = torch.zeros(1, 1, 32, 32, dtype=torch.uint8)
result = postprocess_segmentation(seg, rgb)
assert (result == 0).all() # stays background
def test_non_background_unchanged(self) -> None:
"""Non-background classes untouched even if dark."""
rgb = torch.full((1, 3, 32, 32), 0.1)
seg = torch.full((1, 1, 32, 32), 3, dtype=torch.uint8) # vegetation
result = postprocess_segmentation(seg, rgb)
assert (result == 3).all()
def test_wetland_reclassify_green(self) -> None:
"""Green wetland → vegetation when reclassify_wetland=True."""
rgb = torch.zeros(1, 3, 32, 32)
rgb[0, 1] = 0.6 # green dominant
rgb[0, 0] = 0.2 # low red
seg = torch.full((1, 1, 32, 32), 14, dtype=torch.uint8) # wetland
result = postprocess_segmentation(seg, rgb, reclassify_wetland=True)
assert (result == 3).all() # vegetation
def test_wetland_reclassify_brown(self) -> None:
"""Brown wetland → bare soil when reclassify_wetland=True."""
rgb = torch.zeros(1, 3, 32, 32)
rgb[0, 0] = 0.5 # red dominant
rgb[0, 1] = 0.3 # low green
seg = torch.full((1, 1, 32, 32), 14, dtype=torch.uint8)
result = postprocess_segmentation(seg, rgb, reclassify_wetland=True)
assert (result == 11).all() # bare soil
def test_wetland_unchanged_without_flag(self) -> None:
"""Wetland stays if reclassify_wetland=False."""
rgb = torch.zeros(1, 3, 32, 32)
rgb[0, 1] = 0.6
seg = torch.full((1, 1, 32, 32), 14, dtype=torch.uint8)
result = postprocess_segmentation(seg, rgb, reclassify_wetland=False)
assert (result == 14).all()