v1.0
问题:人脸检测太灵敏了,导致当画面中出现多个人脸时,画面反复跳各个人脸,闪动严重
python
import cv2
import os
from tqdm import tqdm # 进度条
import subprocess
# 加载人脸检测模型
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
def contains_face(frame):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
return len(faces) > 0
def crop_center(frame, crop_width, crop_height):
height, width = frame.shape[:2]
start_x = width//2 - crop_width//2
start_y = height//2 - crop_height//2
return frame[start_y:start_y+crop_height, start_x:start_x+crop_width]
def extract_audio(input_path, audio_path):
subprocess.run(['ffmpeg', '-y', '-i', input_path, '-vn', '-acodec', 'copy', audio_path])
def merge_video_audio(video_path, audio_path, output_path):
subprocess.run(['ffmpeg', '-y', '-i', video_path, '-i', audio_path, '-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', output_path])
def process_video(path, out_path, fps=25):
print(f'[INFO] ===== process video from {path} to {out_path} =====')
# 创建VideoCapture对象
cap = cv2.VideoCapture(path)
# 检查是否成功打开视频
if not cap.isOpened():
print("Error opening video file")
return
frame_rate = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 获取视频的总帧数
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 获取视频的宽度
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 获取视频的高度
print("原视频帧率=", frame_rate, "fps")
print("原视频帧数=", total_frames)
print("原视频尺寸=", frame_width, "x", frame_height)
if frame_rate != fps:
cap.set(cv2.CAP_PROP_FPS, fps)
frame_rate = fps
# 创建VideoWriter对象
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(out_path, fourcc, fps, (512, 512))
frame_count = 0
# 创建一个tqdm进度条
pbar = tqdm(total=total_frames, ncols=70, unit='frame')
while cap.isOpened():
ret, frame = cap.read()
if ret:
if contains_face(frame) and frame_count % (frame_rate // fps) == 0:
frame = crop_center(frame, 512, 512)
out.write(frame)
frame_count += 1
pbar.update(1) # 更新进度条
else:
break
pbar.close() # 关闭进度条
cap.release()
out.release()
# 你的代码中存在一些可能影响最终视频与音频同步的潜在问题:
# 当你使用cap.set(cv2.CAP_PROP_FPS, fps)尝试改变视频的帧率,这实际上并不会改变视频的内部帧率属性。CAP_PROP_FPS是一个读取属性,而不是写入属性。因此,你不能依靠这种方法来更改视频的帧率。
# 当你基于frame_rate // fps的模运算来决定是否抽取帧时,这假定了frame_rate可以被fps整除。如果frame_rate和fps不是整数倍的关系,那么视频的总时长可能会与原视频不完全匹配,这可能导致音频和视频的轻微不同步。
# 为了避免这些问题,你应该:
# 不尝试设置CAP_PROP_FPS,而是通过抽取帧来实现目标帧率。
# 计算每个帧应该抽取的时间戳,确保与原始视频的帧持续时间一致。
# def process_video(path, out_path, target_fps=25):
# print(f'[INFO] ===== process video from {path} to {out_path} =====')
# # 创建VideoCapture对象
# cap = cv2.VideoCapture(path)
# # 检查是否成功打开视频
# if not cap.isOpened():
# print("Error opening video file")
# return
# original_fps = cap.get(cv2.CAP_PROP_FPS)
# total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# frame_duration = 1 / original_fps # 原始视频每帧的持续时间
# # 创建VideoWriter对象
# fourcc = cv2.VideoWriter_fourcc(*'mp4v')
# out = cv2.VideoWriter(out_path, fourcc, target_fps, (512, 512))
# frame_count = 0
# target_frame_time = 0 # 目标帧的累积时间
# # 创建一个tqdm进度条
# pbar = tqdm(total=total_frames, ncols=70, unit='frame')
# while cap.isOpened():
# ret, frame = cap.read()
# if ret:
# current_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000 # 当前帧的时间戳(秒)
# # 如果当前时间等于或超过目标帧时间,则写入帧
# if current_time >= target_frame_time:
# if contains_face(frame):
# frame = crop_center(frame, 512, 512)
# out.write(frame)
# target_frame_time += frame_duration * (original_fps / target_fps) # 下一帧的目标时间
# frame_count += 1
# pbar.update(1) # 更新进度条
# else:
# break
# pbar.close() # 关闭进度条
# cap.release()
# out.release()
print(f'[INFO] ===== processed video =====')
# 打开处理后的视频,获取总帧数、帧率和视频尺寸
cap_out = cv2.VideoCapture(out_path)
total_frames_out = int(cap_out.get(cv2.CAP_PROP_FRAME_COUNT))
frame_rate_out = cap_out.get(cv2.CAP_PROP_FPS)
frame_width = int(cap_out.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap_out.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(f'处理后的视频帧率: {frame_rate_out} fps')
print(f'处理后的视频帧数: {total_frames_out}')
print(f'处理后的视频尺寸: {frame_width}x{frame_height}')
cap_out.release()
def process_video_with_audio(input_path, output_path):
audio_path = output_path.replace('.mp4', '_audio.aac')
output_with_audio_path = output_path.replace('.mp4', '_with_audio.mp4')
extract_audio(input_path, audio_path)
process_video(input_path, output_path)
merge_video_audio(output_path, audio_path, output_with_audio_path)
# 删除临时文件
os.remove(output_path)
os.remove(audio_path)
return output_with_audio_path
if __name__ == "__main__":
for i in tqdm(range(1, 75), desc="Processing videos"):
input_path = f"data/{i}/{i}.mp4"
output_path = f"data/{i}/{i}_fc.mp4"
if not os.path.isfile(input_path):
print(f"文件 {input_path} 不存在.")
continue
final_output_path = process_video_with_audio(input_path, output_path)
print(f"处理后的视频已保存至 {final_output_path}")
通过调整帧率和裁剪视频来处理视频,同时保留音频并保持音频和视频的同步。为了确保音频和视频的同步,关键点在于保持视频帧的持续时间和原始视频一致。当减少帧率时,重要的是要确保抽取的帧在时间上均匀分布,这样每个帧代表的时间间隔与原始视频相同,从而保持音频的同步。
在代码中,通过以下方式实现了这一点:
使用cv2.CAP_PROP_FPS属性读取原始视频的帧率。 设置目标帧率fps(例如25fps)。 在循环中,只写入那些在时间上符合目标帧率的帧。你通过frame_count % (frame_rate // fps) == 0这一条件实现,这确保了只有在正确的时间间隔下才抽取一帧。 然而,你的代码中存在一些可能影响最终视频与音频同步的潜在问题:
当使用cap.set(cv2.CAP_PROP_FPS, fps)尝试改变视频的帧率,这实际上并不会改变视频的内部帧率属性。CAP_PROP_FPS是一个读取属性,而不是写入属性。因此,你不能依靠这种方法来更改视频的帧率。 当你基于frame_rate // fps的模运算来决定是否抽取帧时,这假定了frame_rate可以被fps整除。如果frame_rate和fps不是整数倍的关系,那么视频的总时长可能会与原视频不完全匹配,这可能导致音频和视频的轻微不同步。 为了避免这些问题,你应该:
不尝试设置CAP_PROP_FPS,而是通过抽取帧来实现目标帧率。 计算每个帧应该抽取的时间戳,确保与原始视频的帧持续时间一致。