OpenCV resize()结果是否错误?

标签 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]]

问题是什么?

最佳答案

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

    is the friendly PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.



    PIL.Image.Image.resize (PIL.__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上的此Wikipedia文章。

    该图基本上应该说明从2x2图像放大到4x4图像时发生的情况:

    upscaling

    通过最近邻插值,(0,0)的目标像素将获得(0,0)的源像素的值以及(0,1)(1,0)(1,1)的像素。

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

    Wikipedia: BilinearInterpolation.svg.png

    The four red dots show the data points and the green dot is the point at which we want to interpolate.


    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输出。

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

    用于图像大小调整的双线性插值的粗略定制实现可产生预期的结果。对我来说,两种可能性可以解释这种现象:
  • 这些区别是这些图像处理库使用的方法固有的,而不是错误(与严格的双线性实现相比,它们是否使用一种有效地调整图像大小的方法而造成一些损失)?
  • 以某种方式正确地插补了边界(不包括边界)?

  • 这些库是开放源代码的,您可以浏览其源代码以了解差异的来源。

    linked answer显示插值仅在两个原始蓝点之间起作用,但是我无法解释为什么这种行为。

    为什么回答这个问题?

    这个答案即使部分回答了OP问题,也是我总结关于该主题的一些发现的好方法。我相信它也可以在某种程度上帮助其他可能发现此问题的人。

    关于OpenCV resize()结果是否错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43598373/

    相关文章:

    python - 如何找出图像的平均像素值,从顶部和底部扫描?

    python - 将具有不透明度的图像分层在另一个图像的顶部。 - OpenCV

    css - 是否可以在 css(双线性渐变)中创建 2 轴 4 颜色渐变?

    php - 选择 PHP 的图像大小调整算法

    ios - Swift 中的双线性插值

    python - opencv python connectedComponents 选择每个标签的组件

    python - 三次样条插值仅产生 nans 或引发错误

    python - 将大的不规则网格插入 Python 中的另一个不规则网格

    python - 延长线以平滑连接点

    python - 如何在Windows 10中离线安装python和机器学习库?