OpenCV resize()结果错了?

时间:2017-04-24 21:57:44

标签: opencv interpolation bilinear-interpolation

使用双线性插值将2x2矩阵升级到5x5的示例程序。 对于这种简单的情况,OpenCV产生的结果在边界处具有伪像。

gy, gx = np.mgrid[0:2, 0:2]
gx = np.float32(gx)
print(gx)
res = cv2.resize(gx,(5,5), fx=0, fy=0, interpolation=cv2.INTER_LINEAR)
print(res)

输出:

[[ 0.  1.]
 [ 0.  1.]]

[[ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]]

预期产出:

  [[0 0.25 0.5 0.75 1
    0 0.25 0.5 0.75 1
    0 0.25 0.5 0.75 1
    0 0.25 0.5 0.75 1
    0 0.25 0.5 0.75 1]]

有什么问题?

1 个答案:

答案 0 :(得分:9)

<强> TL; DR

我测试了其他图像处理库(scikit-image,Pillow和Matlab),但没有一个返回预期的结果。

这种行为的可能性是由于执行双线性插值以获得有效结果的方法或某种惯例而不是我认为的错误。

我已经发布了一个示例代码,用双线性插值来执行图像大小调整(当然,检查一切是否正常,我不确定如何正确处理图像索引 ...)输出预期结果。

问题的部分答案。

其他一些图像处理库的输出是什么?

scikit图像

Python模块scikit-image包含许多图像处理算法。这里是skimage.transform.resize方法(skimage.__version__: 0.12.3)的输出:

  • mode='constant'(默认)

代码:

import numpy as np
from skimage.transform import resize

image = np.array( [
                [0., 1.],
                [0., 1.]
    ] )
print 'image:\n', image

image_resized = resize(image, (5,5), order=1, mode='constant')
print 'image_resized:\n', image_resized

结果:

image:
[[ 0.  1.]
 [ 0.  1.]]
image_resized:
[[ 0.    0.07  0.35  0.63  0.49]
 [ 0.    0.1   0.5   0.9   0.7 ]
 [ 0.    0.1   0.5   0.9   0.7 ]
 [ 0.    0.1   0.5   0.9   0.7 ]
 [ 0.    0.07  0.35  0.63  0.49]]
  • mode='edge'

结果:

image:
[[ 0.  1.]
 [ 0.  1.]]
image_resized:
[[ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]]
  • mode='symmetric'

结果:

image:
[[ 0.  1.]
 [ 0.  1.]]
image_resized:
[[ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]
 [ 0.   0.1  0.5  0.9  1. ]]
  • mode='reflect'

结果:

image:
[[ 0.  1.]
 [ 0.  1.]]
image_resized:
[[ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]]
  • mode='wrap'

结果:

image:
[[ 0.  1.]
 [ 0.  1.]]
image_resized:
[[ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]
 [ 0.3  0.1  0.5  0.9  0.7]]

如您所见,默认调整大小模式(constant)会产生不同的输出,但边缘模式返回的结果与OpenCV相同。调整大小模式都不会产生预期的结果。

有关Interpolation: Edge Modes的更多信息。

这张图总结了我们案例中的所有结果:

edge modes

Pillow

  

是Alex Clark和Contributors的友好PIL分支。 PIL是   Fredrik Lundh和贡献者的Python成像库。

PIL.Image.Image.resizePIL.__version__: 4.0.0)怎么样?

代码:

import numpy as np
from PIL import Image

image = np.array( [
                [0., 1.],
                [0., 1.]
    ] )
print 'image:\n', image

image_pil = Image.fromarray(image)
image_resized_pil = image_pil.resize((5,5), resample=Image.BILINEAR)
print 'image_resized_pil:\n', np.asarray(image_resized_pil, dtype=np.float)

结果:

image:
[[ 0.  1.]
 [ 0.  1.]]
image_resized_pil:
[[ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]
 [ 0.          0.1         0.5         0.89999998  1.        ]]

Pillow图像大小调整与OpenCV库的输出匹配。

Matlab的

Matlab提出了一个名为Image Processing Toolbox的工具箱。此工具箱中的函数imresize允许调整图像大小。

代码:

image = zeros(2,1,'double');
image(1,2) = 1;
image(2,2) = 1;
image
image_resize = imresize(image, [5 5], 'bilinear')

结果:

image =

     0     1
     0     1


image_resize =

         0    0.1000    0.5000    0.9000    1.0000
         0    0.1000    0.5000    0.9000    1.0000
         0    0.1000    0.5000    0.9000    1.0000
         0    0.1000    0.5000    0.9000    1.0000
         0    0.1000    0.5000    0.9000    1.0000

同样,它不是Matlab的预期输出,而是与前两个示例相同的结果。

自定义双线图像调整大小方法

基本原则

有关更完整的信息,请参阅Bilinear interpolation上的维基百科文章。

这个数字应该基本上说明了从2x2图片向4x4图像放大时会发生什么:

upscaling

使用最近邻插值,(0,0)处的目标像素将获得(0,0)处的源像素值以及(0,1)(1,0)处的像素值(1,1)

使用双线性插值,(0,0)处的目标像素将获得一个值,该值是源图像中4个邻居的线性组合:

Wikipedia: BilinearInterpolation.svg.png

  

四个红点表示数据点,绿点表示点   我们想要插入的内容。

R1的计算公式为:R1 = ((x2 – x)/(x2 – x1))*Q11 + ((x – x1)/(x2 – x1))*Q21

R2的计算公式为:R2 = ((x2 – x)/(x2 – x1))*Q12 + ((x – x1)/(x2 – x1))*Q22

最后,P计算为R1R2的加权平均值:P = ((y2 – y)/(y2 – y1))*R1 + ((y – y1)/(y2 – y1))*R2

使用[0, 1]之间规范化的坐标简化了formula

C ++实现

此博客文章(Resizing Images With Bicubic Interpolation)包含使用双线性插值执行图像大小调整的C ++代码。

这是我自己的改编(与原始代码相比对索引进行了一些修改,不确定它是否正确)与cv::Mat一起使用的代码:

#include <iostream>
#include <opencv2/core.hpp>

float lerp(const float A, const float B, const float t) {
  return A * (1.0f - t) + B * t;
}

template <typename Type>
Type resizeBilinear(const cv::Mat &src, const float u, const float v, const float xFrac, const float yFrac) {
  int u0 = (int) u;
  int v0 = (int) v;

  int u1 = (std::min)(src.cols-1, (int) u+1);
  int v1 = v0;

  int u2 = u0;
  int v2 = (std::min)(src.rows-1, (int) v+1);

  int u3 = (std::min)(src.cols-1, (int) u+1);
  int v3 = (std::min)(src.rows-1, (int) v+1);

  float col0 = lerp(src.at<Type>(v0, u0), src.at<Type>(v1, u1), xFrac);
  float col1 = lerp(src.at<Type>(v2, u2), src.at<Type>(v3, u3), xFrac);
  float value = lerp(col0, col1, yFrac);

  return cv::saturate_cast<Type>(value);
}

template <typename Type>
void resize(const cv::Mat &src, cv::Mat &dst) {
  float scaleY = (src.rows - 1) / (float) (dst.rows - 1);
  float scaleX = (src.cols - 1) / (float) (dst.cols - 1);

  for (int i = 0; i < dst.rows; i++) {
    float v = i * scaleY;
    float yFrac = v - (int) v;

    for (int j = 0; j < dst.cols; j++) {
      float u = j * scaleX;
      float xFrac = u - (int) u;

      dst.at<Type>(i, j) = resizeBilinear<Type>(src, u, v, xFrac, yFrac);
    }
  }
}

void resize(const cv::Mat &src, cv::Mat &dst, const int width, const int height) {
  if (width < 2 || height < 2 || src.cols < 2 || src.rows < 2) {
    std::cerr << "Too small!" << std::endl;
    return;
  }

  dst = cv::Mat::zeros(height, width, src.type());

  switch (src.type()) {
    case CV_8U:
      resize<uchar>(src, dst);
      break;

    case CV_64F:
      resize<double>(src, dst);
      break;

    default:
      std::cerr << "Src type is not supported!" << std::endl;
      break;
  }
}

int main() {
  cv::Mat img = (cv::Mat_<double>(2,2) << 0, 1, 0, 1);
  std::cout << "img:\n" << img << std::endl;
  cv::Mat img_resize;
  resize(img, img_resize, 5, 5);
  std::cout << "img_resize=\n" << img_resize << std::endl;

  return EXIT_SUCCESS;
}

它产生:

img:
[0, 1;
 0, 1]
img_resize=
[0, 0.25, 0.5, 0.75, 1;
 0, 0.25, 0.5, 0.75, 1;
 0, 0.25, 0.5, 0.75, 1;
 0, 0.25, 0.5, 0.75, 1;
 0, 0.25, 0.5, 0.75, 1]

结论

在我看来,OpenCV resize()函数不太可能是错误的,因为我可以测试的其他图像处理库都没有产生预期的输出,而且可以用良好的参数产生相同的OpenCV输出。 / p>

我针对两个Python模块(scikit-image和Pillow)进行了测试,因为它们易于使用并且面向图像处理。我还能够使用Matlab及其图像处理工具箱进行测试。

用于图像大小调整的双线性插值的粗略自定义实现产生预期结果。我有两种可能性可以解释这种行为:

  • 差异是这些图像处理库使用的方法所固有的而不是错误(可能他们使用一种方法来有效地调整图像大小,与严格的双线性实现相比有一些损失?)?
  • 以某种方式将插值正确地排除在边界之外?

这些库是开源的,可以探索其源代码,以了解差异的来源。

linked answer表明插值仅在两个原始蓝点之间起作用,但我无法解释为什么会出现这种情况。

为什么这个答案?

这个答案,即使它部分回答OP问题,也是一个很好的方式,让我总结一下我发现的关于这个主题的一些事情。我相信它也可能以某种方式帮助其他可能发现这一点的人。