此篇笔记我们会学习在OpenCV中如何定义感兴趣区域ROI,如何使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像混合操作。
一、设定感兴趣区域——ROI(region of interest)
在图像处理领域,我们常常需要设置感兴趣区域(ROI,region of interest),来专注或者简化我们的工作过程 。也就是从图像中选择的一个图像区域,这个区域是我们图像分析所关注的重点。我们圈定这个区域,以便进行进一步处理。而且,使用ROI指定我们想读入的目标,可以减少处理时间,增加精度,给图像处理来带不小的便利。
ROI区域定义的两种方法
注意:图像坐标是先说列(长),再说行(宽),原点在窗口左上角
这样来看坐标:
- 第一种是利用矩形 Rect 框定,指定其左上角坐标(构造函数前两个参数)和矩形的 长宽(后两个参数)
//定义一个Mat类型并给其设定ROI区域
Mat image;
Mat imageROI;
//方法一
imageROI = image(Rect(500,250,logo.cols,logo.rows));
//500是列,250是行
//logoImage 是已加载的图像的列(长)和行(宽)
- 另一种定义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配合使用,显示出最终的结果。
logo图:
最终图:
二、初级图像混合——线性混合操作
线性混合操作是一种典型的二元(两个输入)的像素操作.
我们通过在范围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;
}
来看一下运行效果图,首先是原图:
混合图像:
注意哈:【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;
}
结果展示:
logo:
混合图: