Opencv_05 图像像素的读写和算术操作

一. 像素访问的几种方法对比

① 使用下标M.at<>(i,j)

单通道:灰度图

m.at<uchar>(i,j); // 第i行,第j列

三通道:彩色图

m.at<Vec3b>(i,j)[0] // B分量
m.at<Vec3b>(i,j)[1] // G分量
m.at<Vec3b>(i,j)[2] // R分量
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include "MyOpencv.h"

int main()
{
	Mat imgColor = Mat::zeros(3, 3, CV_8UC3);
	Mat imgGray = Mat::zeros(3, 3, CV_8UC1);
	int rows = imgColor.rows;
	int cols = imgColor.cols;
	int mDims = imgColor.dims;
	int channels = imgColor.channels();
	cout << "彩色图像: rows: " << rows << " cols: " << cols << " dims: " << mDims << " channels: " <<
	 channels << endl;
	// 将颜色全部变成(128,128,128)
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			imgColor.at<Vec3b>(i, j)[0] = 128;
			imgColor.at<Vec3b>(i, j)[1] = 128;
			imgColor.at<Vec3b>(i, j)[2] = 128;
		}
	}
	cout << "重新赋值后imgColor = " << endl;
	cout << imgColor << endl;

	rows = imgGray.rows;
	cols = imgGray.cols;
	mDims = imgGray.dims;
	channels = imgGray.channels();

	cout << "-------------------------------------------" << endl;
	cout << "灰度图像: rows: " << rows << " cols: " << cols << " dims: " << mDims << " channels: " 
	<< channels << endl;
	// 将颜色全部变成111
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			imgGray.at<uchar>(i, j) = 111;
		}
	}
	cout << "重新赋值后imgGray = " << endl;
	cout << imgGray << endl;

	return 0;
}

结果:
在这里插入图片描述

② 指针ptr<>(row)访问图像像素

这里是通过获取每一行的第一个数据的指针地址,然后通过指针的偏移去访问像素元素

uchar * pCurrent  = m.ptr<uchar>(row);
#include "MyOpencv.h"

int main()
{
	// 像素补反
	Mat imgGray = Mat::eye(5, 5, CV_8UC1);
	Mat imgColor = Mat::eye(5, 5, CV_8UC3);
	cout << imgColor << endl;
	int channels = imgGray.channels();
	int rows = imgGray.rows;
	int cols = imgGray.cols;
	for (int row = 0; row < rows; row++)
	{
		uchar *pCurrent = imgGray.ptr<uchar>(row);
		for (int col = 0; col < cols; col++)
		{
			if (channels == 1)
			{
				*pCurrent++ = 1 - *pCurrent;
			}
			else if(channels == 3)
			{
				*pCurrent++ = 1 - *pCurrent;
				*pCurrent++ = 1 - *pCurrent;
				*pCurrent++ = 1 - *pCurrent;
			}
		}
	}

	cout << "灰度图像反转之后imgGray: " << endl;
	cout << imgGray << endl;

	cout << "--------------------------------" << endl;
	rows = imgColor.rows;
	cols = imgColor.cols;
	channels = imgColor.channels();

	for (int row = 0; row < rows; row++)
	{
		uchar *pCurrent = imgColor.ptr<uchar>(row);
		for (int col = 0; col < cols; col++)
		{
			if (channels == 1)
			{
				*pCurrent++ = 1 - *pCurrent;
			}
			else if (channels == 3)
			{
				*pCurrent++ = 1 - *pCurrent;
				*pCurrent++ = 1 - *pCurrent;
				*pCurrent++ = 1 - *pCurrent;
			}
		}
	}
	cout << "彩色图像反转之后imgColor: " << endl;
	cout << imgColor << endl;

	return 0;
}

结果:

③ 使用迭代器iterator访问图像像素
#include "MyOpencv.h"

int main()
{
	Mat imgColor = Mat::zeros(3, 3, CV_8UC3);
	Mat imgGray = Mat::zeros(3, 3, CV_8UC1);
	Mat_<Vec3b>::iterator itBegin = imgColor.begin<Vec3b>();
	Mat_<Vec3b>::iterator itEnd = imgColor.end<Vec3b>();

	while (itBegin != itEnd)
	{
		(*itBegin)[0] = 1;
		(*itBegin)[1] = 2;
		(*itBegin)[2] = 3;
		itBegin++;
	}
	cout << "彩色图像转换后imgColor: " << endl;
	cout << imgColor << endl;
	cout << "-------------------------------------------" << endl;

	Mat_<uchar>::iterator itBegin2 = imgGray.begin<uchar>();
	Mat_<uchar>::iterator itEnd2 = imgGray.end<uchar>();

	while (itBegin2 != itEnd2)
	{
		(*itBegin2) = 123;
		itBegin2++;
	}
	cout << "灰度图像转换后imgGray: " << endl;
	cout << imgGray << endl;

	return 0;
}

结果:

④ 使用ptr<>(row,col)访问图像像素

这个指针,其实相当于是row提供地址,然后col提供地址偏移.本质上还是通过指针地址去访问

#include "MyOpencv.h"

int main()
{
	Mat imgColor = Mat::zeros(3, 3, CV_8UC3);
	Mat imgGray = Mat::zeros(4, 4, CV_8UC1);
	int rows = imgColor.rows;
	int cols = imgColor.cols;
	int channels = imgColor.channels();

	cout << "彩色三通道图像,像素全部变成行和列之和imgColor: " << endl;
	for (int row = 0; row < rows; row++)
	{
		for (int col = 0; col < cols; col++)
		{
			if (channels == 3)
			{
				Vec3b *pCurrent = imgColor.ptr<Vec3b>(row, col);
				(*pCurrent)[0] = row + col;
				(*pCurrent)[1] = row + col;
				(*pCurrent)[2] = row + col;
			}
			else
			{
				uchar *pCurrent = imgColor.ptr<uchar>(row, col);
				*pCurrent = row + col;
			}
		}
	}
	cout << imgColor << endl;

	cout << "------------------------------" << endl;
	rows = imgGray.rows;
	cols = imgGray.cols;
	channels = imgGray.channels();
	for (int row = 0; row < rows; row++)
	{
		for (int col = 0; col < cols; col++)
		{
			if (channels == 3)
			{
				Vec3b *pCurrent = imgGray.ptr<Vec3b>(row, col);
				(*pCurrent)[0] = row * col;
				(*pCurrent)[1] = row * col;
				(*pCurrent)[2] = row * col;
			}
			else if (channels == 1)
			{
				uchar *pCurrent = imgGray.ptr<uchar>(row, col);
				*pCurrent = row * col;
			}
		}
	}
	cout << "灰度图像变成行列编号之积imgGray: " << endl;
	cout << imgGray << endl;

	return 0;
}

结果:

⑤ 使用data结合step访问图像像素

成员函数step是返回该Mat对象一行所占的数据字节数
成员函数data,uchar类型的指针,指向Mat数据矩阵的首地址.注意三通道的要乘以通道数

#include "MyOpencv.h"

int main(void)
{
	Mat imgGray = Mat::zeros(3, 3, CV_8UC1);
	Mat imgColor = Mat::zeros(3, 3, CV_8UC3);
	uchar *pData = imgGray.data;
	MatStep step = imgGray.step;
	int rows = imgGray.rows;
	int cols = imgGray.cols;
	int channels = imgGray.channels();
	for (int row = 0; row < rows; row++)
	{
		for (int col = 0; col < cols; col++)
		{
			if (channels == 3)
			{
				*(pData + row * step + col * channels + 0) = row + col;
				*(pData + row * step + col * channels + 1) = row + col;
				*(pData + row * step + col * channels + 2) = row + col;
			}
			else if (channels == 1)
			{
				*(pData + row * step + col * channels) = row + col;
			}
		}
	}
	cout << "灰度图像将像素值变成行和列之和imgGray = " << endl;
	cout << imgGray << endl;

	rows = imgColor.rows;
	cols = imgColor.cols;
	channels = imgColor.channels();
	pData = imgColor.data;
	MatStep step2 = imgColor.step;
	for (int row = 0; row < rows; row++)
	{
		for (int col = 0; col < cols; col++)
		{
			if (channels == 3)
			{
				*(pData + row * step2 + col * channels + 0) = row * col;
				*(pData + row * step2 + col * channels + 1) = row * col;
				*(pData + row * step2 + col * channels + 2) = row * col;
			}
			else if (channels == 1)
			{
				*(pData + row * step2 + col * channels) = row * col;
			}
		}
	}
	cout << "---------------------------------------" << endl;
	cout << "彩色图像,将全部像素变成行和列之积!imgColor: " << endl;
	cout << imgColor << endl;

	return 0;
}

结果:

⑥ 使用isContinouous() 访问像素

图像的行与行之间的存储可能是不连续的,进行像素值遍历,很大程度上造成数据指针移动的浪费.一般经过裁剪的Mat图像,都不再连续了,如果想要转换为连续的,最简单的办法就是将不连续的img重新clone一份给新的mat就是连续的了.如果像素是连续的,可以通过将数组,按照行展开,这样二维数据的遍历,就变成了1维数组的遍历了,行只有一行,可以避免指针来回移动造成的资源浪费.

注意: 图像是否连续针对的是行与行之间,一行数据是连续的.

#include "MyOpencv.h"

int main(void)
{
	Mat imgColor = Mat::zeros(5, 5, CV_8UC3);// 连续
	Mat imgRect = imgColor(Rect(3, 3, 2, 2)); // 不连续
	Mat rectTemp = imgRect.clone(); // 克隆之后又连续了
	cout << boolalpha;
	cout << "imgColor是否连续: " << (bool)imgColor.isContinuous() << endl;
	cout << "imgRect是否连续: " << (bool)imgRect.isContinuous() << endl;
	cout << "rectTemp(imgRect的克隆)是否连续: " << (bool)rectTemp.isContinuous() << endl;

	int rows = imgColor.rows;
	int cols = imgColor.cols;
	int channels = imgColor.channels();

	if (imgColor.isContinuous())
	{
		// 如果图像是连续的就按照行展开,里面一共有75个元素 5 * 5 *3
		cols = cols * rows * channels;
		rows = 1;
	}
	for (int row = 0; row < rows; row++)
	{
		uchar *pCurrent = imgColor.ptr<uchar>(row);
		for (int col = 0; col < cols; col++)
		{
			*pCurrent++ = row + col;
		}
	}
	cout << "变换之后ImageColor: " << endl;
	cout << imgColor << endl;

	// 其实上面的代码是有问题的,如果使用同样的代码访问rect就有问题
	rows = imgRect.rows;
	cols = imgRect.cols;
	channels = imgRect.channels();
	if (imgRect.isContinuous())
	{
		cols = cols * rows * channels;
		rows = 1;
	}

	cout << "变换之前的ImageRect: " << endl;
	cout << imgRect << endl;
	for (int row = 0; row < rows; row++)
	{
		uchar *pCurrent = imgRect.ptr<uchar>(row);
		for (int col = 0; col < cols; col++)
		{
			//*pCurrent++ = row + col; // 这里相当于是只改变了每一行第一列前两个通道的像素
			// 正确写法应该是
			if (imgRect.isContinuous() || channels == 1)
			{
				*pCurrent++ = row + col;
			}
			else if (channels == 3)
			{
				*pCurrent++ = row + col;
				*pCurrent++ = row + col;
				*pCurrent++ = row + col;
			}
		}
	}
	cout << "变换之后的ImageRect: " << endl;
	cout << imgRect << endl;


}

结果:

⑦ LUT表访问像素

LUT表概述

LUT->(Look-Up Table)实际上就是一张像素灰度值的映射表,它将实际采样到的像素灰度值经过一定的变换阈值,反转,二值化,对比度调整,线性变换等,变成了另外一个与之对应的灰度值,这样可以起到突出图像信息的有用信息,增强图像的光对比度的作用.

LUT的工作原理:

  1. LUT就是一张映射表,一般单通道的会将[0,255]这个区间的像素值作为键,然后其对应的值,作为值.类似map对象.然后再使用的时候,直接去根据这个表进行像素替换
  2. 作用:提高我们扫描图像时的效率.我们要处理一张图片,其实就是对整个图像数据进行处理,那么这就要求我们要对图像的全部数据进行扫描读取,然后进行相应的处理,处理中最耗时的就是赋值,如果是图像特别大,这种赋值的操作效率很很低.所以,我们应该尽量的避免每个都要去进行加减乘除的运算.
  3. LUT就是解决这个问题的,它本质上就是根据源图像(img)中的像素值的灰度值,然后去查询表中去找它对应的是那个灰度值,然后直接赋值给新的图像矩阵就可以了,不需要进行计算.
  4. LUT在颜色缩减,图片取反,以及直方图均衡化等不涉及像素位置相关性的算法中我们都可以应用.但是一旦牵涉到和像素位置变换有关的,LUT就很难实现,其实LUT是一种无差别的像素替换表,和具体的像素位置无关时才可以使用
#include"MyOpencv.h"
int main(void)
{
	Mat imgColor = Mat::zeros(5, 5, CV_8UC3);
	Mat imgGray = Mat::zeros(5, 5, CV_8UC1);

	// 通过查找表,将像素全部加10;
	uchar lutTableGray[256] = {0};
	uchar lutTableColor[256 * 3] = {0};
	for (int i = 0; i < 256; i++)
	{
		lutTableGray[i] = lutTableGray[i] + 10;
	}
	for (int i = 0; i < 256 * 3; i++)
	{
		lutTableColor[i] = lutTableColor[i] + 10;
	}

	Mat lookUpTable(1, 256, CV_8UC3, lutTableColor);
	Mat lookUpTable1(1, 256, CV_8UC1, lutTableGray);

	Mat temp;
	LUT(imgColor, lookUpTable, temp);
	cout << "ImgColor 查找表转换之后: " << endl;
	cout << temp << endl;

	LUT(imgGray, lookUpTable1, temp);
	cout << "ImgGray 查找表转换之后: " << endl;
	cout << temp << endl;

	return 0;
}

结果:

各种方法的效率测试结果:

  1. 使用LUT查找表访问像素,LUT方法通过映射关系大大减少相关操作的时间复杂度,是最快的处理方式,常用于(替换,反转,赋值,阈值,二值化,灰度变换等)图像操作.它的效率是最高的,图像数据越大越明显
  2. 使用isContinuous()访问图像也非常的快,前提是图像是连续的(行与行之间)
  3. 使用ptr<>(row)访问图像速度也非常的快
  4. 使用data结合step访问像素
  5. 使用ptr<>(row,col)访问像素
  6. 使用迭代器,指针可能存在数组越界的情况,而迭代器是绝对安全的
  7. 使用at<>访问图像像素,最直观,但是速度最慢.

二. 图像的算术操作

① 加法

加法操作对应数据溢出的情况,会根据类型进行截断,如果超过了这个类型的极限值,就按照极限值进行取值

imgMat + k   -> k是常数,整幅图的像素值加k,如果是三通道,只会改变第一个通道的值,所以三通道要使用imgMat + Scalar(i,j,k)这种方式
imgMat + Scalar(k) - > 同上
add(imgMat,Scalar(k),imgDst) - > 同上
imgMat1 + imgMat2 -> 两幅图相加
add(imgMat1,imgMat2,imgDst)-> 同上  
#include "MyOpencv.h"

int main(void)
{
	// 创建一个3X3的全部是255的灰度图像
	Mat imageGray = Mat::ones(3, 3, CV_8UC1) * 1;
	// 通过加法,将这个像素加1,然后看看像素溢出之后,会不会被截断
	Mat m1 = imageGray + Scalar(1); // 如果超过了255
	cout << "m1 = " << endl;
	cout << m1 << endl;
	m1 = m1 + 3;
	cout << "m1 = " << endl;
	cout << m1 << endl;

	// 通过add函数
	Mat m2 = Mat::ones(3, 3, CV_8UC1) * 3;
	Mat m3;
	cv::add(m1, m2, m3);
	cout << "add之后的结果: " << endl;
	cout << m3 << endl;
	Mat m4 = m1 + m2; // 直接相加
	cout << "直接相加之后的结果: " << endl;
	cout << m4 << endl;

	return 0;
}

结果:

② 减法

减法操作和加法操作类似,对于超过类型极限的的值,会按照极限位进行截断

imgMat - k -> 所有的像素值都减去k,k是常量,如果是三通道,只会改变第一个通道的值,所以如果是三通道要使用Scalar,建议平时使用的时候就使用Scalar,这样可以防止,三通道的时候这种错误.
imgMat - scalar(k)  -> 同上
subtract(imgMat,scalar(k),dstMat) -> 同上
imgMat1 - imgMat2 -> 两幅图相减
subtract(imgMat1,imgMat2,dstMat) -> 同上
#include "MyOpencv.h"
int main(void)
{
	Mat imgColor = Mat(3, 3, CV_8UC3,Scalar(10,10,10));
	Mat m1 = imgColor - 1; // 这里相当于是Scalar(1),只会改变第一个通道的值
	Mat m2 = imgColor - cv::Scalar(1, 1, 1); // 所有的像素值都减少1
	cout << "m1 = " << endl;
	cout << m1 << endl; // 9
	cout << "m2 = " << endl;
	cout << m2 << endl;

	Mat m3;
	add(imgColor, Scalar(1, 2, 3), m3);
	cout << "m3 = " << endl;
	cout << m3 << endl;

	Mat m4;
	add(m1, m2, m4);
	cout << "m4 = " << endl;
	cout << m4 << endl;


	return 0;
}

结果:

③ 乘法操作
// 矩阵乘法,A的列数和B的行数相同,为什么要满足这个条件,是因为每次做运算的时候,取的都是前面一行乘以后面的一列.
// 这两个数据集要一一对象可以相乘,而前面的一行的元素个数就是它的列数的值,后面的一列的元素个数就是它的行数的值.
// 例如: A(m*n) * B(n*p) = AB(m*p);
Mat AB = MatA * MatB; 

// 点乘操作A.dot(B) type类型只能是CV_32F,CV_64FC1,CV_32FC2,CV_64FC2这四种类型中的一种
// 对于向量A = [a1,a2,...an] 向量 B = [b1,b2,...bn]
// A.dot(B) = a1*b1 + a2*b2 + ... + an*bn;
// 点乘的结果是一个标量,C++中返回一个double类型.
double dotRes = A.dot(B);

// A.mul(B) Multiply(A,B,matDst);
// 都是A和B对应的点位的像素相乘
Mat C = A.mul(B);  // 注意这里不会改变A的结果
Multiply(A,B,matDst); // 用一个值去接收结果
#include "MyOpencv.h"

int main(void)
{
	//Mat imgGray = Mat::ones(3, 3, CV_8UC3) * 10; // 这里如果是三通道的话,只会第一个通道上面的值乘以10
	Mat imgGray = Mat::ones(3, 3, CV_32F) * 10; // 所有的像素都乘以10
	cout << "imgGray: " << endl;
	cout << imgGray << endl;

	// 矩阵相乘 A*B,这里是计算的A和B的矩阵相乘的结果,比如前行后列的规则,取前面的行数据和后面的列数据进行相乘,
	// 得到一个数,放到对应的行列上.
	// 要求A矩阵的列数和B矩阵的行数必须一致 A(m*n) * B(n*p) = C(m * p)注意这里的数据类型只能是
	//CV_32F,cv_64FC1,cv_32FC2,cv_64Fc2
	Mat m1 = Mat::ones(3, 4, CV_32F) * 1;
	Mat m2 = imgGray * m1;
	cout << "矩阵相乘的结果m2 = " << endl;
	cout << m2 << endl;

	// 矩阵的点乘 A.dot(B)
	// A 和 B必须size和type一致,并且返回类型是个double
	double dotRes = m1.dot(m1); // 12个元素分别相乘,然后每个值的结果都是1,所以1 * 12 = 12;
	cout << "dotRes = " << endl;
	cout << dotRes << endl;

	// 矩阵对应位置相乘得到新矩阵
	Mat m3 = imgGray.mul(imgGray);
	cout << "m3 = " << endl;
	cout << m3 << endl;
	cout << "主要看imgGray.mul()之后imgGray是否改变了?imgGray = " << endl;
	cout << imgGray << endl;

	Mat m4;
	cv::multiply(imgGray, imgGray, m4);
	cout << "m4 = " << endl;
	cout << m4 << endl;

	return 0;
}

结果:
在这里插入图片描述

④ 矩阵的除法
// 重载除法运算符 / 
// 1. matA / k
// 如果是单通道的话,就每个位置的像素都除以k,如果是三通道,只有第一个通道才会除以k,如果是int类型,得到的结果四舍五入.

// 2. k / matA
// 如果是单通道的话,就每个位置的像素都被k除,如果是三通道,只有第一个通道才会被k除,如果是int类型,得到的结果四舍五入

// 3. matA / matB
// 对应位置相除,如果是int类型,直接截断,不会四舍五入

// 4. divide()函数
void divide(InputArray src1 InputArray src2,OutputArray dst,double scale = 1, int type=-1);
// 函数将一个数组除以另外一个数组
dst = saturate(src1 * scale / src2);
// 如果src1不存在的时候
dst = saturate(scale / src2);
#include "MyOpencv.h"
int main(void)
{
	Mat src1 = (Mat_<int>(2, 3) << 5, 7, 1, 5, 20, 24);
	Mat src2 = (Mat_<int>(2, 3) << 2, 7, 5, 5, 4, 48);
	Mat dst1, dst2, dst3;
	dst1 = src1 / 4; // 相当于每个位置的像素都除以4,并且四舍五入.
	cout << "src1 / 4 = " << endl;
	cout << dst1 << endl;

	dst2 = 20 / src1;
	cout << "20 / src1 = " << endl; // 相当于是每个位置都是用20来除,并且四舍五入
	cout << dst2 << endl;

	dst3 = src1 / src2;
	cout << "src1 / src2 = " << endl; // 对应位置相除,不四舍五入
	cout << dst3 << endl;

	Mat dst4;
	divide(src1, src2, dst4); // 和上面的结果一致
	cout << "divide(src1,src2) = " << endl;
	cout << dst4 << endl;
	divide(20, src1, dst4);
	cout << "divide(20,src1) = 20 / src1 = " << endl;
	cout << dst4 << endl;
	return 0;
}

结果:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值