Python中OpenCV生成视频无报错但无法播放及批量生成问题
问题修复与批量处理方案
一、视频损坏的原因及修复
你的代码存在几个关键问题导致生成的视频无法播放,同时逻辑也不符合需求:
核心问题点
- 帧顺序完全错误:首帧应该放在视频最开头,你却把它加在中间帧之后,播放器无法识别混乱的时序
- 文件列表无序:
os.listdir返回的文件名不按时间排序,用固定索引files[47]、files[22]根本取不到正确的首/尾帧,甚至可能读取到尺寸不一致的帧,直接导致视频损坏 - 中间帧数量固定:不管目标帧率是多少都只取3帧,不符合不同帧率下的总帧数要求
- 冗余操作:循环里重复读取首帧尾帧,完全没必要
修复后的核心逻辑
- 按时间排序帧文件:从文件名中提取时间戳,对帧文件按时间顺序排序,确保能精准获取首帧(0.00秒)和尾帧(0.98秒)
- 动态计算中间帧数:根据目标帧率和原视频时长(0.98秒)计算总帧数,减去首帧和尾帧,得到需要随机选取的中间帧数量
- 正确构建帧序列:首帧 → 随机选取的中间帧(保持原时间顺序) → 尾帧
- 统一帧尺寸:确保所有写入的帧尺寸一致,避免视频编码错误
二、批量处理实现
同时重构代码,支持批量处理6个视频和5种帧率的组合,无需手动修改参数。
完整修复代码
import cv2 import os import random from os.path import isfile, join def extract_frames(video_path, output_dir): """从指定视频中提取所有帧,按时间命名保存到输出目录""" os.makedirs(output_dir, exist_ok=True) vidcap = cv2.VideoCapture(video_path) if not vidcap.isOpened(): print(f"无法打开视频:{video_path}") return frame_rate = vidcap.get(cv2.CAP_PROP_FPS) frame_interval = 1.0 / frame_rate sec = 0.0 success, image = vidcap.read() while success: # 格式化时间戳为两位小数,确保文件名排序正确 sec_str = f"{sec:.2f}" frame_path = os.path.join(output_dir, f"{os.path.basename(video_path).split('.')[0]}_frame_{sec_str}_sec.jpg") cv2.imwrite(frame_path, image) sec += frame_interval sec = round(sec, 2) success, image = vidcap.read() vidcap.release() print(f"已提取所有帧到:{output_dir}") def sort_frames_by_time(frame_files): """按帧的时间戳对文件列表排序""" def get_time_from_filename(filename): # 从文件名中提取时间戳部分 parts = filename.split("_") for part in parts: if "sec" in part: return float(part.replace("sec.jpg", "")) return 0.0 return sorted(frame_files, key=get_time_from_filename) def generate_target_video(frame_dir, output_video_path, target_fps): """从提取的帧中生成指定帧率的视频,固定首帧和尾帧""" # 获取所有帧文件并按时间排序 files = [f for f in os.listdir(frame_dir) if isfile(join(frame_dir, f))] sorted_files = sort_frames_by_time(files) if len(sorted_files) < 2: print("帧数量不足,无法生成视频") return # 获取首帧和尾帧 first_frame_path = os.path.join(frame_dir, sorted_files[0]) last_frame_path = os.path.join(frame_dir, sorted_files[-1]) first_frame = cv2.imread(first_frame_path) last_frame = cv2.imread(last_frame_path) if first_frame is None or last_frame is None: print("首帧或尾帧读取失败") return height, width = first_frame.shape[:2] size = (width, height) # VideoWriter需要(width, height)顺序,注意和shape的(height,width)相反 # 计算总帧数:目标帧率 × 原视频时长(0.98秒),取整 total_frames = round(target_fps * 0.98) # 中间需要的帧数:总帧数 - 首帧 - 尾帧 middle_frame_count = max(total_frames - 2, 0) # 从中间帧(排除首帧和尾帧)中随机选取对应数量的帧,再按时间排序 middle_files = sorted_files[1:-1] selected_middle_files = random.sample(middle_files, middle_frame_count) if middle_frame_count > 0 else [] selected_middle_files = sort_frames_by_time(selected_middle_files) # 构建帧数组:首帧 → 选中的中间帧 → 尾帧 frame_array = [first_frame] for file in selected_middle_files: img = cv2.imread(os.path.join(frame_dir, file)) if img is not None: frame_array.append(img) frame_array.append(last_frame) # 创建视频写入器 fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_video_path, fourcc, target_fps, size) if not out.isOpened(): print(f"无法创建视频文件:{output_video_path}") return # 写入所有帧 for frame in frame_array: out.write(frame) out.release() print(f"已生成视频:{output_video_path}") def batch_process(video_list, target_fps_list, base_output_dir="output_videos"): """批量处理多个视频和多个帧率组合""" os.makedirs(base_output_dir, exist_ok=True) for video_path in video_list: video_name = os.path.basename(video_path).split('.')[0] # 提取帧到临时目录 frame_dir = os.path.join("temp_frames", video_name) extract_frames(video_path, frame_dir) # 遍历每个目标帧率生成视频 for fps in target_fps_list: output_video_path = os.path.join(base_output_dir, f"{video_name}_{fps}fps.mp4") generate_target_video(frame_dir, output_video_path, fps) # 可选:处理完成后删除临时帧目录 # import shutil # shutil.rmtree("temp_frames") if __name__ == "__main__": # 配置需要处理的视频列表和目标帧率 VIDEO_LIST = [ "/work/rubbing_dooh_50fps.mp4", "/work/video2.mp4", "/work/video3.mp4", "/work/video4.mp4", "/work/video5.mp4", "/work/video6.mp4" ] TARGET_FPS = [5, 10, 20, 30, 40] batch_process(VIDEO_LIST, TARGET_FPS)
使用说明
- 修改
VIDEO_LIST为你的6个视频的完整路径 - 运行代码后,生成的视频会保存在
output_videos目录下,命名格式为原视频名_帧率fps.mp4 - 临时帧会保存在
temp_frames目录下,处理完成后可以手动删除,或者取消代码中删除目录的注释自动删除
内容的提问来源于stack exchange,提问作者neuronella
相关产品推荐
相关产品推荐

