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
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)
|