MogensR commited on
Commit
d38f2ff
Β·
1 Parent(s): a0ffb03

Create utils/composting.py

Browse files
Files changed (1) hide show
  1. utils/composting.py +101 -0
utils/composting.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ utils.compositing
4
+ ─────────────────────────────────────────────────────────────────────────────
5
+ Handles frame-level compositing (foreground frame + mask + background).
6
+
7
+ Public API
8
+ ----------
9
+ replace_background_hq(frame_bgr, mask, background_bgr, fallback_enabled=True) β†’ np.ndarray
10
+ """
11
+
12
+ from __future__ import annotations
13
+ import logging
14
+ from typing import Tuple
15
+
16
+ import cv2
17
+ import numpy as np
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+ __all__ = ["replace_background_hq"]
22
+
23
+
24
+ # ────────────────────────────────────────────────────────────────────────────
25
+ # Main entry
26
+ # ────────────────────────────────────────────────────────────────────────────
27
+ def replace_background_hq(
28
+ frame_bgr: np.ndarray,
29
+ mask: np.ndarray,
30
+ background_bgr: np.ndarray,
31
+ fallback_enabled: bool = True,
32
+ ) -> np.ndarray:
33
+ """
34
+ β€’ Ensures background is resized to frame
35
+ β€’ Accepts mask in {0,1} or {0,255} or float32
36
+ β€’ Tries edge-feathered advanced blend, else simple overlay
37
+ """
38
+ if frame_bgr is None or mask is None or background_bgr is None:
39
+ raise ValueError("Invalid input to replace_background_hq")
40
+
41
+ h, w = frame_bgr.shape[:2]
42
+ background = cv2.resize(background_bgr, (w, h), interpolation=cv2.INTER_LANCZOS4)
43
+ m = _process_mask(mask)
44
+
45
+ try:
46
+ return _advanced_composite(frame_bgr, background, m)
47
+ except Exception as e:
48
+ log.warning(f"Advanced compositing failed: {e}")
49
+ if not fallback_enabled:
50
+ raise
51
+ return _simple_composite(frame_bgr, background, m)
52
+
53
+
54
+ # ────────────────────────────────────────────────────────────────────────────
55
+ # Advanced compositor (feather + subtle colour-match)
56
+ # ────────────────────────────────────────────────────────────────────────────
57
+ def _advanced_composite(fg, bg, mask_u8):
58
+ # 1) Smooth / feather
59
+ mask = cv2.GaussianBlur(mask_u8.astype(np.float32), (5, 5), 1.0) / 255.0
60
+ mask = np.power(mask, 0.8) # shrink bleed
61
+ mask3 = mask[..., None]
62
+
63
+ # 2) Edge colour-match to reduce halo
64
+ fg_adj = _colour_match_edges(fg, bg, mask)
65
+
66
+ # 3) Blend
67
+ comp = fg_adj.astype(np.float32) * mask3 + bg.astype(np.float32) * (1 - mask3)
68
+ return np.clip(comp, 0, 255).astype(np.uint8)
69
+
70
+
71
+ def _colour_match_edges(fg, bg, alpha):
72
+ edge = cv2.Sobel(alpha, cv2.CV_32F, 1, 1, ksize=3)
73
+ edge = (np.abs(edge) > 0.05).astype(np.float32)
74
+ if not np.any(edge):
75
+ return fg
76
+ adj = fg.astype(np.float32).copy()
77
+ mix = 0.1
78
+ adj[edge > 0] = adj[edge > 0] * (1 - mix) + bg[edge > 0] * mix
79
+ return adj.astype(np.uint8)
80
+
81
+
82
+ # ────────────────────────────────────────────────────────────────────────────
83
+ # Simple fallback compositor
84
+ # ────────────────────────────────────────────────────────────────────────────
85
+ def _simple_composite(fg, bg, mask_u8):
86
+ m = mask_u8.astype(np.float32) / 255.0
87
+ m3 = m[..., None]
88
+ return (fg.astype(np.float32) * m3 + bg.astype(np.float32) * (1 - m3)).astype(np.uint8)
89
+
90
+
91
+ # ────────────────────────────────────────────────────────────────────────────
92
+ # Utilities
93
+ # ────────────────────────────────────────────────────────────────────────────
94
+ def _process_mask(mask):
95
+ """Ensure uint8 0/255 single-channel"""
96
+ if mask.ndim == 3:
97
+ mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
98
+ if mask.dtype != np.uint8:
99
+ mask = (mask * 255).astype(np.uint8) if mask.max() <= 1 else mask.astype(np.uint8)
100
+ _, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
101
+ return mask