OpenCV学习05-图像操作 6-两幅图像融合简单实现1;addWeighted()函数;两幅图像尺寸相同时;

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

using namespace std;
using namespace cv;

int main()
{
	Mat src;
	src = imread("D:/demo01.jpg");
	if (src.empty())
	{
		cout << "could not find the image resource..." << endl;
		return -1;
	}
	namedWindow("My Image", CV_WINDOW_AUTOSIZE);
	imshow("My Image", src);
	Mat grayImage;
	//函数可以做下面类型的转换,需要说明的是在opencv2.x时颜色空间转换code用的宏定义是CV_前缀开头,而在opencv3.x版本其颜色空间转换code宏定义更改为COLOR_开头
	cvtColor(src,grayImage,COLOR_BGR2GRAY);
	namedWindow("单通道灰度图像", CV_WINDOW_AUTOSIZE);
	imshow("单通道灰度图像", grayImage);
	//1).empty() 判断文件读取是否正确
	//2).rows 获取图像行数(高度)
	//3).cols 获取图像列数(长度)
	//4).channels() 获取图像通道数
	//5).depth() 获取图像位深度
	//单通道 做反色处理
	int height = grayImage.rows;
	int width = grayImage.cols;
	//Mat.ptr<uchar>(row):获取第row行的图像像素指针。图像的行数从0开始计数
	//获取点P(row, col)的像素值:P(row.col) = Mat.ptr<uchar>(row)[col]
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int gray = grayImage.at<uchar>(row,col);
			grayImage.at<uchar>(row, col) = 255 - gray;
		}
	}
	namedWindow("单通道做反色处理", CV_WINDOW_AUTOSIZE);
	imshow("单通道做反色处理", grayImage);

	//多通道
	Mat dst;
	dst.create(src.size(),src.type());
	//height = src.rows;
	//width = src.cols;

	//int nc = src.channels();

	//for (int row = 0; row < height; row++)
	//{
	//	for (int col = 0; col < width; col++)
	//	{
	//		if (1 == nc)
	//		{
	//			int gray = grayImage.at<uchar>(row, col);
	//			grayImage.at<uchar>(row, col) = 255 - gray;// 表示对当前图像取反
	//		}
	//		else if (3 == nc)
	//		{
	//			//src.at<Vec3b>(row, col)[0];
	//			//意味着从彩色3通道图像中row行col列第0个通道的颜色点
	//			//Vec3b 图像像素值类型
	//			int b = src.at<Vec3b>(row, col)[0];
	//			int g = src.at<Vec3b>(row, col)[1];
	//			int r = src.at<Vec3b>(row, col)[2];
	//			dst.at<Vec3b>(row, col)[0] = 255 - b;
	//			dst.at<Vec3b>(row, col)[1] = 255 - g;
	//			dst.at<Vec3b>(row, col)[2] = 255 - r;
	//		}
	//	}
	//}
	bitwise_not(src, dst); //这个是利用位取反函数对图像进行取反。
	namedWindow("三通道做反色处理", CV_WINDOW_AUTOSIZE);
	imshow("三通道做反色处理", dst);
	waitKey(0);
	return 0;
}

.图像基本运算


图像的基本运算有很多种,比如两幅图像可以相加、相减、相乘、相除、位运算、平方根、对数、绝对值等;图像也可以放大、缩小、旋转,还可以截取其中的一部分作为ROI(感兴趣区域)进行操作,各个颜色通道还可以分别提取及对各个颜色通道进行各种运算操作。总之,对于图像可以进行的基本运算非常的多,只是挑了些常用的操作详解。
  1. void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype= -1); //dst = src1 + src2
  2. void subtract(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype= -1); //dst = src1 - src2
  3. void multiply(InputArray src1, InputArray src2,OutputArray dst, double scale=1, int dtype=-1); //dst = scale*src1*src2
  4. void divide(InputArray src1, InputArray src2, OutputArray dst,double scale=1, int dtype=-1); //dst = scale*src1/src2
  5. void divide(double scale, InputArray src2,OutputArray dst, int dtype=-1); //dst = scale/src2
  6. void scaleAdd(InputArray src1, double alpha, InputArray src2, OutputArray dst); //dst = alpha*src1 + src2
  7. void addWeighted(InputArray src1, double alpha, InputArray src2,double beta, double gamma, OutputArray dst, int dtype=-1); //dst = alpha*src1 + beta*src2 + gamma
  8. void sqrt(InputArray src, OutputArray dst); //计算每个矩阵元素的平方根
  9. void pow(InputArray src, double power, OutputArray dst); //src的power次幂
  10. void exp(InputArray src, OutputArray dst); //dst = e**src(**表示指数的意思)
  11. void log(InputArray src, OutputArray dst); //dst = log(abs(src))

上述的基本操作中都属于将基础数学运算应用于图像像素的处理中,下面将着重介绍

  1. bitwise_and、bitwise_or、bitwise_xor、bitwise_not这四个按位操作函数。
  2. void bitwise_and(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); //dst = src1 & src2
  3. void bitwise_or(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); //dst = src1 | src2
  4. void bitwise_xor(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); //dst = src1 ^ src2
  5. void bitwise_not(InputArray src, OutputArray dst,InputArray mask=noArray()); //dst = ~src
bitwise_and是对二进制数据进行“与”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“与”操作,1&1=1,1&0=0,0&1=0,0&0=0
bitwise_or是对二进制数据进行“或”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“或”操作,1|1=1,1|0=0,0|1=0,0|0=0
bitwise_xor是对二进制数据进行“异或”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“异或”操作,1^1=0,1^0=1,0^1=1,0^0=0

bitwise_not是对二进制数据进行“非”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“非”操作,~1=0,~0=1

l Vec3b 对应三通道的顺序是 blue green red uchar 类型数据。
l Vec3f 对应三通道的 float 类型数据
l CV_8UC1 转换到 CV32F1 实现如下:

src.convertTo(dst,CV_32F);

6-两幅图像融合简单实现1;addWeighted()函数;两幅图像尺寸相同时;

在图像处理的操作中经常会遇到将两幅图像融合成一张图像的问题,在opnecv里利用addWeighted()函数可以很容易实现这个功能。

  • addWeighted()函数中,输入两个图像image1 和 image2。
  • 两个图像可以是任何象素类型,只要它们的类型相同。它们可以是单通道或是三通道,只要它们相符。
  • 且图像尺寸大小必须相同,否则编译会出错!!!

1.addWeighted()函数

此函数的作用是将两幅图像进行融合。计算两个数组的加权和(dst = alpha*src1 + beta*src2 + gamma)。

首先看一下addWeighted()函数定义:

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

参数说明

  • 第一个参数:src1,表示进行加权操作的第一个图像对象,输入图片1
  • 第二个参数:double型的alpha,表示第一个图像的加权系数图片1的融合比例。
  • 第三个参数:src2,表示进行加权操作的第二个图像对象,输入图片2
  • 第四个参数:double型的beta,表示第二个图像的加权系数图片2的融合比例。很多情况下,有关系 alpha+beta=1.0。
  • 第五个参数:double型的gamma,表示一个作用到加权和后的图像上的标量,可以理解为加权和后的图像的偏移量。
  • 第六个参数:dst,表示两个图像加权和后的图像,尺寸和图像类型与src1和src2相同,即输出图像
  • 第七个参数:输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。
公式表达即为;
  •       dst = src1[I] * alpha + src[2] * beta + gamma; 

其中:alpha + beta =1

程序说明

注意:要融合的两个图像的尺寸与类型必须一致!!!

我们说到在两幅图像融合时,我们要求这两幅图像必须类型尺寸一致。但是,若遇到两幅图像尺寸不相同时,我们怎么处理呢?

这节,我们通过两种方式解决这个问题。

  1. 重置其中一副图像的尺寸大小,使其两幅图像尺寸一致;
  2. 在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域;
注意:第一种方式会造成其中一副图像发生形变;而第二种方式不会改变两幅图像的尺寸;

==============分割线===========
addWeighted()函数
作用:计算两个数组的加权和(dst = alpha*src1 + beta*src2 + gamma)。即将两幅图像进行融合。

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

参数说明

  • 第一个参数:src1,表示进行加权操作的第一个图像对象,输入图片1
  • 第二个参数:double型的alpha,表示第一个图像的加权系数图片1的融合比例;
  • 第三个参数:src2,表示进行加权操作的第二个图像对象,输入图片2
  • 第四个参数:double型的beta,表示第二个图像的加权系数图片2的融合比例。很多情况下,有关系 alpha+beta=1.0;
  • 第五个参数:double型的gamma,表示一个作用到加权和后的图像上的标量,可以理解为加权和后的图像的偏移量;
  • 第六个参数:dst,表示两个图像加权和后的图像,尺寸和图像类型与src1和src2相同,即输出图像
  • 第七个参数:输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

1-1-第一种方法代码演示

#include <opencv2\opencv.hpp>

#include <iostream>

using namespace cv;
using namespace std;
//我们说到在两幅图像融合时,我们要求这两幅图像必须类型尺寸一致。但是,若遇到两幅图像尺寸不相同时,我们怎么处理呢?
//这节,我们通过两种方式解决这个问题。
//1.重置其中一副图像的尺寸大小,使其两幅图像尺寸一致;
//2.在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域;
//注意:第一种方式会造成其中一副图像发生形变;而第二种方式不会改变两幅图像的尺寸;
int main()
{
	//1 定义相关变量
	Mat src1, src2, dst;
	//2 读取原始图像并检查图像是否读取成功
	src1 = imread("D:/demo01.jpg");
	//"D:/demo01.jpg" 或者"D:\\demo01.jpg"
	if (!src1.data)
	{
		cout << "读取图像有误,请重新输入正确路径!\n";
		return -1;
	}
	src2 = imread("D:/demo02.jpg");
	if (src2.empty())//如果数组没有元素 返回true
	{
		cout << "读取图像有误,请重新输入正确路径!\n";
		return -1;
	}

	//3 显示原始图像
	namedWindow("原始图像1",CV_WINDOW_AUTOSIZE);
	imshow("原始图像1",src1);



	//1.重置其中一副图像的尺寸大小,使其两幅图像尺寸一致;
	//调整src2的大小与src1的大小一致 融合函数addWeighted()要求输入的两个图像尺寸类型必须相同
	// OpenCV提供了resize函数来改变图像的大小,函数原型如下:
	//void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR );
	//src:输入,原图像,即待改变大小的图像;
	//dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已;
	//dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算:
	//dsize = Size(round(fx*src.cols), round(fy*src.rows))
	//	其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。
	//	fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width / src.cols来计算;
	//	fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height / src.rows来计算;
	resize(src2,src2,Size(src1.cols,src1.rows));
	namedWindow("原始图像2", CV_WINDOW_AUTOSIZE);
	imshow("原始图像2", src2);	
	//bool imwrite(const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());
	//imwrite("D:/demo02.jpg",src2);

	//利用addWeighted()函数对两幅图像进行融合
	addWeighted(src1,0.6,src2,0.4,0,dst);

	namedWindow("图像1与图像2融合效果图");
	imshow("图像1与图像2融合效果图", dst);


	//保持等待状态
	waitKey(0);
	return 0;
}

第一种方法程序说明

通过上面的结果,我们可以看出。
输入的是一大一小的两幅图像,通过将小图像(指的是:手的那副)利用resize()函数,重置其尺寸大小,让它与塔尖的那副大图像尺寸一致。
这样我们就可以利用addWeighted()函数进行融合了。
可以发现,这种方式,对小图像的尺寸进行改变。

OpenCV提供了resize函数来改变图像的大小,函数原型如下:

void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR );

先解释一下各个参数的意思:

src:输入,原图像,即待改变大小的图像;

dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已;

dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算:

dsize = Size(round(fx*src.cols), round(fy*src.rows))

其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。

fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算;

fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算;

interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:

  • INTER_NEAREST - 最邻近插值
  • INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
  • INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
  • INTER_CUBIC - 4x4像素邻域内的双立方插值
  • INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值


使用注意事项:

1. dsize和fx/fy不能同时为0,要么你就指定好dsize的值,让fx和fy空置直接使用默认值,就像

resize(img, imgDst, Size(30,30));

要么你就让dsize为0,指定好fx和fy的值,比如fx=fy=0.5,那么就相当于把原图两个方向缩小一倍!

2. 至于最后的插值方法,正常情况下使用默认的双线性插值就够用了。

几种常用方法的效率是:最邻近插值>双线性插值>双立方插值>Lanczos插值;

但是效率和效果成反比,所以根据自己的情况酌情使用。

3. 正常情况下,在使用之前dst图像的大小和类型都是不知道的,类型从src图像继承而来,大小也是从原图像根据参数计算出来。但是如果你事先已经指定好dst图像的大小,那么你可以通过下面这种方式来调用函数:

resize(src, dst, dst.size(), 0, 0, interpolation);

保存图像:imwrite()函数

bool imwrite( const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());
第一个参数:将要另保存图像的名称,可以自己定义名称。
第二个参数:你要要保存的图像名称,是目前已经存在的图像。
第三个参数:来设置对于图像格式的参数,一般省略,不写。

2-1-第二种方法代码演示

#include <opencv2\opencv.hpp>

#include <iostream>

using namespace cv;
using namespace std;
//我们说到在两幅图像融合时,我们要求这两幅图像必须类型尺寸一致。但是,若遇到两幅图像尺寸不相同时,我们怎么处理呢?
//这节,我们通过两种方式解决这个问题。
//1.重置其中一副图像的尺寸大小,使其两幅图像尺寸一致;
//2.在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域;
//注意:第一种方式会造成其中一副图像发生形变;而第二种方式不会改变两幅图像的尺寸;
int main()
{
	//1 定义相关变量
	Mat src1, src2, dst;
	//2 读取原始图像并检查图像是否读取成功
	src1 = imread("D:/demo01.jpg");
	//"D:/demo01.jpg" 或者"D:\\demo01.jpg"
	if (!src1.data)
	{
		cout << "读取图像有误,请重新输入正确路径!\n";
		return -1;
	}
	src2 = imread("D:/demo02.jpg");
	if (src2.empty())//如果数组没有元素 返回true
	{
		cout << "读取图像有误,请重新输入正确路径!\n";
		return -1;
	}

	//3 显示原始图像
	namedWindow("原始图像1",CV_WINDOW_AUTOSIZE);
	imshow("原始图像1",src1);
	namedWindow("原始图像2", CV_WINDOW_AUTOSIZE);
	imshow("原始图像2", src2);
	//第二种方法代码演示
	//2.在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域;
	//利用ROI,获取将要较小的那个图像尺寸一致的区域
	Mat imageROI;
	imageROI = src2(Rect(20, 40, src1.cols, src1.rows));//在src2图像左上角(20,40)处(即起点位置),获取同src1图像尺寸一致的区域

	//利用addWeighted()函数对两幅图像进行融合
	//void addWeighted(InputArray src1, double alpha, InputArray src2,
	//double beta, double gamma, OutputArray dst, int dtype = -1);
	addWeighted(src1, 0.6, imageROI, 0.4, 0., imageROI);

	namedWindow("图像1与图像2融合效果图");
	imshow("图像1与图像2融合效果图", src2);


	//保持等待状态
	waitKey(0);
	return 0;
}

第二种方法程序说明

通过上面的结果,我们可以看出。
同样输入一大一小的两幅图像,我们在大图像上设置感兴趣区域ROI,此部分区域尺寸和小图像(指的的:手图像)尺寸一致。
这样我们就可以利用addWeighted()函数进行融合了。
可以发现,这种方式,对小图像的尺寸没有发生改变,同时我们还可以设置小图像在大图像的什么位置融合。

最近发现OpenCV中的Rect类非常神奇,其中很多函数使用起来极其方便。一下列举一些比较实用的函数:

  1. size()函数返回矩形的尺寸大小。返回类型为cv::Size。
  2. area()函数返回矩形的面积,也就是矩形包含的像素点个数。也就是矩形的(宽*高)的值。
  3. contains(Point)能检测点是否在矩形内。
  4. inside(Rect)检测矩形是否在矩形内。
  5. tl()返回矩形左上角的点坐标。即top-left。
  6. br()返回矩形右下角点坐标。即bottom-right。

还有更神奇的招数!如果要求两个矩形的交集与并集,opencv的Rect类提供了非常方便的方式。
[cpp]  view plain  copy
  1. Rect rect = rect1 & rect2;    
  2. Rect rect = rect1 | rect2;  

如果想将Rect平移,可以这样写:
[cpp]  view plain  copy
  1. Rect r1(0, 0, 5, 5);  
  2. Point p(2, 3);  
  3. Rect r2 = r1 + p;<span style="white-space:pre">   </span>//平移  

如果想改变矩形的尺寸大小,可以这样写:
[cpp]  view plain  copy
  1. Rect r1(0, 0, 5, 5);      
  2. Size s(-1, -1);  
  3. Rect r2 = r1 + s;<span style="white-space:pre">       </span>//改变尺寸大小  

但是据我观察,rect裁出来的数据只是加上了坐标限制,但是data里存的数据还是全部的数据,所以如果需要访问data的话还是要单点拷贝了,就是写一个循环一个一个赋值。
//Rect类对象的创建示例
//Rect rect(40, 40, 60, 30);
//参数解释:
//参数1:创建矩形的最左角的 x - 坐标 ;
//参数2:创建矩形的最左角的 y - 坐标;
//参数3:创建矩形的宽;
//参数4:创建矩形的高;

#include <opencv2\opencv.hpp>

#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	//【0】定义相关变量
	Rect rect(40, 40, 60, 30);
	Point point(10, 10);	//定义坐标在(10,10)的点
	Point point2(70, 50);
	Rect rect1(20, 20, 30, 40);
	Rect rect2(90, 30, 20, 10);
	Size size(20, 20);

	//【1】Rect类的相关操作
	cout << "矩形rect左上角的横坐标:" << rect.x << endl;
	cout << "矩形rect左上角的纵坐标:" << rect.y << endl;
	cout << "矩形rect的宽度:" << rect.width << endl;
	cout << "矩形rect的高度:" << rect.height << endl;
	cout << "矩形rect的尺寸:" << rect.size() << endl;
	cout << "矩形rect的面积:" << rect.area() << endl;

	//tl()返回矩形左上角的点坐标。即top-left。
	cout << "\n矩形rect左上角的点坐标:" << rect.tl() << endl;
	//br()返回矩形右下角点坐标。即bottom-right
	cout << "矩形rect右下角的点坐标:" << rect.br() << endl;

	//该点在里面则为1,否则为0
	//contains(Point)能检测点是否在矩形内
	cout << "\n判断(10, 10)这个点在不在矩形rect内:" << rect.contains(point) << endl;

	cout << "判断(70, 50)这个点在不在矩形rect内:" << rect.contains(point2) << endl;

	cout << "\n矩阵rect与矩阵rect1的交集:" << (rect1 & rect) << endl;
	cout << "矩阵rect与矩阵rect1的并集:" << (rect | rect2) << endl;

	cout << "\n矩阵rect2进行平移操作:" << (rect2 + point) << endl;
	cout << "矩阵rect2进行平移操作:" << (rect2 - point) << endl;
	cout << "矩阵rect2改变尺寸大小操作:" << (rect2 + size) << endl;
	system("pause");	//作用:暂停黑窗口,否则窗口一闪而过,看不见信息
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_40807247/article/details/80895430