MATLAB+opencv人脸疲劳检测
我们可以通过多种方式从现实世界中获取数字图像,比如:数码相机、扫描仪、计算机扫描和磁共振成像等等。在这些情况中,虽然我们肉眼看到的是图像,但是当需要将图像在数字设备中变换传输时,图像的每个像素则对应一个数值。
例如,上述图像你可以看到一个汽车的后视镜,它可以用包含像素点强度值的矩阵来表示。虽然获取并存储像素点强度值的方法各不相同,但是图像在计算机中最终是以数值矩阵的形式来存储和处理的。OpenCV是一个计算机视觉库,主要用于处理和操作图像像素矩阵信息。因此,你首先需要熟悉OpenCV是如何存储和处理图像的。
Mat
OpenCV诞生于2001年。当时的库均是C语言接口创建,图像以C 语言的数据结构IplImage形式存储。在以往的教程和培训材料中均可以看到这一点,而这同时也反映出了C语言的优缺点。最大的挑战来自于手动的内存管理,它假设由用户来负责内存的分配和释放。对于代码量比较小的程序,手动分配内存没有问题。但是当代码量剧增时,处理起来的难度会急速增大。
幸运的是,C ++引入了类的概念,使得用户更容易实现自动内存管理。同时,C ++与C完全兼容,所以不存在兼容性问题,这也使代码迁移更加容易。OpenCV 2.0引入了一种新的C++接口,无需考虑内存管理问题,使得代码更加简洁,即利用更少的代码量,能够实现更多的功能。C ++接口的主要缺点是:目前许多嵌入式开发系统只支持C。因此,除非是针对嵌入式平台,否则没有必要使用的以往的老方法(除非你是受虐狂程序员,在自找麻烦)。
在使用Mat 时,需要知道的第一件事情是:无需手动分配内存。对于不再使用的内存,无需进行释放。大多数OpenCV函数会自动分配其输出数据。更为便捷的是,如果需要传递Mat 对象,则已经给它分配好了内存空间,可以被重用。换句话说,在任何时候都只用到执行任务所需要的内存,而无需进行额外的操作。
Mat基础类包含两部分数据:矩阵头(包括矩阵大小,存储方法,矩阵存储地址等信息)和包含像素矩阵值的指针(根据所选定的不同存储方法而有不同的维度)。矩阵头大小是一个常量,不同大小的图像的矩阵大小各不相同,通常矩阵大小要比图像大小大几个数量级。
OpenCV是一个图像处理库,其中包含大量图像处理函数。为了解决计算难题,多数情况下选用库中的多个函数来实现计算功能,常见的做法是将图像传递给函数。而图像处理算法的计算量往往非常大,所以要通过避免不必要的图像复制来进一步提升程序的运行速度。
为了解决上述问题, OpenCV采用了一种引用计数系统。具体做法是,每个Mat 对象有其各自的头,两个Mat 对象可以通过将矩阵指针指向同一地址来共享一个矩阵,复制操作只复制Mat 头和指向矩阵的指针,而不是复制数据本身。
上述所有的对象均指向同一个数据矩阵,对矩阵的任何变动均会影响所有的对象。在实际示例中,不同的对象只是对同一数据的不同的访问方式,尽管如此,不同MAT 对象的头各不相同。真正有趣的是,可以创建仅指向部分数据分段的MAT 对象头。例如,为了创建一个图像的感兴趣区域(ROI),你只需要创建一个具有行列边界的新MAT 对象头:
那么问题来了 - 如果像素矩阵可以属于多个MAT 对象, 那么当它不需要再次被使用时,由谁来负责清空?答案是:采用引用计数机制来进行处理, 由最后一个使用它的对象来清空。每次拷贝MAT 对象头时,计数器便会加一;当对MAT 对象头进行清空时,此计数器会减一。当计数器的值为零时,矩阵会被释放。当需要对矩阵自身进行复制时, OpenCV提供cv::Mat::clone() 和cv::Mat::copyTo() 函数。
修改F或者G不会影响指向A MAT 对象头的矩阵,需要记住以下几点:
- 自动为OpenCV函数分配输出图像(除非另行规定);
- 无需考虑OpenCV中C ++接口的内存管理;
- 赋值操作符和拷贝构造函数仅复制MAT 对象头;
- 图像的基本矩阵可以利用cv::Mat::clone() 和cv::Mat::copyTo()两个函数进行复制。
存储方法
本小节的内容是关于如何存储像素值的。首先要做的是选择色彩空间和使用的数据类型。色彩空间是指为给定的颜色编码的色彩分量组合,最简单的色彩空间是灰度,它的颜色空间只有黑色和白色,这两种颜色可以组合出多种灰度。
为了使图像色彩更加丰富,色彩空间可以有更多的选择。每个色彩空间均可将其分解为三到四个基本颜色,利用这三到四个基本颜色的组合来生成其它颜色。主流的色彩空间是RGB,人类的眼睛正是利用RGB构建出多种色彩。其基本的颜色是红色、绿色和蓝色,为了对透明度进行编码,添加了第四个元素:α(A)。
除了RGB外,还有多种其它的色彩空间,它们各有优势:
- RGB是与人眼类似的最为常见的色彩空间,但需要切记的是:OpenCV标准显示系统使用的色彩空间是BGR(互换了红色和蓝色通道);
- HSV和HLS将色彩分解为色调、饱和度和亮度分量,利用这些分量,可以更加自然地对色彩进行描述。例如,去除最后一个分量之后,算法便对输入图像的光照条件不太敏感了;
- JPEG图像格式使用的是YCrCb色彩空间;
- CIE L * A * B *是一个感知均匀的色彩空间,可以用它来测量给定的颜色到另一种颜色之间的距离。
在OpenCV中,每个组件都有其各自的有效域,即采用何种数据类型。组件的存储方式定义了对其有效域的控制方式。最小的数据类型是char,即一个字节或8比特位,char可以是无符号的(值从0到255)或带符号的(值从-127到+127)。在三元组色彩空间(如RGB 色彩空间),可以表示出1600万种颜色。此外,还可以通过使用float数据类型(4字节= 32比特位)或double数据类型(8字节= 64比特位),对色彩实现更为精细的控制和表达。但是,增加组件数据类型长度的同时也会增加整个图片占用的内存大小。
创建Mat对象
在调用、修改和保存图像教程中,你已经学会了如何利用cv::imwrite()函数 将矩阵写入图像文件之中。然而,出于调试的目的,为了更加方便地看到矩阵的实际值,可以直接使用<<运算符。注意,这仅适用于二维矩阵。
作为图像的容器,Mat 的表现相当不错,它同时也是一个通用矩阵类。为此,它还可以创建和操作多维矩阵。你可以用多种方式来创建Mat对象:
- cv::Mat::Mat 构造器
对于二维和多通道图像,首先定义出大小:按行和列计数。
然后指定用于存储元素的数据类型和每个矩阵点的通道数。按照以下约定,构造出多个定义:
例如,CV_8UC3是指:使用的是8位长的unsigned char数据类型,每个像素具有三个通道,可以预定义多达四个通道。cv::Scalar是一个包含四个元素的短矢量。可以通过指定cv::Scalar,将所有的矩阵点初始化为自定义的值。如果需要创建更多的图像矩阵,还可以利用宏来创建,按照下述方式,设置括号中的通道数目:
- 构造C / C ++数组,并对其进行初始化
上述例子说明了如何创建一个维数大于2维的矩阵。首先指定其维数大小,然后传递一个包含每个维度大小的指针,其余部分保持不变。
不能用这种结构初始化矩阵值,如果新的矩阵大小与先前的矩阵大小不一致,会对矩阵数据的存储重新进行分配。
- MATLAB风格初始化: cv::Mat::zeros , cv::Mat::ones , cv::Mat::eye 。指定矩阵大小和数据类型:
- 对于比较小的矩阵,可以使用逗号分隔初始化值或初始化值列表(支持C++ 11):
- 为现有的Mat对象创建一个新的Mat对象头:cv::Mat::clone or cv::Mat::copyTo。
注
可以利用CV :: RANDU()函数,为矩阵写入随机值,此时需要给定随机值的下限和上限:
输出格式化
在上面的例子中,可以看到默认的格式设置选项。OpenCV允许对矩阵输出进行格式化:
- 默认
MATLAB
逗号分隔值 (CSV)
NumPy
C
其他常见的输出
OpenCV中提供了通过<<操作输出常见的OpenCV数据结构:
二维Point
三维Point
cv::Mat的std::vector
std::vector Point