OpenCV4学习笔记(2)——图像的像素遍历

这次要整理记录的知识点呢是对图像的遍历操作(主要分为指针遍历和数组遍历)和对像素的算术操作。
1、首先是使用数组来遍历图像

/********************数组遍历像素点********************/
	int height = image.rows;
	int width = image.cols;
	int ch = image.channels();
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			if (ch == 3)
			{
				Vec3b bgr = image.at<Vec3b>(row, col);				//定义一个包含bgr三通道的向量,存放在图像该点的值
				bgr[0] = 255 - bgr[0];
				bgr[1] = 255 - bgr[1];
				bgr[2] = 255 - bgr[2];
				m4.at<Vec3b>(row, col) = bgr;
			}
			else if (ch == 1)
			{
				int gray;
				gray = image.at<uchar>(row, col);
				m4.at<uchar>(row, col) = 255 - gray;
			}
		}
	}

首先获取了图像的高度、宽度和通道数,分别使用height、width和channels三个变量来保存,然后使用两个for循环分别对图像的高和宽进行遍历。
当遍历到一个像素点时,先判断这幅图像是三通道还是单通道的,如果是三通道图,就用Vec3b定义一个可以存放B、G、R三个数据的向量,用来存放该点的像素值。然后分别对三通道去取反,也就是用255减去该像素值,再将相减后的值赋给图像的该点。在这里使用到了Mat.at<_type>(row, col),意思是把一个Mat对象在(row, col)这个点处的像素值,通过<_type>指定的格式返回,例如使用Vec3b,所以返回的是一个三维向量。
在单通道图的操作也是大同小异,其中

gray = image.at<uchar>(row, col);

表示将该点处的像素值以uchar类型返回,并赋给变量gray。

2、接下来是指针遍历图像像素点

/********************指针遍历像素点********************/
	int height = image.rows;
	int width = image.cols;
	int ch = image.channels();
	for (int row = 0; row < height; row++)
	{
		uchar* currentRow = image.ptr<uchar>(row);			//获取当前行的头指针
		uchar* resultRow = m4.ptr<uchar>(row);			//保存结果图像的当前行的头指针
		for (int col = 0; col < width; col++)
		{
			if (ch == 3)
			{
				int blue = *currentRow;			//每个元素为b、g、r三通道
				currentRow++;
				int green = *currentRow;
				currentRow++;
				int red = *currentRow;
				currentRow++;						//遍历完三通道指针指向下一个像素点

				*resultRow = 255 - blue;
				resultRow++;
				*resultRow = 255 - green;
				resultRow++;
				*resultRow = 255 - red;
				resultRow++;
			}
			else if (ch == 1)
			{
				int gray = *currentRow;
				currentRow++;
				*resultRow = gray;
				resultRow++;
			}
		}
	}

同样是先获取图像的高、宽和通道数,再使用两个for循环对图像的高和宽进行遍历。不同的是,在对每一行循环时,会使用uchar* currentRow = image.ptr<uchar>(row); uchar* resultRow = m4.ptr<uchar>(row);这两行代码来分别获取原图像和结果图像中的当前行指针。行指针指向的是每一行的头部。
在获取到当前行指针后,再对图像的列数进行遍历,通过指针的移动来逐个获取当前行中的每一列的数据。对于三通道图,一个像素有B、G、R三个值,所以要获取一个像素的值,需要遍历这三个通道。

总结:这两种遍历方式都能对图像的每个像素点进行遍历,使用数组遍历方式较容易理解,也比较容易实现,但是没有使用指针时运行那么快;而使用指针遍历方式需要比较好地理解指针所指向的是什么,代码上来实现也比较麻烦,但是优势是执行速度快。

3、对像素的算术操作

/********************像素算术操作********************/
	Mat image1;
	image1 = imread("D:/opencv_c++/LinuxLogo.jpg");
	Mat image2 = imread("D:/opencv_c++/WindowsLogo.jpg");

	int height1 = image1.rows;
	int width1 = image1.cols;
	int ch1 = image1.channels();
	int height2 = image2.rows;
	int width2 = image2.cols;
	int ch2 = image2.channels();
	if (height1 != height2 || width1 != width2 || ch1 != ch2)
	{
		return -1;
	}

	Mat dst = Mat::zeros(image1.size(), image1.type());
	for (int row = 0; row < height1; row++)
	{
		uchar* currentRow1 = image1.ptr<uchar>(row);
		uchar* currentRow2 = image2.ptr<uchar>(row);
		uchar* resultRow = dst.ptr<uchar>(row);
		for (int col = 0; col < width1; col++)
		{
			if (ch1 == 3)
			{
				int b1 = *currentRow1++;
				int g1 = *currentRow1++;
				int r1 = *currentRow1++;

				int b2 = *currentRow2++;
				int g2 = *currentRow2++;
				int r2 = *currentRow2++;

				/*Vec3b bgr_result = dst.at<Vec3b>(row, col);
				bgr_result[0] = saturate_cast<uchar>(b1 + b2);
				bgr_result[1] = saturate_cast<uchar>(g1 + g2);
				bgr_result[2] = saturate_cast<uchar>(r1 + r2);
				dst.at<uchar>(row, col) = bgr_result;*/
				*resultRow = saturate_cast<uchar>(b1 + b2);
				resultRow++;
				*resultRow = saturate_cast<uchar>(g1 + g2);
				resultRow++;
				*resultRow = saturate_cast<uchar>(r1 + r2);
				resultRow++;
			}
			else if (ch1 == 1)
			{
				int gray1 = *currentRow1++;
				int gray2 = *currentRow2++;

				*resultRow = saturate_cast<uchar>(gray1 + gray2);
				resultRow++;
			}
		}
	}


首先读取两张图像,然后获取这两张图像的宽高和通道数并进行比对,只有两张图像的宽高和通道数都相同才可以进行像素的算术操作。
对像素进行算术操作,同样需要对图像遍历像素点,也可以选择数组或者指针的方式来遍历。在上述代码中两种方式都有,其中被注释掉的是用数组方式的遍历。
当访问某个坐标时,获取两张图像在该坐标的像素值,并进行算术操作,例如加法运算:*resultRow = saturate_cast<uchar>(gray1 + gray2);
进行算术操作时,要注意结果值的范围,两个[0, 255]的值相加,其结果可能会超过[0, 255],所以我们不能直接将计算结果赋给像素点。可以用saturate_cast<uchar>这个函数来对计算结果映射为uchar类型,也就是映射到[0, 255]之间,再赋值给像素点,这样子可以防止像素值溢出,显示的时候才能正常显示。
上面的代码是通过遍历像素点来实现算术操作,实际上OpenCV提供了一些简单的API来实现这些功能,如以下代码:

	add(image1, image2, dst);
	subtract(image1, image2, dst);
	multiply(image1, image2, dst);
	divide(image1, image2, dst);

这四个函数分别实现了像素的加、减、乘、除四种基本的算术操作,其参数也非常简单,两个输入图像变量和一个输出图像变量即可。
在刚开始学习的时候,还是比较倾向于自己去实现一些功能,而不是简单地直接调用API,这样比较有利于自身的学习。

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

发布了36 篇原创文章 · 获赞 43 · 访问量 1829

猜你喜欢

转载自blog.csdn.net/weixin_45224869/article/details/104516610