此篇笔记我们会学习在OpenCV中如何定义感兴趣区域ROI,如何使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像混合操作。

一、设定感兴趣区域——ROI(region of interest)

在图像处理领域,我们常常需要设置感兴趣区域(ROI,region of interest),来专注或者简化我们的工作过程 。也就是从图像中选择的一个图像区域,这个区域是我们图像分析所关注的重点。我们圈定这个区域,以便进行进一步处理。而且,使用ROI指定我们想读入的目标,可以减少处理时间,增加精度,给图像处理来带不小的便利。

ROI区域定义的两种方法

注意:图像坐标是先说列(长),再说行(宽),原点在窗口左上角

这样来看坐标:

opencv形状是否有重合_Image

  1. 第一种是利用矩形 Rect 框定,指定其左上角坐标(构造函数前两个参数)和矩形的 长宽(后两个参数)
//定义一个Mat类型并给其设定ROI区域
Mat image;
Mat imageROI;
//方法一
imageROI = image(Rect(500,250,logo.cols,logo.rows));
//500是列,250是行
//logoImage 是已加载的图像的列(长)和行(宽)
  1. 另一种定义ROI的方式是指定感兴趣行或列的范围(Range)。Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。cv::Range可以用来定义Range。如果使用cv::Range来定义ROI,那么前例中定义ROI的代码可以重写为:
//方法二
imageROI = srcImage(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));
//Range(250,250+logoImage.rows)指定行的范围,Range(200,200+logoImage.cols)指定列的范围
//logoImage 是已加载的图像的列(长)和行(宽)
注意:两种ROI定义的col和row输入是反的,Rect函数中是先输入宽度,宽度对应列,而range函数先输入行,所以相反

下面我们来看一个实例,显示如何利用ROI将一幅图加到另一幅图的指定位置。
在下面的代码中,我们通过一个图像掩膜(mask),直接将插入处的像素设置为logo图像的像素值,这样效果会很赞很逼真:

#include<opencv2/opencv.hpp>
using namespace cv;

//ROI_AddImage()
//利用感兴趣区域ROI实现图像叠加

void ROI_AddImage()
{
	//1.读入图像
	Mat srcImage1 = imread("w.jpg");
	Mat logoImage = imread("w_logo.jpg");
	if (!srcImage1.data)
	{
		printf("srcImage1读取错误!");
	}
	if (!logoImage.data)
	{
		printf("logoImage读取错误!");
	}

	namedWindow("[1]王俊凯", WINDOW_NORMAL);
	imshow("[1]王俊凯", srcImage1);

	namedWindow("[2]表情包", WINDOW_NORMAL);
	imshow("[2]表情包", logoImage);

	//2.定义一个Mat类型并给其设定ROI区域
	Mat imageROI = srcImage1(Rect(1400, 100, logoImage.cols, logoImage.rows));

	//3.加载掩膜(必须是灰度图)
	Mat mask = imread("w_logo.jpg", 0);

	//4.将掩膜拷贝到ROI
	logoImage.copyTo(imageROI, mask);

	//5.显示结果
	namedWindow("[3]利用ROI实现图像叠加示例窗口", WINDOW_NORMAL);
	imshow("[3]利用ROI实现图像叠加示例窗口", srcImage1);

	waitKey();
	//如果没有加这个,运行程序就会一闪而退

}

void main()
{
	ROI_AddImage();
}
使用copyTo函数可以得到一个复制的矩阵。
 A.copyTo(B); 就可以得到和A一模一样的矩阵B。(当然需要事先声明B)

copyTo还有一个重构函数copyTo(B,MASK)。意思是可以得到一个附加掩膜MASK的矩阵B。
//【3】加载掩模(必须是灰度图)
	Mat mask = imread("logo.jpg", 0);
	//【4】将掩膜拷贝到ROI
	logoImage.copyTo(imageROI, mask);
我们如何理解上面两句话的含义?

首先第一句 是:读取logo.jpg到mask这个矩阵中,mask就是我们的掩膜,也可以说是衣服

第二句:我们给imageROI这个矩阵 加了mask这个掩膜,给imageROI 穿了衣服后得到了新的logoImage

后续就可以使用logoImage了!!!

这个函数首先是载入了两张jpg图片到srcImage1和logoImage中,然后定义了一个Mat类型的imageROI,并使用cv::Rect设置其感兴趣区域为srcImage1中的一块区域,将imageROI和srcImage1关联起来。接着定义了一个Mat类型的的mask并读入w_logo.jpg,顺势使用Mat:: copyTo把mask中的内容拷贝到imageROI中,于是就得到了最终的效果图,namedWindow和imshow配合使用,显示出最终的结果。

opencv形状是否有重合_opencv形状是否有重合_02


logo图:

opencv形状是否有重合_opencv_03


最终图:

opencv形状是否有重合_opencv_04

二、初级图像混合——线性混合操作

线性混合操作是一种典型的二元(两个输入)的像素操作.
我们通过在范围0到1之间改变alpha值,来对两幅图像(f0(x)和f1(x))或两段视频(同样为(f0(x)和f1(x))产生时间上的画面叠化(cross-dissolve)效果,就像幻灯片放映和电影制作中的那样。即在幻灯片翻页时设置的前后页缓慢过渡叠加效果,以及电影情节过渡时经常出现的画面叠加效果。
实现方面,我们主要运用了OpenCV中addWeighted函数.

addWeighted函数:
这个函数的作用是,计算两个数组(图像阵列)的加权和。原型如下:

void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);
第一个参数,InputArray类型的src1,表示需要加权的第一个数组,常常填一个Mat。
	第二个参数,alpha,表示第一个数组的权重
	第三个参数,src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数。
	第四个参数,beta,表示第二个数组的权重值。
	第五个参数,gamma,一个加到权重总和上的标量值。看下面的式子自然会理解。
	第六个参数,dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数。
	第七个参数,dtype,输出阵列的可选深度,有默认值-1。;当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

代码示例:

#include<opencv2/opencv.hpp>
using namespace cv;

//LinearBlending()
//利用addWeighted()函数实现图像线性混合

void LinearBlending()
{
	//1.定义一些局部变量
	double alphaValue = 0.5;//在这里我们设置alpha值为0.5
	double betaValue;
	Mat srcImage2, srcImage3, dstImage;

	//2.读取图像(两幅图片需要是同样的类型和尺寸)
	srcImage2 = imread("wy.jpg");
	srcImage3 = imread("rain.jpg");
	//在这里需要注意的是,因为我们是对 srcImage1和srcImage2求和,
	//所以它们必须要有相同的尺寸(宽度和高度)和类型,不然多余的部分没有对应的“伴”,
	//肯定会出问题。




	if (!srcImage2.data)
	{
		printf("srcImagae2读取错误!");
		return;
	}
	if (!srcImage3.data)
	{
		printf("srcImagae3读取错误!");
		return;
	}

	//3.做图像混合加权操作
	betaValue = (1.0 - alphaValue);
	addWeighted(srcImage2, alphaValue, srcImage3, betaValue,0.0, dstImage);
	//其中beta值为1-alpha,gamma值为0

	//4.创建并显示原图窗口
	namedWindow("【1】王源", WINDOW_NORMAL);
	imshow("【1】王源",srcImage2);

	namedWindow("【2】雨", WINDOW_NORMAL);
	imshow("【2】雨", srcImage3);

	namedWindow("【3】线性混合示例窗口", WINDOW_NORMAL);
	imshow("【3】线性混合示例窗口", dstImage);

	waitKey(0);

}

int main()
{
	LinearBlending();

	return 0;
}

来看一下运行效果图,首先是原图:

opencv形状是否有重合_c++_05


opencv形状是否有重合_数组_06


混合图像:

opencv形状是否有重合_c++_07

注意哈:【1】和【2】图象尺寸一定要相同,否则会报错!

(把两个图片变成相同尺寸大小的方法:点击这里)

三,综合示例

在前面分别介绍的设定感兴趣区域ROI和使用addWeighted函数进行图像线性混合的基础上,我们还将他们两者中和起来使用,也就是先指定ROI,并用addWeighted函数对我们指定的ROI区域的图像进行混合操作。

#include<opencv2/opencv.hpp>
using namespace cv;

void ROI_LinearBlending()
{
	//1.读入图像
	Mat srcImage1 = imread("w.jpg");
	Mat logoImage = imread("w_logo.jpg");
	if (!srcImage1.data)
	{
		printf("srcImage1读取错误!");
	}
	if (!logoImage.data)
	{
		printf("logoImage读取错误!");
	}

	namedWindow("[1]王俊凯", WINDOW_NORMAL);
	imshow("[1]王俊凯", srcImage1);

	namedWindow("[2]表情包", WINDOW_NORMAL);
	imshow("[2]表情包", logoImage);

	//2.定义一个Mat类型并给其设定ROI区域
	Mat imageROI;
		//方法一	
	imageROI = srcImage1(Rect(1400, 100, logoImage.cols, logoImage.rows));

		//方法二
	//imageROI = srcImage1(Range(100, 100 + logoImage.rows), Range(1400, 1400 + logoImage.cols));

	//3.将logo加到原图上
	addWeighted(imageROI, 0.5, logoImage, 0.3, 0, imageROI);

	//4.显示结果
	namedWindow("【3】区域线性图像混合示例窗口", WINDOW_NORMAL);
	imshow("【3】区域线性图像混合示例窗口", srcImage1);

	waitKey(0);
}


int main()
{
	ROI_LinearBlending();

	return 0;
}

结果展示:

opencv形状是否有重合_数组_08


logo:

opencv形状是否有重合_opencv_09


混合图:

opencv形状是否有重合_c++_10