如果要放大或缩小图片的尺寸,可以使用OpenCV提供的两种方法:

  1.  resize函数,是最直接的方式;
  2.  pyrUp,pyrDown函数,即图像金字塔相关的两个函数,对图像进行上采样和下采样的操作。

一、  resize函数(resize函数在imgproc模块的Geometric Image transformations子模块里)

源码溯源:(resize函数位于..\opencv\sources\modules\imgproc\src\resize.cpp中),如下

void cv::resize( InputArray _src, 
                 OutputArray _dst, 
                 Size dsize,
                 double inv_scale_x,
                 double inv_scale_y, 
                 int interpolation )
{
    CV_INSTRUMENT_REGION()

    Size ssize = _src.size();

    CV_Assert( ssize.width > 0 && ssize.height > 0 );
    CV_Assert( dsize.area() > 0 || (inv_scale_x > 0 && inv_scale_y > 0) );
    if( dsize.area() == 0 )
    {
        dsize = Size(saturate_cast<int>(ssize.width*inv_scale_x),
                     saturate_cast<int>(ssize.height*inv_scale_y));
        CV_Assert( dsize.area() > 0 );
    }
    else
    {
        inv_scale_x = (double)dsize.width/ssize.width;
        inv_scale_y = (double)dsize.height/ssize.height;
    }

    if (interpolation == INTER_LINEAR_EXACT && (_src.depth() == CV_32F || _src.depth() == CV_64F))
        interpolation = INTER_LINEAR; // If depth isn't supported fallback to generic resize

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat() && _src.cols() > 10 && _src.rows() > 10,
               ocl_resize(_src, _dst, dsize, inv_scale_x, inv_scale_y, interpolation))

    Mat src = _src.getMat();
    _dst.create(dsize, src.type());
    Mat dst = _dst.getMat();

    if (dsize == ssize)
    {
        // Source and destination are of same size. Use simple copy.
        src.copyTo(dst);
        return;
    }

    hal::resize(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, inv_scale_x, inv_scale_y, interpolation);
}

 简版:

void cv::resize( InputArray _src, 
                 OutputArray _dst, 
                 Size dsize,
                 double inv_scale_x = 0,
                 double inv_scale_y = 0, 
                 int interpolation = INTER_LINEAR )

 参数解析:

                            dsize = Size( round(fx*src.cols, round(fy*src.rows)) ) ;

                            其中fx,fy,dsize都能不为0

  • 第一个参数,InputArray类型的src,输入图像,Mat类型即可;
  • 第二个参数,OutputArray类型的dst,输出图像,当其非零时,有着dsize(第三个参数)的尺寸或者有src.size()计算出来;
  • 第三个参数,Size类型的dsize,输出图像的大小。如果它等于0,由下式计算:
  •  第四个参数,double类型的fx,沿水平轴的缩放系数,默认值为0,且等于0时,由下式计算:

                            inv_scale_x = (double)dsize.width/ssize.width;

  • 第五个参数,double类型的fy,沿垂直轴的缩放系数,默认值为0,且等于0时,由下式计算:

                            inv_scale_y = (double)dsize.height/ssize.height;

  • 第六个参数,int类型的interpolation,用于指定插值方式,默认值为INTER_LINEAR(线性插值),可选插值方式如下:
  1. INTER_NEAREST——最近邻插值
  2. INTER_LINEAR——线性插值(默认值)
  3. INTER_AREA——区域插值(利用像素区域关系的重采样插值)
  4. INTER_CUBIC——三次样条插值(超过4×4像素领域内的双三次插值)
  5. INTER_LANCZOS4——Lanczos插值(超过8×8像素邻域的Lanczos插值)

         要缩小图像,一般情况下用INTER_AREA来插值;而若要放大图像,一般情况下用INTER_CUBIC(效率不高,不推荐)或INTER_LINEAR(效率高,推荐)

<1>resize函数代码示例:

#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
	//载入原图
	Mat srcImage = imread("1.jpg");
	Mat temImage, dstImage1, dstImage2;//临时变量和目标图的定义
	temImage = srcImage;

	//显示原图
	imshow("【原始图】", srcImage);

	//进行尺寸调整操作
	resize(temImage, dstImage1, Size(temImage.cols / 2, temImage.rows / 2), (0, 0), (0, 0), 3);
	resize(dstImage1, dstImage2, Size(dstImage1.cols / 2, dstImage1.rows / 2), (0, 0)(0, 0), 3);

	//显示效果图
	imshow("【效果图一】", dstImage1);
	imshow("【效果图二】", dstImage2);

	waitKey(0);
	return 0;
}

 运行结果:原始图 / 效果图一 / 效果图二

opencv为什么resize那么快_拉普拉斯金字塔

 

二、图像金字塔相关API函数

1、向上采样:pyrUp()函数

       pyrUp()函数向上采样并模糊一张图像,即放大一张图片;

源码溯源:(位于..\opencv\sources\modules\imgproc\src\pyramids.cpp中)

void cv::pyrUp( InputArray _src, 
                OutputArray _dst, 
                const Size& _dsz, 
                int borderType )
{
    CV_INSTRUMENT_REGION()

    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

#ifdef HAVE_IPP
    bool isolated = (borderType & BORDER_ISOLATED) != 0;
    int borderTypeNI = borderType & ~BORDER_ISOLATED;
#endif
    CV_IPP_RUN(borderTypeNI == BORDER_DEFAULT && (!_src.isSubmatrix() || isolated) && dsz == Size(_src.cols()*2, _src.rows()*2),
        ipp_pyrup( _src,  _dst,  _dsz,  borderType));


    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 );
}

源码简版:

void cv::pyrUp( InputArray _src, 
                OutputArray _dst, 
                const Size& _dsz = SIze(), 
                int borderType = BORDER_DEFAULT )

参数解析:

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

| dstsize.width-src.cols*2 | <= (dstsize.width mod 2)

| dstsize.height-src.rows*2 | <= (dstsize.height mod 2)

  • 第四个参数,int类型的borderType,边界模式。

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

        First,它通过插入可为零的行与列,对源图像进行向上采样操作,

        then,将结果与pyrDown()乘以4的内核做卷积。

<2>pyrUp函数简单示例:

#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
	//载入原图
	Mat srcImage = imread("1.jpg");
	Mat temImage, dstImage;//临时变量和目标图的定义
	temImage = srcImage;

	//显示原图
	imshow("【原始图】", srcImage);
	
	//进行向上采样操作
	pyrUp(temImage, dstImage, Size(temImage.cols * 2, temImage.rows * 2));

	//显示效果图
	imshow("效果图", dstImage);

	waitKey(0);
	return 0;
}

运行结果:

opencv为什么resize那么快_高斯金字塔_02

 

 

2、向下采样:pyrDown()函数

      pyrDown()函数用来向下采样并模糊一张图片,即缩小一张图片。

void cv::pyrDown( InputArray _src, 
                  OutputArray _dst, 
                  const Size& _dsz, 
                  int borderType )
{
    CV_INSTRUMENT_REGION()

    CV_Assert(borderType != BORDER_CONSTANT);

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

    CV_OVX_RUN(_src.dims() <= 2,
               openvx_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();

    CALL_HAL(pyrDown, cv_hal_pyrdown, src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, depth, src.channels(), borderType);

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

#ifdef HAVE_IPP
    bool isolated = (borderType & BORDER_ISOLATED) != 0;
    int borderTypeNI = borderType & ~BORDER_ISOLATED;
#endif
    CV_IPP_RUN(borderTypeNI == BORDER_DEFAULT && (!_src.isSubmatrix() || isolated) && dsz == Size((_src.cols() + 1)/2, (_src.rows() + 1)/2),
        ipp_pyrdown( _src,  _dst,  _dsz,  borderType));


    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 );
}

源码简版:

void cv::pyrDown( InputArray _src, 
                  OutputArray _dst, 
                  const Size& _dsz = Size(), 
                  int borderType = BORDER_DEFAUT )

参数解析:

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

| dstsize.width*2 - src.cols | <= 2

| dstsize.height*2 - src.rows | <= 2

        该pyrDown()函数执行了高斯金字塔建筑,首先,它将源图像与如下内核做卷积运算:

opencv为什么resize那么快_opencv为什么resize那么快_03

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

  • 第四个参数,int类型的borderType,边界模式。

<2>pyrDown()函数简单示例:

#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
	//载入原图
	Mat srcImage = imread("1.jpg");
	Mat temImage, dstImage;//临时变量和目标图的定义
	temImage = srcImage;

	//显示原图
	imshow("【原始图】", srcImage);
	
	//进行向下采样操作
	pyrDown(temImage, dstImage, Size(temImage.cols / 2, temImage.rows / 2));

	//显示效果图
	imshow("效果图", dstImage);

	waitKey(0);
	return 0;
}

运行结果:

opencv为什么resize那么快_图像缩放_04

 

 三、综合实例:图像金字塔与图片尺寸缩放

#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

#define WINDOW_NAME "【程序窗口】"

Mat g_srcImage, g_dstImage, g_tmpImage;

int main() {
	g_srcImage = imread("1.jpg");
	if (!g_srcImage.data) {
		printf("读取g_srcImage错误!\n");
		return false;
	}

	//创建显示窗口
	namedWindow(WINDOW_NAME, 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) {//用1、2、3、4、w、s、a、d来控制图片缩放
		case 27://按下ESC
		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函数的图片放大\n");
			break;
		case 'w'://w被按下,调用resize函数
			resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols * 2, g_tmpImage.rows * 2));
			printf(">检测到按键W被按下,开始基于resize函数的放大\n");
			break;
		case '1'://按下按键1,调用resize函数
			resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols * 2, g_tmpImage.rows * 2));
			printf(">检测到按键1被按下,开始基于resize函数的放大\n");
			break;
		case '3'://按键3被按下,调用pyrUp函数
			pyrUp(g_tmpImage, g_dstImage, Size(g_tmpImage.cols * 2, g_tmpImage.rows * 2));
			printf(">检测到按键3被按下,开始基于pyrUp函数的图片放大\n");
			break;
		case 'd'://按下按键D,调用pyrDown函数
			pyrDown(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
			printf(">检测到按键D被按下,开始基于pyrDown函数的图片缩小\n");
			break;
		case 's':
			resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
			printf(">检测到按键S被按下,开始基于resize函数的缩小\n");
			break;
		case '2'://按键2被按下,调用resize函数
			resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2),(0,0),(0,0),0);
			printf(">检测到按键2被按下,开始基于resize函数的缩小\n");
			break;
		case '4'://按键4被按下,调用pyrDown函数
			pyrDown(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
			printf(">检测到按键4被按下,开始基于pyrDown函数的缩小\n");
			break;

		}

		//经过操作后,显示变化后的图片
		imshow(WINDOW_NAME, g_dstImage);

		//将g_dstImage赋给g_tmpImage,以便于下一次循环
		g_tmpImage = g_dstImage;
	}

	return 0;
}

运行截图:

 

opencv为什么resize那么快_OpenCV_05