【第二部分 图像处理】第3章 Opencv图像处理进阶-【1 图像处理D-图像金字塔】(imgproc组件、feature2D组件)

1.6图像金字塔

1.6.1关于图像金字塔

图像金字塔是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。
图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。
金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

这里写图片描述

图1

一般情况下有两种类型的图像金字塔常常出现在文献和以及实际运用中。他们分别是:
•高斯金字塔(Gaussianpyramid): 用来向下采样,主要的图像金字塔
•拉普拉斯金字塔(Laplacianpyramid): 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。

两者的简要区别:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。
要从金字塔第i层生成第i+1层(我们表示第i+1层为G_i+1),我们先要用高斯核对G_1进行卷积,然后删除所有偶数行和偶数列。当然的是,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像G_0执行操作就可产生出整个金字塔。
当图像向金字塔的上层移动时,尺寸和分辨率就降低。OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PryDown。而通过PryUp将现有的图像在每个维度都放大两遍。
图像金字塔中的向上和向下采样分别通过OpenCV函数 pyrUp 和 pyrDown 实现,概括起来就是:
对图像向上采样:pyrUp函数
对图像向下采样:pyrDown函数
这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。
但需要注意的是,PryUp和PryDown不是互逆的,即PryUp不是降采样的逆操作。这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。PryDown( )是一个会丢失信息的函数。为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关系了。

1.6.1.1高斯金字塔

高斯金字塔是通过高斯平滑和亚采样获得一些列下采样图像,也就是说第K层高斯金字塔通过平滑、亚采样就可以获得K+1层高斯图像,高斯金字塔包含了一系列低通滤波器,其截至频率从上一层到下一层是以因子2逐渐增加,所以高斯金字塔可以跨越很大的频率范围。金字塔的图像如下:

这里写图片描述

图2

另外,每一层都按从下到上的次序编号, 层级 G_i+1 (表示为 G_i+1尺寸小于第i层G_i)。
1)对图像的向下取样
为了获取层级为 G_i+1 的金字塔图像,我们采用如下方法:
1>对图像G_i进行高斯内核卷积;
2>将所有偶数行和列去除。
得到的图像即为G_i+1的图像,显而易见,结果图像只有原图的四分之一。通过对输入图像G_i(原始图像)不停迭代以上步骤就会得到整个金字塔。同时我们也可以看到,向下取样会逐渐丢失图像的信息。以上就是对图像的向下取样操作,即缩小图像。
2)对图像的向上取样
如果想放大图像,则需要通过向上取样操作得到,具体做法如下:
1>将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
2>使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素”的近似值
得到的图像即为放大后的图像,但是与原来的图像相比会发觉比较模糊,因为在缩放的过程中已经丢失了一些信息,如果想在缩小和放大整个过程中减少信息的丢失,这些数据形成了拉普拉斯金字塔。
那么,我们接下来一起看一看拉普拉斯金字塔的概念吧。

1.6.1.2拉普拉斯金字塔

下式是拉普拉斯金字塔第i层的数学定义:

这里写图片描述

式中G_i的表示第i层的图像。而UP()操作是将源图像中位置为(x,y)的像素映射到目标图像的(2x+1,2y+1)位置,即在进行向上取样。 符号表示卷积,g_(5×5)为5x5的高斯内核。
我们下文将要介绍的pryUp,就是在进行上面这个式子的运算。
因此,我们可以直接用OpenCV进行拉普拉斯运算:
Li=GiPyrUp(Gi+1)

也就是说,拉普拉斯金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。个拉普拉斯金字塔运算过程可以通过下图来概括:
这里写图片描述

图3

所以,我们可以将拉普拉斯金字塔理解为高斯金字塔的逆形式。另外再提一点,关于图像金字塔非常重要的一个应用就是实现图像分割。图像分割的话,先要建立一个图像金字塔,然后在G_i和G_i+1的像素直接依照对应的关系,建立起”父与子“关系。而快速初始分割可以先在金字塔高层的低分辨率图像上完成,然后逐层对分割加以优化。

1.6.2 resize( )函数剖析

resize( )为OpenCV中专职调整图像大小的函数。此函数将源图像精确地转换为指定尺寸的目标图像。如果源图像中设置了ROI(Region Of Interest ,感兴趣区域),那么resize( )函数会对源图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。若目标图像中已经设置ROI区域,不难理解resize( )将会对源图像进行尺寸调整并填充到目标图像的ROI中。
很多时候,我们并不用考虑第二个参数dst的初始图像尺寸和类型(即直接定义一个Mat类型,不用对其初始化),因为其尺寸和类型可以由“`
src,dsize,fx和fy这其他的几个参数来确定。

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

【参数】
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,当其非零时,有着dsize(第三个参数)的尺寸,或者由src.size()计算出来。
第三个参数,Size类型的dsize,输出图像的大小;如果它等于零,由下式进行计算:
其中,dsize,fx,fy都不能为0。
第四个参数,double类型的fx,沿水平轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
第五个参数,double类型的fy,沿垂直轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
第六个参数,int类型的interpolation,用于指定插值方式,默认为INTER_LINEAR(线性插值)。
可选的插值方式如下:
INTER_NEAREST - 最近邻插值
INTER_LINEAR - 线性插值(默认值)
INTER_AREA - 区域插值(利用像素区域关系的重采样插值)
INTER_CUBIC –三次样条插值(超过4×4像素邻域内的双三次插值)
INTER_LANCZOS4 -Lanczos插值(超过8×8像素邻域的Lanczos插值)
若要缩小图像,一般情况下最好用CV_INTER_AREA来插值,而若要放大图像,一般情况下最好用CV_INTER_CUBIC(效率不高,慢,不推荐使用)或CV_INTER_LINEAR(效率较高,速度较快,推荐使用)。
关于插值,我们看几张图就能更好地理解。先看原图:

这里写图片描述

图4

当进行6次图像缩小接着6次图像放大操作后,两种不同的插值方式得到的效果图:
这里写图片描述

图5

效果很明显,第一张全是一个个的像素,非常影响美观。另外一张却有雾化的朦胧美感,所以插值方式的选择,对经过多次放大缩小的图片最终得到的效果是有很大影响的。
接着我们来看两种resize的调用范例。
方式一,调用范例:

Mat dst=Mat::zeros(512 ,512, CV_8UC3 );//新建一张512x512尺寸的图片  
Mat src=imread(“1.jpg”);  
//显式指定dsize=dst.size(),那么fx和fy会其计算出来,不用额外指定。  
resize(src, dst, dst.size());  

方式二、调用范例:

Mat dst;  
Mat src=imread(“1.jpg”)  
 //指定fx和fy,让函数计算出目标图像的大小。  
resize(src, dst, Size(), 0.5, 0.5);  

接着我们看看完整的示例程序。
代码参看附件【demo1】,程序运行截图。

这里写图片描述

图6

1.6.3 pyrUp()函数剖析

/*【pyrUp ( )函数源代码】**********************************************************
 * @Version:OpenCV 3.0.0(Opnencv2和Opnencv3差别不大,Linux和PC的对应版本源码完全一样,均在对应的安装目录下)  
 * @源码路径:…\opencv\sources\modules\imgproc\src\pyramids.cpp
 * @起始行数:1246行   
********************************************************************************/
void cv::pyrUp( InputArray _src, OutputArray _dst, const Size& _dsz, int borderType )
{
    CV_Assert(borderType == BORDER_DEFAULT);

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
               ocl_pyrUp(_src, _dst, _dsz, borderType))

    Mat src = _src.getMat();
    Size dsz = _dsz.area() == 0 ? Size(src.cols*2, src.rows*2) : _dsz;
    _dst.create( dsz, src.type() );
    Mat dst = _dst.getMat();
    int depth = src.depth();

#ifdef HAVE_TEGRA_OPTIMIZATION
    if(borderType == BORDER_DEFAULT && tegra::useTegra() && tegra::pyrUp(src, dst))
        return;
#endif

#if IPP_VERSION_X100 >= 801 && 0
    CV_IPP_CHECK()
    {
        bool isolated = (borderType & BORDER_ISOLATED) != 0;
        int borderTypeNI = borderType & ~BORDER_ISOLATED;
        if (borderTypeNI == BORDER_DEFAULT && (!src.isSubmatrix() || isolated) && dsz == Size(src.cols*2, src.rows*2))
        {
            typedef IppStatus (CV_STDCALL * ippiPyrUp)(const void* pSrc, int srcStep, void* pDst, int dstStep, IppiSize srcRoi, Ipp8u* buffer);
            int type = src.type();
            CV_SUPPRESS_DEPRECATED_START
            ippiPyrUp pyrUpFunc = type == CV_8UC1 ? (ippiPyrUp) ippiPyrUp_Gauss5x5_8u_C1R :
                                  type == CV_8UC3 ? (ippiPyrUp) ippiPyrUp_Gauss5x5_8u_C3R :
                                  type == CV_32FC1 ? (ippiPyrUp) ippiPyrUp_Gauss5x5_32f_C1R :
                                  type == CV_32FC3 ? (ippiPyrUp) ippiPyrUp_Gauss5x5_32f_C3R : 0;
            CV_SUPPRESS_DEPRECATED_END

            if (pyrUpFunc)
            {
                int bufferSize;
                IppiSize srcRoi = { src.cols, src.rows };
                IppDataType dataType = depth == CV_8U ? ipp8u : ipp32f;
                CV_SUPPRESS_DEPRECATED_START
                IppStatus ok = ippiPyrUpGetBufSize_Gauss5x5(srcRoi.width, dataType, src.channels(), &bufferSize);
                CV_SUPPRESS_DEPRECATED_END
                if (ok >= 0)
                {
                    Ipp8u* buffer = ippsMalloc_8u(bufferSize);
                    ok = pyrUpFunc(src.data, (int) src.step, dst.data, (int) dst.step, srcRoi, buffer);
                    ippsFree(buffer);

                    if (ok >= 0)
                    {
                        CV_IMPL_ADD(CV_IMPL_IPP);
                        return;
                    }
                    setIppErrorStatus();
                }
            }
        }
    }
#endif

    PyrFunc func = 0;
    if( depth == CV_8U )
        func = pyrUp_<FixPtCast<uchar, 6>, PyrUpVec_32s8u >;
    else if( depth == CV_16S )
        func = pyrUp_<FixPtCast<short, 6>, PyrUpVec_32s16s >;
    else if( depth == CV_16U )
        func = pyrUp_<FixPtCast<ushort, 6>, PyrUpVec_32s16u >;
    else if( depth == CV_32F )
        func = pyrUp_<FltCast<float, 6>, PyrUpVec_32f >;
    else if( depth == CV_64F )
        func = pyrUp_<FltCast<double, 6>, PyrUpNoVec<double, double> >;
    else
        CV_Error( CV_StsUnsupportedFormat, "" );

    func( src, dst, borderType );
}

pyrUp( )函数的作用是向上采样并模糊一张图像,说白了就是放大一张图片。

C++: void pyrUp(InputArray src, 
                OutputArraydst, 
                const Size& dstsize=Size(), 
                int borderType=BORDER_DEFAULT )  

【参数】
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
第三个参数,const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size(src.cols*2,src.rows*2)来进行计算,且一直需要满足下列条件:

第四个参数,int类型的borderType,又来了,边界模式,一般我们不用去管它。
pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。首先,它通过插入可为零的行与列,对源图像进行向上取样操作,然后将结果与pyrDown()乘以4的内核做卷积,就是这样。直接看完整的示例程序。
代码参看附件【demo2】,程序运行截图。

这里写图片描述

图7

1.6.4 pyrDown()函数剖析

/*【pyrDown ( )函数源代码】**********************************************************
 * @Version:OpenCV 3.0.0(Opnencv2和Opnencv3差别不大,Linux和PC的对应版本源码完全一样,均在对应的安装目录下)  
 * @源码路径:…\opencv\sources\modules\imgproc\src\pyramids.cpp
 * @起始行数:1169行   
********************************************************************************/
void cv::pyrDown( InputArray _src, OutputArray _dst, const Size& _dsz, int borderType )
{
    CV_Assert(borderType != BORDER_CONSTANT);

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
               ocl_pyrDown(_src, _dst, _dsz, borderType))

    Mat src = _src.getMat();
    Size dsz = _dsz.area() == 0 ? Size((src.cols + 1)/2, (src.rows + 1)/2) : _dsz;
    _dst.create( dsz, src.type() );
    Mat dst = _dst.getMat();
    int depth = src.depth();

#ifdef HAVE_TEGRA_OPTIMIZATION
    if(borderType == BORDER_DEFAULT && tegra::useTegra() && tegra::pyrDown(src, dst))
        return;
#endif

#if IPP_VERSION_X100 >= 801 && 0
    CV_IPP_CHECK()
    {
        bool isolated = (borderType & BORDER_ISOLATED) != 0;
        int borderTypeNI = borderType & ~BORDER_ISOLATED;
        if (borderTypeNI == BORDER_DEFAULT && (!src.isSubmatrix() || isolated) && dsz == Size((src.cols + 1)/2, (src.rows + 1)/2))
        {
            typedef IppStatus (CV_STDCALL * ippiPyrDown)(const void* pSrc, int srcStep, void* pDst, int dstStep, IppiSize srcRoi, Ipp8u* buffer);
            int type = src.type();
            CV_SUPPRESS_DEPRECATED_START
            ippiPyrDown pyrDownFunc = type == CV_8UC1 ? (ippiPyrDown) ippiPyrDown_Gauss5x5_8u_C1R :
                                      type == CV_8UC3 ? (ippiPyrDown) ippiPyrDown_Gauss5x5_8u_C3R :
                                      type == CV_32FC1 ? (ippiPyrDown) ippiPyrDown_Gauss5x5_32f_C1R :
                                      type == CV_32FC3 ? (ippiPyrDown) ippiPyrDown_Gauss5x5_32f_C3R : 0;
            CV_SUPPRESS_DEPRECATED_END

            if (pyrDownFunc)
            {
                int bufferSize;
                IppiSize srcRoi = { src.cols, src.rows };
                IppDataType dataType = depth == CV_8U ? ipp8u : ipp32f;
                CV_SUPPRESS_DEPRECATED_START
                IppStatus ok = ippiPyrDownGetBufSize_Gauss5x5(srcRoi.width, dataType, src.channels(), &bufferSize);
                CV_SUPPRESS_DEPRECATED_END
                if (ok >= 0)
                {
                    Ipp8u* buffer = ippsMalloc_8u(bufferSize);
                    ok = pyrDownFunc(src.data, (int) src.step, dst.data, (int) dst.step, srcRoi, buffer);
                    ippsFree(buffer);

                    if (ok >= 0)
                    {
                        CV_IMPL_ADD(CV_IMPL_IPP);
                        return;
                    }
                    setIppErrorStatus();
                }
            }
        }
    }
#endif

    PyrFunc func = 0;
    if( depth == CV_8U )
        func = pyrDown_<FixPtCast<uchar, 8>, PyrDownVec_32s8u>;
    else if( depth == CV_16S )
        func = pyrDown_<FixPtCast<short, 8>, PyrDownVec_32s16s >;
    else if( depth == CV_16U )
        func = pyrDown_<FixPtCast<ushort, 8>, PyrDownVec_32s16u >;
    else if( depth == CV_32F )
        func = pyrDown_<FltCast<float, 8>, PyrDownVec_32f>;
    else if( depth == CV_64F )
        func = pyrDown_<FltCast<double, 8>, PyrDownNoVec<double, double> >;
    else
        CV_Error( CV_StsUnsupportedFormat, "" );

    func( src, dst, borderType );
}

pyrDown( )函数的作用是向下采样并模糊一张图片,说白了就是缩小一张图片。

C++: void pyrDown(InputArray src,
                  OutputArray dst, 
                  const Size& dstsize=Size(), 
                  int borderType=BORDER_DEFAULT)  

【参数】
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
第三个参数,const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size Size((src.cols+1)/2, (src.rows+1)/2)来进行计算,且一直需要满足下列条件:

|dstsize.width2src.cols|2|dstsize.height2src.rows|2

该pyrDown函数执行了高斯金字塔建造的向下采样的步骤。首先,它将源图像与如下内核做卷积运算:

12561464141624164624362464162416414641

接着,它便通过对图像的偶数行和列做插值来进行向下采样操作。依然是看看完整的示例程序。
代码参看【demo3】,程序运行截图。

这里写图片描述

图8

1.6.5图像金字塔综合实例

这个示例程序中,分别演示了用resize,pryUp,pryDown来让源图像进行放大缩小的操作,分别用键盘按键1、2、3、4、A、D、W、S来控制图片的放大与缩小。
代码参看附件【demo4】。

这里写图片描述

图9经过多次按键后的效果

参考:
英文: https://docs.opencv.org/master/d4/d1f/tutorial_pyramids.html
中文:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/pyramids/pyramids.html#pyramids

本章附件:

请点击参考链接

【注意】博主在附件中的代码只有Linux版本的,如何使用Windows使用该代码请参看博主的另一篇博文
Opencv环境搭建(Visual Studio+Windows)- 请点击
有任何问题请联系博主。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bruceoxl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值