Initial commit: ASL handshape recognition project
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
145
webcam_capture.py
Executable file
145
webcam_capture.py
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
capture_webcam.py
|
||||
Show webcam preview and, given --letter L, count down 5s, then capture frames
|
||||
every --interval seconds until --count images are saved.
|
||||
Saves PNGs to ./captures as L001.PNG, L002.PNG, ...
|
||||
|
||||
Usage:
|
||||
python capture_webcam.py --letter A
|
||||
python capture_webcam.py --letter B --camera 1
|
||||
python capture_webcam.py --letter C --count 10 --interval 1
|
||||
|
||||
# Default: 5 captures at 2s spacing, 640x480
|
||||
python capture_webcam.py --letter A
|
||||
|
||||
# Ten captures, 1s apart
|
||||
python capture_webcam.py --letter B --count 10 --interval 1
|
||||
|
||||
# USB camera index 1, HD override
|
||||
python capture_webcam.py --letter C --camera 1 --width 1280 --height 720 --count 8 --interval 1.5
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
COUNTDOWN_SECONDS = 5
|
||||
|
||||
def next_sequence_number(captures_dir: Path, letter: str) -> int:
|
||||
"""Return next available sequence number for files like 'A001.PNG'."""
|
||||
pattern = re.compile(rf"^{re.escape(letter)}(\d{{3}})\.PNG$", re.IGNORECASE)
|
||||
max_idx = 0
|
||||
if captures_dir.exists():
|
||||
for name in os.listdir(captures_dir):
|
||||
m = pattern.match(name)
|
||||
if m:
|
||||
try:
|
||||
idx = int(m.group(1))
|
||||
if idx > max_idx:
|
||||
max_idx = idx
|
||||
except ValueError:
|
||||
pass
|
||||
return max_idx + 1
|
||||
|
||||
def draw_text(img, text, org, scale=1.4, color=(0, 255, 0), thickness=2):
|
||||
cv2.putText(img, text, org, cv2.FONT_HERSHEY_SIMPLEX, scale, color, thickness, cv2.LINE_AA)
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--letter", required=True, help="Target letter A–Z. Output files like A001.PNG")
|
||||
ap.add_argument("--camera", type=int, default=0, help="OpenCV camera index (default: 0)")
|
||||
ap.add_argument("--width", type=int, default=640, help="Requested capture width (default: 640)")
|
||||
ap.add_argument("--height", type=int, default=480, help="Requested capture height (default: 480)")
|
||||
ap.add_argument("--count", type=int, default=5, help="Number of captures to take (default: 5)")
|
||||
ap.add_argument("--interval", type=float, default=2.0, help="Seconds between captures (default: 2.0)")
|
||||
args = ap.parse_args()
|
||||
|
||||
letter = args.letter.upper().strip()
|
||||
if not (len(letter) == 1 and "A" <= letter <= "Z"):
|
||||
raise SystemExit("Please pass a single letter A–Z to --letter (e.g., --letter A)")
|
||||
if args.count <= 0:
|
||||
raise SystemExit("--count must be >= 1")
|
||||
if args.interval <= 0:
|
||||
raise SystemExit("--interval must be > 0")
|
||||
|
||||
captures_dir = Path("./captures")
|
||||
captures_dir.mkdir(parents=True, exist_ok=True)
|
||||
start_idx = next_sequence_number(captures_dir, letter)
|
||||
|
||||
cap = cv2.VideoCapture(args.camera)
|
||||
if not cap.isOpened():
|
||||
raise SystemExit(f"❌ Could not open camera index {args.camera}")
|
||||
|
||||
# Try to set resolution (best-effort)
|
||||
cap.set(cv2.CAP_PROP_FRAME_WIDTH, args.width)
|
||||
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, args.height)
|
||||
|
||||
window_title = f"Capture {letter} (press 'q' to quit)"
|
||||
print(f"Showing webcam. Countdown {COUNTDOWN_SECONDS}s, then capturing {args.count} frame(s) every {args.interval}s...")
|
||||
print(f"Saving to: {captures_dir.resolve()} as {letter}NNN.PNG starting at index {start_idx:03d}")
|
||||
|
||||
countdown_done_at = time.time() + COUNTDOWN_SECONDS
|
||||
# Absolute times when we want to capture (after countdown)
|
||||
capture_times = [countdown_done_at + i * args.interval for i in range(args.count)]
|
||||
capture_taken = [False] * args.count
|
||||
captures_made = 0
|
||||
idx = start_idx
|
||||
|
||||
while True:
|
||||
ok, frame = cap.read()
|
||||
if not ok:
|
||||
print("⚠️ Frame grab failed; ending.")
|
||||
break
|
||||
|
||||
now = time.time()
|
||||
|
||||
# Countdown overlay
|
||||
if now < countdown_done_at:
|
||||
remaining = int(round(countdown_done_at - now))
|
||||
overlay = frame.copy()
|
||||
draw_text(overlay, f"Starting in: {remaining}s", (30, 60), scale=2.0, color=(0, 255, 255), thickness=3)
|
||||
draw_text(overlay, f"Letter: {letter}", (30, 120), scale=1.2, color=(0, 255, 0), thickness=2)
|
||||
cv2.imshow(window_title, overlay)
|
||||
else:
|
||||
# Check if it's time for any pending captures
|
||||
for i, tcap in enumerate(capture_times):
|
||||
if (not capture_taken[i]) and now >= tcap:
|
||||
filename = f"{letter}{idx:03d}.PNG"
|
||||
out_path = captures_dir / filename
|
||||
cv2.imwrite(str(out_path), frame)
|
||||
capture_taken[i] = True
|
||||
captures_made += 1
|
||||
idx += 1
|
||||
print(f"📸 Saved {out_path.name}")
|
||||
|
||||
# Overlay progress
|
||||
elapsed_after = now - countdown_done_at
|
||||
total_duration = args.interval * (args.count - 1) if args.count > 1 else 0
|
||||
remaining_after = max(0.0, total_duration - elapsed_after)
|
||||
overlay = frame.copy()
|
||||
draw_text(overlay, f"Capturing {letter}… {captures_made}/{args.count}", (30, 60),
|
||||
scale=1.5, color=(0, 255, 0), thickness=3)
|
||||
draw_text(overlay, f"Time left: {int(round(remaining_after))}s", (30, 110),
|
||||
scale=1.2, color=(0, 255, 255), thickness=2)
|
||||
cv2.imshow(window_title, overlay)
|
||||
|
||||
# If finished all captures, keep preview up until user quits
|
||||
if captures_made >= args.count:
|
||||
draw_text(overlay, "Done! Press 'q' to close.", (30, 160),
|
||||
scale=1.2, color=(0, 200, 255), thickness=2)
|
||||
cv2.imshow(window_title, overlay)
|
||||
|
||||
# Quit on 'q'
|
||||
if cv2.waitKey(1) & 0xFF == ord('q'):
|
||||
break
|
||||
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user