#!/usr/bin/env python3 """摄像头拍照工具 - 获取相机设备列表、选择指定相机拍照、保存照片。""" import os import sys import argparse from pathlib import Path import time # 定义默认的截图保存目录 CAMERA_DIR = os.path.expanduser("~/.aibox/camera") def ensure_camera_dir(): """确保摄像头照片保存目录存在""" os.makedirs(CAMERA_DIR, exist_ok=True) return CAMERA_DIR def list_cameras(max_test=10): """列出可用的摄像头设备""" available_cameras = [] for i in range(max_test): cap = cv2.VideoCapture(i) if cap.isOpened(): ret, frame = cap.read() if ret: height, width = frame.shape[:2] backend = cap.getBackendName() camera_info = { "id": i, "resolution": f"{width}x{height}", "backend": backend, "status": "可用" } # 在Linux系统上尝试获取设备路径 if sys.platform.startswith('linux'): dev_path = f"/dev/video{i}" if os.path.exists(dev_path): camera_info["device_path"] = dev_path try: import subprocess result = subprocess.run( ['udevadm', 'info', '--query=property', '--name', dev_path], capture_output=True, text=True, timeout=2 ) if result.returncode == 0: for line in result.stdout.split('\n'): if 'ID_MODEL=' in line: camera_info["model"] = line.split('=')[1].strip() elif 'ID_VENDOR=' in line: camera_info["vendor"] = line.split('=')[1].strip() except: pass available_cameras.append(camera_info) cap.release() else: cap.release() return available_cameras def get_camera_info(camera_id): """获取指定摄像头的详细信息""" cap = cv2.VideoCapture(camera_id) if not cap.isOpened(): return None info = { "id": camera_id, "properties": {} } # 获取各种摄像头属性 properties = [ (cv2.CAP_PROP_FRAME_WIDTH, "width"), (cv2.CAP_PROP_FRAME_HEIGHT, "height"), (cv2.CAP_PROP_FPS, "fps"), (cv2.CAP_PROP_BRIGHTNESS, "brightness"), (cv2.CAP_PROP_CONTRAST, "contrast"), (cv2.CAP_PROP_SATURATION, "saturation"), (cv2.CAP_PROP_HUE, "hue"), (cv2.CAP_PROP_GAIN, "gain"), (cv2.CAP_PROP_EXPOSURE, "exposure"), (cv2.CAP_PROP_FOCUS, "focus"), (cv2.CAP_PROP_AUTO_EXPOSURE, "auto_exposure"), (cv2.CAP_PROP_AUTO_WB, "auto_white_balance"), (cv2.CAP_PROP_WB_TEMPERATURE, "white_balance_temperature"), ] for prop_id, prop_name in properties: value = cap.get(prop_id) if value >= 0: info["properties"][prop_name] = value ret, frame = cap.read() if ret: height, width = frame.shape[:2] info["current_resolution"] = f"{width}x{height}" info["channels"] = frame.shape[2] if len(frame.shape) > 2 else 1 cap.release() if sys.platform.startswith('linux'): dev_path = f"/dev/video{camera_id}" if os.path.exists(dev_path): info["device_path"] = dev_path return info def capture_photo(camera_id=0, save_path=None, resolution=None, wait_time=1, exposure=None): """调用指定摄像头拍照""" cap = cv2.VideoCapture(camera_id) if not cap.isOpened(): return None, f"无法打开摄像头 #{camera_id},请检查摄像头连接", None, None if resolution: try: width, height = map(int, resolution.lower().replace('x', ' ').split()) cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) except: pass # 如果指定了曝光值,关闭自动曝光并设置手动曝光 if exposure is not None: # 0.25 = 手动模式(部分后端用 1),3 = 自动模式 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) cap.set(cv2.CAP_PROP_EXPOSURE, exposure) time.sleep(wait_time) for _ in range(5): cap.read() ret, frame = cap.read() cap.release() if not ret: return None, "拍照失败,无法获取图像帧", None, None actual_height, actual_width = frame.shape[:2] if save_path is None: ensure_camera_dir() timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"camera_{camera_id}_{timestamp}.jpg" save_path = os.path.join(CAMERA_DIR, filename) else: save_dir = os.path.dirname(save_path) if save_dir: os.makedirs(save_dir, exist_ok=True) if not os.path.splitext(save_path)[1]: save_path += '.jpg' cv2.imwrite(save_path, frame) return save_path, None, actual_width, actual_height def format_camera_list(cameras): """格式化相机列表输出""" if not cameras: return "❌ 未找到可用的摄像头设备" lines = ["📷 可用摄像头设备:"] for cam in cameras: model_info = "" if "model" in cam: model_info = f" ({cam.get('vendor', '')} {cam['model']})".strip() dev_path = cam.get("device_path", f"Camera #{cam['id']}") lines.append(f" [{cam['id']}] {dev_path}{model_info}") lines.append(f" 分辨率: {cam['resolution']}, 后端: {cam['backend']}") return "\n".join(lines) def list_recent_photos(limit=10): """列出最近的摄像头照片""" ensure_camera_dir() if not os.path.exists(CAMERA_DIR): return f"摄像头照片目录不存在: {CAMERA_DIR}" photos = [] for f in os.listdir(CAMERA_DIR): if f.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')): filepath = os.path.join(CAMERA_DIR, f) mtime = os.path.getmtime(filepath) size = os.path.getsize(filepath) photos.append((mtime, f, size)) photos.sort(reverse=True) if not photos: return f"摄像头照片目录中没有照片: {CAMERA_DIR}" result = [f"📸 最近的摄像头照片 (共 {len(photos)} 个):"] for i, (mtime, fname, size) in enumerate(photos[:limit], 1): dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime)) if size < 1024: size_str = f"{size}B" elif size < 1024 * 1024: size_str = f"{size/1024:.1f}KB" else: size_str = f"{size/1024/1024:.1f}MB" result.append(f" {i}. {fname} - {dt} ({size_str})") if len(photos) > limit: result.append(f" ... 还有 {len(photos) - limit} 个文件") return "\n".join(result) def main(): parser = argparse.ArgumentParser( description="摄像头拍照工具 - 获取相机设备列表、选择指定相机拍照、保存照片。", epilog=""" 使用示例: # 列出所有可用摄像头 camera --action list # 查看摄像头详细信息 camera --action info --camera-id 0 # 拍照(自动生成文件名) camera --action capture --camera-id 0 # 拍照并指定分辨率 camera --action capture --camera-id 0 --resolution "1920x1080" # 拍照并自定义文件名 camera --action capture --camera-id 0 --filename "my_photo" # 拍照并等待3秒预热 camera --action capture --camera-id 0 --wait-time 3 # 手动降低曝光(数值越低越暗,-7 比 -5 更暗) camera --action capture --camera-id 0 --exposure -7 # 调整曝光至较亮 camera --action capture --camera-id 0 --exposure -4 # 列出最近的照片 camera --action list-photos --limit 5 照片保存位置: ~/.aibox/camera/ 自动命名格式: camera_{摄像头ID}_{YYYYMMDD_HHMMSS}.jpg 需要安装: pip install opencv-python """ ) parser.add_argument("--action", "-a", required=True, choices=["list", "info", "capture", "list-photos"], help="操作类型") # 摄像头参数 parser.add_argument("--camera-id", "-c", type=int, default=0, help="摄像头ID(默认: 0)") parser.add_argument("--resolution", "-r", help="照片分辨率,格式: 宽度x高度(如 1920x1080)") parser.add_argument("--wait-time", "-w", type=float, default=1, help="等待摄像头预热时间(秒,默认: 1)") parser.add_argument("--exposure", "-e", type=float, help="曝光值(越低画面越暗。设置后自动关闭自动曝光,值范围取决于摄像头驱动,通常 -4 到 -7 为常见范围)") parser.add_argument("--filename", "-f", help="自定义文件名(不含路径,自动保存到默认目录)") parser.add_argument("--limit", "-l", type=int, default=10, help="列出照片时的数量限制(默认: 10)") args = parser.parse_args() # 延迟导入 cv2,确保 --help 不受依赖缺失影响 global cv2 try: import cv2 except ImportError: print("❌ 缺少依赖库: opencv-python。请运行: pip install opencv-python") sys.exit(1) # 执行操作 try: if args.action == "list": print("🔍 正在扫描摄像头设备...") cameras = list_cameras(10) print(format_camera_list(cameras)) if cameras: print(f"\n💡 使用建议:") print(f" 要使用摄像头 #{cameras[0]['id']} 拍照,请执行:") print(f" camera --action capture --camera-id {cameras[0]['id']}") print(f" 要查看摄像头详细信息,请执行:") print(f" camera --action info --camera-id {cameras[0]['id']}") elif args.action == "list-photos": result = list_recent_photos(args.limit) print(result) elif args.action == "info": print(f"🔍 正在获取摄像头 #{args.camera_id} 的详细信息...") info = get_camera_info(args.camera_id) if not info: print(f"❌ 无法打开摄像头 #{args.camera_id},请检查设备是否存在") sys.exit(1) print(f"📷 摄像头 #{args.camera_id} 详细信息:") if "device_path" in info: print(f" 设备路径: {info['device_path']}") print(f" 当前分辨率: {info.get('current_resolution', '未知')}") if "channels" in info: print(f" 色彩通道: {info['channels']}") if info["properties"]: print(f"\n 支持设置的属性:") for name, value in info["properties"].items(): print(f" {name}: {value}") print(f"\n💡 要拍照请执行:") print(f" camera --action capture --camera-id {args.camera_id}") elif args.action == "capture": ensure_camera_dir() if args.filename: save_path = os.path.join(CAMERA_DIR, args.filename) else: timestamp = time.strftime("%Y%m%d_%H%M%S") save_path = os.path.join(CAMERA_DIR, f"camera_{args.camera_id}_{timestamp}.jpg") print(f"📸 正在使用摄像头 #{args.camera_id} 拍照...") if args.resolution: print(f" 请求分辨率: {args.resolution}") print(f" 预热时间: {args.wait_time}秒") if args.exposure is not None: print(f" 曝光值: {args.exposure}(手动模式,自动曝光已关闭)") print(f" 保存目录: {CAMERA_DIR}") image_path, error, width, height = capture_photo( camera_id=args.camera_id, save_path=save_path, resolution=args.resolution, wait_time=args.wait_time, exposure=args.exposure ) if error: print(f"❌ {error}") sys.exit(1) print(f"✅ 拍照成功!") print(f" 📷 实际分辨率: {width}x{height}") print(f" 💾 保存路径: {image_path}") if os.path.exists(image_path): file_size = os.path.getsize(image_path) if file_size < 1024: size_str = f"{file_size}B" elif file_size < 1024 * 1024: size_str = f"{file_size/1024:.1f}KB" else: size_str = f"{file_size/1024/1024:.1f}MB" print(f" 📦 文件大小: {size_str}") photos = [f for f in os.listdir(CAMERA_DIR) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp'))] print(f" 📁 该目录共有 {len(photos)} 张照片") except Exception as e: print(f"❌ 执行错误: {str(e)}") sys.exit(1) if __name__ == "__main__": main()