Update utils/utils.py
Browse files- utils/utils.py +215 -1
utils/utils.py
CHANGED
|
@@ -1479,4 +1479,218 @@ def _advanced_compositing(frame: np.ndarray, mask: np.ndarray, background: np.nd
|
|
| 1479 |
raise
|
| 1480 |
|
| 1481 |
def _color_match_edges(frame: np.ndarray, background: np.ndarray, alpha: np.ndarray) -> np.ndarray:
|
| 1482 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1479 |
raise
|
| 1480 |
|
| 1481 |
def _color_match_edges(frame: np.ndarray, background: np.ndarray, alpha: np.ndarray) -> np.ndarray:
|
| 1482 |
+
"""Subtle color matching at edges to reduce halos"""
|
| 1483 |
+
try:
|
| 1484 |
+
edge_mask = cv2.Sobel(alpha, cv2.CV_64F, 1, 1, ksize=3)
|
| 1485 |
+
edge_mask = np.abs(edge_mask)
|
| 1486 |
+
edge_mask = (edge_mask > 0.1).astype(np.float32)
|
| 1487 |
+
|
| 1488 |
+
edge_areas = edge_mask > 0
|
| 1489 |
+
if not np.any(edge_areas):
|
| 1490 |
+
return frame
|
| 1491 |
+
|
| 1492 |
+
frame_adjusted = frame.copy().astype(np.float32)
|
| 1493 |
+
background_float = background.astype(np.float32)
|
| 1494 |
+
|
| 1495 |
+
adjustment_strength = 0.1
|
| 1496 |
+
for c in range(3):
|
| 1497 |
+
frame_adjusted[:, :, c] = np.where(
|
| 1498 |
+
edge_areas,
|
| 1499 |
+
frame_adjusted[:, :, c] * (1 - adjustment_strength) +
|
| 1500 |
+
background_float[:, :, c] * adjustment_strength,
|
| 1501 |
+
frame_adjusted[:, :, c]
|
| 1502 |
+
)
|
| 1503 |
+
|
| 1504 |
+
return np.clip(frame_adjusted, 0, 255).astype(np.uint8)
|
| 1505 |
+
|
| 1506 |
+
except Exception as e:
|
| 1507 |
+
logger.warning(f"Color matching failed: {e}")
|
| 1508 |
+
return frame
|
| 1509 |
+
|
| 1510 |
+
def _simple_compositing(frame: np.ndarray, mask: np.ndarray, background: np.ndarray) -> np.ndarray:
|
| 1511 |
+
"""Simple fallback compositing method"""
|
| 1512 |
+
try:
|
| 1513 |
+
logger.info("Using simple compositing fallback")
|
| 1514 |
+
|
| 1515 |
+
background = cv2.resize(background, (frame.shape[1], frame.shape[0]))
|
| 1516 |
+
|
| 1517 |
+
if len(mask.shape) == 3:
|
| 1518 |
+
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
|
| 1519 |
+
if mask.max() <= 1.0:
|
| 1520 |
+
mask = (mask * 255).astype(np.uint8)
|
| 1521 |
+
|
| 1522 |
+
_, mask_binary = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
|
| 1523 |
+
|
| 1524 |
+
mask_norm = mask_binary.astype(np.float32) / 255.0
|
| 1525 |
+
mask_3ch = np.stack([mask_norm] * 3, axis=2)
|
| 1526 |
+
|
| 1527 |
+
result = frame * mask_3ch + background * (1 - mask_3ch)
|
| 1528 |
+
return result.astype(np.uint8)
|
| 1529 |
+
|
| 1530 |
+
except Exception as e:
|
| 1531 |
+
logger.error(f"Simple compositing failed: {e}")
|
| 1532 |
+
return frame
|
| 1533 |
+
|
| 1534 |
+
def _create_solid_background(bg_config: Dict[str, Any], width: int, height: int) -> np.ndarray:
|
| 1535 |
+
"""Create solid color background"""
|
| 1536 |
+
color_hex = bg_config["colors"][0].lstrip('#')
|
| 1537 |
+
color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
|
| 1538 |
+
color_bgr = color_rgb[::-1]
|
| 1539 |
+
return np.full((height, width, 3), color_bgr, dtype=np.uint8)
|
| 1540 |
+
|
| 1541 |
+
def _create_gradient_background_enhanced(bg_config: Dict[str, Any], width: int, height: int) -> np.ndarray:
|
| 1542 |
+
"""Create enhanced gradient background with better quality"""
|
| 1543 |
+
try:
|
| 1544 |
+
colors = bg_config["colors"]
|
| 1545 |
+
direction = bg_config.get("direction", "vertical")
|
| 1546 |
+
|
| 1547 |
+
rgb_colors = []
|
| 1548 |
+
for color_hex in colors:
|
| 1549 |
+
color_hex = color_hex.lstrip('#')
|
| 1550 |
+
rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
|
| 1551 |
+
rgb_colors.append(rgb)
|
| 1552 |
+
|
| 1553 |
+
if not rgb_colors:
|
| 1554 |
+
rgb_colors = [(128, 128, 128)]
|
| 1555 |
+
|
| 1556 |
+
if direction == "vertical":
|
| 1557 |
+
background = _create_vertical_gradient(rgb_colors, width, height)
|
| 1558 |
+
elif direction == "horizontal":
|
| 1559 |
+
background = _create_horizontal_gradient(rgb_colors, width, height)
|
| 1560 |
+
elif direction == "diagonal":
|
| 1561 |
+
background = _create_diagonal_gradient(rgb_colors, width, height)
|
| 1562 |
+
elif direction in ["radial", "soft_radial"]:
|
| 1563 |
+
background = _create_radial_gradient(rgb_colors, width, height, direction == "soft_radial")
|
| 1564 |
+
else:
|
| 1565 |
+
background = _create_vertical_gradient(rgb_colors, width, height)
|
| 1566 |
+
|
| 1567 |
+
return cv2.cvtColor(background, cv2.COLOR_RGB2BGR)
|
| 1568 |
+
|
| 1569 |
+
except Exception as e:
|
| 1570 |
+
logger.error(f"Gradient creation error: {e}")
|
| 1571 |
+
return np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
|
| 1572 |
+
|
| 1573 |
+
def _create_vertical_gradient(colors: list, width: int, height: int) -> np.ndarray:
|
| 1574 |
+
"""Create vertical gradient using NumPy for performance"""
|
| 1575 |
+
gradient = np.zeros((height, width, 3), dtype=np.uint8)
|
| 1576 |
+
|
| 1577 |
+
for y in range(height):
|
| 1578 |
+
progress = y / height if height > 0 else 0
|
| 1579 |
+
color = _interpolate_color(colors, progress)
|
| 1580 |
+
gradient[y, :] = color
|
| 1581 |
+
|
| 1582 |
+
return gradient
|
| 1583 |
+
|
| 1584 |
+
def _create_horizontal_gradient(colors: list, width: int, height: int) -> np.ndarray:
|
| 1585 |
+
"""Create horizontal gradient using NumPy for performance"""
|
| 1586 |
+
gradient = np.zeros((height, width, 3), dtype=np.uint8)
|
| 1587 |
+
|
| 1588 |
+
for x in range(width):
|
| 1589 |
+
progress = x / width if width > 0 else 0
|
| 1590 |
+
color = _interpolate_color(colors, progress)
|
| 1591 |
+
gradient[:, x] = color
|
| 1592 |
+
|
| 1593 |
+
return gradient
|
| 1594 |
+
|
| 1595 |
+
def _create_diagonal_gradient(colors: list, width: int, height: int) -> np.ndarray:
|
| 1596 |
+
"""Create diagonal gradient using vectorized operations"""
|
| 1597 |
+
y_coords, x_coords = np.mgrid[0:height, 0:width]
|
| 1598 |
+
max_distance = width + height
|
| 1599 |
+
progress = (x_coords + y_coords) / max_distance
|
| 1600 |
+
progress = np.clip(progress, 0, 1)
|
| 1601 |
+
|
| 1602 |
+
gradient = np.zeros((height, width, 3), dtype=np.uint8)
|
| 1603 |
+
for c in range(3):
|
| 1604 |
+
gradient[:, :, c] = _vectorized_color_interpolation(colors, progress, c)
|
| 1605 |
+
|
| 1606 |
+
return gradient
|
| 1607 |
+
|
| 1608 |
+
def _create_radial_gradient(colors: list, width: int, height: int, soft: bool = False) -> np.ndarray:
|
| 1609 |
+
"""Create radial gradient using vectorized operations"""
|
| 1610 |
+
center_x, center_y = width // 2, height // 2
|
| 1611 |
+
max_distance = np.sqrt(center_x**2 + center_y**2)
|
| 1612 |
+
|
| 1613 |
+
y_coords, x_coords = np.mgrid[0:height, 0:width]
|
| 1614 |
+
distances = np.sqrt((x_coords - center_x)**2 + (y_coords - center_y)**2)
|
| 1615 |
+
progress = distances / max_distance
|
| 1616 |
+
progress = np.clip(progress, 0, 1)
|
| 1617 |
+
|
| 1618 |
+
if soft:
|
| 1619 |
+
progress = np.power(progress, 0.7)
|
| 1620 |
+
|
| 1621 |
+
gradient = np.zeros((height, width, 3), dtype=np.uint8)
|
| 1622 |
+
for c in range(3):
|
| 1623 |
+
gradient[:, :, c] = _vectorized_color_interpolation(colors, progress, c)
|
| 1624 |
+
|
| 1625 |
+
return gradient
|
| 1626 |
+
|
| 1627 |
+
def _vectorized_color_interpolation(colors: list, progress: np.ndarray, channel: int) -> np.ndarray:
|
| 1628 |
+
"""Vectorized color interpolation for performance"""
|
| 1629 |
+
if len(colors) == 1:
|
| 1630 |
+
return np.full_like(progress, colors[0][channel], dtype=np.uint8)
|
| 1631 |
+
|
| 1632 |
+
num_segments = len(colors) - 1
|
| 1633 |
+
segment_progress = progress * num_segments
|
| 1634 |
+
segment_indices = np.floor(segment_progress).astype(int)
|
| 1635 |
+
segment_indices = np.clip(segment_indices, 0, num_segments - 1)
|
| 1636 |
+
local_progress = segment_progress - segment_indices
|
| 1637 |
+
|
| 1638 |
+
start_colors = np.array([colors[i][channel] for i in range(len(colors))])
|
| 1639 |
+
end_colors = np.array([colors[min(i + 1, len(colors) - 1)][channel] for i in range(len(colors))])
|
| 1640 |
+
|
| 1641 |
+
start_vals = start_colors[segment_indices]
|
| 1642 |
+
end_vals = end_colors[segment_indices]
|
| 1643 |
+
|
| 1644 |
+
result = start_vals + (end_vals - start_vals) * local_progress
|
| 1645 |
+
return np.clip(result, 0, 255).astype(np.uint8)
|
| 1646 |
+
|
| 1647 |
+
def _interpolate_color(colors: list, progress: float) -> tuple:
|
| 1648 |
+
"""Interpolate between multiple colors"""
|
| 1649 |
+
if len(colors) == 1:
|
| 1650 |
+
return colors[0]
|
| 1651 |
+
elif len(colors) == 2:
|
| 1652 |
+
r = int(colors[0][0] + (colors[1][0] - colors[0][0]) * progress)
|
| 1653 |
+
g = int(colors[0][1] + (colors[1][1] - colors[0][1]) * progress)
|
| 1654 |
+
b = int(colors[0][2] + (colors[1][2] - colors[0][2]) * progress)
|
| 1655 |
+
return (r, g, b)
|
| 1656 |
+
else:
|
| 1657 |
+
segment = progress * (len(colors) - 1)
|
| 1658 |
+
idx = int(segment)
|
| 1659 |
+
local_progress = segment - idx
|
| 1660 |
+
if idx >= len(colors) - 1:
|
| 1661 |
+
return colors[-1]
|
| 1662 |
+
c1, c2 = colors[idx], colors[idx + 1]
|
| 1663 |
+
r = int(c1[0] + (c2[0] - c1[0]) * local_progress)
|
| 1664 |
+
g = int(c1[1] + (c2[1] - c1[1]) * local_progress)
|
| 1665 |
+
b = int(c1[2] + (c2[2] - c1[2]) * local_progress)
|
| 1666 |
+
return (r, g, b)
|
| 1667 |
+
|
| 1668 |
+
def _apply_background_adjustments(background: np.ndarray, bg_config: Dict[str, Any]) -> np.ndarray:
|
| 1669 |
+
"""Apply brightness and contrast adjustments to background"""
|
| 1670 |
+
try:
|
| 1671 |
+
brightness = bg_config.get("brightness", 1.0)
|
| 1672 |
+
contrast = bg_config.get("contrast", 1.0)
|
| 1673 |
+
|
| 1674 |
+
if brightness != 1.0 or contrast != 1.0:
|
| 1675 |
+
background = background.astype(np.float32)
|
| 1676 |
+
background = background * contrast * brightness
|
| 1677 |
+
background = np.clip(background, 0, 255).astype(np.uint8)
|
| 1678 |
+
|
| 1679 |
+
return background
|
| 1680 |
+
|
| 1681 |
+
except Exception as e:
|
| 1682 |
+
logger.warning(f"Background adjustment failed: {e}")
|
| 1683 |
+
return background
|
| 1684 |
+
|
| 1685 |
+
# ============================================================================
|
| 1686 |
+
# DEFAULT INSTANCES
|
| 1687 |
+
# ============================================================================
|
| 1688 |
+
|
| 1689 |
+
_default_file_manager = None
|
| 1690 |
+
|
| 1691 |
+
def get_file_manager(base_dir: Optional[str] = None) -> FileManager:
|
| 1692 |
+
"""Get or create the default FileManager instance"""
|
| 1693 |
+
global _default_file_manager
|
| 1694 |
+
if _default_file_manager is None or base_dir is not None:
|
| 1695 |
+
_default_file_manager = FileManager(base_dir)
|
| 1696 |
+
return _default_file_manager
|