赞
踩
关于图像金字塔的介绍,这里推荐一篇毛星云写的博客 OpenCV图像金字塔:高斯金字塔、拉普拉斯金字塔与图片尺寸缩放,是基于《OpenCV3编程入门》中图像金字塔部分编写,基础概念信息介绍很详细。这篇文章是针对基本的图像金字塔介绍再详细描述。
图像高斯金字塔是对一张输入图像先模糊再下采样为原来宽高的1/2(宽高缩小一半)、不断重复模糊向下采样的过程就得到了不同分辨率的输出图像,叠加在一起就形成了图像金字塔、所以图像金字塔是图像的空间多分辨率存在形式。这里的模糊是指高斯模糊,所以这个方式生成的金字塔图像又称为高斯金字塔图像。高斯金字塔图像有两个基本操作:
1、reduce 是从原图生成高斯金字塔图像、生成一系列低分辨图像,OpenCV对应的相关API为:
- pyrDown(
- InputArray src,
- OutputArray dst,
- constSize & dstsize = Size(),
- intborderType = BORDER_DEFAULT
- )
其中输出的图像dst大小必须满足下面的要求:
2、expand是从高斯金字塔图像反向生成高分辨率图像,其OpenCV对应的API为:
- pyrUp(
- InputArray src,
- OutputArray dst,
- constSize & dstsize = Size(),
- intborderType = BORDER_DEFAULT
- )
其中输出的图像dst大小必须满足下面的要求:
对输入图像实现金字塔的reduce操作就会生成不同分辨率的图像、对这些图像进行金字塔expand操作,然后使用reduce减去expand之后的结果就会得到图像拉普拉斯金字塔图像。举例如下:
输入图像G(0)
金字塔reduce操作生成 G(1), G(2), G(3)
拉普拉斯金字塔:
L0 = G(0)-expand(G(1))
L1 = G(1)-expand(G(2))
L2 = G(2)–expand(G(3))
G(0) 减去 expand(G(1)) 得到的结果就是两次高斯模糊输出的不同,所以 L0 称为 DOG(高斯不同)、它约等于 LOG 所以又称为拉普拉斯金字塔。所以要求的图像的拉普拉斯金字塔,首先要进行金字塔的 reduce 操作,然后在通过 expand 操作,最后相减得到拉普拉斯金字塔图像。
我们从拉普拉斯金字塔的定义知道,拉普拉斯金字塔的每一层都是一个高斯分差图像(DOG),以第L0层为例:
拉普拉斯金子图 L0 层 = 原图 – expand(高斯金字塔G1层)
对上面的共识变换,得到如下的结果:
原图 = 拉普拉斯金子图 L0 层 + expand(高斯金字塔G1层)
也就是说我们可以基于低分辨率的图像与它的高斯差分图像,重建生成一个高分辨率的图像。
上图左侧是对两幅输入图像生成高斯金字塔,在最小分辨率的时候对他们进行图像融合生成一个低分辨率版本的融合图像,同时生成它们的拉普拉斯金字塔的融合图像,右侧的图像是根据低分辨率版本的融合图像以及它们的拉普拉斯差分图像,不断重建生成了最终的高分辨融合重新的近似原图。
下面是一些功能代码:
- 建立图像高斯金字塔并生成最小分辨率图像代码如下:
-
- vector<Mat> buildGaussianPyramid(Mat &image) {
- vector<Mat> pyramid;
- Mat copy= image.clone();
- pyramid.push_back(image.clone());
- Mat dst;
-
- for( int i = 0; i < level; i++) {
- pyrDown( copy, dst, Size( copy.cols / 2, copy.rows / 2));
- dst.copyTo( copy);
- pyramid.push_back(dst.clone());
- }
-
- smallestLevel = dst;
-
- return pyramid;
- }
- 建立拉普拉斯金字塔并生成最小分差图像代码如下:
-
- vector<Mat> buildLapacianPyramid(Mat &image) {
-
- vector<Mat> lp;
- Mat temp;
- Mat copy= image.clone();
- Mat dst;
-
- for( int i = 0; i < level; i++) {
- pyrDown( copy, dst, Size( copy.cols / 2, copy.rows / 2));
- pyrUp(dst, temp, copy.size());
- Mat lapaian;
- subtract( copy, temp, lapaian);
- lp.push_back(lapaian);
- copy= dst.clone();
- }
-
- smallestLevel = dst;
-
- return lp;
- }
- 实现对每一层融合生成新图的代码如下:
- a :原图1 b :原图2 m :mask掩摸图
-
- Mat blend(Mat &a, Mat &b, Mat &m) {
-
- int width = a.cols;
- int height = a.rows;
- Mat dst = Mat::zeros(a.size(), a. type());
- Vec3b rgb1;
- Vec3b rgb2;
- int r1 = 0, g1 = 0, b1 = 0;
- int r2 = 0, g2 = 0, b2 = 0;
- int red = 0, green = 0, blue = 0;
- int w = 0;
- float w1 = 0, w2 = 0;
-
- for( int row = 0; row < height; row++)
- {
-
- for( int col = 0; col < width; col++)
- {
-
- rgb1 = a.at<Vec3b>(row, col);
- rgb2 = b.at<Vec3b>(row, col);
- w = m.at<uchar>(row, col);
- w2 = w / 255.0f;
- w1 = 1.0f- w2;
- b1 = rgb1[ 0] & 0xff;
- g1 = rgb1[ 1] & 0xff;
- r1 = rgb1[ 2] & 0xff;
- b2 = rgb2[ 0] & 0xff;
- g2 = rgb2[ 1] & 0xff;
- r2 = rgb2[ 2] & 0xff;
- red = ( int)(r1*w1 + r2*w2);
- green = ( int)(g1*w1 + g2*w2);
- blue = ( int)(b1*w1 + b2*w2);
-
- // output
- dst.at<Vec3b>(row, col)[ 0] = blue;
- dst.at<Vec3b>(row, col)[ 1] = green;
- dst.at<Vec3b>(row, col)[ 2] = red;
- }
-
- }
-
- return dst;
- }
- 根据拉普拉斯金字塔重建高分辨率近似原图的代码如下:
-
- // 重建拉普拉斯金字塔
-
- vector<Mat> ls;
-
- for( int i = 0; i<level; i++)
- {
- Mat a = la[i];
- Mat b = lb[i];
- Mat m = maskPyramid[i];
- ls.push_back(blend(a, b, m));
- }
-
- // 重建原图
- Mat temp;
- for( int i = level - 1; i >= 0; i--)
- {
- pyrUp(currentImage, temp, ls[i].size());
- add(temp, ls[i], currentImage);
- }
图像拼接主要可以分为两个步骤:图像配准和图像融合。其中图像配准的目的是将图一场景中不同视角的图像投影到同一平面并进行对准。
经过图像配准之后,就需要进行图像融合。而图像融合的目的就是使两幅图像的重叠区域过渡自然且平滑。在上图中,可以看到明显的边界,这对拼接来说是无法接受的。这主要是因为外部亮度的变化(天空飘过了一朵萌萌的云彩?)以及曝光时相机参数不一致导致的。要消除或缓和这种现象,就需要进行图像融合。
主流的图像融合算法:
1)加权平均法。这个很好理解,即简单的使用加权的方式从左边过渡到右边。这种方法效果一般,但算法实现极其简单,速度快。
2)羽化算法 。这种方法过渡会比加权平均法自然,但会造成不好的模糊效果。
3)拉普拉斯金字塔融合。有的地方也称为多分辨率融合算法。这种方法是将图像建立一个拉普拉斯金字塔,其中金字塔的每一层都包含了图像不同的频段。分开不同频段进行融合效果出奇的好。
1)首先建立两幅图像高斯金字塔,然后建立一定层数的拉普拉斯金字塔。拉普拉斯金字塔的层数越高,融合效果越好。层数N作为一个参数。
2)传入一个mask掩膜,代表了融合的位置。比如说想在两图的中间进行融合,那么掩膜图像的左半为255,右半为0,反过来是一样的。根据这个mask建立一个高斯金字塔,用于后续融合,层数为N+1。
3)根据mask将两幅图像的拉普拉斯金字塔的图像进行相加,mask为权值。相加的结果即为一个新的金字塔。同时,两幅图像的高斯金字塔的N+1层也进行这个操作,记这个图像为IMG1。
4)根据这个新的金字塔重建出最终的图像,重建的过程跟一般的拉普拉斯金字塔一样。首先对IMG1上采样,然后跟新金字塔的顶层相加,得到IMG2。IMG2进行上采样后跟下一层相加,得到IMG3。重复这个过程,最终得到的结果就是拉普拉斯金字塔融合算法的结果。
注:因为mask建立金字塔的过程中使用了高斯模糊,所以融合的边缘是比较平滑的。
- #include <opencv2/opencv.hpp>
- #include <iostream>
- #include <string>
-
- using namespace std;
- using namespace cv;
-
- #define IMG1_PATH "apple.png"
- #define IMG2_PATH "orange.png"
-
- /**
- * @brief The LaplacianBlending class
- * @private leftImg & rightImg 用于拼接的左图和右图
- * @private blendMask 用于融合的掩膜,值为加权平均的系数
- */
- class LaplacianBlending {
- private:
- Mat leftImg; //左图
- Mat rightImg; //右图
- Mat blendMask; //融合所需要的mask
-
- //Laplacian Pyramids 左图、右图、结果图拉普拉斯金字塔
- vector<Mat> leftLapPyr, rightLapPyr, resultLapPyr;
- //左图、右图、结果图 最高层图像(也就是最小分辨率图像)
- Mat leftHighestLevel, rightHighestLevel, resultHighestLevel;
- //掩摸mask高斯金字塔 mask为三通道图像,方便矩阵相乘
- vector<Mat> maskGaussianPyramid;
-
- //层数
- int levels;
-
- //创建金字塔
- void buildPyramids()
- {
- buildLaplacianPyramid(leftImg, leftLapPyr, leftHighestLevel);
- buildLaplacianPyramid(rightImg, rightLapPyr, rightHighestLevel);
- buildGaussianPyramid();
- }
-
- //创建高斯金字塔 /金字塔内容为每一层的掩模mask
- void buildGaussianPyramid()
- {
- assert(leftLapPyr.size() > 0);
-
- maskGaussianPyramid.clear();
- Mat currentImg;
- cvtColor(blendMask, currentImg, CV_GRAY2BGR);
- //保存mask金字塔的每一层图像
- maskGaussianPyramid.push_back(currentImg); //0 - level
-
- currentImg = blendMask;
- for (int l = 1; l < levels + 1; l++) {
- Mat _down;
- if (leftLapPyr.size() > l)
- pyrDown(currentImg, _down, leftLapPyr[l].size());
- else
- pyrDown(currentImg, _down, leftHighestLevel.size()); //lowest level
-
- Mat down;
- cvtColor(_down, down, CV_GRAY2BGR);
- //add color blend mask into mask Pyramid
- maskGaussianPyramid.push_back(down);
- string winName = to_string((long long)l);
- imshow(winName,down);
- // waitKey(0);
- currentImg = _down;
- }
- }
-
- //创建拉普拉斯金字塔
- void buildLaplacianPyramid(const Mat& img, vector<Mat>& lapPyr, Mat& HighestLevel)
- {
- lapPyr.clear();
- Mat currentImg = img;
- for (int l = 0; l < levels; l++) {
- Mat down, up;
- pyrDown(currentImg, down);
- pyrUp(down, up, currentImg.size());
- Mat lap = currentImg - up;
- lapPyr.push_back(lap);
- currentImg = down;
- }
- currentImg.copyTo(HighestLevel);
- }
-
- //重建图片 从 拉普拉斯金字塔中
- Mat reconstructImgFromLapPyramid()
- {
- //将左右laplacian图像拼成的resultLapPyr金字塔中每一层
- //从上到下插值放大并与残差相加,即得blend图像结果
- Mat currentImg = resultHighestLevel;
- for (int l = levels - 1; l >= 0; l--)
- {
- Mat up;
- pyrUp(currentImg, up, resultLapPyr[l].size());
- currentImg = up + resultLapPyr[l];
- }
- return currentImg;
- }
-
- //混合拉普拉斯金字塔
- //获得每层金字塔中直接用左右两图Laplacian,变换拼成的图像resultLapPyr(结果拉普拉斯金字塔)
- void blendLapPyrs()
- {
- //结果拉普拉斯金字塔 最高层 混合
- resultHighestLevel = leftHighestLevel.mul(maskGaussianPyramid.back()) +
- rightHighestLevel.mul(Scalar(1.0, 1.0, 1.0) - maskGaussianPyramid.back());
-
- //结果拉普拉斯金字塔 除最高层以外的其他层混合
- for (int l = 0; l < levels; l++)
- {
- Mat A = leftLapPyr[l].mul(maskGaussianPyramid[l]);
- Mat antiMask = Scalar(1.0, 1.0, 1.0) - maskGaussianPyramid[l];
- Mat B = rightLapPyr[l].mul(antiMask);
- Mat blendedLevel = A + B;
-
- resultLapPyr.push_back(blendedLevel);
- }
- }
-
- public:
- LaplacianBlending(const Mat& _left, const Mat& _right, const Mat& _blendMask, int _levels) : //construct function, used in LaplacianBlending lb(l,r,m,4);
- leftImg(_left), rightImg(_right), blendMask(_blendMask), levels(_levels)
- {
- assert(_left.size() == _right.size());
- assert(_left.size() == _blendMask.size());
- //创建拉普拉斯金字塔和高斯金字塔
- buildPyramids();
- //每层金字塔图像合并为一个
- blendLapPyrs();
- };
-
- Mat blend()
- {
- //重建拉普拉斯金字塔
- return reconstructImgFromLapPyramid();
- }
- };
-
- //拉普拉斯融合
- Mat LaplacianBlend(const Mat &left, const Mat &right, const Mat &mask)
- {
- LaplacianBlending laplaceBlend(left, right, mask, 10);
- return laplaceBlend.blend();
- }
-
- int main() {
- Mat leftImg = imread(IMG1_PATH);
- Mat rightImg = imread(IMG2_PATH);
-
- int hight = leftImg.rows;
- int width = leftImg.cols;
-
- Mat leftImg32f, rightImg32f;
- leftImg.convertTo(leftImg32f, CV_32F);
- rightImg.convertTo(rightImg32f, CV_32F);
-
- //创建用于混合的掩膜,这里在中间进行混合
- Mat mask = Mat::zeros(hight, width, CV_32FC1);
- mask(Range::all(), Range(0, mask.cols * 0.5)) = 1.0;
-
- Mat blendImg = LaplacianBlend(leftImg32f, rightImg32f, mask);
-
- blendImg.convertTo(blendImg, CV_8UC3);
-
- imshow("left", leftImg);
- imshow("right", rightImg);
- imshow("blended", blendImg);
-
- waitKey(0);
- return 0;
- }
结果:
此篇文章是参考了其他博客,然后稍作修改、整合。感谢原博主。
参考博客:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。