1. 项目背景
在视频素材的搜集过程中,尤其是针对人的动作素材,我们常需要从大量包含无人画面的视频中筛选出有人出现的片段。手动进行这一筛选过程不仅耗时,而且容易遗漏关键片段。为了提高效率,我写了一个基于YOLOv8的自动裁剪视频脚本,能够自动检测视频中是否存在人物,并提取出包含人物的片段,最后将这些片段拼接在一起,以便进一步筛选和使用。
2. 环境配置
2.1 安装YOLOv8环境
首先,确保已经安装了能够正常运行YOLOv8的环境。YOLOv8通常需要Python 3.7或更高版本,并且依赖于一些常见的Python库,如ultralytics
、opencv-python
等。可以通过以下命令安装这些依赖库:
pip install ultralytics opencv-python
2.2 安装FFmpeg
FFmpeg是一个强大的多媒体处理工具,支持视频和音频的编解码、转码、剪辑等操作。我们需要安装FFmpeg以便在Python脚本中使用。
2.2.1 使用conda安装FFmpeg(适用于Windows和macOS)
如果你使用的是Anaconda或Miniconda,可以通过以下命令从conda-forge渠道安装FFmpeg:
conda install -c conda-forge ffmpeg
2.2.2 使用apt安装FFmpeg(适用于Linux)
如果你使用的是Linux系统,可以通过以下命令安装FFmpeg:
sudo apt install ffmpeg
2.2.3 验证FFmpeg安装
安装完成后,可以通过以下命令验证FFmpeg是否安装成功:
ffmpeg -version
如果输出了版本信息,则说明FFmpeg已经成功安装并且配置正确。
2.3 安装ffmpeg-python库和tqdm库
ffmpeg-python
是一个Python库,提供了对FFmpeg命令的封装,使得我们可以在Python脚本中方便地调用FFmpeg。而tqdm
是一个快速、可扩展的 Python 进度条库,可以在长循环中添加一个进度提示信息,为长时间的循环操作提供视觉反馈。
pip install ffmpeg-python
pip install tqdm
3. 代码实现(两种方法实现)
3.1 方法一:基于帧检测的视频裁剪
这是第一个脚本,它通过逐帧检测视频中的人物,并在发现人物时标记视频片段的开始和结束时间。当视频处理完成后,将使用FFmpeg将这些片段合并成一个新的视频文件。
# 导入必要的库
import cv2
from ultralytics import YOLO
import os
import ffmpeg
import time
from tqdm import tqdm
import subprocess
# 获取当前目录
current_directory = os.getcwd()
# 加载YOLOv8模型
model_path = os.path.join(current_directory, 'yolov8n.pt')
model = YOLO(model_path) # 使用当前目录中的YOLOv8模型
# 定义视频文件路径和输出文件夹
input_video_path = '27.mp4'
output_folder = 'output27/'
# 确保输出文件夹存在
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 打开视频文件
cap = cv2.VideoCapture(input_video_path)
# 获取视频的基本信息
fps = cap.get(cv2.CAP_PROP_FPS)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 获取总帧数
# 初始化变量
start_time = None
clip_count = 0
min_clip_duration = 0.5 # 最小片段长度阈值,单位为秒
def extract_video_segment(input_path, start_time, end_time, output_path):
(
ffmpeg
.input(input_path, ss=start_time, to=end_time)
.output(output_path, c='copy', an=None)
.run(overwrite_output=True)
)
# 开始计时
start_time_total = time.time()
# 创建进度条
pbar = tqdm(total=total_frames, desc="Processing Frames")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 将帧转换为YOLOv8可以处理的格式
results = model(frame)
# 检查是否有人的检测结果
has_person = False
for result in results:
for box in result.boxes:
if box.cls == 0: # 0 对应 'person' 类别
has_person = True
break
if has_person:
break
# 如果有人的检测结果
if has_person:
if start_time is None:
start_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
else:
if start_time is not None:
end_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
clip_duration = end_time - start_time
if clip_duration >= min_clip_duration:
output_path = f"{output_folder}clip_{clip_count}.mp4"
extract_video_segment(input_video_path, start_time, end_time, output_path)
clip_count += 1
start_time = None
# 更新进度条
pbar.update(1)
# 处理最后一个片段
if start_time is not None:
end_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
clip_duration = end_time - start_time
if clip_duration >= min_clip_duration:
output_path = f"{output_folder}clip_{clip_count}.mp4"
extract_video_segment(input_video_path, start_time, end_time, output_path)
# 释放视频捕获对象
cap.release()
# 关闭进度条
pbar.close()
# 结束计时
end_time_total = time.time()
total_time = end_time_total - start_time_total
print(f"处理完成,总运行时间: {total_time:.2f} 秒")
# 生成 input.txt 文件
def generate_input_txt(folder_path, output_file):
with open(output_file, 'w') as f:
for filename in sorted(os.listdir(folder_path)):
if filename.endswith(('.mp4', '.avi', '.mkv')):
video_path = os.path.join(folder_path, filename)
f.write(f"file '{video_path}'\n")
# 合并视频
def merge_videos(input_file, output_video_path):
command = [
'ffmpeg',
'-f', 'concat',
'-safe', '0',
'-i', input_file,
'-c', 'copy', # 直接复制视频和音频流,不重新编码
output_video_path
]
subprocess.run(command, check=True)
folder_path = output_folder # 你的视频文件夹路径
output_file = 'input.txt' # 生成的 input.txt 文件路径
output_video_path = 'output27.mp4' # 合并后的视频输出路径
# 生成 input.txt 文件
generate_input_txt(folder_path, output_file)
# 定义 input_file 变量
input_file = output_file
# 合并视频
merge_videos(input_file, output_video_path)
print("视频合并完成")
运行后如下,1个G的视频完整处理时间大概二十多分钟,最好用gpu跑cpu跑要慢很多,我用的4060跑的,cuda版本是12.1的。
3.2 方法二:基于帧提取和目标检测的视频裁剪
第二个个方法则首先将视频的每一帧提取出来,然后使用YOLOv8模型检测每帧中是否包含人物,筛选只保留包含人物的帧,最后,将所有包含人物的帧重新组合成一个新的视频。
# 导入必要的库
import cv2
from ultralytics import YOLO
import os
import numpy as np
from tqdm import tqdm
import subprocess
# 加载YOLOv8模型
model = YOLO('yolov8n.pt') # 使用YOLOv8模型
# 定义视频文件路径
input_video_path = '14.mp4'
output_video_path = 'output_14.mp4'
# 打开视频文件
cap = cv2.VideoCapture(input_video_path)
# 获取视频的基本信息
fps = cap.get(cv2.CAP_PROP_FPS)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 获取总帧数
# 创建临时文件夹
temp_folder = 'temp_frames'
if not os.path.exists(temp_folder):
os.makedirs(temp_folder)
# 提取视频帧
frame_count = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame_path = os.path.join(temp_folder, f'frame_{frame_count:04d}.jpg')
cv2.imwrite(frame_path, frame)
frame_count += 1
# 释放视频捕获对象
cap.release()
# 使用YOLOv8进行目标检测
person_frames = []
pbar = tqdm(total=frame_count, desc="Detecting People")
for i in range(frame_count):
frame_path = os.path.join(temp_folder, f'frame_{i:04d}.jpg')
results = model(frame_path)
for result in results:
for box in result.boxes:
if box.cls == 0: # 0 对应 'person' 类别
person_frames.append(frame_path)
break
pbar.update(1)
pbar.close()
# 重新组合视频
output_frames_folder = 'output_frames'
if not os.path.exists(output_frames_folder):
os.makedirs(output_frames_folder)
for i, frame_path in enumerate(person_frames):
output_frame_path = os.path.join(output_frames_folder, f'frame_{i:04d}.jpg')
os.rename(frame_path, output_frame_path)
# 使用FFmpeg重新组合视频
subprocess.run([
'ffmpeg', '-framerate', str(fps), '-i', os.path.join(output_frames_folder, 'frame_%04d.jpg'),
'-c:v', 'libx264', '-r', '30', '-pix_fmt', 'yuv420p', output_video_path
])
# 清理临时文件
for file_name in os.listdir(temp_folder):
file_path = os.path.join(temp_folder, file_name)
os.remove(file_path)
os.rmdir(temp_folder)
for file_name in os.listdir(output_frames_folder):
file_path = os.path.join(output_frames_folder, file_name)
os.remove(file_path)
os.rmdir(output_frames_folder)
print(f"处理完成,新视频文件保存在 {output_video_path}")
运行后结果如下:
性能比较
第一种方法比第二种方法运行得快得多,原因如下:
- I/O操作减少:第一个脚本直接在内存中处理视频帧,而不需要将每一帧写入磁盘。这大大减少了I/O操作,提高了处理速度。
- 内存使用:第一个脚本在处理过程中不需要额外的内存来存储帧图像,因为它直接在视频流上进行操作。
- FFmpeg的使用:第一个脚本利用FFmpeg的高效视频处理能力,直接在视频文件上进行裁剪,而不需要先将帧提取出来再重新编码。
不过第二个脚本相较于第一个更加灵活一些,提取出来的帧可以用做其他分析。