Add sample smoke check and refine foreground despill

This commit is contained in:
Codex
2026-06-30 14:42:08 +08:00
parent 464a60c733
commit 923dbe9f09
6 changed files with 146 additions and 1 deletions
+12 -1
View File
@@ -94,7 +94,18 @@ edge pixels.
```powershell
D:\MiniConda\envs\lightML\python.exe -m bgfilter.quality_cli `
Outputs\TestImage_rgba.png `
--max-edge-green-excess-p95 0.55
--max-edge-green-excess-p95 0.50
```
Run the bundled sample smoke check:
```powershell
D:\MiniConda\envs\lightML\python.exe scripts\smoke_samples.py `
--samples-dir Samples `
--output-dir Outputs\smoke_samples `
--device cpu `
--fallback-to-chroma-alpha `
--max-edge-green-excess-p95 0.50
```
## Current Notes
+24
View File
@@ -7,6 +7,18 @@ from .deps import require_cv2
from .settings import DespillSettings
def _weighted_blur(values: np.ndarray, weights: np.ndarray, radius: int) -> np.ndarray:
if radius <= 0:
return values
cv2 = require_cv2()
kernel_size = radius * 2 + 1
numerator = cv2.GaussianBlur(
values * weights[..., None], (kernel_size, kernel_size), 0
)
denominator = cv2.GaussianBlur(weights, (kernel_size, kernel_size), 0)
return numerator / np.maximum(denominator[..., None], 1e-6)
def despill_green(
rgb: np.ndarray,
alpha: np.ndarray,
@@ -71,4 +83,16 @@ def despill_green(
)
out = out * (1.0 - unmix_mask[..., None]) + recovered * unmix_mask[..., None]
local_fg_weights = (
(alpha >= settings.local_fg_alpha_threshold)
& (bg_confidence <= settings.local_fg_bg_confidence_max)
).astype(np.float32)
local_fg = _weighted_blur(out, local_fg_weights, settings.local_fg_blur_radius)
local_mask = (
mask
* settings.local_fg_strength
* np.clip((1.0 - alpha) / max(1.0 - settings.unmix_alpha_min, 1e-6), 0.0, 1.0)
)
out = out * (1.0 - local_mask[..., None]) + local_fg * local_mask[..., None]
return np.clip(out * 255.0, 0, 255).astype(np.uint8), mask.astype(np.float32)
+4
View File
@@ -47,6 +47,10 @@ class DespillSettings:
alpha_weight_floor: float = 0.35
unmix_strength: float = 0.55
unmix_alpha_min: float = 0.08
local_fg_strength: float = 0.35
local_fg_blur_radius: int = 9
local_fg_alpha_threshold: float = 0.92
local_fg_bg_confidence_max: float = 0.20
@dataclass(frozen=True)
+4
View File
@@ -21,3 +21,7 @@ despill:
alpha_weight_floor: 0.35
unmix_strength: 0.55
unmix_alpha_min: 0.08
local_fg_strength: 0.35
local_fg_blur_radius: 9
local_fg_alpha_threshold: 0.92
local_fg_bg_confidence_max: 0.20
+1
View File
@@ -424,6 +424,7 @@ debug/qa_grid.png
5. 发丝区域不是硬切边。
6. 镂空洞露出绿幕时可透明。
7. 边缘没有明显绿色污染。
8. `scripts/smoke_samples.py` 能处理样例图并验证 RGBA、debug 产物和边缘绿色污染指标。
```
## 19. 推荐落地顺序
+101
View File
@@ -0,0 +1,101 @@
from __future__ import annotations
import argparse
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from bgfilter.pipeline import MattingPipeline
from bgfilter.quality import assert_quality, measure_rgba
from bgfilter.settings import (
AlphaPostSettings,
ChromaSettings,
DespillSettings,
ModelSettings,
PipelineSettings,
TrimapSettings,
)
REQUIRED_DEBUG_FILES = {
"bg_confidence.png",
"trimap.png",
"alpha.png",
"despill_mask.png",
"preview_black.png",
"preview_white.png",
"preview_gray.png",
"preview_red.png",
"preview_blue.png",
"qa_grid.png",
"metadata.json",
}
def build_settings(args: argparse.Namespace) -> PipelineSettings:
return PipelineSettings(
chroma=ChromaSettings(),
trimap=TrimapSettings(),
alpha_post=AlphaPostSettings(),
despill=DespillSettings(),
model=ModelSettings(
model_name=args.model_name,
device=args.device,
matting_method=args.matting_method,
fallback_to_chroma_alpha=args.fallback_to_chroma_alpha,
),
)
def check_debug_files(debug_dir: Path) -> list[str]:
missing = []
for filename in sorted(REQUIRED_DEBUG_FILES):
path = debug_dir / filename
if not path.exists() or path.stat().st_size <= 0:
missing.append(filename)
return missing
def main() -> int:
parser = argparse.ArgumentParser(description="Run sample-image smoke checks.")
parser.add_argument("--samples-dir", type=Path, default=Path("Samples"))
parser.add_argument("--output-dir", type=Path, default=Path("Outputs/smoke_samples"))
parser.add_argument("--device", default="cpu")
parser.add_argument("--matting-method", default="vitmatte", choices=["vitmatte", "chroma"])
parser.add_argument("--model-name", default="hustvl/vitmatte-small-composition-1k")
parser.add_argument("--fallback-to-chroma-alpha", action="store_true")
parser.add_argument("--max-edge-green-excess-p95", type=float, default=0.50)
args = parser.parse_args()
images = sorted(args.samples_dir.glob("*.png"))
if not images:
raise SystemExit(f"No PNG samples found in {args.samples_dir}")
pipeline = MattingPipeline(build_settings(args))
failed = False
for image_path in images:
output_path = args.output_dir / f"{image_path.stem}_rgba.png"
debug_dir = args.output_dir / f"{image_path.stem}_debug"
result = pipeline.run_image(image_path, output_path, debug_dir)
metrics = measure_rgba(output_path)
failures = assert_quality(metrics, args.max_edge_green_excess_p95)
missing_debug = check_debug_files(debug_dir)
if missing_debug:
failures.append(f"missing debug files: {', '.join(missing_debug)}")
print(
f"{image_path.name}: alpha_source={result['alpha_source']} "
f"edge_green_p95={metrics.edge_green_excess_p95:.4f}"
)
for failure in failures:
print(f" FAIL {failure}")
failed = failed or bool(failures)
return 1 if failed else 0
if __name__ == "__main__":
raise SystemExit(main())