使用FFmpeg/CV2将NumPy帧转MP4时多数编码格式失效问题排查
NumPy图像帧转MP4视频损坏问题
问题描述
我有4个np.uint8格式的NumPy图像帧,需转换为MP4文件。使用mpv4编码可正常转换,但压缩率过高导致画质严重损失。尝试h264、h265、av1、vp9等多种编码格式及.mkv、.avi容器,采用cv2.VideoWriter、skvideo.io.FFmpegWriter、ffmpeg-python三种工具转换,生成的视频均出现完全或部分损坏(约末尾1/4帧异常)。用VLC查看无报错,Windows自带播放器有时可正常读取。
所用代码
skvideo实现代码
def to_lossless_mp4(img_list: np.array, target_path): height, width, _ = img_list[0].shape writer = skvideo.io.FFmpegWriter(target_path, inputdict={ '-framerate': str(FPS), '-f': 'rawvideo', '-s': '{}x{}'.format(width, height) }, outputdict={ '-vcodec': 'libx265', # h.264 codec '-crf': '0', '-preset': 'veryslow', # 速度越慢压缩效果越好 '-framerate': str(FPS), }) for i, image in enumerate(img_list): # 部分图像为灰度图,需转为三通道保证帧尺寸一致 if len(image.shape) < 3: image = np.stack([image] * 3, axis=2) image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) writer.writeFrame(image) writer.close()
OpenCV实现代码
def to_mp4(img_list: np.array, target_path): height, width, _ = img_list[0].shape if OVERRIDE or not os.path.exists(target_path): out = cv2.VideoWriter(target_path, cv2.VideoWriter_fourcc(*'x265'), FPS, (width, height)) for image in img_list: if len(image.shape) < 3: image = np.stack([image] * 3, axis=2) image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) out.write(image) out.release()
ffmpeg-python实现代码
def to_mp4(fn, images, framerate=1, vcodec='libx264'): if not isinstance(images, np.ndarray): images = np.asarray(images) n, height, width, channels = images.shape process = ( ffmpeg .input('pipe:', format='rawvideo', pix_fmt='rgb24', s='{}x{}'.format(width, height)) .output(fn, pix_fmt='yuv420p', vcodec=vcodec, r=framerate) .run_async(pipe_stdin=True) .overwrite_output() ) for frame in images: if len(frame.shape) < 3: frame = np.stack([frame] * 3, axis=2) process.write( frame .astype(np.uint8) .tobytes() ) process.stdin.close() process.wait()
FFmpeg输出日志(中文翻译)
[00:00<?, ?it/s]ffmpeg版本 2.7 版权所有 (c) 2000-2015 FFmpeg开发者 编译时使用gcc 4.9.2 (GCC) 配置参数: --enable-gpl --enable-version3 --disable-w32threads --enable-avisynth --enable-bzlib --enable-fontconfig --enable-frei0r --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libdcadec --enable-libfreetype --enable-libgme --enable-libgsm --enable-libilbc --enable-libmodplug --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libschroedinger --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-aacenc --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-lzma --enable-decklink --enable-zlib libavutil 54. 27.100 / 54. 27.100 libavcodec 56. 41.100 / 56. 41.100 libavformat 56. 36.100 / 56. 36.100 libavdevice 56. 4.100 / 56. 4.100 libavfilter 5. 16.101 / 5. 16.101 libswscale 3. 1.101 / 3. 1.101 libswresample 1. 2.100 / 1. 2.100 libpostproc 53. 3.100 / 53. 3.100 [rawvideo @ 00000000051767c0] 流 #0: 帧数不足无法估算码率;考虑增大probesize参数 输入 #0, rawvideo, 来自 'pipe:': 时长: N/A, 起始时间: 0.000000, 码率: 75497 kb/s 流 #0:0: 视频: rawvideo (BGR[24] / 0x18524742), bgr24, 2048x1536, 75497 kb/s, 1 tbr, 1 tbn, 1 tbc x265 [info]: HEVC编码器版本 1.7 x265 [info]: 编译信息 [Windows][GCC 4.9.2][64位] 8bpp x265 [info]: 使用CPU特性: MMX2 SSE2Fast SSSE3 SSE4.2 AVX AVX2 FMA3 LZCNT BMI2 x265 [info]: 主 profile, Level-5 (主 tier) x265 [info]: 创建了包含16个线程的线程池 x265 [info]: 帧线程数 / 池特性 : 5 / wpp(24行) x265 [info]: 编码QT: 最大CU尺寸, 最小CU尺寸 : 64 / 8 x265 [info]: 残差QT: 最大TU尺寸, 最大深度 : 32 / 1 帧间 / 1 帧内 x265 [info]: 运动估计 / 搜索范围 / 亚像素精度 / 合并模式 : hex / 57 / 2 / 2 x265 [info]: 关键帧最小/最大/场景切换阈值 : 1 / 250 / 40 x265 [info]: 前瞻帧数 / B帧数量 / B帧自适应 : 20 / 4 / 2 x265 [info]: B帧金字塔 / P帧加权 / B帧加权 / 参考帧数量: 1 / 1 / 0 / 3 x265 [info]: 自适应量化(AQ): 模式 / 强度 / 量化组尺寸 / CU树 : 1 / 1.0 / 64 / 1 x265 [info]: 码率控制 / qCompress : CRF-24.0 / 0.60 x265 [info]: 工具: rd=3 psy-rd=0.30 signhide tmvp strong-intra-smoothing x265 [info]: 工具: deblock sao 输出 #0, mp4, 输出到 'C:datamerged_datatestsubsubsub2022-10-12_11-03-44_3583968045500ns_io.mp4': 元数据: encoder : Lavf56.36.100 流 #0:0: 视频: hevc (libx265) ([35][0][0][0] / 0x0023), yuv420p, 2048x1536, q=2-31, 1 fps, 16384 tbn, 1 tbc 元数据: encoder : Lavc56.41.100 libx265 流映射: 流 #0:0 -> #0:0 (rawvideo (native) -> hevc (libx265)) frame= 4 fps=0.0 q=0.0 Lsize= 761kB time=00:00:02.00 bitrate=3117.4kbits/s video:759kB audio:0kB subtitle:0kB 其他流:0kB 全局头:1kB 封装开销: 0.236524% x265 [info]: I帧数量: 2, 平均QP:13.55 kb/s: 2078.50 x265 [info]: P帧数量: 1, 平均QP:15.08 kb/s: 1560.14 x265 [info]: B帧数量: 1, 平均QP:18.28 kb/s: 502.67 x265 [info]: 全局统计: 4, 平均QP:15.11 kb/s: 1554.95 x265 [info]: 加权P帧占比: Y:0.0% UV:0.0% x265 [info]: 连续B帧分布: 66.7% 33.3% 0.0% 0.0% 0.0%
解决方向
- 升级FFmpeg版本:当前使用的FFmpeg 2.7是2015年的旧版本,libx265也仅为1.7,旧版本编码器或封装器可能存在小帧数视频的兼容性问题,建议升级到最新稳定版。
- 调整编码器参数:
- 对libx265添加
'-x265-params': 'keyint=1',强制每帧都是关键帧,避免B/P帧依赖导致的末尾帧解码异常。 - 添加
'-bf': '0'关闭B帧,减少帧间依赖,降低小帧数视频损坏概率。
- 对libx265添加
- 校验帧数据一致性:确保所有帧的尺寸、通道数完全一致,避免因帧格式不统一引发封装错误。
- 显式指定像素格式:编码时明确输入输出像素格式匹配,比如输入为BGR24时对应正确的输出格式,避免格式转换过程中的异常。
内容的提问来源于Stack Exchange,提问作者Hi_its_me
相关产品推荐
相关产品推荐

