数字图像在计算机中是以矩阵形式存储的,矩阵中的每一个元素都描述一定的图像信息,如亮度,颜色等
OpenCV提供了一个Mat类用于存储矩阵数据
在OpenCV不断地版本更新中,OpenCV引入了C++接口,提供了Mat类用于存储数据,利用自动内存管理技术很好地解决了内存自动释放的问题,当内存不再需要时,立即释放内存
Mat类用来保存矩阵类型的数据信息,包括向量,矩阵,灰度或彩色图像等数据
Mat类的组成:
矩阵头中包含矩阵的尺寸,存储方法,地址和引用次数等。矩阵头的大小是一个常数,不会随着矩阵尺寸的大小而改变。在绝大多数的情况下,矩阵头大小远小于矩阵数据量的大小,因此图像复制和传递过程中主要的开销是存放矩阵数据
注:在OpenCV中复制和传递图像时,只是复制了矩阵头和指向存储数据的指针
cv::Mat src; // 创建名为src的矩阵头
src = cv::imread("image.jpg"); // 向矩阵src中赋值图像数据,矩阵指针指向像素数据
cv::Mat item = src; // 复制矩阵头
注意:
Mat类可以存储的数据类型包括double,float,uchar,unsigned char,以及自定义的模板等
数据类型 | 具体类型 | 取值范围 |
---|---|---|
CV_8U | 8位无符号整数 | 0~255 |
CV_8S | 8位符号整数 | -128~127 |
CV_16U | 16位无符号整数 | 0~65536 |
CV_16S | 16位符号整数 | -32768~32767 |
CV_32S | 32位符号整数 | -2147483648~2147483647 |
CV_32F | 32位浮点整数 | -FLT_MAX~FLT_MAX,INF,NAN |
CV_64F | 64位浮点整数 | -DBL_MAX~DBL_MAX,INF,NAN |
OpenCV图像通道数:
灰度图像 —— 单通道数据
彩色图像 —— 3通道或者4通道数据
通道数标识:
总结:因为每一种数据类型都存在多个通道的情况,所以将数据类型与通道数表示结合便得到了OpenCV中对图像数据类型的完整定义,例如:CV_8UC1表示8位单通道数据,用于表示8位灰度图
cv::Mat image = Mat_(3,3); // 创建一个3*3的矩阵用于存放double类型数据
cv::Mat image(640,480,CV_8UC3); // 创建一个640*480的3通道矩阵用于存放彩色图像
cv::Mat image(3,3,CV_8UC1); // 创建一个3*3的8位无符号整数的单通道矩阵
cv::Mat image(3,3,CV_8U); // 创建单通道矩阵,C1标识可以省略
(1)利用默认构造函数
cv::Mat::Mat();
默认构造函数的方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型与大小,实现灵活的存储,常用于存储读取的图像数据
(2)根据输入矩阵尺寸和类型构造
cv::Mat::Mat(int rows,int cols,int type);
(3)用Size()结构构造Mat类
cv::Mat::Mat(Size size(),int type);
案例构造代码:
cv::Mat image1(Size(480,640),CV_8UC1); // 构造一个行为640,列为480的单通道矩阵
cv::Mat image2(Size(480,640),CV_32FC3); // 构造一个行为640,列为480的3通道矩阵
(4)利用已有的矩阵构造
cv::Mat::Mat(const Mat& m);
**注:**这种构造方式只是复制了Mat类的矩阵头,矩阵指针指向的是同一个地址,因此,如果通过某一个Mat类变量修改了矩阵中的数据,那么另一个变量中的数据也会发生改变
**知识点:如果希望复制两个一模一样的Mat类而彼此之间不会受影响,那么可以使用image = item.clone()**实现
(5)构造已有Mat类的子类
cv::Mat::Mat(const Mat& m,const Range& rowRange,const Range& colRange=Range::all());
该方法主要用于在原图中截图使用
案例构造代码:
cv::Mat item1(image,Range(2,5),Range(2,5)); // 从image中截取部分数据构造item1
cv::Mat item2(image,Range(2,5)); // 默认最后一个参数构造item2
(1)构造时赋值
cv::Mat::Mat(int rows,int cols,int type,const Scalar& s);
案例赋值代码:
cv::Mat image1(2,2,CV_8UC3,cv::Scalar(0,0,255)); // 创建一个3通道矩阵,每个像素都是0,0,255
cv::Mat image2(2,2,CV_8UC2,cv::Scalar(0,255)); // 创建一个2通道矩阵,每个像素都是0,255
cv::Mat image3(2,2,CV_8UC1,cv::Scalar(255)); // 创建一个单通道矩阵,每个像素都是255
注:Scalar结构中变量的个数一定要与定义中的通道数相对应。如果Scalar结构中变量的个数大于通道数,则位置在大于通道数之后的数值将不会被读取;如果Scalar结构中变量的个数小于通道数,则会以0补充
(2)枚举法赋值
将矩阵中的所有元素一一例举,并用数据流的形式赋值给Mat类
cv::Mat image1 = (cv::Mat_(3,3) << 1,2,3,4,5,6,7,8,9);
cv::Mat image2 = (cv::Mat_(2,3) << 1.0,2.1,3.2,4.5,5.6,6.7);
**注:**在采用枚举法时,输入的数据个数一定要与矩阵元素个数相同
(3)循环法赋值
与通过枚举法赋值方法相似,循环法赋值也是对矩阵中的每一个元素进行赋值,但是可以不在声明变量的时候进行赋值,而且可以对矩阵中的任意部分进行赋值
cv::Mat c = cv::Mat_(3,3); // 定义一个3*3的矩阵
for(int i=0;i(i,j) = i+j;
}
(4)类方法赋值
在Mat类里,提供了可以快速赋值的方法,可以初始化指定的矩阵
cv::Mat image1 = cv::Mat::eye(3,3,CV_8UC1);
cv::Mat image2 = (cv::Mat_(1,3) << 1,2,3);
cv::Mat image3 = cv::Mat::diag(image2);
cv::Mat image4 = cv::Mat::ones(3,3,CV_8UC1);
cv::Mat image5 = cv::Mat::zero(4,2,CV_8UC3);
(5)利用数组进行赋值
这种赋值方式首先将需要存入Mat类中的变量存入一个数组中,之后通过设置Mat类矩阵的尺寸和通道将数组变量拆分成矩阵,这种拆分方式可以自由定义矩阵的通道数
float a[8] = { 1,2,3,4,5,6,7,8 };
cv::Mat b = cv::Mat(2,2,CV_32FC2,a);
cv::Mat c = cv::Mat(2,4,CV_32FC1,a);
多通道的Mat类矩阵类似于三维数据,而计算机的存储空间是一个二维空间,因此Mat类矩阵在计算机中存储时是将三维数据变成二维数据,先存储第一个元素每个通道的数据,之后再存储第二个元素每个通道的数据
Mat类矩阵常用的属性
属性 | 作用 |
---|---|
cols | 矩阵的列数 |
rows | 矩阵的行数 |
step | 以字节为单位的矩阵的有效宽度 |
elemSize() | 每个元素的字节数 |
total() | 矩阵中元素的个数 |
channels() | 矩阵的通道数 |
(1)通过at方法读取Mat类矩阵中的元素
通过at方法读取矩阵元素分为针对单通道的读取方法和针对多通道的读取方法
读取Mat类单通道矩阵元素:
cv::Mat image = (cv::Mat_(3,3) << 1,2,3,4,5,6,7,8,9);
int value = (int)image.at(0,0);
通过at方法读取元素需要在后面跟上"< 数据类型 >",如果此处的数据类型与矩阵定义时的数据类型不相同,就会出现因数据类型不匹配而产生的报错信息
该方法以坐标的形式给出需要读取的元素坐标(行数,列数)
读取Mat类多通道矩阵元素:
cv::Mat image(3,4,CV_8UC3,cv::Scalar(0,0,1));
cv::Vec3b vc3 = image.at(0,0);
int first = (int)vec3.val[0];
int second = (int)vec3.val[1];
int third = (int)vec3.val[2];
知识点:
(2)通过指针ptr读取Mat类矩阵中的元素
Mat类矩阵在内存中的存放方式:矩阵的每一行中的每一个元素都是紧挨着存放的,如果找到每一行元素的起始地址位置,那么读取矩阵中每一行不同位置的元素将指针在起始位置向后移动若干位即可
cv::Mat image(3,4,CV_8UC3,cv::Scalar(0,0,1));
for(int i=0;i(i);
for(int j=0;j
(3)通过迭代器访问Mat类矩阵中的元素
Mat类变量同时也是一个容器变量,因此,Mat类变量拥有迭代器,用于访问Mat类变量中的数据,通过迭代器可以实现对矩阵中每一个元素的遍历
cv::MatIterator_ it = image.begin();
cv::MatIterator_ it_end = image.end();
for(int i=0;it!=it_end;it++) {
cout<<(int)(*it)<
(4)通过矩阵元素的地址定位访问元素
(int)(*(image.data + image.step[0]*row + image.step[1]*col + channel));
cv::Mat cv::imread(const String& filename,int flags=IMREAD_COLOR);
该函数用于读取指定的图像并将其返回给一个Mat类变量,当图像文件不存在,破损或者格式不受支持时,则无法读取图像,此时该函数返回一个空矩阵
我们可以通过imread函数返回矩阵的data属性是否为空或者调用Mat类的empty()函数是否为真来判断是否成功读取图像
imread()函数读取图像形式参数
标志参数 | 简记 | 作用 |
---|---|---|
IMREAD_UNCHANGED | -1 | 按照图像原样读取,保留Alpha通道(第4通道) |
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 |
IMREAD_REDUCED_COLOR_2 | 17 | 将图像转成3通道彩色图像,尺寸缩小1/2。可以更改最后一位数字实现缩小1/4或1/8 |
IMREAD_IGNORE_ORIENTATION | 128 | 不以EXIF的方向旋转图像 |
注意事项:
在默认情况下,读取图像的像素数目必须小于2^30,这个要求在绝大多数图像处理领域是不受影响的,但是卫星遥感图像,超高分辨率图像的像素数目可能会超过这个阈值
void cv::namedWindow(const String& winname,int flags = WINDOW_AUTOSIZE);
该函数会创建一个窗口变量,用于显示图像和滑动条,通过窗口的名称引用该窗口,如果在窗口创建时已经存在具有相同名称的窗口,则该函数不会执行任何操作。
创建一个窗口需要占用部分内存资源,因此,通过该函数创建窗口后,在不需要窗口时需要关闭窗口来释放内存资源
OpenCV的关闭窗口资源函数:
图像窗口函数namedWindow()的第一个参数是声明窗口的名称,用于窗口的唯一识别。第二个参数是声明窗口的属性,主要用于设置窗口的大小是否可调,显示的图像是否充满窗口,在默认情况下,函数加载的标志参数为"WINDOW_AUTOSIZE | WINDOW_KEEPRATIO | WINDOW_GUI_EXPANED"
namedWindow()函数窗口属性标志参数
标志参数 | 作用 |
---|---|
WINDOW_NORMAL | 显示图像后,允许用户随意调整窗口大小 |
WINDOW_AUTOSIZE | 根据图像大小显示窗口,不允许用户调整大小 |
WINDOW_OPENGL | 创建窗口的时候会支持OpenGL |
WINDOW_FULLSCREEN | 全屏显示窗口 |
WINDOW_FREERATIO | 调整图像尺寸以充满窗口 |
WINDOW_KEEPRATIO | 保持图像的比例 |
WINDOW_GUI_EXPANDED | 创建的窗口允许添加工具栏和状态栏 |
WINDOW_GUI_NORMAL | 创建没有状态栏和工具栏的窗口 |
void cv::imshow(const String& winname,InputArray mat);
注意事项:
imshow函数运行后会继续执行后面的程序。这就意味着,如果后面的程序执行完后直接退出,那么显示的图像可能闪一下就消失,因此在需要显示图像的程序中,我们往往会在imshow()函数后跟有cv::waitKey()函数,用于将程序暂停一段时间
waitKey()函数是以毫秒计的等待时长,如果参数默认或者为‘0’,那么表示等待用户按键结束该函数
(1)这是存储在D盘image文件夹中的图片
(2)使用我们之前所学的函数将其显示出来
#include
#include
using namespace cv;
using namespace std;
int main()
{
Mat src = imread("D:/images/cat.jpg"); // 获取D盘images文件夹下的cat.jpg
namedWindow("输出窗口", WINDOW_FREERATIO); // 创建窗口变量,窗口属性标签为调整图像尺寸以充满窗口
imshow("输出窗口", src); // 调用imshow函数显示图像
waitKey(0); // 等待函数,参数为0表示等待用户按键结束该函数
destroyAllWindows(); // 关闭程序中所有的窗口,一般用于程序的最后
return 0;
}
OpenCV提供imwrite()函数用于将Mat类矩阵保存成图像文件
bool cv::imwrite(const String& filename,InputArray img,const std::vector& params = std::vector());
imshow()函数用于将Mat类矩阵保存成图像文件,如果成功保存,则返回true,否则返回false
可以保存的图像格式参考imread()函数能够读取的图像文件格式,通常使用该函数只能保存8位单通道图像和3通道BGR彩色图像