Update visualization.py
Browse files- visualization.py +63 -82
visualization.py
CHANGED
@@ -7,6 +7,7 @@ import numpy as np
|
|
7 |
import pandas as pd
|
8 |
import cv2
|
9 |
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeVideoClip
|
|
|
10 |
from matplotlib.patches import Rectangle
|
11 |
from utils import seconds_to_timecode
|
12 |
from anomaly_detection import determine_anomalies
|
@@ -220,20 +221,16 @@ def create_video_with_heatmap(video_path, df, mse_embeddings, mse_posture, mse_v
|
|
220 |
os.makedirs(output_folder, exist_ok=True)
|
221 |
|
222 |
output_filename = os.path.basename(video_path).rsplit('.', 1)[0] + '_heatmap.mp4'
|
223 |
-
temp_video_path = os.path.join(output_folder, 'temp_' + output_filename)
|
224 |
heatmap_video_path = os.path.join(output_folder, output_filename)
|
225 |
|
226 |
print(f"Heatmap video will be saved at: {heatmap_video_path}")
|
227 |
|
228 |
-
|
229 |
-
|
230 |
-
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
231 |
-
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
232 |
-
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
233 |
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
|
238 |
# Ensure all MSE arrays have the same length as total_frames
|
239 |
mse_embeddings = np.interp(np.linspace(0, len(mse_embeddings) - 1, total_frames),
|
@@ -269,84 +266,50 @@ def create_video_with_heatmap(video_path, df, mse_embeddings, mse_posture, mse_v
|
|
269 |
ax.set_yticklabels(['Face', 'Posture', 'Voice'])
|
270 |
ax.set_xticks([])
|
271 |
plt.tight_layout()
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
print(f"Failed to read frame {frame_count}")
|
279 |
-
break
|
280 |
-
|
281 |
-
if line:
|
282 |
-
line.remove()
|
283 |
-
line = ax.axvline(x=frame_count, color='r', linewidth=2)
|
284 |
-
|
285 |
-
canvas = FigureCanvasAgg(fig)
|
286 |
-
canvas.draw()
|
287 |
-
heatmap_img = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')
|
288 |
-
heatmap_img = heatmap_img.reshape(canvas.get_width_height()[::-1] + (3,))
|
289 |
-
heatmap_img = cv2.resize(heatmap_img, (width, 200))
|
290 |
-
|
291 |
-
# Convert frame from BGR to RGB
|
292 |
-
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
293 |
-
|
294 |
-
# Combine RGB frame with RGB heatmap
|
295 |
-
combined_frame = np.vstack((frame_rgb, heatmap_img))
|
296 |
-
|
297 |
-
seconds = frame_count / original_fps
|
298 |
-
timecode = f"{int(seconds//3600):02d}:{int((seconds%3600)//60):02d}:{int(seconds%60):02d}"
|
299 |
-
|
300 |
-
# Add timecode to the combined frame (which is now in RGB)
|
301 |
-
combined_frame = cv2.putText(combined_frame, f"Time: {timecode}", (10, 30),
|
302 |
-
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
|
303 |
-
|
304 |
-
# Convert back to BGR for OpenCV VideoWriter
|
305 |
-
combined_frame_bgr = cv2.cvtColor(combined_frame, cv2.COLOR_RGB2BGR)
|
306 |
-
|
307 |
-
out.write(combined_frame_bgr)
|
308 |
-
|
309 |
-
if progress is not None:
|
310 |
-
try:
|
311 |
-
progress((frame_count + 1) / total_frames, desc="Generating video with heatmap")
|
312 |
-
except Exception as e:
|
313 |
-
print(f"Error updating progress: {str(e)}")
|
314 |
-
|
315 |
-
except Exception as e:
|
316 |
-
print(f"Error in frame processing: {str(e)}")
|
317 |
-
import traceback
|
318 |
-
traceback.print_exc()
|
319 |
-
finally:
|
320 |
-
cap.release()
|
321 |
-
out.release()
|
322 |
-
plt.close(fig)
|
323 |
-
|
324 |
-
# Add audio to the video
|
325 |
-
try:
|
326 |
-
original_video = VideoFileClip(video_path)
|
327 |
-
heatmap_video = VideoFileClip(temp_video_path)
|
328 |
|
329 |
-
#
|
330 |
-
|
|
|
|
|
331 |
|
332 |
-
|
333 |
-
|
|
|
|
|
|
|
334 |
|
335 |
-
#
|
336 |
-
|
337 |
|
338 |
-
#
|
339 |
-
|
340 |
-
|
341 |
-
final_video.close()
|
342 |
|
343 |
-
#
|
344 |
-
|
|
|
|
|
|
|
|
|
345 |
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
350 |
|
351 |
if os.path.exists(heatmap_video_path):
|
352 |
print(f"Heatmap video created at: {heatmap_video_path}")
|
@@ -354,4 +317,22 @@ def create_video_with_heatmap(video_path, df, mse_embeddings, mse_posture, mse_v
|
|
354 |
return heatmap_video_path
|
355 |
else:
|
356 |
print(f"Failed to create heatmap video at: {heatmap_video_path}")
|
357 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
import pandas as pd
|
8 |
import cv2
|
9 |
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeVideoClip
|
10 |
+
from moviepy.video.fx.all import resize
|
11 |
from matplotlib.patches import Rectangle
|
12 |
from utils import seconds_to_timecode
|
13 |
from anomaly_detection import determine_anomalies
|
|
|
221 |
os.makedirs(output_folder, exist_ok=True)
|
222 |
|
223 |
output_filename = os.path.basename(video_path).rsplit('.', 1)[0] + '_heatmap.mp4'
|
|
|
224 |
heatmap_video_path = os.path.join(output_folder, output_filename)
|
225 |
|
226 |
print(f"Heatmap video will be saved at: {heatmap_video_path}")
|
227 |
|
228 |
+
# Load the original video
|
229 |
+
video = VideoFileClip(video_path)
|
|
|
|
|
|
|
230 |
|
231 |
+
# Get video properties
|
232 |
+
width, height = video.w, video.h
|
233 |
+
total_frames = int(video.duration * video.fps)
|
234 |
|
235 |
# Ensure all MSE arrays have the same length as total_frames
|
236 |
mse_embeddings = np.interp(np.linspace(0, len(mse_embeddings) - 1, total_frames),
|
|
|
266 |
ax.set_yticklabels(['Face', 'Posture', 'Voice'])
|
267 |
ax.set_xticks([])
|
268 |
plt.tight_layout()
|
269 |
+
|
270 |
+
def make_frame(t):
|
271 |
+
frame_count = int(t * video.fps)
|
272 |
+
|
273 |
+
# Get the frame from the original video
|
274 |
+
frame = video.get_frame(t)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
|
276 |
+
# Update heatmap
|
277 |
+
if hasattr(ax, 'line'):
|
278 |
+
ax.line.remove()
|
279 |
+
ax.line = ax.axvline(x=frame_count, color='r', linewidth=2)
|
280 |
|
281 |
+
canvas = FigureCanvasAgg(fig)
|
282 |
+
canvas.draw()
|
283 |
+
heatmap_img = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')
|
284 |
+
heatmap_img = heatmap_img.reshape(canvas.get_width_height()[::-1] + (3,))
|
285 |
+
heatmap_img = resize(ImageClip(heatmap_img), width=width).get_frame(0)
|
286 |
|
287 |
+
# Combine frame with heatmap
|
288 |
+
combined_frame = np.vstack((frame, heatmap_img))
|
289 |
|
290 |
+
# Add timecode
|
291 |
+
seconds = t
|
292 |
+
timecode = f"{int(seconds//3600):02d}:{int((seconds%3600)//60):02d}:{int(seconds%60):02d}"
|
|
|
293 |
|
294 |
+
# We'll use PIL for text as it's easier to work with in this context
|
295 |
+
from PIL import Image, ImageDraw, ImageFont
|
296 |
+
pil_img = Image.fromarray(combined_frame)
|
297 |
+
draw = ImageDraw.Draw(pil_img)
|
298 |
+
font = ImageFont.load_default()
|
299 |
+
draw.text((10, 30), f"Time: {timecode}", font=font, fill=(255, 255, 255))
|
300 |
|
301 |
+
return np.array(pil_img)
|
302 |
+
|
303 |
+
# Create the final video
|
304 |
+
final_clip = VideoFileClip(video_path).fl(make_frame)
|
305 |
+
final_clip = final_clip.set_duration(video.duration)
|
306 |
+
|
307 |
+
# Write the final video
|
308 |
+
final_clip.write_videofile(heatmap_video_path, codec='libx264', audio_codec='aac')
|
309 |
+
|
310 |
+
# Close the video clips
|
311 |
+
video.close()
|
312 |
+
final_clip.close()
|
313 |
|
314 |
if os.path.exists(heatmap_video_path):
|
315 |
print(f"Heatmap video created at: {heatmap_video_path}")
|
|
|
317 |
return heatmap_video_path
|
318 |
else:
|
319 |
print(f"Failed to create heatmap video at: {heatmap_video_path}")
|
320 |
+
return None
|
321 |
+
|
322 |
+
This version of the function:
|
323 |
+
|
324 |
+
Uses MoviePy for all video operations, avoiding OpenCV codec issues.
|
325 |
+
Creates the heatmap for each frame on-the-fly.
|
326 |
+
Combines the original video frame with the heatmap.
|
327 |
+
Adds the timecode to each frame.
|
328 |
+
Preserves the original audio.
|
329 |
+
|
330 |
+
Make sure you have the necessary dependencies installed:
|
331 |
+
|
332 |
+
pip install moviepy Pillow matplotlib numpy
|
333 |
+
|
334 |
+
This approach should resolve the codec issues and create a video with the heatmap, original audio, and correct playback speed. If you still encounter any issues, please provide the new error message, and I'll be happy to help further.
|
335 |
+
Claude does not have the ability to run the code it generates yet.
|
336 |
+
Claude can make mistakes. Please double-check responses.
|
337 |
+
|
338 |
+
|