我的OpenCV4学习之旅(2)-图像数据的载入,显示与保存

我的OpenCV4学习之旅(2)-图像数据的载入,显示与保存

图像存储的容器 — Mat类

​ 数字图像在计算机中是以矩阵形式存储的,矩阵中的每一个元素都描述一定的图像信息,如亮度,颜色等

OpenCV提供了一个Mat类用于存储矩阵数据

Mat类介绍

​ 在OpenCV不断地版本更新中,OpenCV引入了C++接口,提供了Mat类用于存储数据,利用自动内存管理技术很好地解决了内存自动释放的问题,当内存不再需要时,立即释放内存

​ Mat类用来保存矩阵类型的数据信息,包括向量,矩阵,灰度或彩色图像等数据

Mat类的组成

  • 矩阵头
  • 指向存储数据的矩阵指针
矩阵头

​ 矩阵头中包含矩阵的尺寸,存储方法,地址和引用次数等。矩阵头的大小是一个常数,不会随着矩阵尺寸的大小而改变。在绝大多数的情况下,矩阵头大小远小于矩阵数据量的大小,因此图像复制和传递过程中主要的开销是存放矩阵数据

:在OpenCV中复制和传递图像时,只是复制了矩阵头和指向存储数据的指针

案例代码 - 创建Mat类
cv::Mat src; // 创建名为src的矩阵头
src = cv::imread("image.jpg"); // 向矩阵src中赋值图像数据,矩阵指针指向像素数据
cv::Mat item = src; // 复制矩阵头

注意:

  1. 矩阵头中引用次数标记了引用某个矩阵数据的次数,只有当矩阵数据引用次数为0的时候才会释放矩阵的数据
  2. 采用引用次数来释放存储内容是C++中常见的方式,用这种方式可以避免仍然有某个变量引用数据时将这个数据删除造成程序崩溃的问题,同时极大地缩短程序运行时所占用的内存

Mat类数据类型

​ Mat类可以存储的数据类型包括double,float,uchar,unsigned char,以及自定义的模板等
我的OpenCV4学习之旅(2)-图像数据的载入,显示与保存_第1张图片

OpenCV中的数据类型与取值范围
数据类型 具体类型 取值范围
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通道数据

通道数标识:

  • C1 — 单通道
  • C2 — 双通道
  • C3 — 3通道
  • C4 — 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标识可以省略

Mat类的构造与赋值

Mat类的构造

(1)利用默认构造函数

cv::Mat::Mat();

默认构造函数的方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型与大小,实现灵活的存储,常用于存储读取的图像数据

(2)根据输入矩阵尺寸和类型构造

cv::Mat::Mat(int rows,int cols,int type);
  • rows:构造矩阵的行数
  • cols:矩阵的列数
  • type:矩阵中存储的数据类型

(3)用Size()结构构造Mat类

cv::Mat::Mat(Size size(),int type);
  • size:二维数组变量尺寸,通过Size(cols,rows)进行赋值,列在前,行在后
  • 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);
  • m:已经构建完成的Mat类矩阵数据

**注:**这种构造方式只是复制了Mat类的矩阵头,矩阵指针指向的是同一个地址,因此,如果通过某一个Mat类变量修改了矩阵中的数据,那么另一个变量中的数据也会发生改变

**知识点:如果希望复制两个一模一样的Mat类而彼此之间不会受影响,那么可以使用image = item.clone()**实现

(5)构造已有Mat类的子类

cv::Mat::Mat(const Mat& m,const Range& rowRange,const Range& colRange=Range::all());
  • m:已经构建完成的Mat类矩阵数据
  • rowRange:在已有矩阵中需要截取的行数范围,是一个Range变量
  • colRange:在已有的矩阵中需要截取的列数范围,是一个Range变量,当不输入任何值时,表示所有列都会被截取

该方法主要用于在原图中截图使用

案例构造代码:

cv::Mat item1(image,Range(2,5),Range(2,5)); // 从image中截取部分数据构造item1
cv::Mat item2(image,Range(2,5)); // 默认最后一个参数构造item2
Mat类的赋值

(1)构造时赋值

cv::Mat::Mat(int rows,int cols,int type,const Scalar& s);
  • rows:矩阵的行数
  • cols:矩阵的列数
  • type:存储数据的类型
  • s:给矩阵中每个像素赋值的参数变量,例如Scalar(0,0,255)

案例赋值代码:

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);
  • eye():构建一个单位矩阵,前两个参数为矩阵的行数和列数,第三个参数为矩阵存放的数据类型与通道数。如果行数和列数不相等,则在矩阵的各个主对角位置处为1
  • diag():构建对角矩阵,其参数必须是Mat类型的一维变量,用来存放对角元素的数值
  • ones():构建一个全为1的矩阵,参数含义与eye()相同
  • zeros():构建一个全为0的矩阵,参数含义与eye()相同

(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类与常数运算时,可以直接通过加减乘除符号实现
  • 在对图像进行卷积运算时,需要两个矩阵进行乘法运算,OpenCV不但提供了两个Mat类矩阵的乘法运算,而且定义了两个矩阵的内积和对应位的乘法运算

Mat类元素的读取

​ 多通道的Mat类矩阵类似于三维数据,而计算机的存储空间是一个二维空间,因此Mat类矩阵在计算机中存储时是将三维数据变成二维数据,先存储第一个元素每个通道的数据,之后再存储第二个元素每个通道的数据

Mat类矩阵常用的属性

属性 作用
cols 矩阵的列数
rows 矩阵的行数
step 以字节为单位的矩阵的有效宽度
elemSize() 每个元素的字节数
total() 矩阵中元素的个数
channels() 矩阵的通道数
Mat矩阵类元素读取方式
  1. at方式进行读取
  2. 通过指针ptr进行读取
  3. 通过迭代器进行读取
  4. 通过矩阵元素的地址定位方式进行读取

(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];

知识点:

  • 多通道矩阵每一个元素坐标处都是多个数据,因此引入了一个变量(数据类型)用于表示同一个元素的多个数据
  • 在OpenCV中,针对三通道矩阵,定义了cv::Vec3b,cv::Vec3s,cv::Vec3w,cv::Vec3d,cv::Vec3f,cv::Vec3i共六种类型用于表示同一种元素的3个通道数据
  • 以上数据类型的命名规则:其中的数字表示通道的个数,最后一位是数据类型的缩写,而Vec则代表了vector向量
  • OpenCV也为2通道和4通道定义了对应的变量类型,其命名方式也遵循这个命名规则

(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));

图像的读取和显示

图像读取函数imread

cv::Mat cv::imread(const String& filename,int flags=IMREAD_COLOR);
  • filename:需要读取图像的文件名称,包含图像地址,名称和图像文件扩展名
  • flags:读取图像形式的标志,如将彩色图像按照灰度图读取,默认参数是按照彩色图像格式读取

该函数用于读取指定的图像并将其返回给一个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,这个要求在绝大多数图像处理领域是不受影响的,但是卫星遥感图像,超高分辨率图像的像素数目可能会超过这个阈值

图像窗口函数namedWindow

void cv::namedWindow(const String& winname,int flags = WINDOW_AUTOSIZE);
  • winname:窗口名称,用作窗口的标识符
  • flags:窗口属性设置标志

该函数会创建一个窗口变量,用于显示图像和滑动条,通过窗口的名称引用该窗口,如果在窗口创建时已经存在具有相同名称的窗口,则该函数不会执行任何操作。

创建一个窗口需要占用部分内存资源,因此,通过该函数创建窗口后,在不需要窗口时需要关闭窗口来释放内存资源

OpenCV的关闭窗口资源函数:

  • cv::destroyWindow() —— 用于关闭一个指定名称的窗口,即在括号内输入窗口名称的字符串即可将对应窗口关闭
  • cv::destroyAllWindows() —— 关闭程序中所有的窗口,一般用于程序的最后

图像窗口函数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 创建没有状态栏和工具栏的窗口

图像显示函数imshow

void cv::imshow(const String& winname,InputArray mat);
  • winname:要显示图像的窗口的名字,用字符串形式赋值
  • mat:要显示的图像矩阵

注意事项:

​ imshow函数运行后会继续执行后面的程序。这就意味着,如果后面的程序执行完后直接退出,那么显示的图像可能闪一下就消失,因此在需要显示图像的程序中,我们往往会在imshow()函数后跟有cv::waitKey()函数,用于将程序暂停一段时间

​ waitKey()函数是以毫秒计的等待时长,如果参数默认或者为‘0’,那么表示等待用户按键结束该函数

OpenCV显示图像的简单运用

(1)这是存储在D盘image文件夹中的图片

我的OpenCV4学习之旅(2)-图像数据的载入,显示与保存_第2张图片

(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;
}

(3)运行结果
我的OpenCV4学习之旅(2)-图像数据的载入,显示与保存_第3张图片

图像的保存

​ OpenCV提供imwrite()函数用于将Mat类矩阵保存成图像文件

bool cv::imwrite(const String& filename,InputArray img,const std::vector& params = std::vector());
  • filename:保存图像的地址和文件名,包含图像格式
  • img:将要保存的Mat类矩阵变量
  • params:保存图片格式属性设置标志

imshow()函数用于将Mat类矩阵保存成图像文件,如果成功保存,则返回true,否则返回false

可以保存的图像格式参考imread()函数能够读取的图像文件格式,通常使用该函数只能保存8位单通道图像和3通道BGR彩色图像

你可能感兴趣的:(OpenCV4,c++)