OpenCV Android 之 VideoCapture类

2023-07-14 11:10:54 浏览数 (1)

1. 介绍

首先,需要明确一个根本问题。OpenCV 是一个基于 Apache2.0 许可(开源)发行的跨平台计算机视觉和机器学习软件库。它实际上各种图像处理和计算机视觉方面的通用算法的集中库。

简而言之就是:处理图片。

通常都是使用 OpenCV 来进行各种图片处理和计算。所以它并不是一个视频编解码库。不要想着使用 OpenCV 来进行视频播放

所有使用 OpenCV 进行播放视频,实际上都是将视频转图片了,再一张张图片在切换显示,编解码和效率是远远没有专门的视频播放器效率高的。

如果要播放视频,还是建议使用 FFmpeg 处理。

而我们可以通过OpenCV将视频进行解码成Mat文件,进行操作,并将编辑之后的结果存储为视频。

可以将相机拍摄的结果,进行实时处理之后。存储为视频等操作。

而使用到的就是VideoWriterVideoCapture类了。

以下内容基于:OpenCV 4.6.0 版本API进行的介绍和使用。

2. VideoCapture

用于从视频文件、图像序列或相机捕获视频的类。这个类提供了针对视频的各种捕获方法。

提供了几种方法:

1.获取每一帧数据,转为Mat。

2.获取视频的一些配置信息,例如时长,FPS,帧数,宽高等等。

初始化如下:

代码语言:javascript复制
 VideoCapture videoCapture = new VideoCapture(); //创建一个VideoCapture对象

我们其实在创建过程中的时候,也可以进行初始化传参。这些构造初始化时传的参数和调用open()方法传的参数实际是一样的。

PS:使用 OpenCV 的方法时,请注意需要提前进行初始化加载 OpenCV 库。否则会出现相关类找不到而崩溃

代码语言:javascript复制
OpenCVLoader.initDebug(false);//加载OpenCV库

2.1 加载 open() 方法

下面不管是相机加载,还是网络地址加载。我在 Android 端上没有成功。只有加载本地视频成功了。

加载摄像头应该是 Android 本身不支持的原因造成的。尝试了各种 cameraId 值和相关 apiPreference 都失败了。(我们可以使用CameraX加载摄像头并进行处理和存储)

加载网络视频失败我估计,应该是因为 openCV 默认编译的 Android SDK 中没有相关依赖造成的。

(如果是缺少依赖库造成的,希望能够有明白的小伙伴指点一下吧。各种尝试我都失败了)。

代码语言:javascript复制
 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_ANYVideoio.CAP_DSHOW,Videoio.CAP_ANDROID等.

VideoCapture 中传入的apiPrefreence的可选参数列表如下所示:

代码语言:javascript复制
 // 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。示例如下:

代码语言:javascript复制
boolean isOpen = videoCapture.open(fileUrl, Videoio.CAP_ANY); 

boolean isOpen = videoCapture.open(fileUrl, Videoio.CAP_ANDROID);

我有尝试过使用CAP_FFMPEG当做值,进行加载。

代码语言:javascript复制
 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格式的。

例如:获取当前帧:

代码语言:javascript复制
Mat m = new Mat();
videoCapture.read(m);
//我们就能够得到当前帧了。
//官方建议我们不要直接操作获取的Mat对象。我们可以进行拷贝之后再对Mat进行操作
Mat temp =m.clone()

除此之外,还有以下方法也可以获取当前帧:

代码语言:javascript复制
boolean isFrame =videoCapture.grab(); //从视频文件或捕获设备中抓取下一帧。抓取成功为true,否则为false
Mat tt =new Mat();
boolean isRetrieve =videoCapture.retrieve(tt); //解码并返回抓取的视频帧。如果没有帧返回false。

其实read()grab() retrieve()方法的合集。

grab()方法只是检测视频帧,不会解析视频帧。所以它速度比较快。

retrieve()方法会进行视频帧的解析。会比grab()方法更耗时。这两个方法通常都是一起使用的。

但是,大部分情况下都是使用read() 循环,遍历整个视频的所有帧,并进行处理。

代码语言:javascript复制
 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()方法获取视频的相关信息。

示例如下:

代码语言:javascript复制
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 值,该值的取值参数有如下:

代码语言:javascript复制
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了。

在我的实际使用过程中,大部分都是取不到真实数据。而宽高等数据,还得读取过一帧数据之后,才能取到值。

代码语言:javascript复制
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的相关使用吧。

0 人点赞