OpenCV图像缩放原理与实现:从最近邻插值到双线性插值
OpenCV图像缩放原理与实现:从最近邻插值到双线性插值
OpenCV中的图像缩放是图像处理中的一个基本操作,它涉及到图像的大小调整和像素值的重新计算。本文将详细介绍OpenCV中图像缩放的原理和实现方法,包括最近邻插值和双线性插值等常用的插值方法。通过本文的学习,读者将能够理解图像缩放的基本原理,并能够使用OpenCV实现图像的缩放操作。
一、OpenCV中的图像缩放
在OpenCV中,图像缩放主要通过cv.resize
函数实现。该函数的参数包括输入图像、输出图像、目标大小、缩放因子以及插值方法等。具体参数说明如下:
src
:输入的原图像,即待改变大小的图像。dst
:输出的改变后的图像,这个图像和原图像具有相同的内容,只是大小不同。dsize
:输出图像的大小。如果这个参数不为0,则将原图像缩放到这个指定的大小;如果这个参数为0,则原图像缩放之后的大小会根据公式dsize = Size(round(fx*src.cols), round(fy*src.rows))
来计算。fx
:图像在水平方向(宽度)的缩放因子,控制图像在宽度方向上的缩放比例。fy
:图像在垂直方向(高度)的缩放因子,控制图像在高度方向上的缩放比例。interpolation
:插值方式,指定了像素重计算的方法。OpenCV中提供了多种插值方法,如最邻近插值(INTER_NEAREST)、双线性插值(INTER_LINEAR)、双立方插值(INTER_CUBIC)和Lanczos插值(INTER_LANCZOS4)等。
在使用cv.resize()
函数时需要注意以下几点:
dsize
和fx
/fy
的关系:不能同时为0。要么指定好dsize
的值,让fx
和fy
保持默认值;要么让dsize
为0,指定好fx
和fy
的值。这两组参数是互斥的,用于指定输出图像的大小和缩放比例。- 插值方法的选择:默认情况下,如果不指定最后一个参数,
cv.resize()
会使用双线性插值(INTER_LINEAR
)。根据不同的应用场景和对结果质量的要求,可以选择合适的插值方法。
下面是一个使用cv.resize()
函数进行图像缩放的示例代码:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img = cv.imread(r'p.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
res1 = cv.resize(img, None, fx=2, fy=2, interpolation=cv.INTER_CUBIC)
height, width = img.shape[:2]
res2 = cv.resize(img, (2*width, 2*height), interpolation=cv.INTER_CUBIC)
flg, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 8), dpi=100)
axes[0].imshow(img[::-1, ::-1, ::-1])
axes[0].set_xlim(0, 1500)
axes[0].set_ylim(0, 1500)
axes[1].imshow(res1[::-1, ::-1, ::-1])
axes[1].set_xlim(0, 1500)
axes[1].set_ylim(0, 1500)
axes[2].imshow(res2[::-1, ::-1, ::-1])
axes[2].set_xlim(0, 1500)
axes[2].set_ylim(0, 1500)
二、最近邻插值(INTER_NEAREST)
最近邻插值是一种相对简单的灰度值插值方法,也称为零阶插值。其基本原理是,对于变换后的每个像素,其灰度值被设定为与其在源图像中最邻近的像素的灰度值相同。这种方法不涉及复杂的计算或加权,而仅需要判断目标像素点的位置,并直接选取原始图像中对应位置最近的像素值作为插值结果。
最近邻插值在图像处理中常用于图像的缩放操作。例如,当需要将一个较小尺寸的图像放大到较大尺寸时,可以使用最近邻插值方法来填充新增的像素点。具体做法是,对于目标图像中的每个像素点,找到其在原始图像中对应位置最近的像素点,然后将该最近像素点的灰度值赋给目标像素点。
尽管最近邻插值方法实现简单且计算效率高,但由于它仅考虑了最近邻像素的信息,而没有利用更多周围像素的信息,因此插值结果可能不够平滑,容易出现色块现象。特别是在图像放大时,可能会出现像素的明显块状效应,导致图像质量下降。因此,在需要更高质量的图像插值结果时,通常会考虑使用更复杂但效果更好的插值方法,如双线性插值或双三次插值等。
假设原图是hw,目标图片要缩放到nm,目标图片中每个像素点和原图的对应关系如下:
目标图片中(i, j)对应原图中的( round((h / n) * i), round((w / m) * i) )
比如上述例子中,就是从(3x3)缩放到(4x4),目标图片(2, 2)对应原图中的((3 / 4)* 2,(3 / 4)* 2)),四舍五入就是(2, 2),即5。
下面是代码实现:
import numpy as np
import cv2 as cv
import math
import matplotlib.pyplot as plt
img = cv.imread(r'F:\Python\2024_last\image\multimodal_image_283822130U794804.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
height, width = img.shape[:2]
dh, dw = 2, 2
# 最近邻插值
def inter_nearest(img, dh, dw):
res = np.zeros((img.shape[0] * dh, img.shape[1] * dw, img.shape[-1]), np.uint8)
for i in range(res.shape[0]-1):
for j in range(res.shape[1]-1):
h_ = (height / res.shape[0]) * i
w_ = (width / res.shape[1]) * j
res[i, j, :] = img[round(h_), round(w_), :]
return res
dst = inter_nearest(img, dh, dw)
flg, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 8), dpi=100)
axes[0].imshow(img[::-1, ::-1, ::-1])
axes[0].set_xlim(0, 1500)
axes[0].set_ylim(0, 1500)
axes[1].imshow(dst[::-1, ::-1, ::-1])
axes[1].set_xlim(0, 1500)
axes[1].set_ylim(0, 1500)
三、双线性插值法(INTER_LINEAR)
双线性插值算法,也称为双线性内插,是一种在数学上的插值算法,特别适用于有两个变量的插值函数的线性插值扩展。其核心思想是在两个方向(通常是水平和垂直方向)上分别进行一次线性插值。
具体来说,当我们想要得到未知函数f在点P(x, y)的值时,如果我们已知函数f在四个点Q11(x1, y1)、Q12(x1, y2)、Q21(x2, y1)以及Q22(x2, y2)的值,我们就可以通过双线性插值算法来估算f在P点的值。这个过程首先是在x方向上进行线性插值,然后再在y方向上进行线性插值,综合两次插值的结果,就得到了双线性插值的最终结果。
双线性插值算法在图像处理、数字地图等领域有着广泛的应用。例如,在图像处理中,通过对目标图像上的每个像素点应用双线性插值,可以用原始图像中相邻像素的加权平均值来估计新像素的值,从而实现图像的缩放、旋转等操作,同时保持图像的细节和质量。在数字地图等应用中,双线性插值可以用于估计非采样点的数值,从而实现地图的平滑显示和连续性。
直接给出对应像素的关系:
i,和j是整数部分,u和v是浮点数部分。依然使用上面的例子进行说明,目标图片(2, 2),原图中就是((3 / 4) * 2 = 1.5, (3 / 4) * 2 = 1.5),则i=1,u=0.5,j=1,v=0.5。
OpenCV中并没有使用以下:
目标图片中(i, j)对应原图中的( round((h / n) * i), round((w / m) * i) )
这部分的内容直接转载,末尾附有原文链接:
这部分的前提是,你已经明白什么是双线性插值并且在给定源图像和目标图像尺寸的情况下,可以用笔计算出目标图像某个像素点的值。当然,最好的情况是你已经用某种语言实现了网上一大堆博客上原创或转载的双线性插值算法,然后发现计算出来的结果和matlab、openCV对应的resize()函数得到的结果完全不一样。
那这个究竟是怎么回事呢?
其实答案很简单,就是坐标系的选择问题,或者说源图像和目标图像之间的对应问题。
按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下:
图4:原点选择左上角示意图
只画了一行,用做示意,从图中可以很明显的看到,如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。
那么,让坐标加1或者选择右下角为原点怎么样呢?很不幸,还是一样的效果,不过这次得到的图像将偏右偏下。
最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是matlab和openCV的做法。如下图:
图5:更改后示意图
如果你不懂我上面说的什么,没关系,只要在计算对应坐标的时候改为以下公式即可,
int x=(i+0.5)*n/a-0.5
int y=(j+0.5)*m/b-0.5
代替
int x=in/a
int y=jm/b
利用上述公式,将得到正确的双线性插值结果
代码实现:
import numpy as np
import cv2 as cv
import math
import matplotlib.pyplot as plt
img = cv.imread(r'F:\Python\2024_last\image\2.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
# 双线性插值法
height, width = img.shape[:2]
dh, dw = 2, 2
def inter_linear(img, dh, dw):
res = np.zeros((img.shape[0] * dh, img.shape[1] * dw, img.shape[-1]), np.uint8)
for i in range(res.shape[0]-1):
for j in range(res.shape[1]-1):
_h = (i + 0.5) * (height / res.shape[0]) - 0.5
_w = (j + 0.5) * (width / res.shape[1]) - 0.5
low_h = math.floor(_h)
low_w = math.floor(_w)
h_float = _h - low_h
w_float = _w - low_w
res[i, j] = (1-h_float)*(1-w_float)*img[low_h,low_w] +\
(1-h_float)*w_float*img[low_h,low_w+1] +\
h_float*(1-w_float)*img[low_h+1,low_w] +\
h_float*w_float*img[low_h+1,low_w+1]
return res
dst = inter_linear(img, dh, dw)
flg, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 8), dpi=100)
axes[0].imshow(img[::-1, ::-1, ::-1])
axes[0].set_xlim(0, 1500)
axes[0].set_ylim(0, 1500)
axes[1].imshow(dst[::-1, ::-1, ::-1])
axes[1].set_xlim(0, 1500)
axes[1].set_ylim(0, 1500)
本文原文来自CSDN