1. 介绍
首先,需要明确一个根本问题。OpenCV 是一个基于 Apache2.0 许可(开源)发行的跨平台计算机视觉和机器学习软件库。它实际上各种图像处理和计算机视觉方面的通用算法的集中库。
简而言之就是:处理图片。
通常都是使用 OpenCV 来进行各种图片处理和计算。所以它并不是一个视频编解码库。不要想着使用 OpenCV 来进行视频播放
所有使用 OpenCV 进行播放视频,实际上都是将视频转图片了,再一张张图片在切换显示,编解码和效率是远远没有专门的视频播放器效率高的。
如果要播放视频,还是建议使用 FFmpeg 处理。
而我们可以通过OpenCV将视频进行解码成Mat文件,进行操作,并将编辑之后的结果存储为视频。
可以将相机拍摄的结果,进行实时处理之后。存储为视频等操作。
而使用到的就是VideoWriter 和 VideoCapture类了。
以下内容基于:OpenCV 4.6.0 版本API进行的介绍和使用。
2. VideoCapture
用于从视频文件、图像序列或相机捕获视频的类。这个类提供了针对视频的各种捕获方法。
提供了几种方法:
1.获取每一帧数据,转为Mat。
2.获取视频的一些配置信息,例如时长,FPS,帧数,宽高等等。
初始化如下:
VideoCapture videoCapture = new VideoCapture(); //创建一个VideoCapture对象
我们其实在创建过程中的时候,也可以进行初始化传参。这些构造初始化时传的参数和调用open()方法传的参数实际是一样的。
PS:使用 OpenCV 的方法时,请注意需要提前进行初始化加载 OpenCV 库。否则会出现相关类找不到而崩溃
OpenCVLoader.initDebug(false);//加载OpenCV库
2.1 加载 open() 方法
下面不管是相机加载,还是网络地址加载。我在 Android 端上没有成功。只有加载本地视频成功了。
加载摄像头应该是 Android 本身不支持的原因造成的。尝试了各种 cameraId 值和相关 apiPreference 都失败了。(我们可以使用CameraX加载摄像头并进行处理和存储)
加载网络视频失败我估计,应该是因为 openCV 默认编译的 Android SDK 中没有相关依赖造成的。
(如果是缺少依赖库造成的,希望能够有明白的小伙伴指点一下吧。各种尝试我都失败了)。
boolean isOpen = videoCapture.open("/storage/emulated/0/Android/data/com.zinyan.demo/files/demo.mp4", Videoio.CAP_ANDROID); //加载本地视频
boolean isOpen = videoCapture.open(0); //加载摄像头
boolean isOpen = videoCapture.open("https://host:port/script_name?script_params|auth", Videoio.CAP_ANDROID); //加载网络视频。
open方法传递主要是以下一种参数:
- String filename:文件地址,可以是Url地址也可以是本地文档地址。
- int index:相机id, 如果0 会调用设备默认的后置摄像头。
- int apiPreference: api首选项。该参数为:Videoio.CAP_ANY,Videoio.CAP_DSHOW,Videoio.CAP_ANDROID等.
VideoCapture 中传入的apiPrefreence的可选参数列表如下所示:
// C++: enum VideoCaptureAPIs
public static final int
CAP_ANY = 0,
CAP_VFW = 200,
CAP_V4L = 200,
CAP_V4L2 = CAP_V4L,
CAP_FIREWIRE = 300,
CAP_FIREWARE = CAP_FIREWIRE,
CAP_IEEE1394 = CAP_FIREWIRE,
CAP_DC1394 = CAP_FIREWIRE,
CAP_CMU1394 = CAP_FIREWIRE,
CAP_QT = 500,
CAP_UNICAP = 600,
CAP_DSHOW = 700,
CAP_PVAPI = 800,
CAP_OPENNI = 900,
CAP_OPENNI_ASUS = 910,
CAP_ANDROID = 1000,
CAP_XIAPI = 1100,
CAP_AVFOUNDATION = 1200,
CAP_GIGANETIX = 1300,
CAP_MSMF = 1400,
CAP_WINRT = 1410,
CAP_INTELPERC = 1500,
CAP_REALSENSE = 1500,
CAP_OPENNI2 = 1600,
CAP_OPENNI2_ASUS = 1610,
CAP_GPHOTO2 = 1700,
CAP_GSTREAMER = 1800,
CAP_FFMPEG = 1900,
CAP_IMAGES = 2000,
CAP_ARAVIS = 2100,
CAP_OPENCV_MJPEG = 2200,
CAP_INTEL_MFX = 2300,
CAP_XINE = 2400;
调用open()方法后,如果加载成功了就会返回true,失败则返回false。
由于,我只是加载本地视频能够实现成功加载。所以下面的介绍也是基于该成功之后进行的。
在Android端中,如果想能够正确的打开视频并进行解析。apiPrefreence的值只有:
Videoio.CAP_ANY 或者 Videoio.CAP_ANDROID才能正确加载视频
返回的isOpen才是true。示例如下:
boolean isOpen = videoCapture.open(fileUrl, Videoio.CAP_ANY);
boolean isOpen = videoCapture.open(fileUrl, Videoio.CAP_ANDROID);
我有尝试过使用CAP_FFMPEG当做值,进行加载。
boolean isOpen = videoCapture.open(fileUrl, Videoio.CAP_FFMPEG);
//错误输出如下内容:
com.zinyan.demo E/cv::error(): OpenCV(4.6.0) Error: Requested object was not found (could not open directory: /data/app/com.zinyan.demo-Wr3nLeu2TTtG12e53ogTGw==/base.apk!/lib/arm64-v8a) in glob_rec, file /build/master_pack-android/opencv/modules/core/src/glob.cpp, line 267
应该是默认的OpenCV Android SDK中。并没有FFmpeg相关库。
所以想通过https或者rtsp等协议加载在线视频也失败。原因在于openCV 预编译的Android SDK中,并没有那么多第三方项目。可能是需要我们自己配置吧。
PS:自己配置编译,有点繁琐。我也没有进行过尝试。
当我们加载成功视频之后。就可以进行解析操作了。
2.2 解析 read(),grab()和retrieve()方法
这三个方法主要就是用来获取视频的每一帧的数据,并将帧数据转为Mat对象。
请注意哦,它们获取的Mat对象是BGR格式的。
例如:获取当前帧:
Mat m = new Mat();
videoCapture.read(m);
//我们就能够得到当前帧了。
//官方建议我们不要直接操作获取的Mat对象。我们可以进行拷贝之后再对Mat进行操作
Mat temp =m.clone()
除此之外,还有以下方法也可以获取当前帧:
boolean isFrame =videoCapture.grab(); //从视频文件或捕获设备中抓取下一帧。抓取成功为true,否则为false
Mat tt =new Mat();
boolean isRetrieve =videoCapture.retrieve(tt); //解码并返回抓取的视频帧。如果没有帧返回false。
其实read() 是grab()+retrieve()方法的合集。
grab()方法只是检测视频帧,不会解析视频帧。所以它速度比较快。
retrieve()方法会进行视频帧的解析。会比grab()方法更耗时。这两个方法通常都是一起使用的。
但是,大部分情况下都是使用read()+循环,遍历整个视频的所有帧,并进行处理。
while (videoCapture.read(mat)) {
Mat m = new Mat();
Imgproc.cvtColor(mat, m, Imgproc.COLOR_BGR2HSV_FULL);
}
read():方法返回的false时,代表视频已经没有下一帧了。也就是解析到最后一帧了。
通过循环的方式,可以快速的解析视频中的每一帧数据,并转为Mat进行处理。
注意,VideoCapture 在调用 read() 获取视频帧之后。一直获取到最后之后。不会回到第一帧获取。我们只能重新调用open()方法再次加载才行。
2.3 修改 set()和get()方法
我们除了可以遍历视频帧数据以外。还可以通过get()方法获取视频的相关信息。
示例如下:
double ftp = videoCapture.get(Videoio.CAP_PROP_FPS);
double width = videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);
double count = videoCapture.get(Videoio.CAP_PROP_FRAME_COUNT);
double htight = videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
这个方法要传入的是 propId 值,该值的取值参数有如下:
Videoio.CAP_PROP_POS_MSEC = 0,
Videoio.CAP_PROP_POS_FRAMES = 1,
Videoio.CAP_PROP_POS_AVI_RATIO = 2,
Videoio.CAP_PROP_FRAME_WIDTH = 3,
Videoio.CAP_PROP_FRAME_HEIGHT = 4,
Videoio.CAP_PROP_FPS = 5,
Videoio.CAP_PROP_FOURCC = 6,
Videoio.CAP_PROP_FRAME_COUNT = 7,
Videoio.CAP_PROP_FORMAT = 8,
Videoio.CAP_PROP_MODE = 9,
Videoio.CAP_PROP_BRIGHTNESS = 10,
Videoio.CAP_PROP_CONTRAST = 11,
Videoio.CAP_PROP_SATURATION = 12,
Videoio.CAP_PROP_HUE = 13,
Videoio.CAP_PROP_GAIN = 14,
Videoio.CAP_PROP_EXPOSURE = 15,
Videoio.CAP_PROP_CONVERT_RGB = 16,
Videoio.CAP_PROP_WHITE_BALANCE_BLUE_U = 17,
Videoio.CAP_PROP_RECTIFICATION = 18,
Videoio.CAP_PROP_MONOCHROME = 19,
Videoio.CAP_PROP_SHARPNESS = 20,
Videoio.CAP_PROP_AUTO_EXPOSURE = 21,
Videoio.CAP_PROP_GAMMA = 22,
Videoio.CAP_PROP_TEMPERATURE = 23,
Videoio.CAP_PROP_TRIGGER = 24,
Videoio.CAP_PROP_TRIGGER_DELAY = 25,
Videoio.CAP_PROP_WHITE_BALANCE_RED_V = 26,
Videoio.CAP_PROP_ZOOM = 27,
Videoio.CAP_PROP_FOCUS = 28,
Videoio.CAP_PROP_GUID = 29,
Videoio.CAP_PROP_ISO_SPEED = 30,
Videoio.CAP_PROP_BACKLIGHT = 32,
Videoio.CAP_PROP_PAN = 33,
Videoio.CAP_PROP_TILT = 34,
Videoio.CAP_PROP_ROLL = 35,
Videoio.CAP_PROP_IRIS = 36,
Videoio.CAP_PROP_SETTINGS = 37,
Videoio.CAP_PROP_BUFFERSIZE = 38,
Videoio.CAP_PROP_AUTOFOCUS = 39,
Videoio.CAP_PROP_SAR_NUM = 40,
Videoio.CAP_PROP_SAR_DEN = 41,
Videoio.CAP_PROP_BACKEND = 42,
Videoio.CAP_PROP_CHANNEL = 43,
Videoio.CAP_PROP_AUTO_WB = 44,
Videoio.CAP_PROP_WB_TEMPERATURE = 45,
Videoio.CAP_PROP_CODEC_PIXEL_FORMAT = 46,
Videoio.CAP_PROP_BITRATE = 47;
但是,我们很多时候使用上面的关键字进行获取的数据,结果值都是0
这是因为 openCV 使用的解析器在获取视频时,如果正确获取了相关配置项参数就会返回具体指。如果没有正确获取就会返回0了。
在我的实际使用过程中,大部分都是取不到真实数据。而宽高等数据,还得读取过一帧数据之后,才能取到值。
videoCapture.set(int propId, double value)
而set()方法,就是将这些配置信息修改到 VideoCapture 中。
如果在open()方法中调用的解码器支持的话。就可以将这些配置信息添加到解码器中。进行生效了。
我们如果只是单纯调用 openCV 的 API。那么set()方法使用空间不大了。
2.4 关闭 release()
当我们遍历完毕,可以调用release()方法 关闭文件的加载。释放内存。
同时底层C++代码中的相关方法也会进行释放。
3. 小结
总的来说,我们可以使用VideoCapture进行视频帧的遍历,并在遍历过程中对每一帧数据进行编辑修改操作。
我们如果想使用 openCV 对视频每一帧进行操作之后,再存储为视频。那么就还需要结合VideoWriter 进行存储。
默认情况下Android下,是可以实现视频的每一帧获取,并修改然后存储为新的视频文件的。
通过这些方法可以实现,例如视频添加水印,背景替换,黑白转换等等。图片能实现的一些编辑操作都可以通过获取每一帧,处理完毕后。再将每一帧存储为视频来实现。
openCV 官网说明文档:
https://docs.opencv.org/4.6.0/d4/d15/groupvideoioflags__base.html#ga023786be1ee68a9105bf2e48c700294d
下一篇简单介绍下VideoWriter的相关使用吧。