理论
- 通常我们会把图像转换为不同的大小。通常存在两种不同的选择:
1. 放大
2. 缩小
- 尽管在OpenCV中存在函数来进行几何转换(cv::resize,在后面会用到),在这一部分我们首先分析使用图像金字塔,它在视觉应用中被广泛应用。
图像金字塔
- 图像金字塔是一系列图像,所有的这些图像都来源于原始图像,连续降低采样得到。
有两种常见的图像金字塔
- Gaussian 金字塔:来进行降低采样
- Laplacian 金字塔:来进行上采样
在本部分介绍Gaussian 金字塔
Gaussian 金字塔
- 设想金字塔是一系列的图层,越向上层尺寸越小。
- 图层的序号从底到上递增,所以 (i+1) 代表 Gi+1 ,它比 Gi 小 。
为了得到Gaussian 金字塔中的 (i+1) 层,我们需要:
- 使用Gaussian 核对
Gi
进行卷积。
116⎡⎣⎢⎢⎢⎢⎢⎢1464141624164624362464162416414641⎤⎦⎥⎥⎥⎥⎥⎥
- 移除每一个偶数行和列
- 使用Gaussian 核对
Gi
进行卷积。
你会很容易的发现,得到的结果是前一个图像的四分之一,这样迭代下去,原始图像 G0 产生出整个金字塔。
- 以上是进行降低采样的程序,如果我们要对图像进行扩大怎么办?难道将整个列填充为0?
- 首先,将图像每个维度扩大为原来的二倍。
- 使用同样的卷积核,计算出丢失的值。
- 这两个过程,加降低采样和上采样,在OpenCV中使用 cv::pyrUp 和cv::pyrDown 实现,我们在后面的代码中会用到。
代码
下面是示例代码,你可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, dst, tmp;
const char* window_name = "Pyramids Demo";
int main( void )
{
printf( "\n Zoom In-Out demo \n " );
printf( "------------------ \n" );
printf( " * [u] -> Zoom in \n" );
printf( " * [d] -> Zoom out \n" );
printf( " * [ESC] -> Close program \n \n" );
src = imread( "../data/chicky_512.png" ); // Loads the test image
if( src.empty() )
{ printf(" No data! -- Exiting the program \n");
return -1; }
tmp = src;
dst = tmp;
imshow( window_name, dst );
for(;;)
{
char c = (char)waitKey(0);
if( c == 27 )
{ break; }
if( c == 'u' )
{ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
printf( "** Zoom In: Image x 2 \n" );
}
else if( c == 'd' )
{ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
printf( "** Zoom Out: Image / 2 \n" );
}
imshow( window_name, dst );
tmp = dst;
}
return 0;
}
代码说明
我们来看一看代码结构:
- 加载图像
src = imread( "../data/chicky_512.png" ); // Loads the test image
if( src.empty() )
{ printf(" No data! -- Exiting the program \n");
return -1; }
- 创建Mat 对象dst来保存操作结果,tmp对象保存临时结果
Mat src, dst, tmp;
/* ... */
tmp = src;
dst = tmp;
- 创建窗口来显示结果
imshow( window_name, dst );
- 创建一个无限循环等待用户输入
for(;;)
{
char c = (char)waitKey(0);
if( c == 27 )
{ break; }
if( c == 'u' )
{ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
printf( "** Zoom In: Image x 2 \n" );
}
else if( c == 'd' )
{ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
printf( "** Zoom Out: Image / 2 \n" );
}
imshow( window_name, dst );
tmp = dst;
}
用户输入ESC将退出。除此之外,它还有两个选项:
- 按下’u’执行升采样
if( c == 'u' )
{ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
printf( "** Zoom In: Image x 2 \n" );
}
我们使用的 cv::pyrUp函数有三个参数:
tmp:当前对象,由原始图像初始化。
dst:输出对象,默认是输入图像的二倍。
Size( tmp.cols*2, tmp.rows*2 ) :目标对象大小,这里是上升采样,所以 cv::pyrUp 期望的输出是输入的二倍。
- 按下’d’执行降采样
else if( c == 'd' )
{ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
printf( "** Zoom Out: Image / 2 \n" );
}
与上面类似cv::pyrDown 也有三个参数:
tmp:当前对象,由原始图像初始化。
dst:输出对象,默认是输入图像的一半
Size( tmp.cols/, tmp.rows/2 ) :目标对象大小,这里是上升采样,所以 cv::pyrUp 期望的输出是输出的一半。
注意到输入图像必须能够被2整除,否则会报错。
我们使用输出结果更新tmp,这样它可以作为下一轮操作的输入。
结果
- 在编译之后,图像chicky_512.jpg位于 samples/data文件夹下。这个图像大小
512×512
,因此进行降采样不会产生错误。原始图像如下。
- 现在我们进行连续两次的cv::pyrDown操作结果如下。
- 我们在执行两次cv::pyrUp操作,会发现我们丢失了一些信息。