chenemii commited on
Commit
c3f5155
·
1 Parent(s): 4a0e3a8

comparison photo

Browse files
README.md CHANGED
@@ -11,8 +11,8 @@ A tool for analyzing golf swings using computer vision and AI.
11
  - Club and ball trajectory analysis
12
  - LLM-powered swing analysis and coaching tips (OpenAI GPT-4/3.5 or local Ollama models)
13
  - Annotated video generation
14
- - Side-by-side comparison with professional golfer
15
- - Improvement recommendations from AI analysis
16
 
17
  ## Setup
18
 
@@ -74,8 +74,12 @@ streamlit run app/streamlit_app.py
74
  2. Click "Analyze Swing" to process the video
75
  3. View the swing phase breakdown and metrics
76
  4. Generate an annotated video showing the analysis
77
- 5. Compare your swing side-by-side with a professional golfer
78
- 6. Get AI-powered improvement recommendations
 
 
 
 
79
 
80
  ## Technical Details
81
 
 
11
  - Club and ball trajectory analysis
12
  - LLM-powered swing analysis and coaching tips (OpenAI GPT-4/3.5 or local Ollama models)
13
  - Annotated video generation
14
+ - Key position comparison with professional golfer (3 critical swing positions)
15
+ - Detailed improvement recommendations with visual analysis
16
 
17
  ## Setup
18
 
 
74
  2. Click "Analyze Swing" to process the video
75
  3. View the swing phase breakdown and metrics
76
  4. Generate an annotated video showing the analysis
77
+ 5. Compare your swing at 3 key positions with a professional golfer:
78
+ - Starting position (setup)
79
+ - Top of backswing
80
+ - Impact with ball
81
+ 6. Get detailed improvement recommendations for each swing phase
82
+ 7. Download comparison images and analysis results
83
 
84
  ## Technical Details
85
 
app/streamlit_app.py CHANGED
@@ -23,7 +23,7 @@ from app.models.pose_estimator import analyze_pose
23
  from app.models.swing_analyzer import segment_swing, analyze_trajectory
24
  from app.models.llm_analyzer import generate_swing_analysis, create_llm_prompt, prepare_data_for_llm, check_llm_services
25
  from app.utils.visualizer import create_annotated_video
26
- from app.utils.comparison import create_frame_by_frame_comparison
27
 
28
  # Set page config
29
  st.set_page_config(page_title="Par-ity Project: Golf Swing Analysis 🏌️‍♀️",
@@ -304,7 +304,7 @@ def main():
304
  with options_col3:
305
  if enable_pro_comparison and st.session_state.pro_reference_path:
306
  st.info(
307
- "**Option 3: Compare With Pro**\n\nSee a side-by-side frame-by-frame comparison with a professional golfer's swing."
308
  )
309
 
310
  except Exception as e:
@@ -365,7 +365,7 @@ def main():
365
  # Add pro comparison button if enabled
366
  if enable_pro_comparison and st.session_state.pro_reference_path and button_col3:
367
  with button_col3:
368
- comparison_clicked = st.button("Compare With Pro",
369
  key="pro_comparison",
370
  use_container_width=True)
371
  else:
@@ -480,41 +480,91 @@ def main():
480
  # Handle pro comparison video creation
481
  if comparison_clicked and st.session_state.pro_reference_path:
482
  try:
483
- with st.spinner("Creating frame-by-frame comparison..."):
484
  # Get data from session state
485
  user_video_path = st.session_state.analysis_data['video_path']
486
- pro_video_path = st.session_state.pro_reference_path
487
 
488
- # Create the comparison video
489
- comparison_path = create_frame_by_frame_comparison(
 
490
  user_video_path,
491
- pro_video_path
 
492
  )
493
 
494
- # Verify the file exists
495
- if not os.path.exists(comparison_path):
496
- raise FileNotFoundError(
497
- f"Comparison video file not found at {comparison_path}")
498
-
499
- # Store the comparison video path in session state
500
- st.session_state.comparison_video_path = comparison_path
501
 
502
- # Display success message and video
503
- st.success("Frame-by-frame comparison created successfully!")
504
- st.subheader("Side-by-Side Comparison with Pro Golfer")
505
 
506
- # Display video with larger width
507
- display_video(comparison_path, width=800)
508
 
509
- # Show download button
510
- with open(comparison_path, "rb") as file:
511
- video_bytes = file.read()
512
- st.download_button(
513
- label="Download Comparison Video",
514
- data=video_bytes,
515
- file_name=os.path.basename(comparison_path),
516
- mime="video/mp4"
517
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
 
519
  # Add some guidance for interpreting the comparison
520
  with st.expander("How to use this comparison", expanded=True):
@@ -532,9 +582,6 @@ def main():
532
 
533
  Try pausing the video at key positions to analyze differences in detail.
534
  """)
535
-
536
- except Exception as e:
537
- st.error(f"Error creating comparison video: {str(e)}")
538
 
539
 
540
  if __name__ == "__main__":
 
23
  from app.models.swing_analyzer import segment_swing, analyze_trajectory
24
  from app.models.llm_analyzer import generate_swing_analysis, create_llm_prompt, prepare_data_for_llm, check_llm_services
25
  from app.utils.visualizer import create_annotated_video
26
+ from app.utils.comparison import create_key_frame_comparison
27
 
28
  # Set page config
29
  st.set_page_config(page_title="Par-ity Project: Golf Swing Analysis 🏌️‍♀️",
 
304
  with options_col3:
305
  if enable_pro_comparison and st.session_state.pro_reference_path:
306
  st.info(
307
+ "**Option 3: Compare With Pro**\n\nSee side-by-side comparisons of 3 key swing positions with a professional golfer, including improvement tips for each phase."
308
  )
309
 
310
  except Exception as e:
 
365
  # Add pro comparison button if enabled
366
  if enable_pro_comparison and st.session_state.pro_reference_path and button_col3:
367
  with button_col3:
368
+ comparison_clicked = st.button("Compare Key Positions",
369
  key="pro_comparison",
370
  use_container_width=True)
371
  else:
 
480
  # Handle pro comparison video creation
481
  if comparison_clicked and st.session_state.pro_reference_path:
482
  try:
483
+ with st.spinner("Creating key frame comparison..."):
484
  # Get data from session state
485
  user_video_path = st.session_state.analysis_data['video_path']
486
+ user_swing_phases = st.session_state.analysis_data['swing_phases']
487
 
488
+ # Create the key frame comparison using static pro reference images
489
+ # Don't pass pro_video_path to ensure it uses the static images
490
+ comparison_data = create_key_frame_comparison(
491
  user_video_path,
492
+ user_swing_phases=user_swing_phases,
493
+ use_pro_images=True
494
  )
495
 
496
+ # Store the comparison data in session state
497
+ st.session_state.comparison_data = comparison_data
 
 
 
 
 
498
 
499
+ # Display success message
500
+ st.success("Key frame comparison created successfully!")
501
+ st.subheader("Swing Analysis: Key Position Comparison")
502
 
503
+ # Display each comparison with comments
504
+ phases = ['setup', 'backswing', 'impact']
505
 
506
+ for phase in phases:
507
+ if phase in comparison_data:
508
+ data = comparison_data[phase]
509
+
510
+ # Display the comparison image
511
+ st.subheader(f"{data['title']}")
512
+
513
+ # Display the image
514
+ if os.path.exists(data['image_path']):
515
+ st.image(data['image_path'], use_column_width=True)
516
+
517
+ # Create download button for the image
518
+ with open(data['image_path'], "rb") as file:
519
+ image_bytes = file.read()
520
+ st.download_button(
521
+ label=f"Download {data['title']} Comparison",
522
+ data=image_bytes,
523
+ file_name=os.path.basename(data['image_path']),
524
+ mime="image/jpeg",
525
+ key=f"download_{phase}"
526
+ )
527
+
528
+ # Display improvement comments
529
+ comments = data['comments']
530
+
531
+ col1, col2 = st.columns(2)
532
+
533
+ with col1:
534
+ st.markdown("**🏆 Professional Analysis:**")
535
+ for analysis in comments['pro_analysis']:
536
+ st.markdown(f"• {analysis}")
537
+
538
+ with col2:
539
+ st.markdown("**🔄 User vs Professional Comparison:**")
540
+ for comparison in comments['comparison']:
541
+ st.markdown(f"• {comparison}")
542
+
543
+ st.markdown("---") # Add separator between phases
544
+
545
+ # Add general guidance
546
+ with st.expander("How to Use This Analysis", expanded=False):
547
+ st.markdown("""
548
+ ### How to Interpret These Comparisons
549
+
550
+ Each comparison shows your swing position (left) next to a professional golfer's position (right) at three critical moments:
551
+
552
+ 1. **Starting Position**: Your setup and address position
553
+ 2. **Top of Backswing**: The highest point of your backswing
554
+ 3. **Impact with Ball**: The moment of contact with the ball
555
+
556
+ **Tips for Improvement:**
557
+ - Compare your body positioning, posture, and club position to the pro
558
+ - Focus on one aspect at a time (e.g., posture, then weight distribution)
559
+ - Practice the positions slowly without a ball first
560
+ - Use a mirror or video recording to check your positions
561
+ - Work with a golf instructor for personalized feedback
562
+
563
+ **Remember:** Every golfer is different, so focus on the fundamental principles rather than trying to copy every detail exactly.
564
+ """)
565
+
566
+ except Exception as e:
567
+ st.error(f"Error creating key frame comparison: {str(e)}")
568
 
569
  # Add some guidance for interpreting the comparison
570
  with st.expander("How to use this comparison", expanded=True):
 
582
 
583
  Try pausing the video at key positions to analyze differences in detail.
584
  """)
 
 
 
585
 
586
 
587
  if __name__ == "__main__":
app/utils/comparison.py CHANGED
@@ -52,6 +52,302 @@ def extract_frames(video_path, max_frames=100):
52
  return frames
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def normalize_frames(frames, target_height=480):
56
  """
57
  Normalize frames to a consistent size while maintaining aspect ratio
 
52
  return frames
53
 
54
 
55
+ def extract_key_swing_frames(video_path, swing_phases=None):
56
+ """
57
+ Extract 3 key frames from a golf swing video:
58
+ 1. Starting position (setup)
59
+ 2. Top of backswing
60
+ 3. Impact with ball
61
+
62
+ Args:
63
+ video_path (str): Path to the video file
64
+ swing_phases (dict): Optional swing phase data for precise frame selection
65
+
66
+ Returns:
67
+ dict: Dictionary with keys 'setup', 'backswing', 'impact'
68
+ and frame images as values
69
+ """
70
+ if not os.path.exists(video_path):
71
+ raise ValueError(f"Video file not found: {video_path}")
72
+
73
+ cap = cv2.VideoCapture(video_path)
74
+
75
+ if not cap.isOpened():
76
+ raise ValueError(f"Could not open video: {video_path}")
77
+
78
+ # Get total frame count
79
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
80
+
81
+ key_frames = {}
82
+
83
+ if swing_phases:
84
+ # Use provided swing phase data for precise frame selection
85
+ frame_indices = {
86
+ 'setup': swing_phases.get('setup', [0])[0] if swing_phases.get('setup') else 0,
87
+ 'backswing': swing_phases.get('backswing', [total_frames//3])[-1] if swing_phases.get('backswing') else total_frames//3,
88
+ 'impact': swing_phases.get('impact', [total_frames//2])[len(swing_phases.get('impact', [total_frames//2]))//2] if swing_phases.get('impact') else total_frames//2
89
+ }
90
+ else:
91
+ # Use estimated frame positions for 3 frames
92
+ frame_indices = {
93
+ 'setup': 0, # First frame
94
+ 'backswing': total_frames // 3, # 33% through
95
+ 'impact': int(total_frames * 0.6) # 60% through
96
+ }
97
+
98
+ # Extract the specific frames
99
+ for phase, frame_idx in frame_indices.items():
100
+ cap.set(cv2.CAP_PROP_POS_FRAMES, min(frame_idx, total_frames - 1))
101
+ ret, frame = cap.read()
102
+ if ret:
103
+ # Keep original orientation - no rotation
104
+ key_frames[phase] = frame
105
+ else:
106
+ # If frame extraction fails, use a black frame
107
+ key_frames[phase] = np.zeros((480, 640, 3), dtype=np.uint8)
108
+
109
+ cap.release()
110
+
111
+ return key_frames
112
+
113
+
114
+ def generate_improvement_comments(phase):
115
+ """
116
+ Generate improvement comments for each swing phase in Professional/Comparison format
117
+
118
+ Args:
119
+ phase (str): The swing phase ('setup', 'backswing', 'impact')
120
+
121
+ Returns:
122
+ dict: Dictionary with 'pro_analysis' and 'comparison' keys
123
+ """
124
+ comments = {
125
+ 'setup': {
126
+ 'pro_analysis': [
127
+ "Balanced stance with feet shoulder-width apart",
128
+ "Even weight distribution on both feet",
129
+ "Neutral grip with hands in proper position",
130
+ "Athletic posture with slight forward bend",
131
+ "Ball positioned correctly for club selection"
132
+ ],
133
+ 'comparison': [
134
+ "Compare your stance width to the pro's balanced setup",
135
+ "Check if your weight is evenly distributed like the pro",
136
+ "Ensure your grip matches the pro's neutral hand position",
137
+ "Adjust your posture to match the pro's athletic stance",
138
+ "Position the ball in your stance similar to the pro"
139
+ ]
140
+ },
141
+ 'backswing': {
142
+ 'pro_analysis': [
143
+ "Full 90+ degree shoulder rotation",
144
+ "Controlled hip turn with stable lower body",
145
+ "Club on proper swing plane at top",
146
+ "Consistent spine angle throughout",
147
+ "Minimal weight shift to right side"
148
+ ],
149
+ 'comparison': [
150
+ "Increase your shoulder turn to match the pro's full rotation",
151
+ "Control your hip movement like the pro's stable base",
152
+ "Adjust your club position to match the pro's swing plane",
153
+ "Maintain spine angle consistency like the professional",
154
+ "Minimize weight shift compared to the pro's centered position"
155
+ ]
156
+ },
157
+ 'impact': {
158
+ 'pro_analysis': [
159
+ "Weight shifted to front foot (70-80%)",
160
+ "Hands ahead of ball at impact",
161
+ "Square club face to target line",
162
+ "Head behind ball with steady position",
163
+ "Hips and shoulders aligned to target"
164
+ ],
165
+ 'comparison': [
166
+ "Shift more weight to your front foot like the pro",
167
+ "Get your hands ahead of the ball like the professional",
168
+ "Square your club face to match the pro's alignment",
169
+ "Keep your head steady and behind the ball like the pro",
170
+ "Align your body to the target like the professional"
171
+ ]
172
+ }
173
+ }
174
+
175
+ return comments.get(phase, {'pro_analysis': [], 'comparison': []})
176
+
177
+
178
+ def load_pro_reference_images(pro_images_dir="pro_reference"):
179
+ """
180
+ Load professional golfer reference images from directory
181
+
182
+ Args:
183
+ pro_images_dir (str): Directory containing professional reference images
184
+
185
+ Returns:
186
+ dict: Dictionary with phase names as keys and image arrays as values
187
+ """
188
+ # Get the absolute path to the pro_reference directory
189
+ # This ensures it works regardless of the current working directory
190
+ if not os.path.isabs(pro_images_dir):
191
+ # Get the directory where this script is located
192
+ script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
193
+ pro_images_dir = os.path.join(script_dir, pro_images_dir)
194
+
195
+ pro_frames = {}
196
+
197
+ # Expected filenames for the 3 phases
198
+ phase_files = {
199
+ 'setup': 'setup.jpg',
200
+ 'backswing': 'backswing.jpg',
201
+ 'impact': 'impact.jpg'
202
+ }
203
+
204
+ for phase, filename in phase_files.items():
205
+ image_path = os.path.join(pro_images_dir, filename)
206
+ if os.path.exists(image_path):
207
+ image = cv2.imread(image_path)
208
+ if image is not None:
209
+ pro_frames[phase] = image
210
+ else:
211
+ # Create a placeholder if image can't be loaded
212
+ pro_frames[phase] = np.zeros((480, 640, 3), dtype=np.uint8)
213
+ else:
214
+ # Create a placeholder if file doesn't exist
215
+ pro_frames[phase] = np.zeros((480, 640, 3), dtype=np.uint8)
216
+
217
+ return pro_frames
218
+
219
+
220
+ def create_key_frame_comparison(user_video_path, pro_video_path=None, user_swing_phases=None, pro_swing_phases=None, output_dir="downloads", use_pro_images=True):
221
+ """
222
+ Create a comparison of 3 key frames between user and pro golfer swings
223
+
224
+ Args:
225
+ user_video_path (str): Path to the user's golf swing video
226
+ pro_video_path (str): Path to the professional golfer's swing video (optional if use_pro_images=True)
227
+ user_swing_phases (dict): Optional swing phase data for user video
228
+ pro_swing_phases (dict): Optional swing phase data for pro video
229
+ output_dir (str): Directory to save the comparison images
230
+ use_pro_images (bool): Whether to use provided pro reference images instead of video
231
+
232
+ Returns:
233
+ dict: Dictionary with phase names as keys and image paths as values
234
+ """
235
+ # Extract key frames from user video
236
+ user_frames = extract_key_swing_frames(user_video_path, user_swing_phases)
237
+
238
+ # Get pro frames either from provided images or video
239
+ if use_pro_images:
240
+ pro_frames = load_pro_reference_images()
241
+ else:
242
+ pro_frames = extract_key_swing_frames(pro_video_path, pro_swing_phases)
243
+
244
+ # Create output directory with absolute path
245
+ output_dir = os.path.abspath(output_dir)
246
+ os.makedirs(output_dir, exist_ok=True)
247
+
248
+ comparison_data = {}
249
+ phases = ['setup', 'backswing', 'impact']
250
+ phase_titles = ['Starting Position', 'Top of Backswing', 'Impact with Ball']
251
+
252
+ for i, phase in enumerate(phases):
253
+ # Get frames for this phase
254
+ user_frame = user_frames.get(phase, np.zeros((480, 640, 3), dtype=np.uint8))
255
+ pro_frame = pro_frames.get(phase, np.zeros((480, 640, 3), dtype=np.uint8))
256
+
257
+ # Resize frames to consistent size while maintaining portrait orientation
258
+ target_height = 400
259
+ user_frame = resize_frame_maintain_aspect(user_frame, target_height)
260
+ pro_frame = resize_frame_maintain_aspect(pro_frame, target_height)
261
+
262
+ # Create side-by-side comparison
263
+ comparison_image = create_side_by_side_image(user_frame, pro_frame, phase_titles[i])
264
+
265
+ # Save the comparison image with absolute path
266
+ video_name = os.path.splitext(os.path.basename(user_video_path))[0]
267
+ output_path = os.path.join(output_dir, f"{video_name}_{phase}_comparison.jpg")
268
+
269
+ # Ensure the image is saved successfully
270
+ success = cv2.imwrite(output_path, comparison_image)
271
+ if not success:
272
+ print(f"Warning: Failed to save image to {output_path}")
273
+ else:
274
+ print(f"Successfully saved comparison image: {output_path}")
275
+
276
+ # Get improvement comments
277
+ comments = generate_improvement_comments(phase)
278
+
279
+ comparison_data[phase] = {
280
+ 'image_path': output_path,
281
+ 'title': phase_titles[i],
282
+ 'comments': comments
283
+ }
284
+
285
+ return comparison_data
286
+
287
+
288
+ def resize_frame_maintain_aspect(frame, target_height):
289
+ """
290
+ Resize frame to target height while maintaining aspect ratio
291
+
292
+ Args:
293
+ frame (numpy.ndarray): Input frame
294
+ target_height (int): Target height
295
+
296
+ Returns:
297
+ numpy.ndarray: Resized frame
298
+ """
299
+ h, w = frame.shape[:2]
300
+ target_width = int(w * (target_height / h))
301
+ return cv2.resize(frame, (target_width, target_height))
302
+
303
+
304
+ def create_side_by_side_image(user_frame, pro_frame, title):
305
+ """
306
+ Create a side-by-side comparison image
307
+
308
+ Args:
309
+ user_frame (numpy.ndarray): User's swing frame
310
+ pro_frame (numpy.ndarray): Pro's swing frame
311
+ title (str): Title for the comparison
312
+
313
+ Returns:
314
+ numpy.ndarray: Combined comparison image
315
+ """
316
+ # Get dimensions
317
+ user_h, user_w = user_frame.shape[:2]
318
+ pro_h, pro_w = pro_frame.shape[:2]
319
+
320
+ # Create padding and title space
321
+ padding = 20
322
+ title_height = 60
323
+ max_height = max(user_h, pro_h)
324
+ total_width = user_w + pro_w + padding
325
+ total_height = max_height + title_height
326
+
327
+ # Create blank canvas
328
+ canvas = np.ones((total_height, total_width, 3), dtype=np.uint8) * 255
329
+
330
+ # Add title
331
+ font = cv2.FONT_HERSHEY_SIMPLEX
332
+ title_size = cv2.getTextSize(title, font, 1.2, 2)[0]
333
+ title_x = (total_width - title_size[0]) // 2
334
+ cv2.putText(canvas, title, (title_x, 40), font, 1.2, (0, 0, 0), 2)
335
+
336
+ # Add user frame
337
+ y_offset = title_height + (max_height - user_h) // 2
338
+ canvas[y_offset:y_offset + user_h, 0:user_w] = user_frame
339
+
340
+ # Add pro frame
341
+ y_offset = title_height + (max_height - pro_h) // 2
342
+ canvas[y_offset:y_offset + pro_h, user_w + padding:user_w + padding + pro_w] = pro_frame
343
+
344
+ # Draw vertical separator line
345
+ line_x = user_w + padding // 2
346
+ cv2.line(canvas, (line_x, title_height), (line_x, total_height), (200, 200, 200), 2)
347
+
348
+ return canvas
349
+
350
+
351
  def normalize_frames(frames, target_height=480):
352
  """
353
  Normalize frames to a consistent size while maintaining aspect ratio
app/utils/visualizer.py CHANGED
@@ -94,14 +94,11 @@ def create_annotated_video(video_path,
94
  elif rotation_value == 270: # 270 degrees clockwise
95
  rotation = 90 # We'll rotate counterclockwise, so 90
96
  except:
97
- # If metadata reading fails, use the dimensions-based detection
98
  rotation = 0
99
 
100
- # If no rotation metadata or reading failed, use dimensions-based detection
101
- if rotation == 0:
102
- # Check if video is in portrait mode (height > width)
103
- if height > width * 1.2: # If height is significantly greater than width
104
- rotation = 90 # Rotate 90 degrees counterclockwise
105
 
106
  # Close the video capture
107
  cap.release()
 
94
  elif rotation_value == 270: # 270 degrees clockwise
95
  rotation = 90 # We'll rotate counterclockwise, so 90
96
  except:
97
+ # If metadata reading fails, don't apply any rotation
98
  rotation = 0
99
 
100
+ # Don't apply automatic rotation based on dimensions
101
+ # Keep the video in its original orientation
 
 
 
102
 
103
  # Close the video capture
104
  cap.release()