-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathvideo_analysis.py
More file actions
227 lines (180 loc) · 6.99 KB
/
Copy pathvideo_analysis.py
File metadata and controls
227 lines (180 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/env python3
"""
Video analysis module for extracting color information from video frames.
Supports multiple analysis modes: dominant color, edge analysis (Ambilight-style), etc.
"""
import cv2
import numpy as np
class VideoAnalyzer:
"""Analyzes video frames to extract color information for light control."""
def __init__(self, mode="dominant_color", edge_thickness=0.15, smoothing=0.5):
"""
Initialize the video analyzer.
Args:
mode: Analysis mode - 'dominant_color', 'edge_analysis', or 'average'
edge_thickness: Percentage of frame to use for edge analysis (0.0-0.5)
smoothing: Smoothing factor for color transitions (0.0-1.0)
"""
self.mode = mode
self.edge_thickness = edge_thickness
self.smoothing = smoothing
self.last_color = np.array([0, 0, 0], dtype=float)
def analyze_frame(self, frame):
"""
Analyze a video frame and extract RGB color.
Args:
frame: OpenCV frame (BGR format)
Returns:
tuple: (r, g, b) values 0-255
"""
if self.mode == "dominant_color":
return self._dominant_color(frame)
elif self.mode == "edge_analysis":
return self._edge_analysis(frame)
elif self.mode == "average":
return self._average_color(frame)
else:
return self._dominant_color(frame)
def _dominant_color(self, frame):
"""
Extract dominant color using K-means clustering.
Fast and effective for most scenes.
"""
# Resize for faster processing
small_frame = cv2.resize(frame, (150, 150))
# Convert BGR to RGB
rgb_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)
# Reshape to list of pixels
pixels = rgb_frame.reshape((-1, 3)).astype(np.float32)
# K-means clustering to find dominant colors
k = 3 # Find top 3 colors
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
_, labels, centers = cv2.kmeans(
pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS
)
# Find most common cluster
unique, counts = np.unique(labels, return_counts=True)
dominant_idx = unique[np.argmax(counts)]
dominant_color = centers[dominant_idx]
# Apply smoothing
smoothed = self._smooth_color(dominant_color)
return tuple(map(int, smoothed))
def _edge_analysis(self, frame):
"""
Ambilight-style edge analysis.
Extracts colors from the edges of the frame.
"""
h, w = frame.shape[:2]
edge_w = int(w * self.edge_thickness)
edge_h = int(h * self.edge_thickness)
# Extract edge regions
top = frame[:edge_h, :]
bottom = frame[-edge_h:, :]
left = frame[:, :edge_w]
right = frame[:, -edge_w:]
# Combine all edge pixels
edges = np.vstack([
top.reshape(-1, 3),
bottom.reshape(-1, 3),
left.reshape(-1, 3),
right.reshape(-1, 3)
])
# Calculate average edge color
avg_color = np.mean(edges, axis=0)
# Convert BGR to RGB
rgb_color = avg_color[::-1]
# Apply smoothing
smoothed = self._smooth_color(rgb_color)
return tuple(map(int, smoothed))
def _average_color(self, frame):
"""
Simple average of all pixels.
Fast but may be washed out for complex scenes.
"""
# Convert BGR to RGB
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Calculate average
avg_color = np.mean(rgb_frame.reshape(-1, 3), axis=0)
# Apply smoothing
smoothed = self._smooth_color(avg_color)
return tuple(map(int, smoothed))
def _smooth_color(self, color):
"""
Apply exponential smoothing to color transitions.
Prevents jarring color jumps between frames.
"""
# Exponential moving average
self.last_color = (
self.smoothing * self.last_color + (1 - self.smoothing) * np.array(color)
)
return self.last_color
class SceneBrightnessAnalyzer:
"""Analyzes scene brightness for dynamic brightness control."""
def __init__(self, smoothing=0.5):
"""
Initialize brightness analyzer.
Args:
smoothing: Smoothing factor for brightness transitions
"""
self.min_brightness = 10
self.max_brightness = 100
self.smoothing = smoothing
self.last_brightness = self.min_brightness
def analyze_brightness(self, frame):
"""
Analyze frame brightness and map to light brightness.
Args:
frame: OpenCV frame (BGR format)
Returns:
int: Brightness value 0-100
"""
# Convert to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Calculate average brightness (0-255)
avg_brightness = np.mean(gray)
# Map to brightness range (0-255 -> min-max)
brightness_range = self.max_brightness - self.min_brightness
brightness = self.min_brightness + (avg_brightness / 255.0) * brightness_range
# Apply smoothing
self.last_brightness = (
self.smoothing * self.last_brightness + (1 - self.smoothing) * brightness
)
return int(np.clip(self.last_brightness, self.min_brightness, self.max_brightness))
class HybridAnalyzer:
"""
Combines video visual analysis with audio analysis.
Colors from video, brightness from audio beats.
"""
def __init__(self, video_analyzer, audio_analyzer=None, brightness_from_audio=True):
"""
Initialize hybrid analyzer.
Args:
video_analyzer: VideoAnalyzer instance
audio_analyzer: AudioAnalyzer instance (optional)
brightness_from_audio: Whether to use audio for brightness control
"""
self.video_analyzer = video_analyzer
self.audio_analyzer = audio_analyzer
self.brightness_from_audio = brightness_from_audio
self.scene_brightness = SceneBrightnessAnalyzer()
def analyze(self, frame, audio_chunk=None):
"""
Analyze both video and audio.
Args:
frame: OpenCV frame
audio_chunk: Audio data (optional)
Returns:
tuple: (r, g, b, brightness)
"""
# Get color from video
r, g, b = self.video_analyzer.analyze_frame(frame)
# Get brightness
if self.brightness_from_audio and audio_chunk is not None and self.audio_analyzer:
# Use audio energy for brightness
bass, mids, treble = self.audio_analyzer.analyze(audio_chunk)
amplitude = self.audio_analyzer.get_amplitude(audio_chunk)
brightness = int(np.clip(10 + amplitude * 90, 0, 100))
else:
# Use scene brightness
brightness = self.scene_brightness.analyze_brightness(frame)
return r, g, b, brightness