Opencv学习笔记(二)core模块的使用

大纲

1.如何进行图像混合
2. 如何进行图像拼接
3. 如何进行多通道图像分离和混合
4. 如何调整图像对比度和亮度
5. 如何对图像进行傅立叶变换

一、图像混合
图像混合的核心在于圈出感兴趣区域和addWeighted()函数的使用,当两张图像大小不一致时,就需要先在较大的图上圈出待混合区域,再通过addWeighted()函数与小图进行叠加。
圈出感兴趣区域的方法有两种,一种是通过在通过在原图上截取Rect类的区域,如:

Roi=srcImg(Rect(x,y,width,height));

另一种方法是通过Range函数直接在原图上截取:

Roi=srcImg(Range(y,y+height),Range(x,x+width));

addWeighted()函数用于计算两个矩阵的加权和,其原型为:

addWeighted(InputArray src1, double alpha, InputArray src2,double beta, double gamma, OutputArray dst, int dtype = -1);

第一,三个参数为要进行加权的矩阵;
第二、四个参数为相应矩阵的权重;
第五个参数为线性加权的偏置值;
第六个参数为加权结果的输出矩阵即 d s t = s r c 1 ∗ α + s r c 2 ∗ β + γ dst=src1*\alpha+src2*\beta+\gamma dst=src1α+src2β+γ
第七个参数为输出矩阵的可选深度,当两个加权矩阵深度相同时,取默认值1-输出矩阵即为该深度;
核心介绍完毕给出图像混合使用范例:

int main()
{
	Mat mat_1 = imread("E:\\material\\assassin.jpeg");   //原代码此处第三个参数取199会影响图片大小,估计是opencv版本的锅,现取默认1
	Mat mat_2 = imread("E:\\material\\symbol.png");
	imshow("assassin", mat_1);
	imshow("symbol", mat_2);
	Mat imgROI;
	imgROI = mat_1(Rect(200, 300, mat_2.cols, mat_2.rows));   //此处imgROI与原图像共享内存
	addWeighted(imgROI, 0.5, mat_2, 0.3, 0., imgROI);  //共享内存的缘故,操作也对源图像造成了影响
	imshow("mixture", mat_1); 
	imwrite("mixture.png", mat_1);
	waitKey(0);
	return 0;
}

二、图像拼接
图像拼接和图像混合类似,一般情况下其实可以看作是图像混合时将小图的权重取1,大图的权重取0;但通常我们也会使用掩模使得拼接更加合理,从而与图像混合区别开来。图像拼接的核心在于感兴趣区域的选取和copyTo函数的使用,前者已经介绍过,矩阵的copyTo成员函数用于矩阵的真复制,我们通过将小图真复制到感兴趣区域从而完成图像拼接,copyTo函数原型如下:

copyTo(OutputArray dst, InputArray mask)

第一个参数为复制去向目标矩阵;
第二个参数为掩模矩阵,需要为灰度图,将复制来源矩阵和此掩模取与之后再给到目标矩阵,如果不输入此参数的话则直接将复制来源矩阵给到目标矩阵
图像拼接示例如下:

int main()
{
	Mat assassin = imread("E:\\material\\assassin.jpeg");
	Mat logo = imread("E:\\material\\symbol.png");
	Mat mask = imread("E:\\material\\symbol.png", 0);
	imshow("assassin", assassin);
	imshow("symbol", logo);

	Mat imgROI = assassin(Rect(0, 0, logo.cols, logo.rows));

	logo.copyTo(imgROI, mask);
	//这种类型的copy函数指将logo与mask重叠后,将mask中为像素值为0位置的logo位置像素变透明,其他像素值位置保持不变,再复制到ROI位置
	//相当于logo和mask取与再复制到ROI,如果不用mask的话,结果就直接是logo代替原区域,用了就是logo为零的地方为原区域,不为零的地方为logo
	//要求mask与logo的size相同,且通道数为1,或者与ROI的通道数相同
	imshow("mixture", assassin);
	waitKey(0);
	return 0;
}

三、多通道图像的分离与混合
通道分离通过split()函数实现,其原型如下:

void split(InputArray m, OutputArrayOfArrays mv);

第一个参数为待分离的图像,应该是一个多通道矩阵;
第二个参数为储存分离后矩阵的数组或者Vector;
通道混合通过merge()函数实现,其原型如下:

void merge(InputArrayOfArrays mv, OutputArray dst);

参数含义和split()函数正好对偶,不再赘述
通过对多通道图像的分离与混合,结合前述图像混合的知识,我们可以将某张灰度图与另一图片的某一通道混合,给出示例如下:

int main()
{
	//读取图像
	Mat assassin = imread("E:\\material\\assassin.jpeg");
	Mat symbol = imread("E:\\material\\symbol.png",0);//因为要进行的是单通道的混合,这里要读取灰度图片
	imshow("原图", assassin);
	if (!assassin.data || !symbol.data)
	{
		cout << "完了,图片路径写错了" << endl;
		return false;
	}
	//创建接收分离通道的vector
	vector<Mat> channels;
	Mat BlueChannel;
	//分离
	split(assassin, channels);
	//引用蓝色通道
	BlueChannel = channels.at(0);   //vector类型用at访问可以有效避免越界,但也可使用[]访问,只是不安全
	//勾选矩形
	Mat ROI = BlueChannel(Rect(0, 0, symbol.cols, symbol.rows));
	//混合图像
	addWeighted(ROI, 0.8, symbol, 0.2, 0., ROI);
	//三通道合并
	merge(channels, assassin);
	imshow("混合之后的图", assassin);
	return 0;
}

四、调整图像的对比度和亮度
图像的对比度和亮度实际指的是相对于原图的增益及偏置,数学公式为 g ( x ) = a ∗ f ( x ) + b g(x)=a*f(x)+b g(x)=af(x)+b,其中 g ( x ) g(x) g(x)即为调整后的图像; f ( x ) f(x) f(x)为原图; a 、 b a、b ab就分别是对比度和亮度了,可以看出a的存在使得原图中像素的极值差增大,b使得所有像素增大或减小,也就不难理解“对比”和“亮”的含义了。
给出对比度亮度调整示例代码如下:

void Contrast_Bright(int, void*);

int Contrast_value=100;  //对比度定义
int Bright_value=100;  //亮度定义
Mat srcImg = imread("E:\\material\\assassin.jpeg");

int main()
{
	
	namedWindow("原始图像");
	imshow("原始图像", srcImg);

	namedWindow("处理后图像");
	createTrackbar("对比度", "处理后图像", &Contrast_value, 300, Contrast_Bright);
	createTrackbar("亮 度", "处理后图像", &Bright_value, 200, Contrast_Bright);

	//设置初值
	Contrast_Bright(Contrast_value, 0);
	Contrast_Bright(Bright_value, 0);
	//图像一直显示
	while ((char)waitKey(0) != 'q');
	return 0;
}

void Contrast_Bright(int, void*)
{
	Mat dst_Img;
	dst_Img.create(srcImg.rows, srcImg.cols, srcImg.type());
	for (int i = 0;i < srcImg.rows;i++)
	{
		Vec3b* it1 = srcImg.ptr<Vec3b>(i);
		Vec3b* it2 = dst_Img.ptr<Vec3b>(i);
		for (int j = 0;j < srcImg.cols;j++)
		{
			for (int k = 0;k <3;k++)
			{
				it2[j][k] = saturate_cast<uchar>((Contrast_value *it1[j][k]* 0.01) + (Bright_value-100));
				//由算子得调整方案,原图*对比+亮度,satutate防止溢出
				//×0.01是因为滑动条取值一般为整数,而对比度为0~3.0浮点
			}
		}
	}
	imshow("处理后图像", dst_Img);

}

五、图像傅立叶变换
图像傅立叶变换所需数学知识为DFT,参见另一篇博客FFT介绍及python源码编写,此处仅介绍在Opencv中的API及使用方法,Opencv中df函数原型如下:

void dft(InputArray src, OutputArray dst, int flags = 0, int nonzeroRows = 0);

第一个参数为待变换的时域图像;
第二个参数为储存变换结果的频域矩阵;
(注意通常情况下上述两参数都应该为双通道的矩阵,两通道分别保存实部和虚部 ,即使我们的图像都是实数也要分成复数的形式;因为如果src值只用单通道矩阵的,输出结果会采用CCS(复共轭对称)的压缩格式输出为单通道,即:在这里插入图片描述
详情查看dft官方文档:opencv官方文档

第三个参数为变换标识符,默认取零为正向变换,取其他值时:
在这里插入图片描述
第四个值不为0时,函数会默认只有输入矩阵的前nonzeroRows行(未设置DFT_INVERSE)是非零行,或者只有输出矩阵的前nonzeroRows(设置了DFT_INVERSE)行是非零行,因此,函数在处理剩余行是可以节省一些时间,这项技术在采用DFT计算矩阵卷积时尤为明显,通常我们会去这个值为src.rows;
除了最基本的dft函数之外,我们还需要了解其他辅助函数以帮助我们更快速的进行傅立叶变换,如下:
返回DFT最优尺寸

int getOptimalDFTSize(int vecsize);

在FFT相关学习中,我们知道当计算序列为2的幂时更加方便运算,同样对于dft()函数而言,当矩阵各维度长度均为2,3,5的倍数(原理尚未理解)时更方便计算,我们通过getOptimalDFTSize()函数算出这一较好值,其唯一参数就是矩阵某维度的长度,如src.cows.
扩充图像

copyMakeBorder(InputArray src, OutputArray dst,int top, int bottom, int left, int right,int borderType, const Scalar& value = Scalar() );

通过getOptimalDFTSize()函数得到图像的最优尺寸后,就需要使用copyMakeBorder()函数对图像进行填充以使得计算更加迅速,参数信息如下:
第一个参数为填充前图像;
第二个参数为填充后图像;
第三、四、五、六个参数分别为在图像相应方向上添加像素行\列的数目;
第六个参数为填充类型,常见取值为BORDER_CONSTANT表示用指定值填充,而后第七个参数即为此指定边界值Scalar取0,其余具体标识符见下源码:

enum BorderTypes {
    BORDER_CONSTANT    = 0, //!< `iiiiii|abcdefgh|iiiiiii`  with some specified `i`
    BORDER_REPLICATE   = 1, //!< `aaaaaa|abcdefgh|hhhhhhh`
    BORDER_REFLECT     = 2, //!< `fedcba|abcdefgh|hgfedcb`
    BORDER_WRAP        = 3, //!< `cdefgh|abcdefgh|abcdefg`
    BORDER_REFLECT_101 = 4, //!< `gfedcb|abcdefgh|gfedcba`
    BORDER_TRANSPARENT = 5, //!< `uvwxyz|abcdefgh|ijklmno`
    BORDER_REFLECT101  = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
    BORDER_DEFAULT     = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
    BORDER_ISOLATED    = 16 //!< do not look outside of ROI
};
enum符号说明
BORDER_CONSTANTiiiiii|abcdefgh|iiiiiii用指定值i填充
BORDER_REPLICATEaaaaaa|abcdefgh|hhhhhhh用边界值填充
BORDER_REFLECTfedcba|abcdefgh|hgfedcb以边界(含边界)镜像对称填充
BORDER_WRAPcdefgh|abcdefgh|abcdefg以边界重复填充
BORDER_REFLECT_101gfedcb|abcdefgh|gfedcba以边界(不含边界)镜像对称填充
BORDER_TRANSPARENTuvwxyz|abcdefgh|ijklmno尚未理解
BORDER_ISOLATED尚未理解

对数尺度缩放

log(InputArray src, OutputArray dst);

进行傅立叶变换之后,频域图像幅度值插值过大,高值在屏幕上显示为白点,低值在屏幕上显示为黑点,彼此相互不连续,需要进行对数尺度缩放以使得图像更加连贯。使用常用对数函数,参数即为输入输出矩阵。
归一化

normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0, int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());

对数尺度缩放之后,频域图像极值差虽然减下了,但是并没有到float型图像显示的0~1范围之内,所以我们还需要将图像映射到此范围之内,用到了归一化函数:
第一、二个参数为输入输出矩阵,大小通道相同;
第三个参数确定了选择归一化数学公式中参数的下限;
第四个参数确定了选择归一化数学公式中参数的上限;
第五个参数确定归一化的数学公式,通常选用NORM_MINMAX,即线性归一,其他标识符:
在这里插入图片描述
第六个参数决定了输出矩阵的深度,当为负,输出在大小深度通道数都等于输入,当为正,输出只在深度与输入不同,不同的地方由dtype决定;
第七个参数决定了进行归一化的范围,同样是灰度图取与操作。
经由上述介绍给出图像傅立叶变换实例:

int main()
{
	//导入图片
	Mat srcImg = imread("E:\\material\\assassin.jpeg",0);
	if (srcImg.empty())
	{
		cout << "图片导入失败" << endl;
		return -1;
	}
	//确定输入图像最佳尺寸
	Size srcsize;
	srcsize.width = getOptimalDFTSize(srcImg.cols);
	srcsize.height = getOptimalDFTSize(srcImg.rows);
	//扩充图像
	Mat padded;
	copyMakeBorder(srcImg, padded, 0, srcsize.height - srcImg.rows, 0, srcsize.width - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));
	//为傅立叶变化后虚部实部预留空间
	Mat planes[] = { Mat_<float>{padded},Mat::zeros(padded.size(),CV_32F )};
	//将planes数组合并成一个多通道的矩阵
	Mat complexI;
	merge(planes, 2, complexI);  //2表示待合成的数组个数为2
	//傅立叶变换
	dft(complexI, complexI);;
	//提取实部虚部
	split(complexI, planes);
	//计算幅值,并储存在magnitudeImg中
	Mat magnitudeImg;
	magnitude(planes[0], planes[1],magnitudeImg);
	//进行对数尺度缩放
	magnitudeImg += Scalar::all(1);
	log(magnitudeImg, magnitudeImg);
	//剪切并重新排布图像
	magnitudeImg = magnitudeImg(Rect(0, 0, magnitudeImg.cols & -2, magnitudeImg.rows & -2)); //这里通过位运算的方式判断奇偶性,并使得奇数减一
	int cx = magnitudeImg.cols / 2;
	int cy = magnitudeImg.rows / 2;
	//图像分为四个象限
	Mat q0 = magnitudeImg(Rect(0, 0, cx, cy)); //左上
	Mat q1= magnitudeImg(Rect(cx, 0, cx, cy));//右上
	Mat q2 = magnitudeImg(Rect(0, cy, cx, cy));//左下
	Mat q3 = magnitudeImg(Rect(cx, cy, cx, cy));//右下
	//交换象限
	Mat temp;
	q0.copyTo(temp);
	q3.copyTo(q0);
	temp.copyTo(q3);
	q1.copyTo(temp);
	q2.copyTo(q1);
	temp.copyTo(q2);
	//归一化,以显示图片
	normalize(magnitudeImg, magnitudeImg, 0, 1, NORM_MINMAX);//最后一个参数指归一化的方式,这里选择线性归一化
	//显示图片
	imshow("频谱图", magnitudeImg);
	imshow("原图", srcImg);
	while ((char)waitKey(0) != 'q');
	return 0;
}

参考文献
OpenCV库成员——BorderTypes
opencv函数介绍(1)——normalize

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值