MogensR commited on
Commit
8ad08ed
·
1 Parent(s): 06fe5c0

Create processing/video/video_processor.py

Browse files
Files changed (1) hide show
  1. processing/video/video_processor.py +121 -0
processing/video/video_processor.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Compatibility shim: CoreVideoProcessor
4
+
5
+ Bridges the legacy import `from processing.video.video_processor import CoreVideoProcessor`
6
+ to the modern pipeline functions living in `utils.cv_processing` and models in `core.models`.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Optional, Dict, Any, Tuple
13
+
14
+ import cv2
15
+ import numpy as np
16
+
17
+ from utils.logger import get_logger
18
+ from core.models import ModelManager
19
+ # ← these funcs are the ones you showed (in utils/cv_processing.py)
20
+ from utils.cv_processing import (
21
+ segment_person_hq,
22
+ refine_mask_hq,
23
+ replace_background_hq,
24
+ create_professional_background,
25
+ validate_video_file,
26
+ )
27
+
28
+ @dataclass
29
+ class ProcessorConfig:
30
+ background_preset: str = "minimalist" # key in PROFESSIONAL_BACKGROUNDS
31
+ write_fps: Optional[float] = None # None -> keep source fps
32
+
33
+
34
+ class CoreVideoProcessor:
35
+ """
36
+ Minimal, safe implementation used by core/app.py.
37
+ It relies on ModelManager (SAM2 + MatAnyone) and your cv_processing helpers.
38
+ """
39
+
40
+ def __init__(self, config: Optional[ProcessorConfig] = None, models: Optional[ModelManager] = None):
41
+ self.log = get_logger(f"{__name__}.CoreVideoProcessor")
42
+ self.config = config or ProcessorConfig()
43
+ self.models = models or ModelManager()
44
+ try:
45
+ self.models.load_all()
46
+ except Exception as e:
47
+ self.log.warning(f"Model load issue (will use fallbacks if needed): {e}")
48
+
49
+ # --- single-frame API (useful for images or per-frame video loop) ---
50
+ def process_frame(self, frame: np.ndarray, background: np.ndarray) -> Dict[str, Any]:
51
+ """Return dict with composited frame + mask; always succeeds with fallbacks."""
52
+ predictor = None
53
+ try:
54
+ predictor = self.models.get_sam2().predictor
55
+ except Exception as e:
56
+ self.log.warning(f"SAM2 predictor unavailable, using fallback: {e}")
57
+
58
+ # 1) segment
59
+ mask = segment_person_hq(frame, predictor, fallback_enabled=True)
60
+
61
+ # 2) refine
62
+ matanyone = None
63
+ try:
64
+ matanyone = self.models.get_matanyone()
65
+ except Exception as e:
66
+ self.log.warning(f"MatAnyone unavailable, using OpenCV refinement: {e}")
67
+
68
+ mask_refined = refine_mask_hq(frame, mask, matanyone, fallback_enabled=True)
69
+
70
+ # 3) composite
71
+ out = replace_background_hq(frame, mask_refined, background, fallback_enabled=True)
72
+
73
+ return {"frame": out, "mask": mask_refined}
74
+
75
+ # --- simple video API (covers typical usage in older core/app.py code) ---
76
+ def process_video(self, input_path: str, output_path: str, bg_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
77
+ """Process a full video; returns basic stats."""
78
+ ok, msg = validate_video_file(input_path)
79
+ if not ok:
80
+ raise ValueError(f"Invalid video: {msg}")
81
+ self.log.info(f"Video validation: {msg}")
82
+
83
+ cap = cv2.VideoCapture(input_path)
84
+ if not cap.isOpened():
85
+ raise RuntimeError(f"Could not open video: {input_path}")
86
+
87
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
88
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
89
+ fps = cap.get(cv2.CAP_PROP_FPS)
90
+ fps_out = self.config.write_fps or (fps if fps and fps > 0 else 25.0)
91
+
92
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
93
+ writer = cv2.VideoWriter(output_path, fourcc, fps_out, (width, height))
94
+
95
+ # Build background (once)
96
+ from utils.cv_processing import PROFESSIONAL_BACKGROUNDS # local import to avoid circulars
97
+ preset = self.config.background_preset
98
+ cfg = bg_config or PROFESSIONAL_BACKGROUNDS.get(preset, PROFESSIONAL_BACKGROUNDS["minimalist"])
99
+ background = create_professional_background(cfg, width, height)
100
+
101
+ frame_count = 0
102
+ try:
103
+ while True:
104
+ ret, frame = cap.read()
105
+ if not ret:
106
+ break
107
+ res = self.process_frame(frame, background)
108
+ writer.write(res["frame"])
109
+ frame_count += 1
110
+ finally:
111
+ cap.release()
112
+ writer.release()
113
+
114
+ self.log.info(f"Processed {frame_count} frames → {output_path}")
115
+ return {"frames": frame_count, "width": width, "height": height, "fps_out": fps_out}
116
+
117
+ # Backward-compat export name (if someone expects `VideoProcessor`)
118
+ try:
119
+ VideoProcessor
120
+ except NameError:
121
+ VideoProcessor = CoreVideoProcessor