You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
7.5 KiB

import cv2
import time
import os
from datetime import datetime, timedelta
import logging
import yaml
from PIL import Image, ImageFont, ImageDraw
import numpy as np # 添加导入
# 初始化日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def create_training_directory(path_name):
if not os.path.exists(path_name):
os.makedirs(path_name)
def is_within_time_range(start_time_str, end_time_str):
"""判断当前时间是否在指定的时间范围内"""
now = datetime.now().time()
start_time = datetime.strptime(start_time_str, "%H:%M").time() # 24小时制
end_time = datetime.strptime(end_time_str, "%H:%M").time()
return start_time <= now <= end_time
def calculate_required_time(start_time_str, end_time_str, percentage):
"""计算所需的最少时间(分钟)"""
start_time = datetime.strptime(start_time_str, "%H:%M")
end_time = datetime.strptime(end_time_str, "%H:%M")
total_time = (end_time - start_time).total_seconds() / 60 # 总时长(分钟)
return total_time * (percentage / 100)
def load_config(config_path='config.yaml'):
base_dir = os.path.dirname(os.path.abspath(__file__)) # 获取脚本所在目录
config_full_path = os.path.join(base_dir, config_path) # 构建配置文件的绝对路径
with open(config_full_path, 'r', encoding='utf-8') as file:
return yaml.safe_load(file)
def put_chinese_text(frame, text, position, font_path, font_size, color, font_cache={}):
"""
使用 PIL 绘制中文文本并转换回 OpenCV 图像
"""
# 检查字体文件是否存在
if not os.path.exists(font_path):
logging.error(f"字体文件未找到: {font_path}")
return frame
# 使用缓存的字体对象
if font_path in font_cache:
font = font_cache[font_path]
else:
try:
font = ImageFont.truetype(font_path, font_size)
font_cache[font_path] = font
except Exception as e:
logging.error(f"加载字体失败: {e}")
return frame
# 将 OpenCV 的 BGR 图像转换为 RGB
img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
# 绘制文本
draw.text(position, text, font=font, fill=color)
# 将 PIL 图像转换回 OpenCV 的 BGR 图像
frame = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
return frame
def capture_faces_with_timer(window_name, camera_id, path_name, max_num, required_time, start_time_str, end_time_str, font_path):
cv2.namedWindow(window_name)
try:
cap = cv2.VideoCapture(camera_id) # 直接初始化 VideoCapture
face_cascade = cv2.CascadeClassifier(
config['face_cascade_path']) # 使用配置文件中的路径
color = (0, 255, 0)
face_count = 0
countdown_started = False
remaining_time = required_time * 60 # 转换为秒
pause_time = None # 初始化暂停时间
create_training_directory(path_name)
while cap.isOpened() and face_count < max_num:
ret, frame = cap.read()
if not ret:
break
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
# 检查当前时间是否在指定的时间范围内
if is_within_time_range(start_time_str, end_time_str):
if len(faces) > 0:
if not countdown_started:
countdown_started = True
if pause_time is not None:
# 调整 start_time 以考虑暂停的时长
start_time += time.time() - pause_time
else:
start_time = time.time()
remaining_time = required_time * 60 - int(time.time() - start_time)
for (x, y, w, h) in faces:
face_image = frame[y:y + h, x:x + w]
image_name = os.path.join(path_name, f'face_{face_count}.jpg')
cv2.imwrite(image_name, face_image)
face_count += 1
cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
# 显示剩余学习时长
text = f'剩余学习时长: {remaining_time // 60}m {remaining_time % 60}s'
frame = put_chinese_text(
frame,
text,
(10, 30),
font_path,
30, # 字体大小
(0, 0, 255) # 颜色
)
if remaining_time <= 0:
break
else:
# 如果计时器已经暂停,继续显示剩余时间
text = f'剩余学习时长: {remaining_time // 60}m {remaining_time % 60}s'
frame = put_chinese_text(
frame,
text,
(10, 30),
font_path,
30, # 字体大小
(0, 0, 255) # 颜色
)
else:
if countdown_started:
logging.info('未检测到人脸,暂停计时。')
countdown_started = False
pause_time = time.time() # 记录暂停时间
# 显示外部时间范围提示
frame = put_chinese_text(
frame,
'不在指定的时间范围内',
(10, 30),
font_path,
30, # 字体大小
(0, 0, 255) # 颜色
)
cv2.imshow(window_name, frame)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
except Exception as e:
logging.error(f"发生错误: {e}")
finally:
cap.release() # 确保摄像头资源被释放
cv2.destroyAllWindows()
logging.info('人脸捕捉完成。')
def get_time_input(prompt):
while True:
time_str = input(prompt)
try:
datetime.strptime(time_str, "%H:%M")
return time_str
except ValueError:
print("时间格式错误,请输入如 17:00 这样的24小时制时间。")
if __name__ == '__main__':
config = load_config()
start_time_str = get_time_input("Enter the start time (e.g., 17:00 for 5pm): ")
end_time_str = get_time_input("Enter the end time (e.g., 18:00 for 6pm): ")
while True:
try:
percentage = int(input("Enter the minimum percentage of the time to monitor (0-100): "))
if 0 <= percentage <= 100:
break
else:
print("请输入 0 到 100 之间的整数。")
except ValueError:
print("请输入有效的整数。")
required_time = calculate_required_time(start_time_str, end_time_str, percentage)
# 指定中文字体的路径
font_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fonts', 'SimHei.ttf') # 确保路径正确
logging.info(f'Starting face capture, will monitor for at least {required_time} minutes...')
capture_faces_with_timer('FaceCapture', config['camera_id'], config['path_name'], 1000, required_time, start_time_str,
end_time_str, font_path)