fix: 摄像头每次拍照后立即关闭释放资源,添加全黑检测和重试机制
This commit is contained in:
99
camera.py
99
camera.py
@@ -3,6 +3,7 @@
|
||||
"""
|
||||
import cv2
|
||||
import datetime
|
||||
import time
|
||||
from pathlib import Path
|
||||
from config import config_mgr
|
||||
|
||||
@@ -13,17 +14,30 @@ class CameraCapture:
|
||||
def __init__(self):
|
||||
self.camera_index = config_mgr.get('camera_index', 0)
|
||||
self.cap = None
|
||||
self._closed = True
|
||||
self._release_time = 0
|
||||
|
||||
def open(self):
|
||||
"""打开摄像头"""
|
||||
if self.cap is None or not self.cap.isOpened():
|
||||
if self.cap is not None and self.cap.isOpened():
|
||||
return True
|
||||
|
||||
# 确保之前的摄像头已完全关闭
|
||||
if not self._closed:
|
||||
self.close()
|
||||
import time
|
||||
time.sleep(0.5) # 等待摄像头完全释放
|
||||
|
||||
self.camera_index = config_mgr.get('camera_index', 0)
|
||||
self.cap = cv2.VideoCapture(self.camera_index, cv2.CAP_DSHOW) # Windows DirectShow
|
||||
if not self.cap.isOpened():
|
||||
raise Exception(f"无法打开摄像头 {self.camera_index}")
|
||||
|
||||
# 设置分辨率
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
|
||||
|
||||
self._closed = False
|
||||
return self.cap.isOpened()
|
||||
|
||||
def close(self):
|
||||
@@ -31,50 +45,103 @@ class CameraCapture:
|
||||
if self.cap is not None:
|
||||
self.cap.release()
|
||||
self.cap = None
|
||||
self._closed = True
|
||||
self._release_time = time.time()
|
||||
|
||||
def capture(self, save_path=None):
|
||||
"""拍照并保存(按日期文件夹组织)
|
||||
def capture(self, save_path=None, retry_count=3):
|
||||
"""拍照并保存(每次拍照后自动关闭摄像头)
|
||||
|
||||
Args:
|
||||
save_path: 保存路径
|
||||
retry_count: 重试次数
|
||||
|
||||
Returns:
|
||||
dict: {'success': bool, 'path': str, 'timestamp': str, 'date_folder': str, 'error': str}
|
||||
"""
|
||||
for attempt in range(retry_count):
|
||||
try:
|
||||
# 每次拍照都重新打开摄像头
|
||||
self.open()
|
||||
|
||||
# 等待摄像头稳定(Windows 上需要预热)
|
||||
for _ in range(5):
|
||||
self.cap.read()
|
||||
time.sleep(0.5)
|
||||
|
||||
ret, frame = self.cap.read()
|
||||
# 读取帧(丢弃前几帧确保稳定)
|
||||
for _ in range(5):
|
||||
ret, _ = self.cap.read()
|
||||
if not ret:
|
||||
self.close()
|
||||
if attempt < retry_count - 1:
|
||||
print(f"[Camera] 预热失败,重试 {attempt + 2}/{retry_count}")
|
||||
time.sleep(1)
|
||||
continue
|
||||
return {'success': False, 'error': '摄像头预热失败'}
|
||||
|
||||
# 拍照
|
||||
ret, frame = self.cap.read()
|
||||
|
||||
# 立即关闭摄像头释放资源
|
||||
self.close()
|
||||
|
||||
if not ret or frame is None:
|
||||
if attempt < retry_count - 1:
|
||||
print(f"[Camera] 捕获失败,重试 {attempt + 2}/{retry_count}")
|
||||
time.sleep(1)
|
||||
continue
|
||||
return {'success': False, 'error': '无法捕获图像'}
|
||||
|
||||
# 获取当前日期
|
||||
now = datetime.datetime.now()
|
||||
date_str = now.strftime('%Y-%m-%d')
|
||||
timestamp = now.strftime('%Y%m%d_%H%M%S')
|
||||
# 检查图像是否全黑
|
||||
if self._is_black_frame(frame):
|
||||
if attempt < retry_count - 1:
|
||||
print(f"[Camera] 全黑图像,重试 {attempt + 2}/{retry_count}")
|
||||
time.sleep(2)
|
||||
continue
|
||||
return {'success': False, 'error': '捕获到全黑图像'}
|
||||
|
||||
# 获取按日期的保存目录
|
||||
date_folder = config_mgr.get_images_dir(date_str)
|
||||
# 成功,保存图片
|
||||
now = datetime.datetime.now()
|
||||
timestamp = now.strftime('%Y%m%d_%H%M%S')
|
||||
date_str = now.strftime('%Y-%m-%d')
|
||||
|
||||
if save_path is None:
|
||||
date_folder = config_mgr.get_images_dir(date_str)
|
||||
filename = f"capture_{timestamp}.jpg"
|
||||
save_path = date_folder / filename
|
||||
|
||||
# 保存图片
|
||||
cv2.imwrite(str(save_path), frame, [cv2.IMWRITE_JPEG_QUALITY, 90])
|
||||
|
||||
print(f"[Camera] 拍照成功: {save_path.name}")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'path': str(save_path),
|
||||
'timestamp': timestamp,
|
||||
'date_folder': date_str,
|
||||
'date_folder': save_path.parent.name,
|
||||
'filename': save_path.name
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.close()
|
||||
if attempt < retry_count - 1:
|
||||
print(f"[Camera] 异常: {e}, 重试 {attempt + 2}/{retry_count}")
|
||||
time.sleep(2)
|
||||
continue
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
return {'success': False, 'error': '重试后仍失败'}
|
||||
|
||||
def _is_black_frame(self, frame):
|
||||
"""检测图像是否全黑"""
|
||||
if frame is None:
|
||||
return True
|
||||
|
||||
# 计算平均亮度
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
mean_brightness = cv2.mean(gray)[0]
|
||||
|
||||
# 如果平均亮度小于 5,视为全黑
|
||||
return mean_brightness < 5
|
||||
|
||||
def get_camera_info(self):
|
||||
"""获取摄像头信息"""
|
||||
try:
|
||||
@@ -82,14 +149,16 @@ class CameraCapture:
|
||||
width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
fps = int(self.cap.get(cv2.CAP_PROP_FPS))
|
||||
self.close()
|
||||
return {
|
||||
'index': self.camera_index,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'fps': fps,
|
||||
'status': 'opened'
|
||||
'status': 'ok'
|
||||
}
|
||||
except Exception as e:
|
||||
self.close()
|
||||
return {
|
||||
'index': self.camera_index,
|
||||
'status': 'error',
|
||||
|
||||
Reference in New Issue
Block a user