Remove dead colour-model/despill code; simplify despill; refresh stale docs

- chroma: drop BackgroundModel.hsv_center/hue_sigma (unused since the green gating
  went away), their estimation, _hue_distance, and the now-unused hsv channel from
  convert_color_spaces (one fewer cvtColor per image).
- despill: delete the dead green-channel-algebra path (the pipeline always passes a
  colour) and fold the remaining Lab-direction despill into a single despill() with
  no screen_color arg (127 -> 60 lines). Renamed despill_green -> despill.
- settings: remove the now-dead ChromaSettings.hue_sigma_min and
  DespillSettings.green_excess_margin (plus the matching config lines).
- docs/comments: generalise stale "green" wording to "background colour" (module
  docstring, alpha_post/trimap/foreground/settings) and rename the trimap variable
  green_bg -> screen_bg.

Kept: the unmix foreground fallback, --matting-method chroma, and green/blue as
illustrative examples. Verified: package compiles, both configs parse, and an
auto-detect end-to-end run is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 17:49:20 +08:00
parent a6d84845c6
commit fe97d692a7
10 changed files with 50 additions and 140 deletions
+7 -24
View File
@@ -12,8 +12,6 @@ from .settings import ChromaSettings
class BackgroundModel:
rgb_center: tuple[float, float, float]
rgb_sigma: tuple[float, float, float]
hsv_center: tuple[float, float, float]
hue_sigma: float
lab_center: tuple[float, float, float]
lab_sigma: float
sample_count: int
@@ -22,11 +20,6 @@ class BackgroundModel:
return asdict(self)
def _hue_distance(hue: np.ndarray, center: float) -> np.ndarray:
diff = np.abs(hue.astype(np.float32) - float(center))
return np.minimum(diff, 180.0 - diff)
def _border_mask(height: int, width: int, ratio: float) -> np.ndarray:
border = max(8, int(round(max(height, width) * ratio)))
border = min(border, height // 2, width // 2)
@@ -38,13 +31,12 @@ def _border_mask(height: int, width: int, ratio: float) -> np.ndarray:
return mask
def convert_color_spaces(rgb: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
def convert_color_spaces(rgb: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""Return ``(rgb_f, lab)``: RGB normalised to 0..1 and Lab (L 0..100, a/b signed)."""
cv2 = require_cv2()
rgb_f = rgb.astype(np.float32) / 255.0
hsv = cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV).astype(np.float32)
hsv[..., 1:] /= 255.0
lab = cv2.cvtColor(rgb_f, cv2.COLOR_RGB2LAB).astype(np.float32)
return rgb_f, hsv, lab
return rgb_f, lab
def parse_hex_color(text: str) -> tuple[float, float, float]:
@@ -116,7 +108,7 @@ def detect_background_color(
Pure: reads pixels only, with no matting side effects.
"""
settings = settings or ChromaSettings()
rgb_f, _, lab = convert_color_spaces(rgb)
rgb_f, lab = convert_color_spaces(rgb)
mask, _, _ = _background_border_cluster(lab, settings)
return tuple(float(x) for x in np.median(rgb_f[mask], axis=0))
@@ -126,7 +118,7 @@ def estimate_background_model(
settings: ChromaSettings,
screen_color: tuple[float, float, float] | None = None,
) -> tuple[BackgroundModel, tuple[np.ndarray, np.ndarray, np.ndarray]]:
rgb_f, hsv, lab = convert_color_spaces(rgb)
rgb_f, lab = convert_color_spaces(rgb)
if screen_color is None:
# AUTO: sample the flat colour that dominates the border (raises if there is
# no clean flat background). Its own pixels are the model -- no seed search.
@@ -146,7 +138,6 @@ def estimate_background_model(
sample_mask = dist <= np.percentile(dist, 10.0)
rgb_samples = rgb_f[sample_mask]
hsv_samples = hsv[sample_mask]
lab_samples = lab[sample_mask]
rgb_center = np.median(rgb_samples, axis=0)
@@ -154,12 +145,6 @@ def estimate_background_model(
np.percentile(np.abs(rgb_samples - rgb_center), 75, axis=0) * 1.4826,
settings.rgb_sigma_min,
)
hsv_center = np.median(hsv_samples, axis=0)
hue_center = float(hsv_center[0])
hue_dists = _hue_distance(hsv_samples[:, 0], hue_center)
hue_sigma = max(
float(np.percentile(hue_dists, 75) * 1.4826), settings.hue_sigma_min
)
lab_center = np.median(lab_samples, axis=0)
lab_dists = np.linalg.norm(lab_samples - lab_center, axis=1)
lab_sigma = max(
@@ -169,13 +154,11 @@ def estimate_background_model(
model = BackgroundModel(
rgb_center=tuple(float(x) for x in rgb_center),
rgb_sigma=tuple(float(x) for x in rgb_sigma),
hsv_center=tuple(float(x) for x in hsv_center),
hue_sigma=float(hue_sigma),
lab_center=tuple(float(x) for x in lab_center),
lab_sigma=float(lab_sigma),
sample_count=int(sample_mask.sum()),
)
return model, (rgb_f, hsv, lab)
return model, (rgb_f, lab)
def compute_bg_confidence(
@@ -189,7 +172,7 @@ def compute_bg_confidence(
model, spaces = estimate_background_model(rgb, settings, screen_color)
else:
spaces = convert_color_spaces(rgb)
rgb_f, hsv, lab = spaces
rgb_f, lab = spaces
lab_center = np.asarray(model.lab_center, dtype=np.float32)
lab_dist = np.linalg.norm(lab - lab_center, axis=2)