AI辅助搞定视频字幕提取与视频背景替换
昨天遇到一个问题,我想把电视剧的片尾飞字字幕提取出来,然后将它贴到一张图片上,最终形成一个新的视频。
之前学过一点数字图像处理的皮毛,大概知道应该是一个怎样的处理流程。但我一行代码也不想写,于是求助于chatGPT。
我上传的图片是4:3的,而原视频是16:9的,视频左右两侧有黑边,chatGPT在后续的对话中也识别到了这一子任务,于给出了以下处理流程。
步骤1:去除黑边,生成4:3比例的视频
步骤2:提取文字并替换背景
经过几轮对话与测试,得到了以下代码,完美地解决了我的需求。
点击查看代码
import cv2
import numpy as np
from moviepy.editor import VideoFileClip, ImageSequenceClip
import os
import sys
# 确保中文显示正常
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
def crop_black_borders(input_video_path, cropped_video_path):
"""第一步:去除视频左右黑边,生成4:3比例的视频"""
try:
video = VideoFileClip(input_video_path)
except Exception as e:
print(f"错误:无法加载原始视频 {input_video_path},错误信息:{e}")
return False
w, h = video.size
print(f"原始视频尺寸:{w}x{h}")
# 计算需要裁剪的左右黑边宽度(目标4:3比例,高度不变,调整宽度)
# 4:3比例的宽度 = 高度 * 4 / 3
target_width = int(h * 4 / 3)
if target_width > w:
print("警告:原始视频宽度小于4:3比例所需宽度,可能无黑边或黑边在上下方")
# 若宽度不足,直接使用原始宽度(避免错误)
crop_clip = video
else:
# 计算左右各裁剪的宽度(总裁剪宽度 = 原始宽度 - 目标宽度)
crop_pixels = (w - target_width) // 2
# 裁剪左右黑边(x1:左边界,x2:右边界)
crop_clip = video.crop(x1=crop_pixels, x2=w - crop_pixels)
print(f"裁剪黑边:左右各裁剪 {crop_pixels} 像素,裁剪后尺寸:{crop_clip.size[0]}x{crop_clip.size[1]}(4:3)")
# 保存裁剪后的视频(中间文件)
crop_clip.write_videofile(cropped_video_path, codec="libx264", audio_codec="aac")
video.close()
return True
def process_video_with_new_bg(cropped_video_path, background_img_path, output_video_path):
"""使用指定背景图像替换视频背景,并保留原视频中的红色文字(优化版)"""
# 加载背景并调整尺寸
bg = cv2.imread(background_img_path)
if bg is None:
print(f"错误:无法加载背景图片 {background_img_path}")
return False
bg = cv2.cvtColor(bg, cv2.COLOR_BGR2RGB)
# 加载裁剪后的视频
try:
video = VideoFileClip(cropped_video_path)
except Exception as e:
print(f"错误:无法加载裁剪后的视频 {cropped_video_path},错误信息:{e}")
return False
fps = video.fps
w, h = video.size
bg = cv2.resize(bg, (w, h))
frames_out = []
total_frames = int(video.duration * fps)
print(f"开始处理视频,总帧数:{total_frames}")
for i, frame in enumerate(video.iter_frames()):
# 显示进度
if i % 10 == 0:
progress = f"\r处理进度:{i}/{total_frames} 帧"
sys.stdout.write(progress)
sys.stdout.flush()
# 转换为HSV颜色空间
hsv = cv2.cvtColor(frame, cv2.COLOR_RGB2HSV)
# 1. 提取红色字幕主体(扩大红色检测范围)
lower_red1 = np.array([0, 50, 30]) # 降低饱和度和明度阈值,捕获更淡、更暗的红色
# upper_red1 = np.array([15, 255, 255]) # 扩大低色相红色范围
lower_red2 = np.array([165, 50, 30]) # 扩大高色相红色范围
upper_red2 = np.array([180, 255, 255])
# lower_red1 = np.array([0, 70, 50])
upper_red1 = np.array([10, 255, 255])
# lower_red2 = np.array([170, 70, 50])
# upper_red2 = np.array([180, 255, 255])
mask_red = cv2.inRange(hsv, lower_red1, upper_red1) | cv2.inRange(hsv, lower_red2, upper_red2)
# 增强红色提取:修复断裂,扩大范围
kernel_red = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
mask_red = cv2.morphologyEx(mask_red, cv2.MORPH_CLOSE, kernel_red, iterations=1) # 填充内部空洞
mask_red = cv2.dilate(mask_red, kernel_red, iterations=1) # 轻微膨胀,扩大红色范围
# 2. 对红色区域进行膨胀(确定"红色周围"的范围,即可能的白边区域)
kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) # 增大膨胀核,捕获更粗白边
mask_red_dilated = cv2.dilate(mask_red, kernel_dilate, iterations=2)
# 3. 提取红色周围的白色
lower_white = np.array([0, 0, 200])
upper_white = np.array([180, 40, 255])
mask_white = cv2.inRange(hsv, lower_white, upper_white)
mask_white_exterior = cv2.bitwise_and(mask_white, mask_red_dilated) # 红色周围的白色
# 4. 提取文字内部的白色(解决"口"字内部白边缺失问题)
kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
mask_red_eroded = cv2.erode(mask_red, kernel_erode, iterations=1) # 腐蚀红色,得到内部区域
mask_text_interior = cv2.subtract(mask_red, mask_red_eroded) # 文字内部区域
mask_white_interior = cv2.bitwise_and(mask_white, mask_text_interior) # 文字内部的白色
# 5. 合并三种掩膜:红色主体 + 红色周围白边 + 文字内部白边
mask = cv2.bitwise_or(mask_red, mask_white_exterior)
mask = cv2.bitwise_or(mask, mask_white_interior)
# 6. 形态学优化(清理噪点,修复边缘)
kernel_clean = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6, 6))
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_clean, iterations=1) # 填充小空洞
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_clean, iterations=1) # 去除小噪点
# 7. 转换为RGB掩膜并替换背景
mask_rgb = np.stack([mask]*3, axis=2) // 255
out_frame = np.where(mask_rgb == 1, frame, bg)
frames_out.append(out_frame)
print(f"\n视频处理完成,正在合成...")
# 合成最终视频(保留原音频)
final_clip = ImageSequenceClip(frames_out, fps=fps).set_audio(video.audio)
final_clip.write_videofile(output_video_path, codec="libx264", audio_codec="aac")
print(f"视频已保存至:{output_video_path}")
return True
if __name__ == "__main__":
# 文件路径设置(可根据实际情况修改)
input_video = "大结局.mp4" # 原始视频(带黑边)
cropped_video = "a.mp4" # 裁剪黑边后的4:3视频(中间文件)
background_img = "尾帧.png" # 新背景图
output_video = "最终输出视频(本地版).mp4" # 最终结果
# 检查原始文件是否存在
if not os.path.exists(input_video):
print(f"错误:找不到原始视频 {input_video}")
exit(1)
if not os.path.exists(background_img):
print(f"错误:找不到背景图片 {background_img}")
exit(1)
# 步骤1:去除黑边,生成4:3比例的视频
print("===== 步骤1:裁剪黑边,生成4:3视频 =====")
if not crop_black_borders(input_video, cropped_video):
print("裁剪黑边失败,程序终止")
exit(1)
# 步骤2:提取文字并替换背景
print("\n===== 步骤2:提取文字,替换背景 =====")
process_video_with_new_bg(cropped_video, background_img, output_video)
# 清理中间文件(可选)
if os.path.exists(cropped_video):
os.remove(cropped_video)
print(f"已删除中间文件:{cropped_video}")