使用双线性插值将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]]
有什么问题?
答案 0 :(得分:9)
<强> TL; DR 强>
我测试了其他图像处理库(scikit-image,Pillow和Matlab),但没有一个返回预期的结果。
这种行为的可能性是由于执行双线性插值以获得有效结果的方法或某种惯例而不是我认为的错误。
我已经发布了一个示例代码,用双线性插值来执行图像大小调整(当然,检查一切是否正常,我不确定如何正确处理图像索引 ...)输出预期结果。
问题的部分答案。
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的更多信息。
这张图总结了我们案例中的所有结果:
是Alex Clark和Contributors的友好PIL分支。 PIL是 Fredrik Lundh和贡献者的Python成像库。
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提出了一个名为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
图像放大时会发生什么:
使用最近邻插值,(0,0)
处的目标像素将获得(0,0)
处的源像素值以及(0,1)
,(1,0)
处的像素值(1,1)
。
使用双线性插值,(0,0)
处的目标像素将获得一个值,该值是源图像中4个邻居的线性组合:
四个红点表示数据点,绿点表示点 我们想要插入的内容。
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
计算为R1
和R2
的加权平均值:P = ((y2 – y)/(y2 – y1))*R1 + ((y – y1)/(y2 – y1))*R2
。
使用[0, 1]
之间规范化的坐标简化了formula。
此博客文章(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问题,也是一个很好的方式,让我总结一下我发现的关于这个主题的一些事情。我相信它也可能以某种方式帮助其他可能发现这一点的人。