Skip to content

图像金字塔

在本教程中,你将学到如何:

  • 使用 OpenCV 函数 pyrUp() 和 pyrDown() 对给定图像进行下采样或上采样。

注: 以下说明取自 Bradski 与 Kaehler 所著的 Learning OpenCV 一书。

  • 通常我们需要把图像转换到与原始尺寸不同的尺寸。为此有两种可选方式:
    1. 放大图像(zoom in),或
    2. 缩小图像(zoom out)。
  • 尽管 OpenCV 中有一个几何变换函数可以字面意义上地调整图像尺寸(即 resize,我们将在后续教程中介绍),但在本节中,我们先分析**图像金字塔(Image Pyramids)**的用法,它在大量的视觉应用中被广泛采用。
  • 图像金字塔是一组图像的集合——它们全部由同一幅原始图像派生而来——通过逐次下采样,直到达到某个期望的终止点。
  • 常见的图像金字塔有两类:
    • 高斯金字塔(Gaussian pyramid): 用于对图像下采样
    • 拉普拉斯金字塔(Laplacian pyramid): 用于从金字塔中较低层(分辨率较低)的图像重建一幅上采样图像
  • 在本教程中我们将使用高斯金字塔。
  • 把金字塔想象成一组层叠的层:层越高,尺寸越小。
  • 每一层从下到上编号,因此第 (i+1)(i+1) 层(记作 Gi+1G_{i+1})比第 ii 层(GiG_{i})更小。
  • 要在高斯金字塔中生成第 (i+1)(i+1) 层,我们执行如下操作:
    • 用一个高斯核对 GiG_{i} 进行卷积:

1256[1464141624164624362464162416414641]\frac{1}{256} \begin{bmatrix} 1 & 4 & 6 & 4 & 1 \\ 4 & 16 & 24 & 16 & 4 \\ 6 & 24 & 36 & 24 & 6 \\ 4 & 16 & 24 & 16 & 4 \\ 1 & 4 & 6 & 4 & 1 \end{bmatrix}

- 去掉所有偶数行和偶数列。
  • 不难发现,得到的图像面积恰好是其前一层的四分之一。在输入图像 G0G_{0}(原始图像)上反复执行该过程即可生成整个金字塔。
  • 上述过程用于对图像进行下采样。如果我们想把图像放大呢?
    • 首先,将图像在每个维度上放大到原来的两倍,新增的偶数行和偶数列用 00 填充。
    • 用上面所示的同一个核(乘以 4)进行卷积,以近似「缺失像素」的值。
  • 上述两个过程(如上所述的下采样与上采样)由 OpenCV 函数 pyrUp() 和 pyrDown() 实现,我们将在下面的代码示例中看到。

注: 当我们缩小图像尺寸时,实际上会丢失图像的信息。

下面是本教程的代码。

C++:

你也可以从 这里 下载。

/**
* @file Pyramids.cpp
* @brief Sample code of image pyramids (pyrDown and pyrUp)
* @author OpenCV team
*/
#include "iostream"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace std;
using namespace cv;
const char* window_name = "Pyramids Demo";
/**
* @function main
*/
int main( int argc, char** argv )
{
/// General instructions
cout << "\n Zoom In-Out demo \n "
"------------------ \n"
" * [i] -> Zoom in \n"
" * [o] -> Zoom out \n"
" * [ESC] -> Close program \n" << endl;
const char* filename = argc >=2 ? argv[1] : "chicky_512.png";
// Loads an image
Mat src = imread( samples::findFile( filename ) );
// Check if image is loaded fine
if(src.empty()){
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default chicky_512.png] \n");
return EXIT_FAILURE;
}
for(;;)
{
imshow( window_name, src );
char c = (char)waitKey(0);
if( c == 27 )
{ break; }
else if( c == 'i' )
{ pyrUp( src, src, Size( src.cols*2, src.rows*2 ) );
printf( "** Zoom In: Image x 2 \n" );
}
else if( c == 'o' )
{ pyrDown( src, src, Size( src.cols/2, src.rows/2 ) );
printf( "** Zoom Out: Image / 2 \n" );
}
}
return EXIT_SUCCESS;
}

Java:

你也可以从 这里 下载。

import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class PyramidsRun {
String window_name = "Pyramids Demo";
public void run(String[] args) {
/// General instructions
System.out.println("\n" +
" Zoom In-Out demo \n" +
"------------------ \n" +
" * [i] -> Zoom [i]n \n" +
" * [o] -> Zoom [o]ut \n" +
" * [ESC] -> Close program \n");
String filename = ((args.length > 0) ? args[0] : "../data/chicky_512.png");
// Load the image
Mat src = Imgcodecs.imread(filename);
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image!");
System.out.println("Program Arguments: [image_name -- default ../data/chicky_512.png] \n");
System.exit(-1);
}
while (true){
HighGui.imshow( window_name, src );
char c = (char) HighGui.waitKey(0);
c = Character.toLowerCase(c);
if( c == 27 ){
break;
}else if( c == 'i'){
Imgproc.pyrUp( src, src, new Size( src.cols()*2, src.rows()*2 ) );
System.out.println( "** Zoom In: Image x 2" );
}else if( c == 'o'){
Imgproc.pyrDown( src, src, new Size( src.cols()/2, src.rows()/2 ) );
System.out.println( "** Zoom Out: Image / 2" );
}
}
System.exit(0);
}
}
public class Pyramids {
public static void main(String[] args) {
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new PyramidsRun().run(args);
}
}

Python:

你也可以从 这里 下载。

import sys
import cv2 as cv
def main(argv):
print("""
Zoom In-Out demo
------------------
* [i] -> Zoom [i]n
* [o] -> Zoom [o]ut
* [ESC] -> Close program
""")
filename = argv[0] if len(argv) > 0 else 'chicky_512.png'
# Load the image
src = cv.imread(cv.samples.findFile(filename))
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: pyramids.py [image_name -- default ../data/chicky_512.png] \n')
return -1
while 1:
rows, cols, _channels = map(int, src.shape)
cv.imshow('Pyramids Demo', src)
k = cv.waitKey(0)
if k == 27:
break
elif chr(k) == 'i':
src = cv.pyrUp(src, dstsize=(2 * cols, 2 * rows))
print ('** Zoom In: Image x 2')
elif chr(k) == 'o':
src = cv.pyrDown(src, dstsize=(cols // 2, rows // 2))
print ('** Zoom Out: Image / 2')
cv.destroyAllWindows()
return 0
if __name__ == "__main__":
main(sys.argv[1:])

Pyramids_Tutorial_Pyramid_Theory.png

我们来查看程序的整体结构。

C++:

const char* filename = argc >=2 ? argv[1] : "chicky_512.png";
// Loads an image
Mat src = imread( samples::findFile( filename ) );
// Check if image is loaded fine
if(src.empty()){
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default chicky_512.png] \n");
return EXIT_FAILURE;
}

Java:

String filename = ((args.length > 0) ? args[0] : "../data/chicky_512.png");
// Load the image
Mat src = Imgcodecs.imread(filename);
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image!");
System.out.println("Program Arguments: [image_name -- default ../data/chicky_512.png] \n");
System.exit(-1);
}

Python:

filename = argv[0] if len(argv) > 0 else 'chicky_512.png'
# Load the image
src = cv.imread(cv.samples.findFile(filename))
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: pyramids.py [image_name -- default ../data/chicky_512.png] \n')
return -1

C++:

imshow( window_name, src );

Java:

HighGui.imshow( window_name, src );

Python:

cv.imshow('Pyramids Demo', src)

C++:

for(;;)
{
imshow( window_name, src );
char c = (char)waitKey(0);
if( c == 27 )
{ break; }
else if( c == 'i' )
{ pyrUp( src, src, Size( src.cols*2, src.rows*2 ) );
printf( "** Zoom In: Image x 2 \n" );
}
else if( c == 'o' )
{ pyrDown( src, src, Size( src.cols/2, src.rows/2 ) );
printf( "** Zoom Out: Image / 2 \n" );
}
}

Java:

while (true){
HighGui.imshow( window_name, src );
char c = (char) HighGui.waitKey(0);
c = Character.toLowerCase(c);
if( c == 27 ){
break;
}else if( c == 'i'){
Imgproc.pyrUp( src, src, new Size( src.cols()*2, src.rows()*2 ) );
System.out.println( "** Zoom In: Image x 2" );
}else if( c == 'o'){
Imgproc.pyrDown( src, src, new Size( src.cols()/2, src.rows()/2 ) );
System.out.println( "** Zoom Out: Image / 2" );
}
}

Python:

while 1:
rows, cols, _channels = map(int, src.shape)
cv.imshow('Pyramids Demo', src)
k = cv.waitKey(0)
if k == 27:
break
elif chr(k) == 'i':
src = cv.pyrUp(src, dstsize=(2 * cols, 2 * rows))
print ('** Zoom In: Image x 2')
elif chr(k) == 'o':
src = cv.pyrDown(src, dstsize=(cols // 2, rows // 2))
print ('** Zoom Out: Image / 2')

程序执行一个无限循环,等待用户输入。如果用户按下 ESC,程序退出。此外,它还有两个选项:

  • 执行上采样——放大(按 ‘i’ 键)

    我们使用函数 pyrUp(),它带有三个参数:

    • src:当前图像和目标图像(用于在屏幕上显示,尺寸假定为输入图像的两倍)

    • Size( tmp.cols*2, tmp.rows*2 ):目标尺寸。由于我们在上采样,pyrUp() 期望的尺寸是输入图像(本例中为 src)的两倍。

      C++:

      else if( c == 'i' )
      { pyrUp( src, src, Size( src.cols*2, src.rows*2 ) );
      printf( "** Zoom In: Image x 2 \n" );
      }

      Java:

      }else if( c == 'i'){
      Imgproc.pyrUp( src, src, new Size( src.cols()*2, src.rows()*2 ) );
      System.out.println( "** Zoom In: Image x 2" );

      Python:

      elif chr(k) == 'i':
      src = cv.pyrUp(src, dstsize=(2 * cols, 2 * rows))
      print ('** Zoom In: Image x 2')
  • 执行下采样——缩小(按 ‘o’ 键)

    我们使用函数 pyrDown(),它带有三个参数(与 pyrUp() 类似):

    • src:当前图像和目标图像(用于在屏幕上显示,尺寸假定为输入图像的一半)

    • Size( tmp.cols/2, tmp.rows/2 ):目标尺寸。由于我们在下采样,pyrDown() 期望的尺寸是输入图像(本例中为 src)的一半。

      C++:

      else if( c == 'o' )
      { pyrDown( src, src, Size( src.cols/2, src.rows/2 ) );
      printf( "** Zoom Out: Image / 2 \n" );
      }

      Java:

      }else if( c == 'o'){
      Imgproc.pyrDown( src, src, new Size( src.cols()/2, src.rows()/2 ) );
      System.out.println( "** Zoom Out: Image / 2" );

Pyramids_Tutorial_Original_Image.jpg

**Python:**
```python
elif chr(k) == 'o':
src = cv.pyrDown(src, dstsize=(cols // 2, rows // 2))
print ('** Zoom Out: Image / 2')
```

Pyramids_Tutorial_PyrDown_Result.jpg Pyramids_Tutorial_PyrUp_Result.jpg

请注意,输入图像(在两个维度上)必须能被 2 整除,否则会报错。

  • 程序默认使用 samples/data 文件夹中的图像 chicky_512.png。注意该图像为 512×512512 \times 512,因此下采样不会产生错误(512=29512 = 2^{9})。

  • 首先我们按 ‘d’ 键连续执行两次 pyrDown() 操作,图像尺寸逐次减半。

  • 注意,由于我们缩小了图像尺寸,应该会损失一些分辨率。在我们连续执行两次 pyrUp()(按 ‘u’ 键)之后这一点就很明显了:恢复后的图像比原图更加模糊。