昨天遇到一个问题,我想把电视剧的片尾飞字字幕提取出来,然后将它贴到一张图片上,最终形成一个新的视频。
之前学过一点数字图像处理的皮毛,大概知道应该是一个怎样的处理流程。但我一行代码也不想写,于是求助于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}")
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |