一、图像存储器

OpenCV提供了一个Mat类用于存储矩阵数据。Mat类用来保存矩阵类型的数据信息,包括向量、矩阵、灰度或彩色图像等数据。

Mat类分为矩阵头和指向存储数据的矩阵指针的两部分。矩阵头中包含矩阵的尺寸、存储方法、地址和引用次数。矩阵头的大小是一个常数,不随着矩阵尺寸的大小而改变。

创建Mat类:

Mat a;    //创建一个名为a的矩阵头
a = imread("j2.png");//向a中赋值图像数据,矩阵指针指向像素数据
Mat b = a;    //复制矩阵头,并命名为b

从上面的程序可以看出,虽然a和b有各自的矩阵头,但是其矩阵指针指向的矩阵数据是相同的,通过任意一个矩阵头修改矩阵中的数据,另一个矩阵头指向的数据也会随之发生改变。但是,当删除a变量时,b变量并不会指向一个空数据,因为矩阵头中引用次数标记了引用某个矩阵数据的次数,只有当矩阵数据引用次数为0时才会释放矩阵数据。

Mat类可以存储的数据类型包括:double、float、uchar、unsigned char以及自定义的模板等。

声明一个指定类型的Mat类:

Mat A = Mat_<double>(3,3);//创建一个3*3的矩阵用于存放double类型数据

opencv中的数据类型:

数据类型

具体类型

CV_8U

8位无符号整数

CV_8S

8位符号整数

CV_16U

16位无符号整数

CV_16S

16位符号整数

CV_32S

32位符号整数

CV_32F

32位浮点整数

CV_64F

64位浮点整数

在Opencv中,如果用8位无符号整数存储16位图像,会造成严重的图像颜色失真或造成数据的错误。对于图像来说,只有数据类型是不够的,还需要定义图像数据的通道数,例如灰度图像数据是单通道数据,彩色图像数据是3通道或者4通道数据。因此,opencv定义了通道数标识,C1、C2、C3、C4分别表示单通道、双通道、三通道和四通道。

Mat a(640,480,CV_8UC3);//创建一个640*480的3通道矩阵用于存储彩色图像
Mat b(3,3,CV_8UC1);//创建一个3*3的8位无符号整数的单通道矩阵

1、Mat的赋值

(1)构造时赋值

这种方法是在构造的同时进行赋值,需要注意的是赋值的位数要等于通道数

Mat (int rows
     int cols
     int type
     const Scalar & s 
)
//rows:矩阵的行数
//cols:矩阵的列数
//type:存储数据的类型
//s:给矩阵中每个像素赋值的参考变量

Mat a(2,2,CV_8UC3,Scalar(0,0,255));//创建一个3通道矩阵,每个像素都是0,0,255

(2)、枚举法赋值

这种赋值方式是将矩阵中的所有元素一一列举,并用数据流的形式赋值给Mat类。

Mat a =(Mat_<int>(3,3) << 1,2,3,4,5,6,7,8,9);

(3)、利用数组赋值

float a[8] = {5,6,7,8,1,2,3,4};
Mat b = Mat b = Mat(2,2,CV_32FC2,a);

2、Mat类支持的运算

Mat类变量支持矩阵的加减乘除运算,也可以实现Mat类矩阵的內积和对应位乘法。

需要注意的是,当两个Mat类变量进行加减运算时,必须保证两个矩阵中的数据类型是相同的。

3、Mat类元素的读取

Mat类矩阵在计算机中存储的方式:先存储第一个元素的每个通道的数据,之后再存储第二个元素每个通道的数据。每一行的元素都按照这种方式进行存储。

Mat类矩阵常用的属性:

属性

作用

cols

矩阵的列数

rows

矩阵的行数

step

以字节为单位的矩阵的有效宽度

elemSize()

每个元素的字节数

total()

矩阵中元素的个数

channels()

矩阵的通道数

(1)通过at方法读取Mat类矩阵的元素

Mat类数据的读取分为单通道和多通道。

单通道:

Mat a = (Mat_<uchar>(3,3) << 1,2,3,4,5,6,7,8,9);
int value = (int)a.at<uchar>(0,0);

通过at方法读取元素需要在后面跟上“<数据类型>”,如果此处的数据类型与矩阵的数据类型不同,就会出现因数据类型不匹配而产生的报错信息。该方法以坐标的形式给出需要读取的元素坐标(行,列)。

多通道:

在opencv中,针对三通道矩阵,定义了Vec3b、Vec3s、Vec3w、Vec3d、Vec3f、Vec3i共六种类型用于表示同一个元素的3个通道数据;定义中的数字表示通道的个数,最后一位是数据类型的缩写。 b是uchar类型的缩写,s是short类型的缩写,w是ushort类型的缩写,d是double类型的缩写,f是float类型的缩写,i是int 类型的缩写。相应的二通道和四通道的定义也是类似的。

Mat b(3,4,CV_8UC3,Scalar(0,0,1));
Vec3b vc3 = b.at<Vec3b>(0,0);
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];

(2)通过指针ptr读取Mat类矩阵中的元素

Mat b(3,4,CV_8UC3,Scalar(0,0,1));
for(int i=0; i< b.rows ;i++)
{
    uchar* ptr = b.ptr<uchar>(i);
    for(int j=0 ; j< b.cols*b.channels() ; j++)
    {
        cout << (int)ptr[j] << endl;
    }
}

也可以直接通过给出行数和指针后移的位数进行访问,如读取第2行和第3个数据时,可以用a.ptr<uchar>(1)[2]的形式直接访问。

(3)通过迭代器访问Mat类矩阵的元素

Mat类变量同时也是一个容器变量,因此,Mat类变量拥有迭代器。

MatIterator_<uchar> it = a.begin<uchar>();
MatIterator_<uchar> it_end = a.end<uchar>();
for(int i=0 ; it != it_end ; it++)
{
    cout << (int)(*it) << " ";
    if((++i% a.cols) == 0)
    {
        cout << endl;
    }
}

二、图像的读取与显示

1、图像读取函数imread()

函数原型:

imread(const String & filename ,

             int  flags = IMREAD_COLOR

            )

filename:需要读取图像的文件名称,包括图像地址、名称和图像文件扩展名

flags:读取图像形式的标志,如将彩色图像按照灰度图读取、默认参数是按照彩色图像格式读取

函数用于读取指定的图像并将其返回给一个Mat类变量,当图像文件不存在、破损或者格式不受支持时,则无法读取图像,此时函数返回一个空矩阵,因此可以根据返回 矩阵的data属性是否为空或者empty()函数是否为真来判断是否成功读取图像,如果图像读取失败data属性返回值为0,empty()函数返回值为1。

函数读取图像形式参数见下表:

标志参数

简记

作用

IMREAD_UNCHANGED

-1

按照原图像读取,保留Alpha通道(第四通道)

IMREAD_GRAYSCALE

0

将图像转换成单通道灰度图像后读取

IMREAD_COLOR

1

将图像转换成3通道BGR彩色图像

IMREAD_ANYDEPTH

2

保留原图像的16位、32位深度,不声明该参数则转成8位读取

IMREAD_ANYCOLOR

4

以任何可能的颜色读取图像

IMREAD_LOAD_GDAL

8

使用gdal驱动程序加载图像

IMREAD_REDUCED_GRAYSCALE_2

16

将图像转换为单通道灰度图像,尺寸缩小1/2.可以修改最后一位数字实现缩小1/4和1/8(最后一位数组为4和8)

IMREAD_REDUCED_COLOR_2

17

将图像转换为3通道彩色图像,尺寸缩小1/2.可以修改最后一位数字实现缩小1/4和1/8(最后一位数组为4和8)

IMREAD_IGNORE_ORIENTATION

128

不以EXIF的方向旋转图像

 

这些标志参数在功能不冲突的情况下可以同时声明多个,不同参数之间用“ | ”隔开。

2、图像窗口函数 namedWindow()

函数原型:
namedWindow(const String & winname

                         int  flags = WINDOW_AUTOSIZE

                        )

winname:窗口名称,用作窗口的标识符

flags :窗口属性设置标志

该函数会创建一个窗口变量,用于显示图像和滑动条,通过窗口的名称引用该窗口,如果在创建窗口时已经存在具有相同名称的窗口,则该函数不会执行任何操作。创建窗口需要占用内存资源,所以在不需要窗口的时候需要关闭窗口来释放内存资源。OpenCV提供了两个关闭窗口的函数:destroyAllWindows()和destroyWindows(),第一个函数是关闭所有窗口,第二个函数关闭指定的窗口。

函数窗口属性标志参数见下表:

标志参数

简记

作用

WINDOW_NORMAL

0x00000000

显示图像后,允许用户随意调整窗口大小

WINDOW_AUTOSIZE

0x00000001

根据图像大小显示窗口,不允许用户调整大小

WINDOW_OPENGL

0x00001000

创建窗口的时候支持OpenGL

WINDOW_FULLSCREEN

1

全屏显示窗口

WINDOW_FREERATIO

0x00000100

调整图像尺寸以充满窗口

WINDOW_KEEPRATIO

0x00000000

保持图像的比例

WINDOW_GUI_EXPANDED

0x00000000

创建的窗口允许添加工具栏和状态栏

WINDOW_GUI_NORMAL

0x00000010

创建没有状态栏和工具栏的窗口

 

默认情况下,函数加载的标志参数为:“WINDOW_AUTOSIZE | WINDOW_KEEPRATIO | WINDOW_GUI_EXPANDED”

3、图像显示函数imshow()

函数原型:

imshow(const String & winname

              InputArray   mat

             )

winname:要显示窗口的名字,用字符串形式赋值

mat:要显示的图像矩阵

该函数会在指定的窗口中显示图像。如果在此函数之前没有创建同名的窗口,就会以WINDOW_AUTOSIZE标志创建一个窗口,显示图像的原始大小;如果创建了图像窗口,那么会缩放图像以适应窗口属性。该函数会根据图像的深度将其缩放,缩放规则如下:

                                   (1)如果图像是8位无符号类型,那么按照原样显示

                                   (2)如果图像是16位无符号类型或者32位整数类型,那么会将像素除以256,将范围由[0,255x256]映射到[0,255]

                                   (3)如果图像是32位或64位浮点类型,那么将像素乘以255,将范围由[0,1]映射到[0,255]

三、视频加载与摄像头调用

1、视频数据的读取VideoCapture()

函数的构造有两种方式:

(1)VideoCapture();

(2)VideoCapture(const String & filename ,

                        int apiPreference = CAP_ANY

                     )

filename :读取的视频文件或者图片系列名称

apiPreference :读取数据时设置的属性,例如编码格式等

第一种方式只是声明了一个能读取视频数据的类,具体读取什么视频文件,需要在使用时通过open()函数指出,例如video.open("1.avi")

第二种方式在构造函数时声明变量并将视频数据赋值给变量。读取图像序列需要将多个图像的名称统一为“前缀+数字”的形式,通过“前缀+%02d”的形式调用(稍后会在代码中体现),也可以使用isOpened()函数判断是否读取成功,成功返回true,失败返回false。

当我们需要使用视频中的图像时,需要将图像由VideoCapture类变量里导出到Mat类变量里,用于后期数据处理,该操作可以通过“>>”运算符将图像按照视频顺序由VideoCapture类变量赋值给Mat类变量,当视频中的所有图像赋值完成后,再次赋值的Mat类变量会变为空矩阵,因此可以通过empty()函数判断是否完成操作。

VideoCapture类变量同时提供了可以查看视频属性的get()函数。

get()方法中的标志参数如下表:

标志参数

简记

作用

CAP_PROP_POS_MSEC

0

视频文件的当前位置(以毫秒为单位)

CAP_PROP_FRAME_WIDTH

3

视频流中图像的宽度

CAP_PROP_FRAME_HEIGHT

4

视频流中图像的高度

CAP_PROP_FPS

5

视频流中图像的帧率(每秒帧数)

CAP_PROP_FOURCC

6

编解码器的4字符代码

CAP_PROP_FRAME_COUNT

7

视频流中图像的帧数

CAP_PROP_FORMAT

8

返回的Mat对象的格式

CAP_PROP_BRIGHTNESS

10

图像的亮度(仅适用于支持的相机)

CAP_PROP_CONTRAST

11

图像对比度(仅适用于相机)

CAP_PROP_SATURATION

12

图像饱和度(仅适用于相机)

CAP_PROP_HUE

13

图像的色调(仅适用于相机)

CAP_PROP_GAIN

14

图像的增益(仅适用于支持的相机)

2、摄像头的调用VideoCapture()

函数形式:

VideoCapture( int  index ,

                         int  apiPerference = CAP_ANY

                          )

index :要打开的摄像头设备的ID

其他的内容和上面的一样。

举例如下:

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;

int main()
{
	
	/***********************************读取视频********************************/
	
	system("color F0");//更改输出界面颜色
	VideoCapture video("D:/image/j%02d.jpg");	//读取多个图片	//VideoCapture video("E:/OpenCV/learnOpenCV4/data/cup.mp4");//读取视频
	if (video.isOpened())//判断视频读取是否成功
	{
		cout << "视频中图像的宽度=" << video.get(CAP_PROP_FRAME_WIDTH) << endl;
		cout << "视频中图像的高度=" << video.get(CAP_PROP_FRAME_HEIGHT) << endl;
		cout << "视频帧数=" << video.get(CAP_PROP_FPS) << endl;
		cout << "视频总帧数=" << video.get(CAP_PROP_FRAME_COUNT) << endl;
		
	}
	else
	{
		cout << "请输入正确的视频" << endl;
		return -1;
	}
	while (1)
	{
		Mat frame;
		video >> frame;//将图片按照视频顺序由VideoCapture类变量赋值给Mat类变量
		if (frame.empty())//判断是否是否所有图像都已经读取完毕
		{
			break;
		}
		imshow("video", frame);
		waitKey(2000);
	}
	waitKey(0);

/***********************************读取摄像头视频********************************/
/*
	system("color F0");//更改输出界面颜色
	VideoCapture video(0);	//读取摄像头的视频	
	if (video.isOpened())//判断视频读取是否成功
	{
		cout << "视频中图像的宽度=" << video.get(CAP_PROP_FRAME_WIDTH) << endl;
		cout << "视频中图像的高度=" << video.get(CAP_PROP_FRAME_HEIGHT) << endl;
		cout << "视频帧数=" << video.get(CAP_PROP_FPS) << endl;
		cout << "视频总帧数=" << video.get(CAP_PROP_FRAME_COUNT) << endl;
	}
	else
	{
		cout << "请输入正确的视频" << endl;
		return -1;
	}
	while (1)
	{
		Mat frame;
		video >> frame;//将图片按照视频顺序由VideoCapture类变量赋值给Mat类变量
		if (frame.empty())//判断是否是否所有图像都已经读取完毕
		{
			break;
		}
		imshow("video", frame);
		waitKey(1000 / video.get(CAP_PROP_FPS));
	}
	waitKey(0);*/
	destroyAllWindows();
	return 0;

}

四、数据保存

1、图像的保存imwrite()

函数原型:

imwrite( const String & filename,

             InPutArray img,

             Const std::vector<int>& params = std::vector<int>()

            )

filename:保存图像的地址和文件名,包含图像格式

img:将要保存的Mat类矩阵变量

params :保存图片格式属性设置标志

该函数用于将Mat类矩阵保存成图像文件,成功返回true,失败返回false。

不同图像格式能够保存的图像位数如下:

16位无符号(CV_16U)图像可以保存PNG、JPEG、TIFF格式文件

32位浮点(CV_32F)图像可以保存成PFM、TIFF、OpenEXR和RadianceHDR格式文件

4通道(Alpha通道)图像可以保存成PNG格式文件

该函数的第三个参数在一般情况下不需要填写,保存成指定的文件格式只需要直接在第一个参数后面更改文件后缀就可以了,但是当需要保存的Mat类矩阵中数据比较特殊时,则需要设置第三个参数。

第三个参数设置方式:

vector<int> compression_params;
compression_params.push_back(IMWRITE_PNG_COMPRESSION);//png格式图像压缩标志
compression_params.push_back(9);	//设置最高压缩质量
imwrite(filename , img,compression_params);

第三个参数课选择的标志见下表:

标志参数

简记

作用

IMWRITE_JPEG_QUALITY

1

保存成JPEG格式的文件的图片质量,分成0~100等级,默认95

IMWRITE_JPEG_PROGRESSIVE

2

增强JPEG格式,启用为1,默认值为0

IMWRITE_JPEG_OPTIMIZE

3

对JPEG格式进行优化,启用为1,默认值为0

IMWRITE_JPEG_LUMA_QUALITY

5

JPEG格式文件单独的亮度质量等级,分成0~100,默认为0

IMWRITE_JPEG_CHROMA_QUALITY

6

JPEG格式文件单独的色度质量等级,分成0~100,默认为0

IMWRITE_PNG_COMPRESSION

16

保存成PNG格式文件压缩级别,0~9,值越大意味着更小的尺寸和更长的压缩时间,默认为1(最佳速度设置)

IMWRITE_TIFF_COMPRESSION

259

保存成TIFF格式文件压缩方案

举例:

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;


void AlphaMat(Mat &mat)
{
	CV_Assert(mat.channels() == 4);//CV_Assert()若括号中的表达式值为false,则返回一个错误信息
	for (int i = 0; i < mat.rows; ++i)
	{
		for (int j = 0; j < mat.cols; ++j)
		{
			Vec4b& bgra = mat.at<Vec4b>(i, j);
			bgra[0] = UCHAR_MAX;	//蓝色通道
			bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols) * UCHAR_MAX);//绿色通道
			bgra[2] = saturate_cast<uchar>((float(mat.rows - j)) / ((float)mat.rows) * UCHAR_MAX);//红色通道
			bgra[3] = saturate_cast<uchar>(05 * (bgra[1] + bgra[2]));	//Alpha通道
		}
	}
}
int main()
{
	//create mat with alpha channel
	Mat mat(480, 640, CV_8UC4);
	AlphaMat(mat);
	vector<int> compression_params;
	compression_params.push_back(IMWRITE_PNG_COMPRESSION);//png格式图像压缩标志
	compression_params.push_back(9);	//设置最高压缩质量
	bool result = imwrite("D:/image/alpha.png" , mat , compression_params);//bool布尔型,只有一个字节true或者false
	if (!result)
	{
		cout << "保存成PNG失败" << endl;
		return -1;
	}
	cout << "保存成PNG成功" << endl;
	return 0;

}

2、视频的保存VideoWriter()

函数的构造有两种方式:

(1)VideoWriter();

(2)VideoWriter(const String & filename ,

                               int  fource,

                              double fps,

                              Size frameSize,

                              bool isColor=true

                     )

filename :保存视频的地址和文件名,包含视频格式

fource:压缩帧的4字符编解码器代码

fps:保存视频的帧率,即视频中每秒图像的张数

frameSize:视频帧的尺寸

isColor:保存视频是否为彩色视频

第一种构造函数的使用方法与VideoCapture() 相同,都是创建一个用于保存视频的数据流,后续通过open()函数设置保存文件名称、编解码器、帧数等。

第二种构造函数的用法和VideoCapture()的第二种方式创建的函数相同 ,可以使用isOpened()函数判读是否成功创建一个视频流,可以通过get()查看视频流中的属性。

在保存视频时,需要将生成视频的图像一帧一帧地通过“<<”操作符(或者write()函数)赋值给视频流,最后使用release()关闭视频流。

视频编码格式:

OpenCV4.1版本标志

作用

VideoWriter::fourcc('D', 'I', 'V', 'X')

MPEG-4编码

VideoWriter::fourcc('P', 'I', 'M', '1')

MPEG-1编码

VideoWriter::fourcc('M', 'J', 'P', 'G')

JPEG编码

VideoWriter::fourcc('M', 'P', '4', '2')

MPEG-4.2编码

VideoWriter::fourcc('D', 'I', 'V', '3')

MPEG-4.3编码

VideoWriter::fourcc('U', '2', '6', '3')

H263编码

VideoWriter::fourcc('I', '2', '6', '3')

H263I编码

VideoWriter::fourcc('F', 'L', 'V', '1')

FLV1编码

举例:

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat img;	
	VideoCapture video(0);  //使用某个摄像头
	//读取视频
	//VideoCapture video;
	//video.open("D:/image/j%02d.jpg");  

	if (!video.isOpened())  // 判断是否调用成功
	{
		cout << "打开摄像头失败,请确实摄像头是否安装成功";
		return -1;
	}

	video >> img;  //获取图像
				   //检测是否成功获取图像
	if (img.empty())   //判断有没有读取图像成功
	{
		cout << "没有获取到图像" << endl;
		return -1;
	}
	bool isColor = (img.type() == CV_8UC3);  //判断相机(视频)类型是否为彩色

	VideoWriter writer;
	int codec = VideoWriter::fourcc('M', 'J', 'P', 'G');  // 选择编码格式
														  //OpenCV 4.0版本设置编码格式
														  //int codec = CV_FOURCC('M', 'J', 'P', 'G'); 

	double fps = 0.5;  //设置视频帧率 
	string filename = "D:/image/live.avi";  //保存的视频文件名称
	writer.open(filename, codec, fps, img.size(), isColor);  //创建保存视频文件的视频流

	if (!writer.isOpened())   //判断视频流是否创建成功
	{
		cout << "打开视频文件失败,请确实是否为合法输入" << endl;
		return -1;
	}

	while (1)
	{
		//检测是否执行完毕
		if (!video.read(img))   //判断能都继续从摄像头或者视频文件中读出一帧图像
		{
			cout << "摄像头断开连接或者视频读取完成" << endl;
			break;
		}
		writer.write(img);  //把图像写入视频流
							//writer << img;
		imshow("Live", img);  //显示图像
		char c = waitKey(1000);
		if (c == 27)  //按ESC案件退出视频保存
		{
			break;
		}
	}
	// 退出程序时刻自动关闭视频流
	//video.release();
	//writer.release();	
	return 0;
}