高斯图像金字塔是一个将原图像进行不断下采样,或者称为不断模糊的这样一个金字塔,我们称之为模糊类型的图像金字塔。那么这种将图像进行模糊,在如果我对它进行上采用,也就是还原过程,那么这个经过模糊又还原之后的图像,与原始图像相差多少,就构成了拉普拉斯图像金字塔(残差图像金字塔)。
假设我们有一种高斯图像金字塔中的高斯图像,那么将它去除,如果想计算下一层的高斯图像金字塔,那么就可以对它进行下采样,所谓的高斯模糊就是我们下采样的过程,这里我们假设下采样将尺寸缩小为原来的一半,之后可以得到我们的高斯图像,将这张得到的高斯图像再次进行上采样,上采样必然伴随着差值这样的操作,通过上采样后,图像的尺寸又回到了与原图像的尺寸相同的图像,将两者进行运算,得到的结果就是第k层的拉普拉斯图像。
假如第k层的高斯图像是一个n*n的图像,那么经过下采样后,它的图像变成了n/2*n/2,所以第k+1的高斯图像,它的尺寸就是n/2*n/2,将这张图像进行上采样后,得到一张n*n的图像,这张图像与第k层的高斯图像进行运算,也就是第k层的高斯图像减去这种通过下采样又上采样后的图像,得到的差值,我们称之为第k层的拉普拉斯图像,通过这种方式,我们就可以构建出拉普拉斯图像,这个图像与高斯图像结合就可以较好的去描述一张图像,经过拉伸变化可以得到什么样的效果。
在opencv中提供了与下采样相对应的上采样的函数
拉普拉斯图像金字塔实现
pyrUp()
void cv::pyrUp(InputArray src,
OutputArray dst,
const Size & dstsize = Size(),
int borderType = BORDER_DEFAULT
)
·src:输入待上采样的图像。
·dst:输出上采样后的图像,图像尺寸可以指定,但是数据类型和通道数与src相同。
·dstsize:输出图像尺寸,可以缺省。
·borderType:像素边界外推方法的标志
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv; //opencv的命名空间
using namespace std;
int main()
{
Mat img = imread("E:/opencv/opencv-4.6.0-vc14_vc15/opencv/lenac.png");
vector<Mat> Guass; //存放高斯金字塔的向量
int level = 3;//定义金字塔的层数
Guass.push_back(img); //将高斯金字塔的第0层设置为原图像
for (int i = 0; i < level; i++) //对高斯金字塔的第0层进行循环采样
{
Mat guass; //存放采样后图像
pyrDown(Guass[i], guass);
Guass.push_back(guass);//将采样后的图像存入向量中
}
//通过上面的程序我们得到了高斯图像金字塔,后面读取高斯图像金字塔中的图片,每读取一张就计算一个拉普拉斯金字塔中的图片
vector<Mat> Lap;//存放拉普拉斯图像金字塔的向量
for (int i = Guass.size() - 1; i > 0 ; i--)//从图像最上面一层开始读(上节运行展示过高斯图像金字塔,是尺寸最小的图像在最上层,下面依次增大)
{
Mat lap,upGuass;
if (i == Guass.size() - 1)//这里需要注意,当第一次读取高斯图像金字塔中图像时,在高斯图像金字塔中没有一个图像比这张图像更小了,所以需要人为的对图像进行缩小再放大
{ //所以要进行一个判断,先下采样,再上采样
Mat down;
pyrDown(Guass[i], down);
pyrUp(down, upGuass);
lap = Guass[i] - upGuass;
/* Lap.push_back(lap);*/
}
//如果读取的高斯图像金字塔中的图像不是最上面的层,那么比它尺寸还小的图像就是它id+1的图像,因此我们可以直接从高斯图像金字塔获取
pyrUp(Guass[i], upGuass);
lap = Guass[i - 1] - upGuass;
Lap.push_back(lap);
}
//读取高斯金字塔中图像,由于读取图像时,高斯图像金字塔是最大的图像在底部,而拉普拉斯图像金字塔是最小的图像在底部,因此我们要反序读出,从而实现尺寸的对应
for (int i = 0; i < Guass.size(); i++)
{
string name = to_string(i);
Mat guass, lap;//这样操作才可通过image watch形式查看
guass = Guass[i];
lap = Lap[Guass.size() - 1 - i];
imshow("G" + name, Guass[i]);
imshow("L" + name, Lap[Guass.size() - 1 - i]);
}
//由于我们的一系列图像是放置在向量中的,因此即使我们用Image watch查看也没有办法查看到每一张图像,因此我们再通过for循环,将里面的每一张图像读取出来
for (int i = 0; i < level; i++)
{
//我们希望在循环过程中直接显示图像,因此我们需要设置不同的名称
string name = to_string(i); //存放用于标识每一张图像的序号
//依次读取图像
imshow(name, Guass[i]);//name时图像的序号名称
}
waitKey(0);
return 0;
}