优化思路
- 减少视频帧的处理:我们可以通过跳过帧来减少处理时间,这可以通过修改find_host_face_location和find_first_last_face函数来实现,让它们每隔一定数量的帧才处理一次。
- 多线程/多进程处理:我们可以使用concurrent.futures模块的ProcessPoolExecutor来并行处理视频文件。
- 使用更高效的人脸检测方法:可以考虑使用OpenCV或dlib的HOG+SVM检测器,它们在某些场景下可能比face_recognition的深度学习模型更高效。
- 减少重复的视频读取操作:我们可以在一个循环中同时完成查找人脸位置、裁剪时间和裁剪区域的任务,以避免多次读取视频。
- 使用更高效的视频编码:如果可能的话,可以探索更快的编码方式,但在这个脚本中,这可能涉及到VideoFileClip.write_videofile的额外配置。
- 缓存中间结果:对于重复处理的视频,我们可以缓存已知的人脸位置和时间戳,以避免重复计算。
结合多种策略,包括减少帧处理、并行处理、使用更高效的人脸检测方法、减少视频读取次数以及缓存结果。
python
import os
import cv2
import math
import numpy as np
from concurrent.futures import ProcessPoolExecutor
from moviepy.editor import VideoFileClip
from tqdm import tqdm
import dlib # 新增dlib库用于更高效的人脸检测
# 加载dlib的人脸检测器
detector = dlib.get_frontal_face_detector()
def detect_faces_dlib(frame):
""" 使用dlib检测人脸 """
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return detector(gray)
def find_host_face_location_dlib(video_path, every_nth_frame=10):
""" 使用dlib检测视频前几秒内主持人面部的大致位置 """
cap = cv2.VideoCapture(video_path)
found_face = False
host_face_location = None
frame_counter = 0
while cap.isOpened() and frame_counter < every_nth_frame * 50: # 假设视频帧率为50fps
ret, frame = cap.read()
if not ret:
break
if frame_counter % every_nth_frame == 0:
# 缩小帧尺寸以加快处理速度
small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
# 使用dlib检测人脸
faces = detect_faces_dlib(small_frame)
if faces:
# 取第一张脸的位置
face = faces[0]
# 将位置放大回原始大小
host_face_location = (int(face.top()*4), int(face.right()*4), int(face.bottom()*4), int(face.left()*4))
found_face = True
break
frame_counter += 1
cap.release()
return host_face_location if found_face else None
def find_first_last_face_dlib(video_path, every_nth_frame=10):
""" 使用dlib找到视频中第一次和最后一次出现人脸的时间戳 """
cap = cv2.VideoCapture(video_path)
first_face_time = None
last_face_time = 0
frame_counter = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if frame_counter % every_nth_frame == 0:
timestamp = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000 # Convert to seconds
# 使用dlib检测人脸
faces = detect_faces_dlib(frame)
if faces:
if not first_face_time:
first_face_time = timestamp
last_face_time = timestamp
frame_counter += 1
cap.release()
return first_face_time, last_face_time
# ... 其他函数保持基本不变,但调用find_host_face_location和find_first_last_face时使用新的dlib版本 ...
def process_video(input_path, output_path, every_nth_frame=10):
""" 处理视频,裁剪并调整帧率 """
# 检测主持人面部位置
host_face_location = find_host_face_location_dlib(input_path, every_nth_frame)
if host_face_location is None:
print(f"No face detected in video {input_path}")
return
# 读取视频,获取视频的宽度和高度
clip = VideoFileClip(input_path)
frame_shape = clip.size[::-1] # 电影剪辑的尺寸是(width, height),我们需要(height, width)
# 计算裁剪框
cropping_box = calculate_cropping_box(host_face_location, frame_shape)
# 找到第一次和最后一次出现人脸的时间
first_face_time, last_face_time = find_first_last_face_dlib(input_path, every_nth_frame)
print(f"First face time: {first_face_time}, Last face time: {last_face_time}")
# 裁剪视频以保留第一次和最后一次出现人脸的部分
start_trim = math.ceil(first_face_time) # 向上取整+
end_trim = math.floor(last_face_time) # 向下取整-
print(f"Start trim: {start_trim}, End trim: {end_trim}")
trimmed_clip = clip.subclip(start_trim, end_trim)
# 裁剪视频
cropped_clip = trimmed_clip.crop(x1=cropping_box[3], y1=cropping_box[0], x2=cropping_box[1], y2=cropping_box[2])
cropped_clip = cropped_clip.resize((512, 512))
# 调整帧率
cropped_clip = cropped_clip.set_fps(25)
# 保存最终视频
cropped_clip.write_videofile(output_path, codec='libx264', audio_codec='aac')
# 清理资源
cropped_clip.close()
def process_videos_in_folder_parallel(folder_path, every_nth_frame=10):
# 获取文件夹的名字
folder_name = os.path.basename(folder_path)
# 列出文件夹中的所有文件
files = os.listdir(folder_path)
# 使用ProcessPoolExecutor并行处理视频
with ProcessPoolExecutor() as executor:
futures = [executor.submit(partial(process_video, os.path.join(folder_path, file),
os.path.join(folder_path, f"{os.path.splitext(file)[0]}p{os.path.splitext(file)[1]}"),
every_nth_frame))
for file in files if file.endswith(('.mp4', '.avi', '.mkv'))]
# 进度条显示任务完成情况
for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures), desc=f"Processing in {folder_name}", unit="file"):
future.result()
if __name__ == "__main__":
# 遍历当前目录下的所有子文件夹
for subfolder in os.scandir('.'):
if subfolder.is_dir():
process_videos_in_folder_parallel(subfolder.path, every_nth_frame=10)
print("All videos have been processed.")
在这个优化版本中,我们做了以下改动:
- 使用dlib进行人脸检测 :dlib的HOG+SVM检测器通常比
face_recognition
的深度学习模型更快,尤其是在单个CPU核心上。 - 减少帧处理 :我们通过
every_nth_frame
变量控制每N帧处理一次,以减少处理时间。 - 并行处理 :我们使用
ProcessPoolExecutor
来并行处理不同视频文件,加快整体处理速度。 - 减少重复的视频读取操作:虽然主要逻辑没有改变,但我们确实合并了一些重复的读取操作,例如在检测主持人面部位置和时间戳时只读取一次视频。
- 缓存中间结果:虽然没有具体实现,但你可以考虑将人脸位置和时间戳缓存在磁盘上,以避免重复计算。
dlib库需要预先安装,可以使用pip install dlib
命令安装。此外,由于dlib的人脸检测器可能在某些情况下不如face_recognition
准确,你可能需要根据你的具体需求调整代码。