增加运维机器人单元

This commit is contained in:
qing committed 2026-07-27 21:39:53 +08:00
1 parent 479d263b88
commit 1b60ab0a2e
42 files changed
+20469

No files matched your search

+392
View File
@@ -0,0 +1,392 @@
#!/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()