KillD00zer commited on
Commit
8e36162
·
verified ·
1 Parent(s): cf69cd2

Update frame_slicer.py

Browse files
Files changed (1) hide show
  1. frame_slicer.py +126 -58
frame_slicer.py CHANGED
@@ -1,58 +1,126 @@
1
- import cv2
2
- import numpy as np
3
- import random
4
-
5
- def extract_video_frames(video_path, n_frames=30, frame_size=(96, 96)):
6
- """
7
- Simplified robust frame extractor for short videos (2-10 sec)
8
- - Automatically handles varying video lengths
9
- - Ensures consistent output shape
10
- - Optimized for MP4/MPEG
11
- """
12
- # Open video
13
- cap = cv2.VideoCapture(video_path)
14
- if not cap.isOpened():
15
- print(f"Error: Could not open video {video_path}")
16
- return None
17
-
18
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
19
- fps = cap.get(cv2.CAP_PROP_FPS)
20
-
21
- # Basic video validation
22
- if total_frames < 1 or fps < 1:
23
- print(f"Error: Invalid video (frames:{total_frames}, fps:{fps})")
24
- cap.release()
25
- return None
26
-
27
- # Calculate how many frames to skip (adaptive based on video length)
28
- video_length = total_frames / fps
29
- frame_step = max(1, int(total_frames / n_frames))
30
-
31
- frames = []
32
- last_good_frame = None
33
-
34
- for i in range(n_frames):
35
- # Calculate position to read (spread evenly across video)
36
- pos = min(int(i * (total_frames / n_frames)), total_frames - 1)
37
- cap.set(cv2.CAP_PROP_POS_FRAMES, pos)
38
-
39
- ret, frame = cap.read()
40
-
41
- # Fallback strategies if read fails
42
- if not ret or frame is None:
43
- if last_good_frame is not None:
44
- frame = last_good_frame.copy()
45
- else:
46
- # Generate placeholder frame (light gray)
47
- frame = np.full((*frame_size[::-1], 3), 0.8, dtype=np.float32)
48
- else:
49
- # Process valid frame
50
- frame = cv2.resize(frame, frame_size)
51
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
52
- frame = frame.astype(np.float32) / 255.0
53
- last_good_frame = frame
54
-
55
- frames.append(frame)
56
-
57
- cap.release()
58
- return np.array(frames)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- START OF FILE frame_slicer.py ---
2
+
3
+ import cv2
4
+ import numpy as np
5
+ import random
6
+ import os
7
+
8
+ def extract_video_frames(video_path, n_frames=30, frame_size=(96, 96)):
9
+ """
10
+ Extracts frames from a video, handling various lengths and potential errors.
11
+
12
+ Args:
13
+ video_path (str): Path to the video file.
14
+ n_frames (int): The target number of frames to extract.
15
+ frame_size (tuple): The target (width, height) for each frame.
16
+
17
+ Returns:
18
+ np.ndarray: An array of shape (n_frames, height, width, 3) with normalized
19
+ pixel values (0-1), or None if extraction fails critically.
20
+ Frames will be padded if the video is too short or has read errors.
21
+ """
22
+ if not os.path.exists(video_path):
23
+ print(f"Error: Video file not found at {video_path}")
24
+ return None
25
+
26
+ cap = cv2.VideoCapture(video_path)
27
+ if not cap.isOpened():
28
+ print(f"Error: Could not open video file {video_path}")
29
+ return None
30
+
31
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
32
+ fps = cap.get(cv2.CAP_PROP_FPS)
33
+
34
+ # Basic validation
35
+ if total_frames < 1:
36
+ print(f"Warning: Video has {total_frames} frames. Cannot extract.")
37
+ cap.release()
38
+ # Return array of zeros matching the expected shape
39
+ return np.zeros((n_frames, *frame_size[::-1], 3), dtype=np.float32)
40
+ if fps < 1:
41
+ print(f"Warning: Video has invalid FPS ({fps}). Proceeding, but timing might be off.")
42
+ # Use a default assumption if FPS is invalid but frames exist
43
+ fps = 30.0 # Or another sensible default
44
+
45
+ frames = []
46
+ extracted_count = 0
47
+ last_good_frame_processed = None # Store the last successfully processed frame
48
+
49
+ # Calculate indices of frames to attempt extraction (evenly spaced)
50
+ # Ensure indices are within the valid range [0, total_frames - 1]
51
+ indices = np.linspace(0, total_frames - 1, n_frames, dtype=int)
52
+
53
+ for i, frame_index in enumerate(indices):
54
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
55
+ ret, frame = cap.read()
56
+
57
+ processed_frame = None
58
+ if ret and frame is not None:
59
+ try:
60
+ # Process valid frame
61
+ frame_resized = cv2.resize(frame, frame_size)
62
+ frame_rgb = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)
63
+ processed_frame = frame_rgb.astype(np.float32) / 255.0
64
+ last_good_frame_processed = processed_frame # Update last good frame
65
+ extracted_count += 1
66
+ except cv2.error as e:
67
+ print(f"Warning: OpenCV error processing frame {frame_index}: {e}")
68
+ # Fallback to last good frame if available
69
+ if last_good_frame_processed is not None:
70
+ processed_frame = last_good_frame_processed.copy()
71
+ else: # If no good frame seen yet, create a placeholder
72
+ processed_frame = np.zeros((*frame_size[::-1], 3), dtype=np.float32)
73
+ except Exception as e:
74
+ print(f"Warning: Unexpected error processing frame {frame_index}: {e}")
75
+ if last_good_frame_processed is not None:
76
+ processed_frame = last_good_frame_processed.copy()
77
+ else:
78
+ processed_frame = np.zeros((*frame_size[::-1], 3), dtype=np.float32)
79
+
80
+ else:
81
+ # Handle read failure (e.g., end of video reached early, corrupted frame)
82
+ print(f"Warning: Failed to read frame at index {frame_index}. Using fallback.")
83
+ if last_good_frame_processed is not None:
84
+ processed_frame = last_good_frame_processed.copy()
85
+ else:
86
+ # If read fails and no previous frame exists, use a zero frame
87
+ processed_frame = np.zeros((*frame_size[::-1], 3), dtype=np.float32)
88
+
89
+ frames.append(processed_frame)
90
+
91
+ cap.release()
92
+
93
+ if extracted_count == 0 and total_frames > 0:
94
+ print("Warning: Failed to extract or process any valid frames, returning array of zeros.")
95
+ # This case should ideally be covered by fallbacks, but as a safeguard:
96
+ return np.zeros((n_frames, *frame_size[::-1], 3), dtype=np.float32)
97
+
98
+ # Ensure the final output always has n_frames by padding if necessary
99
+ # (This should technically be handled by the loop logic now, but double-check)
100
+ final_frames = np.array(frames)
101
+ if final_frames.shape[0] < n_frames:
102
+ print(f"Warning: Padding needed, final array shape {final_frames.shape} vs target {n_frames}")
103
+ if final_frames.shape[0] == 0: # If somehow array is empty
104
+ padding = np.zeros((n_frames, *frame_size[::-1], 3), dtype=np.float32)
105
+ else:
106
+ padding_needed = n_frames - final_frames.shape[0]
107
+ # Use the very last frame in the list (could be a fallback frame) for padding
108
+ last_frame_for_padding = final_frames[-1][np.newaxis, ...]
109
+ padding = np.repeat(last_frame_for_padding, padding_needed, axis=0)
110
+ final_frames = np.concatenate((final_frames, padding), axis=0)
111
+ elif final_frames.shape[0] > n_frames:
112
+ # Should not happen with linspace logic, but truncate if it does
113
+ print(f"Warning: More frames than expected ({final_frames.shape[0]}), truncating to {n_frames}")
114
+ final_frames = final_frames[:n_frames]
115
+
116
+
117
+ # Final check of output shape
118
+ if final_frames.shape != (n_frames, frame_size[1], frame_size[0], 3):
119
+ print(f"Error: Final frame array shape mismatch! Expected {(n_frames, frame_size[1], frame_size[0], 3)}, Got {final_frames.shape}")
120
+ # Attempt to reshape or return None/zeros? Returning zeros is safer.
121
+ return np.zeros((n_frames, *frame_size[::-1], 3), dtype=np.float32)
122
+
123
+
124
+ return final_frames
125
+
126
+ --- END OF FILE frame_slicer.py ---