OpenCV学习笔记(二)——初始Mat类

前言:

     在计算机内存中,数字图像以矩阵的形式存储和运算,比如,在MatLab中,图像读取之后对应一个矩阵,在OpenCV中,同样也是如此。

    在早期的OpenCV1.x版本中,图像的处理是通过IplImage(该名称源于Intel的另一个开源库Intel Image Processing Library ,缩写成IplImage)结构来实现的。早期的OpenCV是用C语言编写,因此提供的借口也是C语言接口,其源代码完全是C的编程风格。IplImage结构是OpenCV矩阵运算的基本数据结构。

    到OpenCV2.x版本,OpenCV开源库引入了面向对象编程思想,大量源代码用C++重写,Mat类 (Matrix的缩写) 是OpenCV用于处理图像而引入的一个封装类。从功能上讲,Mat类在IplImage结构的基础上进一步增强,并且,由于引入C++高级编程特性,Mat类的扩展性大大提高,Mat类的内容在后期的版本中不断丰富,如果你查看Mat类的定义的话(OpenCV3.1\sources\modules\core\include\opencv2\core\mat.hpp),会发现其设计实现十分全面而具体,基本覆盖计算机视觉对于图像处理的基本要求。

    接下来我们学习OpenCV的Mat类,并学习如何对Mat对象进行一些常见的操作。

一、Mat的常见属性

Mat对象的常用函数如下:

  • data  uchar型的指针。Mat类分为了两个部分:矩阵头和指向矩阵数据部分的指针,data就是指向矩阵数据的指针。
  • dims 矩阵的维度,例如5*6矩阵是二维矩阵,则dims=2,三维矩阵dims=3.
  • rows  矩阵的行数
  • cols   矩阵的列数
  • size 矩阵的大小,size(cols,rows),如果矩阵的维数大于2,则是size(-1,-1)
  • channels 矩阵元素拥有的通道数,例如常见的彩色图像,每一个像素由RGB三部分组成,则channels = 3
  • type 
    表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值: 
    CV_8UC1CV_8UC2CV_8UC3CV_8UC4
    CV_8SC1CV_8SC2CV_8SC3CV_8SC4
    CV_16UC1CV_16UC2CV_16UC3CV_16UC4
    CV_16SC1CV_16SC2CV_16SC3CV_16SC4
    CV_32SC1CV_32SC2CV_32SC3CV_32SC4
    CV_32FC1CV_32FC2CV_32FC3CV_32FC4
    CV_64FC1CV_64FC2CV_64FC3CV_64FC4
    这里U(unsigned integer)表示的是无符号整数,S(signed integer)是有符号整数,F(float)是浮点数。 
    例如:CV_16UC2,表示的是元素类型是一个16位的无符号整数,通道为2. 
    C1,C2,C3,C4则表示通道是1,2,3,4 
    type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用下面的depth
  • depth 
    矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。depth也是一系列的预定义值, 
    将type的预定义值去掉通道信息就是depth值: 
    CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F
  • elemSize 
    矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes
  • elemSize1 
    矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16  / 8 = 2 bytes = elemSize  / channels

二、图像读取

首先我们看一下测试照片,我们编写代码,读取测试图片,并将其显示出来:  

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


//  author: 行歌
//  time: 2018-4-12


using namespace cv;
using namespace std;
int main()
{
	Mat img;
	img = imread("test3.png");
	if (img.empty())
	{
		printf("could not load the picture...");
	}
	namedWindow("原图:", CV_WINDOW_AUTOSIZE);
	imshow("原图:", img);
	waitKey(0);
	system("PAUSE");
	return 0;
}

运行程序,可以看到这张测试图片:



三、彩色图像灰度化

    在OpenCV中实现将彩色像素(一个向量)转化为灰度像素(一个数值)的公式如下:


同时,OpenCV中定义了cvtColor函数实现BGR彩色空间的图像和其他颜色空间转换。cvCvtColor是Opencv里的颜色空间转换函数,可以实现RGB颜色向HSV,HSI等颜色空间的转换,也可以转换为灰度图像,其中参数CV_RGB2GRAY是RGB到gray。函数声明如下:

void cvCvtColor(const CvArr* src, CvArr* dst, int code)

参数解释如下:

src表示输入的 8 - bit, 16 - bit或 32 - bit单倍精度浮点数影像。

dst表示输出的8 - bit, 16 - bit或 32 - bit单倍精度浮点数影像。

code表示色彩空间转换的模式,该code来实现不同类型的颜色空间转换。比如CV_BGR2GRAY表示转换为灰度图,CV_BGR2HSV将图片从RGB空间转换为HSV空间。其中当code选用CV_BGR2GRAY时,dst需要是单通道图片。当code选用CV_BGR2HSV时,对于8位图,需要将RGB值归一化到0 - 1之间。这样得到HSV图中的H范围才是0 - 360,S和V的范围是0 - 1。

接下来,我们来实际操作,编写程序:

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

//  author: 行歌
//  time: 2018-4-12


using namespace cv;
using namespace std;
int main()
{
	Mat img;
	img = imread("test3.png");
	if (img.empty())
	{
		printf("could not load the picture...");
	}
	namedWindow("原图:", CV_WINDOW_AUTOSIZE);
	imshow("原图:", img);
	// 将彩色图转换为灰度图,常采用以下方法:
	Mat gray_img;
	cvtColor(img, gray_img, CV_RGB2GRAY);
	namedWindow("灰度图片:", CV_WINDOW_AUTOSIZE);
	imshow("灰度图片:", gray_img);
	waitKey(0);
	system("PAUSE");
	return 0;
}

运行程序,我们得到结果:



四、分离通道

      有时候我们需要将多通道矩阵分离成单通道矩阵,即将所有向量的第一个值组成的单通道矩阵作为第一通道,将所有向量的第二元素组成的单通道矩阵作为第二通道,依次类推。那么OpenCV当中如何才能够实现呢?其实很简单!

     使用OpenCV提供的split函数可分离多通道,如将多通道矩阵img分离为多个单通道,这些单通道矩阵被存放在vector容器中。我们修改代码:

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


using namespace cv;
using namespace std;
int main()
{
	Mat img;
	img = imread("test3.png");
	if (img.empty())
	{
		printf("could not load the picture...");
	}
	namedWindow("原图:", CV_WINDOW_AUTOSIZE);
	imshow("原图:", img);
	// 将彩色图进行通道分离,常采用以下方法:
	vector<Mat> planes;
	split(img, planes);
	imshow("蓝色通道:", planes[0]);
	imshow("绿色通道:", planes[1]);
	imshow("红色通道:", planes[2]);
	waitKey(0);
	system("PAUSE");
	return 0;
}

运行代码,得到结果:



五、访问单通道Mat对象中的值

     访问Mat对象中的值,最直接的方式是使用Mat的成员函数at,如对于单通道且数据类型为CV_32F的对象m,访问它的第r行第c列的值,格式为:m.at<float>(r,c)。接下来我们就尝试用成员函数at依次访问Mat对象中的所有值,编写代码:

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


using namespace cv;
using namespace std;
int main()
{
	Mat img;
	img = imread("test3.png");
	if (img.empty())
	{
		printf("could not load the picture...");
	}
	// 将彩色图转换为灰度图,常采用以下方法:
	Mat gray_img;
	cvtColor(img, gray_img, CV_RGB2GRAY);
	namedWindow("灰度图片:", CV_WINDOW_AUTOSIZE);
	imshow("灰度图片:", gray_img);
	// 使用成员函数at访问Mat对象中的值,如对于单通道且数据类型为CV_32F的对象m,访问它的第r行第c列的值,格式为:m.at<float>(r,c)。
	// 接下来我们利用函数at依次访问Mat对象当中的所有值。
	for (int r = 0; r < gray_img.rows; ++r)
	{
		for (int c = 0; c < gray_img.cols; ++c)
		{
			gray_img.at<uchar>(r, c) = 255 - int(gray_img.at<uchar>(r, c));  //反差处理
		}

	}
	namedWindow("新的图片:", CV_WINDOW_AUTOSIZE);
	imshow("新的图片:", gray_img);
	waitKey(0);
	system("PAUSE");
	return 0;
}

运行程序,如下所示:



六、访问多通道Mat对象中的值

        利用成员函数at访问多通道Mat的元素值,可以将三通道Mat看作一个特殊的二维数组,只是在每一个位置上不是一个数值而是一个向量(元素)。在此之前,首先介绍一下OpenCV当中的一个重要的类——向量类Vec。

        这里的向量可以理解为数学意义上的列向量,构造一个_cn*1的列向量,数据类型为_Tp,格式如下:

         Vec <Typename _Tp,int _cn>

比如我们构造一个长度为3,数据类型为uchar且初始化为10,11,12的列向量,可以这样:  Vec <uchar,3> vec_ (10,11,12);

那么如何访问这个列向量中的值呢?可以利用“[ ]”或者“( )”操作符访问向量中的值。如:vec_ [ 0 ]或者vec_( 0 )等都可以实现。

     还有很重要的一点就是,OpenCV为向量类的声明取了一个别名,例如:

   typedef Vec<uchar, 3> Vec3b;

   typedef Vec<int, 2> Vec2i;

   typedef Vec<float, 4> Vec4f;

   typedef Vec<double, 3> Vec3d;

理解上述概念以后,我们编写代码:

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

using namespace cv;
using namespace std;
int main()
{
	Mat img;
	img = imread("test3.png");
	if (img.empty())
	{
		printf("could not load the picture...");
	}
	namedWindow("原图:", CV_WINDOW_AUTOSIZE);
	imshow("原图:", img);
	//接下我们基于成员函数at和向量类vec对三个通道的Mat对象img进行访问
	Mat dst;
	dst = Mat(img.size(),img.type());   // 创建一个与img同类型和大小的Mat对象
	int height = img.rows;
	int width = img.cols;
	int channel_num = img.channels();
	if (channel_num ==3)
	{
	for (int r = 0; r < height; ++r)
	{
		for (int c = 0; c < width; ++c)
		{
			dst.at<Vec3b >(r, c)[0] = 255 - img.at<Vec3b >(r, c)[0];
			dst.at<Vec3b >(r, c)[1] = 255 - img.at<Vec3b >(r, c)[1];
			dst.at<Vec3b >(r, c)[2] = 255 - img.at<Vec3b >(r, c)[2];
			cout << img.at<Vec3b>(r, c) << ",";    
			// 将三通道Mat看作一个特殊的二维数组,只是在每一个位置上不是一个数值而是一个向量(元素),依次打印每一个向量元素
		}
		cout << endl;
	}
	}
	namedWindow("新的图片:", CV_WINDOW_AUTOSIZE);
	imshow("新的图片:", dst);
	waitKey(0); 
	system("PAUSE");
	return 0;
}

运行程序,得到:

这是通过按行号、列号取出的每一个元素Vec3b:


这是对三通道矩阵作反差处理以后的结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值