Commit Graph

18 Commits

Author SHA1 Message Date
lhk229 b0a634dfd4 Add recolor pass to clean residual contamination colour
Add bgfilter/recolor.py: after despill, propagate clean neighbour chroma into
pixels whose colour is still off (residual green, or the magenta a green unmix
overshoots into), keeping luminance partway so hair texture survives. It installs
a real observed colour rather than subtracting, so it cannot overshoot. Despill
already neutralises the matte edge (off ~ 0 there), so recolor only touches the
contaminated interior and the wisps the dropout-fill rescued.

Runs as a final colour pass on the despilled foreground; pymatting + despill keep
their stronger edge handling. On the samples the magenta fringe drops sharply
(TestImage vis-magenta 0.065->0.014, TestImage2 0.085->0.031) with edge green
unchanged (vis-green ~0.02). Debug output color_mask.png replaces the despill /
foreground-background / foreground-correction maps; smoke updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:19:15 +08:00
lhk229 42d65b0d25 Rescue ViTMatte dropouts with the segmentation mask
Add fill_seg_dropouts: where ViTMatte's alpha is ~0 but BiRefNet confidently
asserts foreground, restore a faint alpha from the mask (strength * seg_mask).
This recovers green-contaminated wisps that ViTMatte cuts because they are
nearly the background colour, while leaving ViTMatte's sharper edges elsewhere
untouched (gated to true dropouts) and not refilling the finger gap (low mask).

On the TestImage2 shoulder, faint wisps ViTMatte cut drop from 72% to 33%; the
finger gap stays transparent. The rescued wisps are still green-tinted -- the
recolor pass (next) restores their colour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:04:34 +08:00
lhk229 5ab3d7eb7c Lower seg_loose_threshold to 0.08
Route faint hair (BiRefNet M 0.08-0.15) into the unknown band instead of sure
background, giving ViTMatte a chance at it. On its own this does not recover the
green-contaminated shoulder wisps: those are ~pure green (dominance +0.88, like
the screen) so ViTMatte cuts them regardless of trimap -- that is handled by the
upcoming segmentation dropout-fill. The finger gap (M~0.02) stays sure background.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:01:23 +08:00
lhk229 711669ea51 Fuse semantic segmentation mask into the trimap
Wire BiRefNet into the pipeline (lazy-loaded) and build the trimap from the
semantic subject mask refined by the green key, used when segmentation is
enabled. generate_trimap (chroma-only) stays as the no-segmentation fallback.

Authority split: the mask decides subject topology (keeps green-tinted hair as
foreground, drops see-through holes), the bright-green key sharpens the screen
boundary, ViTMatte refines a small protective band. Background follows the mask
directly so interior holes survive; the band is small so it does not swallow
finger gaps.

End-to-end on the samples: green hair preserved, finger-gap green 99%
transparent (BiRefNet marks it background; was 100% foreground before), leg
edges smooth -- topology/colour/edge problems local cues could not solve, with
no hard alpha surgery.

- SegmentationSettings wired into PipelineSettings/config; --device propagates.
- TrimapSettings: seg_core_threshold, seg_loose_threshold.
- New debug output seg_mask.png.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:32:38 +08:00
lhk229 a6a313d2d0 Add BiRefNet semantic segmentation module
Adds bgfilter/segmentation.py: BiRefNetSegmenter produces a soft subject mask
that defines topology -- it includes colour-contaminated foreground (green-tinted
hair) and excludes see-through holes (finger gaps), the distinction chroma/colour
cues cannot make. Validated on both samples: green hair -> foreground, finger gap
-> background, both correct.

Foundation of the semantic-guided redesign; the mask will drive trimap
construction next, with ViTMatte still refining soft edges.

- SegmentationSettings (ZhengPeng7/BiRefNet, 1024 input, fp32).
- Deps: torchvision, timm, einops, kornia. Weights load from HuggingFace via
  trust_remote_code; behind a firewall set HF_ENDPOINT=https://hf-mirror.com and
  bypass the proxy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:07:24 +08:00
lhk229 a9886f1479 Revert "Carve background green embedded in the foreground (hole handling)"
This reverts commit f7e12d3.

The carve set alpha to 0 on a colour-thresholded mask across the whole
foreground, which cannot distinguish background green from green the AI
generator bled onto the subject. It therefore (1) deleted green-tinted hair
strands and (2) hard-cut the green-spill silhouette edge, replacing ViTMatte's
anti-aliased edge with a jagged one. Local cues (surround, opacity, thinness,
connectivity, neighbour colour) could not reliably separate green hair from
green holes, so the mask is not fixable by tuning. Reverting restores intact
hair and smooth edges; the finger-gap green stays a minor residual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:12:27 +08:00
lhk229 f7e12d3498 Carve background green embedded in the foreground (hole handling)
The green in the finger gap survives keying because it is dark and desaturated
(brightness ~0.58, hue-shifted), so chroma confidence reads ~0 and the trimap
leaves it in the unknown band, where ViTMatte fills the pocket as foreground.
It is also connected to the exterior screen (an open notch), so neither
confidence suppression nor classic hole-filling removes it.

Add carve_background_holes (doc section 10): a pixel that is clearly
green-dominant in the input *and* mostly surrounded by genuine (non-green)
foreground is set to alpha 0. The surround cue separates such an intrusion
(finger gaps, inter-strand pockets) from the outer hair edge, which has
background on one side and is preserved. Runs after alpha cleanup.

On TestImage the finger-gap survivors (green & alpha>0.5) drop 170 -> 28 with
no visible loss of hair wisps; inter-strand green pockets clear too. Knobs in
AlphaPostSettings (hole_*, default on, surround_min 0.4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:36:43 +08:00
lhk229 d6cf6ffe0c Add chroma-guided alpha suppression for background holes
Pull alpha toward 0 in the unknown band where bg_confidence is high, so green
that survives in hair gaps and hole pockets goes transparent (doc section 10).
Runs between trimap enforcement and alpha cleanup; sure-foreground pixels are
never touched, keyed by the bg_confidence we already compute.

On the two samples this clears the faint outer green halo (edge pixels
19.3k -> 16.0k) but has little *visible* effect, because their residual is no
longer green: it is magenta from the pymatting unmix on genuine semi-transparent
hair (vis-magenta p95 ~0.07, identical pre/post despill), which this pass
deliberately leaves alone. The magenta needs a separate colour fix.

Knobs live in AlphaPostSettings (chroma_suppress*, default on).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:03:09 +08:00
lhk229 9b7a192599 Use pymatting ML foreground estimation to remove edge spill
After the full-foreground despill (5ddf0cd) the residual green was confined to
semi-transparent hair (alpha < 0.5): 85% of the worst pixels had alpha < 0.3,
where a pixel-wise unmix (divide by small alpha) is too noisy to trust.

Replace the hand-rolled unmix/local-blur estimator with pymatting's
estimate_foreground_ml (Germer et al. multi-level closed form), which propagates
reliable foreground colour from high-alpha neighbours into the fringe and
estimates the background, so green spill is unmixed rather than clamped. The
despill pass stays as a light cleanup on top.

- foreground.method selects "ml" (default) or "unmix" (legacy heuristic kept as
  a fallback when pymatting is unavailable, matching the chroma fallback idiom).
- ForegroundEstimate now exposes rgb (F), background (B) and a correction map;
  debug outputs become foreground_rgb / foreground_background / foreground_correction.

Controlled comparison (same ViTMatte alpha, only foreground method changed),
edge_green_excess:
  TestImage  p95 0.176->0.031, mean 0.061->0.005
  TestImage2 p95 0.165->0.004, mean 0.052->0.002

Adds pymatting (pulls in numba) to requirements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 17:33:35 +08:00
lhk229 5ddf0cd392 Despill across full foreground to cut edge green spill
Green spill bleeds into pixels ViTMatte marks as solid foreground (alpha~1),
but despill was gated to the soft edge band (0.005<alpha<0.995) and weakened
exactly there: the alpha tent peaked at 0.5 and bg_confidence (low on the
character edge) further suppressed the correction.

- (1) Apply despill to the whole foreground (alpha > edge_low) instead of the
  soft band only; neutral pixels stay untouched via the green-excess term.
- (2) Weight by alpha at full strength across the opaque range (alpha>=0.5),
  easing off only where mostly transparent, instead of a tent peaking at 0.5.
- (3) Drop bg_confidence as a despill gate; localise purely by green excess.

Controlled comparison (same alpha, only despill changed), edge_green_excess:
  ViTMatte TestImage  p95 0.239->0.176, mean 0.131->0.061
  ViTMatte TestImage2 p95 0.231->0.165, mean 0.115->0.052

Removes now-unused DespillSettings fields edge_high and bg_confidence_weight.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:58:32 +08:00
Codex d6bb658244 Add foreground color estimation pass 2026-06-30 16:25:56 +08:00
Codex 1a670088c0 Use ViTMatte base composition model 2026-06-30 15:36:07 +08:00
Codex 10705d49df Support YAML pipeline configuration 2026-06-30 15:03:15 +08:00
Codex 923dbe9f09 Add sample smoke check and refine foreground despill 2026-06-30 14:42:08 +08:00
Codex 464a60c733 Improve despill and add quality checks 2026-06-30 14:29:19 +08:00
Codex 8fc53925e0 Implement green screen matting pipeline 2026-06-30 14:14:15 +08:00
Codex 4d2a2015fe Ignore sample and output directories 2026-06-30 13:43:37 +08:00
Codex bb61d2d3b6 Add green screen matting workflow 2026-06-30 13:39:24 +08:00