Python-OpenCV录制H264编码的MP4视频

10,240 阅读6分钟

前言

因最近项目需求涉及计算机视觉相关内容,需要实现在Python录制视频,并且录制完成后可在浏览器前端中进行视频回放的功能;特写下此篇文章以记录整体实现过程。

2019-08-02 更新

之前一直在忙别的事,没有继续深入探究,这篇文章也暂时搁置了;但是最近发现之前的实现方式(录制avi视频后由Java调用FFmpeg转换为mp4)会影响到系统的性能,原因为调用FFmpeg转换视频时CPU占用较高QAQ,于是在此前的基础上继续寻找解决方式。

降低FFmpeg的CPU占用

既然FFmpeg的CPU占用较高,那么我们首先尝试如何降低对CPU的占用,搜索发现可以在FFmpeg命令中添加-threads参数来指定CPU的使用

FFmpeg转换测试

此次测试均使用相同avi视频文件,大小为113

1. 原始转换命令
ffmpeg -i test.avi -vcodec libx264 -f mp4 test.mp4
# 转换用时 30s~31s
# CPU占用 950%~1000%
2. 添加-threads 6参数
ffmpeg -i test.avi -threads 6 -vcodec libx264 -f mp4 test.mp4
# 转换用时 45s~46s
# CPU 占用490%~550%
3. 添加-threads 2参数
ffmpeg -i test.avi -threads 2 -vcodec libx264 -f mp4 test.mp4
# 转换用时 87s~88s
# CPU占用 205%~230%

可以看出,添加-threads参数后CPU的占用确实少了,但相应的视频转换耗时也增加了,显然这不是我们想要的效果;所以还是逃避不了录制H264视频的问题

编译安装OpenCV录制视频

之前一直无法录制H264编码的MP4视频是因为使用的为pip安装的opencv-python,这个库中自带FFmpeg,所以不论我们如何折腾系统的FFmpeg都不会有任何作用;如果我们想要调用系统的FFmpeg则需要手动编译安装OpenCV。具体原因可以参考下图:

如何编译安装OpenCV就不过多叙述了,这也不是此篇文章的重点,但还是给懒癌患者放个链接吧! Ubuntu16.04 install OpenCV with ffmpeg

编译安装后import cv2正常引入即可,代码就不放了,原文和网上都有,只是改个fourcc。

至此在Python中调用OpenCV录制H264编码的MP4视频已经可以实现,没有特殊需求的同学看到这里就可以了~撒花!

使用vidgear库录制视频

因为项目原因我们还不能使用手动编译的OpenCV(WTF!!!),所以不得不继续寻找解决方案QAQ

vidgear-github官方链接,这个方案已经脱离主题,只是由于项目原因而采用,在此就不过多叙述了,感兴趣的同学可以看一下。

以下内容为原文


Python-OpenCV录制视频

环境

python 3.7.1
opencv-python 3.4.4.19

引入库支持

import cv2

调用摄像头

入参传入“0”、“1”、“2”等数字为摄像头索引,0为自带摄像头,可按顺序调用摄像头,也可传入视频文件路径

cap = cv2.VideoCapture(0)

获取摄像头宽高

width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)

使用摄像头帧率录制视频后播放存在快进情况,暂时写死在VideoWriter中 不知道是否与摄像头有关,此处未进行深入了解 fps = cap.get(cv2. CV_CAP_PROP_FPS)

指定视频编解码

需要传入fourcc(four character code)四字符编解码代码: fourcc参考

encode = cv2.VideoWriter_fourcc(*'mp4v')

初始化VideoWriter

入参参考:官方文档

out = cv2.VideoWriter( './test.mp4', encode, 10, (width, height), True)

获取图像帧并写入视频文件

  • 循环从摄像头/视频中获取单帧图像
  • 新开一个窗口展示图像帧,每隔25毫秒播放下一帧,键入“q”跳出循环
  • 将图像帧写入视频文件
while True:
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break
    ret, frame = cap.read()
    cv2.imshow('test', frame)
    out.write(frame)

释放资源

  • 释放VideoWriter
  • 释放摄像头
  • 关闭窗口
out.release()
cap.release()
cv2.destroyAllWindows()

完整代码

此处代码为演示demo,仅供参考

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import cv2

# 调用摄像头
cap = cv2.VideoCapture(0)
# 获取摄像头宽高
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 获取摄像头帧率
#fps = cap.get(cv2.CAP_PROP_FPS)
# 指定fourcc编解码
encode = cv2.VideoWriter_fourcc(*'mp4v')
# 初始化VideoWriter
out = cv2.VideoWriter('./test.mp4', encode, 10, (width, height), True)
while True:
    # 每隔25毫秒播放下一帧,若键入“q”跳出循环
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break
    # 从摄像头获取下一帧
    ret, frame = cap.read()
    # 新开窗口展示图像
    cv2.imshow('test', frame)
    # 将当前帧写入视频文件
    out.write(frame)
# 释放VideoWriter
out.release()
# 释放摄像头
cap.release()
# 关闭窗口
cv2.destroyAllWindows()

浏览器中播放视频

环境

macOS Mojave 10.14.3
Ubuntu 16.04
vue 2.9.6
nginx 1.15.5
前端为vue项目,打包后部署在nginx,配置server块/location块提供图片/视频等静态资源访问

h5中video无法播放视频问题

问题排查
  1. 代码错误
    • python录制视频是否成功
    • 前端中video的src是否正确
  2. 网络请求
    • 浏览器控制台是否报错
    • nginx服务是否启动
    • 请求路径是否正确
    • 是否跨域问题
  3. 浏览器支持
格式 IE Firefox Opera Chrome Safari
Ogg - 3.5+ 10.5+ 5.0+ -
MPEG 4 9.0+ - - 5.0+ 3.0+
WebM - 4.0+ 10.6+ 6.0+ -
  1. 视频编解码
格式 视频编码 音频编码
Ogg Theora Vorbis
MPEG 4 H.264 AAC
WebM VP8 Vorbis

问题定位

排除代码及网络请求问题后,可以将问题定位在浏览器,我使用的浏览器为Chrome,排除版本问题,因此可以确定是视频编解码问题,在python中录制视频时未使用H.264编解码:

encode = cv2.VideoWriter_fourcc(*'mp4v')

查看视频简介可以发现该视频也确实非H.264编解码,因此造成该视频可以在视频播放软件中正常播放却无法在h5的video中播放,见下图:

视频编解码-MP4V.jpg

尝试更改fourcc重新录制视频

encode = cv2.VideoWriter_fourcc(*'X264')

报错信息.jpg
貌似不支持这个编解码QAQ,好像需要FFmpeg的库,Ubuntu下在终端输入:

$sudo apt-get install ffmpeg x264 libx264-dev

安装完成后Ubuntu上无法录制(视频文件都无法生成),但是在我自己的电脑不影响录制:

视频编解码-X264.jpg

Java使用FFmpeg转换视频

因暂时未能实现录制H.264编解码的MP4视频,所以采用迂回战术:在python中录制.avi格式视频后,前端请求后台,在java中使用FFmpeg将.avi格式视频转换为.mp4格式视频

首先安装FFmpeg (Ubuntu下我没有安装,好像是自带的?) macOS安装FFmpeg Ubuntu安装FFmpeg java这边就不再详述了,直接上代码~(同样为演示demo,仅供参考)

    // FFmpeg转换命令
    String transferCommand = "ffmpeg -i filePath/fileName.avi -vcodec libx264 -f mp4 filePath/fileName.mp4";
    Process process = Runtime.getRuntime().exec("/bin/bash");
    printWriter = new PrintWriter(new BufferedWriter(new 
    OutputStreamWriter(process.getOutputStream())), true);
    printWriter.println(transferCommand);
    // 这个命令必须执行,否则in流不结束。
    printWriter.println("exit");
    printWriter.close();
    process.waitFor();

转换过程需要些许时间,采取方案为启一条线程完成视频转换,不影响当前接口响应时间,在用户无感知的情况下完成视频转换。

总结

以上内容为本次实现过程记录,代码均为演示demo,非实际应用代码,如有需要可根据实际需求加以调整。因为时间原因未能在录制H.264视频上投入过多精力,可能未来会继续尝试~

如有疑问或遇到类似需求可以留言或私信我~
如能实现录制.264视频或有更优解决方案也请不吝赐教~

参考链接

video不能播放mp4的问题

什么是python OpenCV中mp4视频的编解码器

FFmpeg限制CPU的使用率

opencv-python-issues

vidgear-github