官方文档:Opencv resize()
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR );
参数含义:
InputArray src -原图像
OutputArray dst -输出图像
Size dsize -目标图像的大小
double fx=0 -在x轴上的缩放比例
double fy=0 -在y轴上的缩放比例
int interpolation -插值方式,有以下5种方式
INTER_NEAREST -最近邻插值
INTER_LINEAR -双线性插值 (默认使用)
INTER_AREA -区域插值。
INTER_CUBIC -三次样条插值,也叫立方插值(超过4*4像素邻域内的双三次插值)
INTER_LANCZOS4 -Lanczos插值(超过8*8像素邻域的Lanczos插值)
当不输入fx和fy时,函数会自动计算
fx = float(src.shape[1] / dsize[1])
fy = float(src.shape[0] / dsize[0])
python中cv2.resize
默认的是INTER_LINEAR
。官方建议:如果是缩小图片,使用INTER_AREA插值算法看起来是最好的,如果是放大图片,可以选择INTER_CUBIC(慢)或INTER_LINEAR(快但效果还不错)。
官方原文:To shrink an image, it will generally look best with INTER_AREA interpolation, whereas to enlarge an image, it will generally look best with INTER_CUBIC (slow) or INTER_LINEAR (faster but still looks OK).
最近邻插值,也称为零阶插值。计算原理为,目标图像位置直接采用与它最邻近位置的原始图像的像素点为其赋值。
目标图像位置(dst_x, dst_y)
最邻近的原始图像位置(src_x, src_y)
的计算:src_x = int(dst_x / scale_x)
;src_y = int(dst_y / scale_y)
,其中scale_x
和scale_y
分别表示在图像宽度方向和高度方向的缩放比例。
使用一个灰度图实例进行放大缩小
import numpy as np
import cv2
# 生成3*3的灰度图
img = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.uint8)
# 放大一倍
resized_up = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_NEAREST)
# 缩小一倍
resized_down = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST)
# 缩小一倍再放大一倍
resized_down_up = cv2.resize(resized_down, None, fx=2, fy=2, interpolation=cv2.INTER_NEAREST)
print(img, resized_up, resized_down, resized_down_up)
(array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype=uint8),
array([[1, 1, 2, 2, 3, 3],
[1, 1, 2, 2, 3, 3],
[4, 4, 5, 5, 6, 6],
[4, 4, 5, 5, 6, 6],
[7, 7, 8, 8, 9, 9],
[7, 7, 8, 8, 9, 9]], dtype=uint8),
array([[1, 3],
[7, 9]], dtype=uint8)),
array([[1, 1, 3, 3],
[1, 1, 3, 3],
[7, 7, 9, 9],
[7, 7, 9, 9]], dtype=uint8)
由上可知:
- 当dsize不指定的时候,则由fx和fy计算后四舍五入得到
- 对同一张图缩小再放大,与原图不会保持一致
import cv2
# 读取图像
img = cv2.imread('F:/Datas/kaggle/archive/test/apple/apple.jpg')
# 缩放尺寸
dim = (int(img.shape[1] * 2), int(img.shape[0] * 2))
# 使用最近邻插值缩放图像
resized = cv2.resize(img, dim, fx=2, fy=2, interpolation = cv2.INTER_NEAREST)
# 显示缩放后的图像
cv2.imshow("Original image", img)
cv2.imshow("Resized image", resized)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 将图片写入本地
cv2.imwrite("F:/Datas/kaggle/archive/test/apple/applex2.jpg", resized)
def resizeNearestRGB(src, dsize, fx, fy):
cols, rows = dsize
dst = np.zeros((rows, cols, 3), dtype=np.uint8)
for i in range(rows):
for j in range(cols):
x = int(j / fx)
y = int(i / fy)
dst[i, j][0] = src[y, x][0]
dst[i, j][1] = src[y, x][1]
dst[i, j][2] = src[y, x][2]
return dst
img = cv2.imread('F:/Datas/kaggle/archive/test/apple/apple.jpg')
dim = (int(img.shape[1] * 2), int(img.shape[0] * 2))
new_image = resizeNearestRGB(img, dim, 2, 2)
cv2.imshow("Original image", img)
cv2.imshow("Resized image", new_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 比较两个矩阵是否相等
if np.array_equal(resized, new_image):
print("两个矩阵相等")
else:
print("两个矩阵不相等")
OUT: 两个矩阵相等
由于是以最近的点作为新的插入点,因此边缘不会出现渐变过渡区域,这也导致缩放后的图像容易出现锯齿的现象
双线性插值,又称双线性内插。其核心思想是在x和y两个方向分别进行线性插值。
1) 将目标图像的位置(dst_x, dst_y)
映射到原图P(src_x, src_y)
中,但这时取得的是float
格式的结果;
src_x = float(dst_x / scale_x)
src_y = float(dst_y / scale_y)
2)得到P
点在原图中最邻近的4个点Q11
、Q12
、Q21
、Q22
;
3)首先在x方向进行两次线性插值得到R1
和R2
两个点的像素值f(R1)
和f(R2)
,然后再在y方向一次线性插值得到最终点P的像素值f(P)
,将该值赋值给目标图像(dst_x, dst_y)
【注意此处如果先在y方向插值,再在x方向插值,其结果是一样的】
import cv2
# 读取图像
img = cv2.imread('F:/Datas/kaggle/archive/test/apple/apple.jpg')
# 缩放尺寸
dim = (int(img.shape[1] * 2), int(img.shape[0] * 2))
# 使用最近邻插值缩放图像
resized = cv2.resize(img, dim, fx=2, fy=2, interpolation = cv2.INTER_LINEAR)
# 显示缩放后的图像
cv2.imshow("Original image", img)
cv2.imshow("Resized image", resized)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 将图片写入本地
cv2.imwrite("F:/Datas/kaggle/archive/test/apple/applex2.jpg", resized)
def resizeNearestRGB(src, dsize, fx, fy):
cols, rows = dsize
dst = np.zeros((rows, cols, 3), dtype=np.uint8)
for i in range(rows):
for j in range(cols):
x = j / fx
y = i / fy
x1, y1 = int(x), int(y)
x2 = min(x1 + 1, src.shape[1] - 1)
y2 = min(y1 + 1, src.shape[0] - 1)
Q11, Q21 = src[y1, x1], src[y1, x2]
Q12, Q22 = src[y2, x1], src[y2, x2]
if x2 == x1:
R1 = Q11
R2 = Q12
else:
R1 = Q11 * (x2 - x)/(x2 - x1) + Q21 * (x - x1)/(x2 - x1)
R2 = Q12 * (x2 - x)/(x2 - x1) + Q22 * (x - x1)/(x2 - x1)
if y2 == y1:
P = R1
else:
P = R1 * (y2 - y)/(y2 - y1) + R2 * (y - y1)/(y2 - y1)
dst[i, j] = P
return dst
image = cv2.imread('F:/Datas/kaggle/archive/test/apple/apple.jpg')
new_size = (int(image.shape[1] * 2), int(image.shape[0] * 2))
new_image = resizeNearestRGB(image, new_size, 2, 2)
cv2.imshow('new image', new_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
自己复现的代码得到的像素值和opencv中内部实现存在一点偏差,可能opencv内部有进行优化
双线性内插值算法放大后的图像质量较高,不会出现像素值不连续的情况,然而此算法具有低通滤波器的性能,使高频分量受损,所以可能会使图像轮廓在一定程序上变得模糊。
双立方插值是双线性插值的扩展,又称双三次插值,使用相邻的16(4x4)个像素点的加权之和进行插值,每个像素的权重由基于距离的函数取得。
双立方插值算法比双线性插值能更好地保留细节、增加锐度和清晰度,但是,它可能会导致波纹。
三种方法的对比:25(5x5)个拼凑在一起的单位方块,颜色表示函数值,黑点是指定数据被插值的位置。双线性插值得到的图像在正方形边界处会有像素值突变现象。
参考