OpenCV学习笔记(4)_Mat的简单理解及像素获取

OpenCV学习笔记(4)_Mat的简单理解

文章目录

  • OpenCV学习笔记(4)_Mat的简单理解
    • 0. 引言
    • 1. Mat的简单理解
    • 2. 用Mat来存放图像数据
    • 3. 像素的访问和操作
      • 3.1 以数组方式访问
      • 3.2 以指针方式访问

0. 引言

​ OpenCV作为一个开源的图像处理库,它的任务主要是对图像进行操作. 那么对于我们学习者来说,首先要弄明白的是OpenCV的基础数据结构.

​ 稍微有点图像处理基础的同学都知道,图像的基础结构就是像素. 那么对于OpenCV2.1以上的版本来说,不需要再使用IplImage这个数据结构来作为图像的基础数据结构了,现在我们只需要了解Mat.

​ 今天的例程是一个对图像进行反色处理的样例.

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第1张图片

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第2张图片
OpenCV学习笔记(4)_Mat的简单理解及像素获取_第3张图片

​ 老规矩,先上代码,再进行分析.

​ 代码1.数组方法访问像素.

int test1()
{
	std::string filename = "../images/lena.jpg";
	cv::Mat srcImg = cv::imread(filename, CV_8UC2); // 读灰度图时改为CV_8UC1
	cv::Mat dstImg = cv::Mat::zeros(srcImg.size(), srcImg.type());
	//uchar grayVal = 0;
	//uchar Blue = 0, Green = 0, Red = 0;

	for (int i = 0; i < srcImg.rows; ++i)
	{
		uchar* curr_row = srcImg.ptr<uchar>(i);
		uchar* dst_row = dstImg.ptr<uchar>(i);
		for (int j = 0; j < srcImg.cols; ++j)
		{
			if (3 == srcImg.channels())
			{
				//Blue = 255 - srcImg.at(i, j)[0];
				//Green = 255 - srcImg.at(i, j)[1];
				//Red = 255 - srcImg.at(i, j)[2];
				//dstImg.at(i, j) = cv::Vec3b(Blue, Green, Red);

				dstImg.at<cv::Vec3b>(i, j)[0] = 255 - srcImg.at<cv::Vec3b>(i, j)[0];
				dstImg.at<cv::Vec3b>(i, j)[1] = 255 - srcImg.at<cv::Vec3b>(i, j)[1];
				dstImg.at<cv::Vec3b>(i, j)[2] = 255 - srcImg.at<cv::Vec3b>(i, j)[2];
			}
			else if (1 == srcImg.channels())
			{
				//grayVal = 255 - srcImg.at(i, j);
				//dstImg.at(i, j) = grayVal;
				dstImg.at<uchar>(i, j) = 255 - srcImg.at<uchar>(i, j);
			}

		}
	}

	cv::namedWindow("result", CV_WINDOW_AUTOSIZE);
	cv::imshow("result", dstImg);
	cv::waitKey();
	cv::destroyAllWindows();
	return 0;
}

​ 代码2.利用指针访问像素.

int test2()
{
	std::string filename = "../images/lena.jpg";
	cv::Mat srcImg = cv::imread(filename, CV_8UC2);
	cv::Mat dstImg = cv::Mat::zeros(srcImg.size(), srcImg.type());
	//uchar grayVal = 0;
	//uchar Blue = 0, Green = 0, Red = 0;

	for (int i = 0; i < srcImg.rows; ++i)
	{
		uchar* curr_row = srcImg.ptr<uchar>(i);
		uchar* dst_row = dstImg.ptr<uchar>(i);
		for (int j = 0; j < srcImg.cols; ++j)
		{
			if (3 == srcImg.channels())
			{
				//Blue = 255 - *curr_row++;
				//Green = 255 - *curr_row++;
				//Red = 255 - *curr_row++;
				//*dst_row++ = Blue;
				//*dst_row++ = Green;
				//*dst_row++ = Red;
				*dst_row++ = 255 - *curr_row++;
				*dst_row++ = 255 - *curr_row++;
				*dst_row++ = 255 - *curr_row++;
			}
			else if (1 == srcImg.channels())
			{
				//grayVal = 255 - *curr_row++;
				//*dst_row++ = grayVal;
				*dst_row++ = 255 - *curr_row++;
			}

		}
	}

	cv::namedWindow("result", CV_WINDOW_AUTOSIZE);
	cv::imshow("result", dstImg);
	cv::waitKey();
	cv::destroyAllWindows();
	return 0;
}

注:当以灰度图像读取时,则imread参数2选择CV_8UC1,若以RGB三通道图像读取时,则imread参数2选择CV_8UC2(注意此处并不是CV_8UC3,CV_8UC2表示RGB3通道图像,CV_8UC3表示的是RGB+透明色即4通道图像)

1. Mat的简单理解

​ Mat的数据结构可以理解为Head部分+Data部分.

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第4张图片

​ 这里的Head部分用来存储Mat的属性参数,例如维度(dim)、尺寸大小(size)等.

​ Data部分用来存储Mat的图像矩阵,实际上这里只是保存了图像矩阵像素数据的指针.

​ 对于图像像素矩阵来说, rows表示行数(图像的高度),cols表示列数(图像的宽度),如同代码中的srcImg.rowssrcImg.cols.

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第5张图片

​ 以类型为CV_8UC1为例,像素中存储的是uchar格式的数据,范围为0~255.

​ 以类型为CV_8UC2为例 ,像素中存储的是Vec3b格式的数据,Vec3b由三个uchar数据组成,依次存储B、G、R三个通道的像素值.

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第6张图片

​ 若以内存的角度来看Mat的像素矩阵存储,对于CV_8UC2来说,则如上图所示,存储的顺序为

pix[0,0].B, pix[0,0].G, pix[0,0].R, pix[0,1].B, pix[0,1].G, pix[0,1].R ... pix[0, cols-1].B, pix[0, cols-1].G, pix[0, cols-1].R
pix[1,0].B, pix[1,0].G, pix[1,0].R, pix[1,1].B, pix[1,1].G, pix[1,1].R ... pix[1, cols-1].B, pix[1, cols-1].G, pix[1, cols-1].R
...
pix[rows-1,0].B, pix[rows-1,0].G, pix[rows-1,0].R, pix[rows-1,1].B, pix[rows-1,1].G, pix[rows-1,1].R ... pix[rows-1, cols-1].B, pix[rows-1, cols-1].G, pix[rows-1, cols-1].R

​ 这个顺序非常重要,在后面如何访问像素时,我们将会再次提到它.

​ 来看一下读取到的lena图像在Mat中的存储状态:

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第7张图片

2. 用Mat来存放图像数据

std::string filename = "../images/lena.jpg";
	cv::Mat srcImg = cv::imread(filename, CV_8UC2);
	cv::Mat dstImg = cv::Mat::zeros(srcImg.size(), srcImg.type());

cv::imread此函数通过带路径的图像文件名,和图像的存储类型,来读取图像.

如前所述,若类型选为CV_8UC1,则读取的是灰度图像;

若类型选为CV_8UC2,则读取的是三通道图像(注意,源图像是RGB或者是BGR没有关系,读取进来以后是以BGR通道形式.

3. 像素的访问和操作

3.1 以数组方式访问

​ 访问Mat中的像素,有两种方式,第一种方式是以数组的方式访问.

for (int i = 0; i < srcImg.rows; ++i)
	{
		uchar* curr_row = srcImg.ptr<uchar>(i);
		uchar* dst_row = dstImg.ptr<uchar>(i);
		for (int j = 0; j < srcImg.cols; ++j)
		{
			if (3 == srcImg.channels())
			{
				//Blue = 255 - srcImg.at(i, j)[0];
				//Green = 255 - srcImg.at(i, j)[1];
				//Red = 255 - srcImg.at(i, j)[2];
				//dstImg.at(i, j) = cv::Vec3b(Blue, Green, Red);

				dstImg.at<cv::Vec3b>(i, j)[0] = 255 - srcImg.at<cv::Vec3b>(i, j)[0];
				dstImg.at<cv::Vec3b>(i, j)[1] = 255 - srcImg.at<cv::Vec3b>(i, j)[1];
				dstImg.at<cv::Vec3b>(i, j)[2] = 255 - srcImg.at<cv::Vec3b>(i, j)[2];
			}
			else if (1 == srcImg.channels())
			{
				//grayVal = 255 - srcImg.at(i, j);
				//dstImg.at(i, j) = grayVal;
				dstImg.at<uchar>(i, j) = 255 - srcImg.at<uchar>(i, j);
			}

		}
	}

​ 若是灰度图像(1==srcImg.channels()),则像素为uchar类型,则srcImg.at(i,j)读取了第i行(i从0到rows-1),第j列(j从0到cols-1)的像素.

​ 若是彩色图像(3==srcImg.channels()),则像素为Vec3b类型,则srcImg.at(i,j)读取了第i行(i从0到rows-1),第j列(j从0到cols-1)的像素. 在这个像素里,srcImg.at(i,j)[0]为Blue通道的灰度值,srcImg.at(i,j)[1]为Green通道的灰度值,srcImg.at(i,j)[2]为Red通道的灰度值.

​ 反色的处理即用255减去每个通道的灰度值,将结果对应赋值给dstImg的对应像素.

​ 看一下程序运行过程中的dstImg像素值变化情况:

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第8张图片

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第9张图片

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第10张图片

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第11张图片

​ 对第一行操作完成后,才开始操作第二行,依此类推:

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第12张图片

3.2 以指针方式访问

​ 第二种方式是以指针方式访问:

for (int i = 0; i < srcImg.rows; ++i)
	{
		uchar* curr_row = srcImg.ptr<uchar>(i);
		uchar* dst_row = dstImg.ptr<uchar>(i);
		for (int j = 0; j < srcImg.cols; ++j)
		{
			if (3 == srcImg.channels())
			{
				//Blue = 255 - *curr_row++;
				//Green = 255 - *curr_row++;
				//Red = 255 - *curr_row++;
				//*dst_row++ = Blue;
				//*dst_row++ = Green;
				//*dst_row++ = Red;
				*dst_row++ = 255 - *curr_row++;
				*dst_row++ = 255 - *curr_row++;
				*dst_row++ = 255 - *curr_row++;
			}
			else if (1 == srcImg.channels())
			{
				//grayVal = 255 - *curr_row++;
				//*dst_row++ = grayVal;
				*dst_row++ = 255 - *curr_row++;
			}

		}
	}

srcImg.ptr提供了访问像素中数据的指针方法,回忆起像素的存储顺序,则可知:

​ 若为灰色图像,则srcImg.ptr(i)指向每一行的像素首地址,向下移动,直至移动到第j列,即到行末.

​ 若为彩色图像,则srcImg.ptr(i)也同样指向每一行的像素首地址,顺序为BGRBGRBGR…,向下移动3个位置则到下一个像素,直至移动到第j列,即到行末.

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第13张图片

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第14张图片

OpenCV学习笔记(4)_Mat的简单理解及像素获取_第15张图片
OpenCV学习笔记(4)_Mat的简单理解及像素获取_第16张图片

你可能感兴趣的:(学习数字图像处理,VS,C++,OpenCV+Qt,opencv,学习,计算机视觉)