Opencv图像识别从零到精通(29)-----图像金字塔,向上上下采样,resize插值

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_20823641/article/details/52221442

      金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低

                                                      

一、两个金字塔

  • 高斯金字塔(Gaussianpyramid): 用来向下采样,主要的图像金字塔
  • 拉普拉斯金字塔(Laplacianpyramid): 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。

      高斯金字塔不同(DoG)又称为拉普拉斯金字塔,给出计算方式前,先加强一下定义

     记得在上面我们定义了G0,G1,G2

     G0下采样获得G1

     G1上采样获得Upsample(G1),注意Upsample(G1)不等于G0,上采样和下采样不是可逆过程,这是因为下采样损失了图片信息

     在此,给出计算拉普拉斯金字塔(DOG)的公式:L(i) = G(i) –Upsample(G(i+1))

 

 二、采样

  • 对图像向上采样:pyrUp函数
    •    <1>对图像G_i进行高斯内核卷积
    •     <2>将所有偶数行和列去除
  • 对图像向下采样:pyrDown函数
    • <1>将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
    • <2>使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素”的近似值

 

       注意:这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。

      注意:PryUp和PryDown不是互逆的,即PryUp不是降采样的逆操作。这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。

PryDown( )是一个会丢失信息的函数。为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关系了。

 

 三、函数介绍

<span style="font-size:18px;">C++: void pyrUp(InputArray src, OutputArraydst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT )  
</span>

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

  • 第四个参数,int类型的borderType,又来了,边界模式,一般我们不用去管它。

 

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)来进行计算,且一直需要满足下列条件:



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

 

 

接着,它便通过对图像的偶数行和列做插值来进行向下采样操作。

pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。

首先,它通过插入可为零的行与列,对源图像进行向上取样操作,然后将结果与pyrDown()乘以4的内核做卷积,就是这样。

<span style="font-size:18px;">C++: void resize(InputArray src,OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )</span>
  • 第一个参数,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(效率较高,速度较快,推荐使用)。


四、插值介绍

1、最邻近元法

  这是最简单的一种插值方法,不需要计算,在待求象素的四邻象素中,将距离待求象素最近的邻象素灰度赋给待求象素。设i+u, j+v(i, j为正整数, u, v为大于零小于1的小数,下同)为待求象素坐标,则待求象素灰度的值 f(i+u, j+v) 如下图所示:

 

 

如果(i+u, j+v)落在A区,即u<0.5, v<0.5,则将左上角象素的灰度值赋给待求象素,同理,落在B区则赋予右上角的象素灰度值,落在C区则赋予左下角象素的灰度值,落在D区则赋予右下角象素的灰度值。

最邻近元法计算量较小,但可能会造成插值生成的图像灰度上的不连续,在灰度变化的地方可能出现明显的锯齿状。

 

2、双线性内插法

双线性内插法是利用待求象素四个邻象素的灰度在两个方向上作线性内插,如下图所示:

 

 

对于 (i, j+v),f(i, j) 到 f(i, j+1) 的灰度变化为线性关系,则有:

      f(i, j+v) = [f(i, j+1) - f(i, j)] * v + f(i, j)

同理对于 (i+1, j+v) 则有:

                  f(i+1, j+v) = [f(i+1, j+1) - f(i+1, j)] * v + f(i+1, j)

从f(i, j+v) 到 f(i+1, j+v) 的灰度变化也为线性关系,由此可推导出待求象素灰度的计算式如下:

                  f(i+u, j+v) = (1-u) * (1-v) * f(i, j) + (1-u) * v * f(i, j+1) + u * (1-v) * f(i+1, j) + u * v * f(i+1, j+1)

双线性内插法的计算比最邻近点法复杂,计算量较大,但没有灰度不连续的缺点,结果基本令人满意。它具有低通滤波性质,使高频分量受损,图像轮廓可能会有一点模糊。

 

3、三次内插法

该方法利用三次多项式S(x)求逼近理论上最佳插值函数sin(x)/x, 其数学表达式为:

待求像素(x, y)的灰度值由其周围16个灰度值加权内插得到,如下图:

 

 

待求像素的灰度计算式如下:

 

f(x, y) = f(i+u, j+v) = ABC

 

其中: 

 

三次曲线插值方法计算量较大,但插值后的图像效果最好。

五、综合示例

<span style="font-size:18px;">#include <opencv2/opencv.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>  
using namespace std;  
using namespace cv;  
Mat g_srcImage, g_dstImage, g_tmpImage;   
int main( )  
{  
 
  
    ShowHelpText();  
    g_srcImage = imread("lena.jpg");
    if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }  
    namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );  
    imshow(WINDOW_NAME, g_srcImage);  
    g_tmpImage = g_srcImage;  
    g_dstImage = g_tmpImage;  
     int key =0;  
    while(1)  
    {  
        key=waitKey(9) ;
        //根据key变量的值,进行不同的操作  
        switch(key)  
        {  
 
        case 27:
            return 0;  
            break;   
  
        case 'q':
            return 0;  
            break;   

        case 'a'://按键A按下,调用pyrUp函数  
            pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ) );  
            printf( ">检测到按键【A】被按下,开始进行基于【pyrUp】函数的图片放大:图片尺寸×2 \n" );       
            break;   
  
        case 'w'://按键W按下,调用resize函数  
            resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));  
            printf( ">检测到按键【W】被按下,开始进行基于【resize】函数的图片放大:图片尺寸×2 \n" );          
            break;   
  
        case '1'://按键1按下,调用resize函数  
            resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));  
            printf( ">检测到按键【1】被按下,开始进行基于【resize】函数的图片放大:图片尺寸×2 \n" );  
            break;   
  
        case '3': //按键3按下,调用pyrUp函数  
            pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));  
            printf( ">检测到按键【3】被按下,开始进行基于【pyrUp】函数的图片放大:图片尺寸×2 \n" );  
            break;   
        //======================【图片缩小相关键值处理】=======================    
        case 'd': //按键D按下,调用pyrDown函数  
            pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));  
            printf( ">检测到按键【D】被按下,开始进行基于【pyrDown】函数的图片缩小:图片尺寸/2\n" );  
            break;   
  
        case  's' : //按键S按下,调用resize函数  
            resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));  
            printf( ">检测到按键【S】被按下,开始进行基于【resize】函数的图片缩小:图片尺寸/2\n" );  
            break;   
  
        case '2'://按键2按下,调用resize函数  
            resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ),(0,0),(0,0),2);  
            printf( ">检测到按键【2】被按下,开始进行基于【resize】函数的图片缩小:图片尺寸/2\n" );  
            break;   
  
        case '4': //按键4按下,调用pyrDown函数  
            pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ) );  
            printf( ">检测到按键【4】被按下,开始进行基于【pyrDown】函数的图片缩小:图片尺寸/2\n" );  
            break;   
        }  
          
        //经过操作后,显示变化后的图  
        imshow( WINDOW_NAME, g_dstImage );  
  
        //将g_dstImage赋给g_tmpImage,方便下一次循环  
        g_tmpImage = g_dstImage;  
    }  
  
    return 0;  
}  
</span>


                                  

六、matlab

<span style="font-size:18px;">I = imread('d:\lena.jpg');  
A = imresize(I, 1.5, 'nearest');  
B = imresize(I, 1.5, 'bilinear');  
C = imresize(I, 1.5, 'bicubic');  
subplot(2,2,1), imshow(I), title('original');  
subplot(2,2,2), imshow(A), title('nearest');  
subplot(2,2,3), imshow(B), title('bilinear');  
subplot(2,2,4), imshow(C), title('bicubic');  </span>


图像识别算法交流 QQ群:145076161,欢迎图像识别与图像算法,共同学习与交流


猜你喜欢

转载自blog.csdn.net/qq_20823641/article/details/52221442