竖屏视频转横屏并添加动态模糊背景的Python实现研究目的
将手机竖屏拍摄的视频(1080x1920)转换为横屏格式(3414x1920),并在左右两侧填充动态变化的模糊背景,模仿Instagram Reels和抖音的视觉效果。
研究方法
方法概述
本研究使用OpenCV和MoviePy库实现视频格式转换和背景填充,提供了两种背景生成策略。
方案1:全屏模糊背景法 (create_dynamic_blur_background)
核心原理:
背景生成:将原始竖屏视频拉伸到整个画布尺寸(3414x1920)
模糊处理:对拉伸后的背景应用高斯模糊(kernel size = blur_strength×2+1)
透明度调整:通过 opacity 参数控制背景亮度(模拟半透明效果)
前景叠加:将原始清晰视频居中放置在模糊背景上
关键参数:
数学实现:
# 模糊处理blurred_bg=GaussianBlur(background, (161, 161), σ=0)# 透明度混合final_bg=black_canvas× (1-0.3) +blurred_bg×0.3# 前景叠加(直接覆盖,不透明)final_canvas[中央区域] =original_frame
方案2:边缘渐变法 (create_gradient_edge_background)
核心原理:
边缘采样:仅提取视频左右边缘的1列像素
拉伸填充:将边缘像素拉伸到左右边框宽度(1167像素)
模糊处理:对拉伸的边缘区域应用模糊
透明度混合:与黑色背景按比例混合
优势:
更自然,避免显示完整画面的模糊版本
只使用边缘颜色,视觉干扰更小
技术实现细节
视频处理流程:
输入视频 (1080×1920) ↓1. 读取帧 → OpenCV VideoCapture ↓2. 背景生成 ├─ 方案1: 拉伸整个画面 → 模糊 └─ 方案2: 提取边缘 → 拉伸 → 模糊 ↓3. 透明度混合 公式: result = overlay × α + base × (1-α) ↓4. 前景叠加 (居中,1080×1920) ↓5. 写入临时视频 (无音频) ↓6. 音频合成 → MoviePy ↓输出视频 (3414×1920)
关键算法
1. 高斯模糊(Gaussian Blur)
cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma)
2. Alpha混合(Alpha Blending)
cv2.addWeighted(src1, α, src2, 1-α, gamma)
3. 帧定位计算
x_offset= (3414-1080) //2# = 1167y_offset=0# 高度保持一致
参数对比分析
| 参数 | 方案1示例 | 效果说明 |
|---|
blur_strength | 80 | 高斯核 161×161,强烈模糊 |
opacity | 0.3 | 背景保留30%亮度,较暗 |
target_width | 3414 | 输出宽度(左右各填充1167px) |
调整建议:
依赖库
importcv2# 视频帧处理、模糊、混合importnumpyasnp# 数组运算frommoviepyimportVideoFileClip# 音频合成
性能考量
计算复杂度:
每帧处理时间
≈ O(W×H×k²)
总处理时间 ≈ 帧数 × 单帧时间
优化策略:
使用 INTER_AREA 插值(缩放时更快)
每30帧显示进度(减少I/O)
临时文件避免内存溢出
应用场景
社交媒体内容创作:Instagram、抖音、快手
视频后期制作:补全画面比例
自动化批处理:大量竖屏视频转横屏
import cv2import numpy as npimport osimport timedef create_dynamic_blur_background(input_video, output_video, target_width=3414, blur_strength=50, opacity=0.5): """ 创建动态模糊背景效果 - 左右两边显示模糊放大的视频内容 参数: - input_video: 输入视频路径 - output_video: 输出视频路径 - target_width: 目标宽度,默认3414 - blur_strength: 模糊强度,默认50 - opacity: 背景透明度 0-1,默认0.5 (50%) """ temp_video = "temp_video_no_audio.mp4" # 打开视频 cap = cv2.VideoCapture(input_video) original_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) original_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv2.CAP_PROP_FPS)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(f"原始尺寸: {original_width}x{original_height}") # 新尺寸 new_width = target_width new_height = original_height x_offset = (new_width - original_width) // 2 print(f"新尺寸: {new_width}x{new_height}") print(f"左右边框宽度: {x_offset}像素") print(f"模糊强度: {blur_strength}") print(f"背景透明度: {int(opacity * 100)}%") # 创建视频写入对象 fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(temp_video, fourcc, fps, (new_width, new_height)) print(f"处理视频中...") frame_count = 0 while True: ret, frame = cap.read() if not ret: break # 1. 创建背景:将原视频拉伸到整个画布 background = cv2.resize(frame, (new_width, new_height)) # 2. 应用高斯模糊 blurred_background = cv2.GaussianBlur(background, (blur_strength*2+1, blur_strength*2+1), 0) # 3. 创建透明度效果(将模糊背景变暗) dimmed_background = (blurred_background * opacity).astype(np.uint8) # 4. 创建最终画布(黑色底 + 半透明模糊背景) canvas = np.zeros((new_height, new_width, 3), dtype=np.uint8) canvas = cv2.addWeighted(canvas, 1 - opacity, dimmed_background, 1, 0) # 5. 将原始清晰视频放在中央 canvas[0:original_height, x_offset:x_offset+original_width] = frame # 写入视频 out.write(canvas) frame_count += 1 if frame_count % 30 == 0: progress = (frame_count / total_frames) * 100 print(f"进度: {progress:.1f}%") cap.release() out.release() print("视频处理完成!") time.sleep(0.5) # 添加音频 try: print("正在添加音频...") from moviepy import VideoFileClip video_clip = VideoFileClip(temp_video) original_clip = VideoFileClip(input_video) if original_clip.audio: final_clip = video_clip.with_audio(original_clip.audio) final_clip.write_videofile(output_video, codec='libx264', audio_codec='aac', logger=None) print("音频添加成功!") final_clip.close() else: print("原视频没有音频") video_clip.write_videofile(output_video, codec='libx264', logger=None) video_clip.close() original_clip.close() time.sleep(1) except Exception as e: print(f"音频处理错误: {e}") import shutil time.sleep(1) if os.path.exists(output_video): os.remove(output_video) shutil.copy(temp_video, output_video) # 清理临时文件 try: if os.path.exists(temp_video) and os.path.exists(output_video): time.sleep(0.5) os.remove(temp_video) except: print(f"无法删除临时文件: {temp_video}") print(f"完成!输出文件: {output_video}")def create_gradient_edge_background(input_video, output_video, target_width=3414, blur_strength=30, opacity=0.5): """ 创建渐变边缘效果 - 左右边缘使用视频边缘像素的模糊渐变 这个版本的背景更自然,因为只使用视频左右边缘的颜色 """ temp_video = "temp_video_no_audio.mp4" cap = cv2.VideoCapture(input_video) original_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) original_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv2.CAP_PROP_FPS)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(f"原始尺寸: {original_width}x{original_height}") new_width = target_width new_height = original_height x_offset = (new_width - original_width) // 2 print(f"新尺寸: {new_width}x{new_height}") print(f"边框宽度: {x_offset}像素") fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(temp_video, fourcc, fps, (new_width, new_height)) print(f"处理视频中...") frame_count = 0 while True: ret, frame = cap.read() if not ret: break # 创建画布 canvas = np.zeros((new_height, new_width, 3), dtype=np.uint8) # 提取左右边缘 left_edge = frame[:, 0:1, :] # 最左边一列像素 right_edge = frame[:, -1:, :] # 最右边一列像素 # 将边缘拉伸填充左右区域 left_fill = cv2.resize(left_edge, (x_offset, new_height)) right_fill = cv2.resize(right_edge, (x_offset, new_height)) # 应用模糊 left_fill = cv2.GaussianBlur(left_fill, (blur_strength*2+1, blur_strength*2+1), 0) right_fill = cv2.GaussianBlur(right_fill, (blur_strength*2+1, blur_strength*2+1), 0) # 应用透明度 left_fill = (left_fill * opacity).astype(np.uint8) right_fill = (right_fill * opacity).astype(np.uint8) # 填充左右区域 canvas[:, 0:x_offset] = left_fill canvas[:, x_offset+original_width:] = right_fill # 放置原始视频 canvas[:, x_offset:x_offset+original_width] = frame out.write(canvas) frame_count += 1 if frame_count % 30 == 0: progress = (frame_count / total_frames) * 100 print(f"进度: {progress:.1f}%") cap.release() out.release() print("视频处理完成!") time.sleep(0.5) # 添加音频(与上面相同) try: print("正在添加音频...") from moviepy import VideoFileClip video_clip = VideoFileClip(temp_video) original_clip = VideoFileClip(input_video) if original_clip.audio: final_clip = video_clip.with_audio(original_clip.audio) final_clip.write_videofile(output_video, codec='libx264', audio_codec='aac', logger=None) final_clip.close() else: video_clip.write_videofile(output_video, codec='libx264', logger=None) video_clip.close() original_clip.close() time.sleep(1) except Exception as e: print(f"音频处理错误: {e}") import shutil time.sleep(1) if os.path.exists(output_video): os.remove(output_video) shutil.copy(temp_video, output_video) try: if os.path.exists(temp_video) and os.path.exists(output_video): time.sleep(0.5) os.remove(temp_video) except: pass print(f"完成!输出文件: {output_video}")# 使用示例if __name__ == "__main__": input_file = "input.mp4" # 方案1: 全屏模糊背景 (类似Instagram/抖音效果) print("=" * 50) print("方案1: 全屏模糊背景") print("=" * 50) create_dynamic_blur_background( input_file, "output_blur_bg.mp4", target_width=3414, blur_strength=80, # 模糊强度 (数值越大越模糊) opacity=0.3 # 50% 透明度 ) # 方案2: 边缘渐变效果 (更自然,只使用边缘颜色) # print("\n" + "=" * 50) # print("方案2: 边缘渐变效果") # print("=" * 50) # create_gradient_edge_background( # input_file, # "output_edge_gradient.mp4", # target_width=3414, # blur_strength=30, # opacity=0.5 # ) # 调整参数示例: # - blur_strength: 10-100,数值越大越模糊 # - opacity: 0.0-1.0,0是完全透明(黑色),1是完全不透明