Merge master: matte-confidence gate + cross-model hair-gap veto
Brings in the two algorithm optimisations from master: - Gate chroma suppression by the matte's own confidence (639fdf0) - Add cross-model veto of hair-gap background residue (adbbb9d) Server-edition integration on top of the plain merge: - PipelineManager caches the cross-check model (BiRefNet_HR-matting) and injects it into MattingPipeline, so HTTP requests never reload it. - MattingResult carries cross_check_alpha; debug output matches master. - /healthz reports crossCheck; README lists models/BiRefNet_HR-matting in the local-weights bundle. Verified: CLI --no-cross-check baseline and full cross-check run both pass on samples/TestImage3.png; cross_check_alpha.png written to debug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+36
-3
@@ -8,8 +8,8 @@ import numpy as np
|
||||
|
||||
from .config import _SEG_MODELS, override_settings
|
||||
from .pipeline import MattingPipeline, MattingResult
|
||||
from .segmentation import make_segmenter
|
||||
from .settings import PipelineSettings
|
||||
from .segmentation import BiRefNetSegmenter, make_segmenter
|
||||
from .settings import PipelineSettings, SegmentationSettings
|
||||
from .vitmatte_infer import ViTMatteRunner
|
||||
|
||||
logger = logging.getLogger("bgfilter.service")
|
||||
@@ -33,12 +33,15 @@ class PipelineManager:
|
||||
self._base = base_settings
|
||||
self._runner: ViTMatteRunner | None = None
|
||||
self._segmenters: dict[str, object] = {}
|
||||
self._cross_checker: BiRefNetSegmenter | None = None
|
||||
self._load_lock = threading.Lock()
|
||||
if preload:
|
||||
if base_settings.model.matting_method == "vitmatte":
|
||||
self._get_runner()
|
||||
if base_settings.segmentation.enabled:
|
||||
self._get_segmenter(base_settings.segmentation.backend)
|
||||
if base_settings.cross_check.enabled:
|
||||
self._get_cross_checker()
|
||||
|
||||
@property
|
||||
def default_seg_model(self) -> str:
|
||||
@@ -48,6 +51,10 @@ class PipelineManager:
|
||||
def device(self) -> str:
|
||||
return self._base.model.device
|
||||
|
||||
@property
|
||||
def cross_check_enabled(self) -> bool:
|
||||
return self._base.cross_check.enabled
|
||||
|
||||
def _get_runner(self) -> ViTMatteRunner:
|
||||
if self._runner is None:
|
||||
with self._load_lock:
|
||||
@@ -81,6 +88,27 @@ class PipelineManager:
|
||||
self._segmenters[backend] = seg
|
||||
return seg
|
||||
|
||||
def _get_cross_checker(self) -> BiRefNetSegmenter:
|
||||
# The cross-check model depends only on base settings (never on per-request
|
||||
# params), so one shared instance serves all requests.
|
||||
if self._cross_checker is None:
|
||||
with self._load_lock:
|
||||
if self._cross_checker is None:
|
||||
check = self._base.cross_check
|
||||
logger.info(
|
||||
"loading cross-check model: model=%s device=%s",
|
||||
check.model_name,
|
||||
self._base.segmentation.device,
|
||||
)
|
||||
self._cross_checker = BiRefNetSegmenter(
|
||||
SegmentationSettings(
|
||||
model_name=check.model_name,
|
||||
device=self._base.segmentation.device,
|
||||
input_size=check.input_size,
|
||||
)
|
||||
)
|
||||
return self._cross_checker
|
||||
|
||||
def process(
|
||||
self, rgb: np.ndarray, *, screen_color: str | None, seg_model: str
|
||||
) -> MattingResult:
|
||||
@@ -97,5 +125,10 @@ class PipelineManager:
|
||||
segmenter = (
|
||||
self._get_segmenter(seg_model) if settings.segmentation.enabled else None
|
||||
)
|
||||
pipeline = MattingPipeline(settings, runner=runner, segmenter=segmenter)
|
||||
cross_checker = (
|
||||
self._get_cross_checker() if settings.cross_check.enabled else None
|
||||
)
|
||||
pipeline = MattingPipeline(
|
||||
settings, runner=runner, segmenter=segmenter, cross_checker=cross_checker
|
||||
)
|
||||
return pipeline.run_rgb(rgb)
|
||||
|
||||
Reference in New Issue
Block a user