Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一文讲解图像插值算法原理!附Python实现

一文讲解图像插值算法原理!附Python实现

作者头像
Datawhale
发布于 2020-04-24 10:39:58
发布于 2020-04-24 10:39:58
6.9K00
代码可运行
举报
文章被收录于专栏:Datawhale专栏Datawhale专栏
运行总次数:0
代码可运行

作者:姚童,Datawhale优秀学习者

寄语:本文梳理了最近邻插值法、双线性插值法和三次样条插值法的原理,并以图像缩放为例,对原理进行了C++及Python实现。

在图像处理中,几何变换是将一幅图像映射到另外一幅图像内的操作,可以大概分为放缩、翻转、仿射(平移、旋转)、透视、重映射几部分。

在几何变换时,无法给有些像素点直接赋值,例如,将图像放大两倍,必然会多出一些无法被直接映射的像素点,对于这些像素点,通过插值决定它们的值。且不同插值方式的结果不同。

在一幅输入图像[u,v]中,灰度值仅在整数位置上有定义。然而,输出图像的坐标映射回原图像后,一般为非整数的坐标。所以输出图像[x,y]的灰度值,一般由非整数坐标来决定,非整数坐标的像素值,就需要插值算法来进行处理。常见的插值算法有最近邻插值、双线性插值和三次样条插值。

本文目标

  • 了解插值算法与常见几何变换之间的关系
  • 理解插值算法的原理
  • 掌握OpenCV框架下插值算法API的使用

插值算法原理介绍

近邻插值算法

1. 原理简介

将目标图像中的点,对应到原图像中后,找到最相邻的整数坐标点的像素值,作为该点的像素值输出。

如上图所示,目标图像中的某点投影到原图像中的位置为点P,与P距离最近的点为Q11,此时易知,f(P)=f(Q11)。

2. 例子说明

如图所示:

将一幅3*3图像放大到4*4,用f(x , y)表示原图像,h(x ,y)表示目标图像,我们有如下公式:

3. 缺点

由最邻近插值法,放大后的图像有很严重的马赛克,会出现明显的块状效应;缩小后的图像有很严重的失真。

这是一种最基本、最简单的图像缩放方式。变换后的每个像素点的像素值,只由原图像中的一个像素点确定。例如上面,点(0,0.75)的像素只由(0,1)确定,这样的效果显然不好。点(0,0.75)的像素不止和(0,1)有关,和(0,0)也有关,只是(0,1)的影响更大。如果可以用附近的几个像素点按权重分配,共同确定目标图像某点的像素,效果会更好。下面的双线性插值就解决了这个问题。

双线性插值算法

1. 线性插值

在讲双线性插值之前先了解一下线性插值。线性插值:使用连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值。线性插值形式:

如下图所示:

线性插值多项式:

其实,即使x不在x0到x1之间,这个公式也是成立的。在这种情况下,这种方法叫作线性外插。

线性插值的误差:线性插值其实就是拉格朗日插值有2个结点时的情况。插值余项为:

从插值余项可以看出,随着二阶导数的增大,线性插值的误差增大。即函数的曲率越大,线性插值近似的误差也越大。

举个例子。下图中,左边为原图像,拉伸后,理想的输出图像的像素分布应该为绿色箭头指向的,但是按照线性插值,会得到红色箭头指向的结果。

2. 双线性插值

双线性插值形式:

双线性插值是线性插值在二维时的推广,在两个方向上共做了三次线性插值。定义了一个双曲抛物面与四个已知点拟合。

具体操作为在X方向上进行两次线性插值计算,然后在Y方向上进行一次插值计算。如下图所示:

首先,f(x,y)为二元函数,假设我们知道f(x0,y0),f(x1,y1),f(x0,y1),f(x1,y0)四个点的值。这四个点确定一个矩形,我们希望通过插值得到矩形内任意点的函数值。

先在x方向上进行两次线性插值,得到:

再在y方向上进行一次线性插值,得到:

综合起来,就是双线性插值的结果:

如果选择一个坐标系统,使f(x)已知的四个点的坐标分别为(0,0),(0,1),(1,0),(1,1),那么确定一个单位正方形,四个点分别为正方形的四个顶点:

  • 首先对上端的两个顶点进行线性插值得:
  • 再对底端的两个顶点进行线性插值得:
  • 最后,做垂直方向的线性插值,以确定:
  • 整理得插值公式的化简形式:

3. 原图像和目标图像的几何中心对齐

在计算目标图像中,对应原图像的虚拟坐标点时,一般的变换是:

这种变换下,原图像的有些点没有参与计算。举个例子,把9∗9的原图像缩小成3∗3,原图像的原点(0,0)和目标图像的原点(0,0)都为左上角,目标图像右上角的坐标为(0,2),对应原图像的坐标为(0∗(9/3),2∗(9/3))=(0,6)。目标图像右边已经没有点了,(0,6)右边的像素点也就用不到了。

原图像和目标图像的像素之间的对应关系如下:

从图片可以看出,只有圈出来的红色部分参与运算了。目标图像的每个像素点的灰度值相对于原图像偏左上方,右下角的元素实际上没有参与运算。

为了让原图像和目标图像的中心对齐,我们规定另外一种变换方式:

就是在原来的变换后面加了调节因子:

0.5(src_width/dst_width−1)

这种变换下,目标图像的中心点(1,1),对应了原图像的中心点(4,4),两个图像的几何中心重合,能充分利用原图像的点,并且目标图像的每个像素点之间都是等间隔的,也都和两边有一定的边距。实际上,在openCv中也是这种变换方式。

4. cv.resize()的计算过程

对于缩小图像,目标图像中每个点都能找到原图像中包围它的四个临近点,每个点都进行双线性插值即可。

对于放大图像,边界附近的点经过坐标变换可能超出了原图像的范围。举个例子,把3∗3的原图像放大成4∗4。

  • 中间的点:双线性插值

中间的点都能在原图像中找到包围它的四个临近点,做双线性插值即可。

  • 边界上的点(除了顶点):线性插值

例如,目标图像中的点(1,3),对应原图像的点为(0.625,2.125),原图像的纵坐标最大为2,找不到包围(0.625,2.125)四个点,所以用它最邻近的两个点(0,2)和(1,2)做线性插值(外插),得到目标图像中(1,3)的像素值。

  • 四个顶点:最邻近插值

例如,目标图像右上角的顶点(0,3),对于原图像的点为(0,2.125),直接用原图像右上角的顶点(0,2)作为它的值即可。

计算过程:

用h(x,y)表示目标图像,f(x,y)表示原图像

  • 中间的点:双线性插值
  • 边界上的点(除了顶点):线性插值
  • 四个顶点:最邻近插值

可以用代码举例子测试:

  • cv2import numpy as npsrc = np.array([[56,23,15],[65,32,78],[12,45,62]],dtype=np.uint8)print(src)dst = cv2.resize(src,dsize=(4,4),interpolation=cv2.INTER_LINEAR)print(dst)

三次样条插值算法

给定n+1个点,a=x_0<x_1<...<x_n=b,以及他们的函数值f(x_i),i=0,1,2,...n上,确定一个三次多项式:

每个三次多项式中有四个未知参数,有n个区间,n个多项式,共4n个未知参数。我们知道“n个未知数需要n个已知条件确定唯一解”,所以要确定这4n个未知参数,共需要4n个已知条件。

每个三次多项式满足如下条件:

以上共4n−2个条件,还差2个条件,由如下三种边界条件确定:

4n个条件有了,就可以确定每个区间上的三次多项式。

对于每个区间内的点,就可以用Si(x)得到插值结果。三次样条插值具有良好的收敛性,稳定性和光滑性,优点明显,是非常重要的插值工具。

这里主要了解三次样条插值的作用,具体的推导过程比较繁琐,想了解的可以查阅资料。

两种映射方法

向前映射和向后映射都是将一个图像经过几何变换得到另一个图像的过程,它们的目的都是得到目标图像的像素,只是方式不同。

向前映射

图像变换的本质是将像素点的坐标通过某一种函数关系,映射到另外的位置。

向前映射的过程可以分解为两步:坐标变换+分配像素值

向前映射的坐标变换:由原图像坐标推算该像素在目标图像的位置。

例如,我们知道原图像的某个像素点的坐标(x,y),变换后在新图像的坐标为(x′,y′),变换后的坐标一般为非整数的,而非整数的坐标是没意义的,所以将这个点的像素按权重分配给周围四个像素点。对于变换后坐标仍为整数的点,直接把其像素值分配给目标图像中对应的点即可。将原图像的所有像素点都进行这种坐标变换和分配像素值,就得到了新图像。

所以,新图像的每个像素点的像素值,都是由它周围的非整数坐标的点的像素分配给它并叠加得到的(或者直接等于某个整数坐标点的像素值)。由于这个分配、叠加的特性,向前映射法有时也叫像素移交映射。

对于向前映射,虽然原图像中的每个点分配系数之和为1。但目标图像上每个点的像素值是多个分配值叠加而成的,所以不能保证所有分配到其上的权重之和为1。因此必须记录下所有分配到其上的权重并累加起来,最后利用累加权重进行归一化,才能得到正确的插值结果。所以,确定目标图像某一点的像素值,需要遍历原图像的所有像素值,进行坐标变换和分配像素值。这是向前映射法的缺点。

向后映射

向后映射的过程可以分解为两步:坐标变换+插值。

向后映射的坐标变换:由输出图像坐标反过来推算该像素在原图像的位置

前面说的几种插值方式,就是向后映射的例子。是由目标图像的坐标算出在原图像的坐标,再确定它的像素值由原图像的哪几个点按权重分配得到。然后进行插值操作,得到该点的像素值。某一点的像素值进行一次操作就可以得到,不需要遍历全部像素点。向后映射法也叫像素填充算法。向后映射法解决了漏点的问题,出现了马赛克。

动手实现

c++实现

1. 函数原型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )

2. 插值方式

注:目标图像大小可以通过“参数dsize”和“参数fx和fy”两种方式确定。两种参数不能同时为0。

  • 通过参数dsize确定

dsize的第一个参数为列数,第二个参数为行数,都为整数。若指定了dsize的值,无论是否指定了fx和fy的值,都由参数dsize来决定目标图像的大小。

  • 通过参数fx和fy确定

如果dsize的值None,目标图像的大小通过fx和fy确定。fx为列数缩放的倍数,fy为行数缩放的倍数。

3.代码实现

  • #include <opencv2/opencv.hpp>#include <iostream> using namespace cv;using namespace std; int main(int argc, char* argv[]){ Mat img = imread(""C:/Users/94890/Desktop/picture/luelue.jpg""); if (img.empty()) { cout << "无法读取图像" << endl; return 0; } int height = img.rows;//原图像行数 int width = img.cols;//原图像列数 // 缩小图像,比例为(0.2, 0.2),行列数必须为整数 Size dsize = Size(round(0.2 * width), round(0.2 * height)); Mat shrink; //使用双线性插值 resize(img, shrink, dsize, 0, 0, INTER_LINEAR); // 在缩小图像的基础上,放大图像,比例为(1.5, 1.5) float fx = 1.5; float fy = 1.5; Mat enlarge1, enlarge2; resize(shrink, enlarge1, Size(), fx, fy, INTER_NEAREST); resize(shrink, enlarge2, Size(), fx, fy, INTER_LINEAR); // 显示 imshow("src", img); imshow("shrink", shrink); imshow("INTER_NEAREST", enlarge1); imshow("INTER_LINEAR", enlarge2); //保存图像 imwrite("C:/Users/94890/Desktop/picture/shrink2.jpg",shrink); imwrite("C:/Users/94890/Desktop/picture/INTER_NEAREST2.jpg", enlarge1); imwrite("C:/Users/94890/Desktop/picture/INTER_LINEAR2.jpg", enlarge2); waitKey(0); return 0;}

原图像

0.2倍缩小,双线性插值

缩小后的图像1.5倍放大,最近邻插值

缩小后的图像1.5倍放大,双线性插值

python实现

1. 函数原型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#dst为输出图像,类型与原图像相同dst = cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])

2. 插值方式

通常,缩小图像使用区域插值(cv.INTER_AREA),放大图像使用三次样条插值(cv.INTER_CUBIC)和双线性插值(cv.INTER_LINEAR)。三次样条插值方式速度较慢,双线性插值方式较快且效果也不错。

3. 代码实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import cv2
if __name__ == "__main__":    img = cv2.imread('C:/Users/94890/Desktop/smile.jpg', cv2.IMREAD_UNCHANGED)    print('Original shape : ', img.shape)#img.shape属性种第一个值对应行数,第二个值对应列数    width = int(img.shape[1] * 0.3)#列数必须是整数    height = int(img.shape[0] * 0.3)#行数必须是整数    dsize = (width, height)#dsize属性值第一个数对应列数,第二个数对应行数    # resize image    resized = cv2.resize(img, dsize, interpolation=cv2.INTER_LINEAR)#双线性插值方式    print('Resized shape : ', resized.shape)
    fx = 1.5#列数变为原来的1.5倍    fy = 1.5#行数变为原来的1.5倍    resized1 = cv2.resize(resized, dsize=None, fx=fx, fy=fy, interpolation=cv2.INTER_NEAREST)#最邻近插值    resized2 = cv2.resize(resized, dsize=None, fx=fx, fy=fy, interpolation=cv2.INTER_LINEAR)#双线性插值    print('Resized1 shape : ', resized1.shape)
    #显示图像    cv2.imshow("Resized image", resized)    cv2.imshow("INTER_NEAREST image", resized1)    cv2.imshow("INTER_LINEAR image", resized2)    #保存图像    cv2.imwrite("C:/Users/94890/Desktop/Resized_image.jpg", resized)    cv2.imwrite("C:/Users/94890/Desktop/INTER_NEAREST_image.jpg", resized1)    cv2.imwrite("C:/Users/94890/Desktop/INTER_LINEAR_image.jpg", resized2)    cv2.waitKey(0)    cv2.destroyAllWindows()

原图像

0.3倍缩小,双线性插值

缩小后的图像1.5倍放大,最近邻插值

缩小后的图像1.5倍放大,双线性插值

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Datawhale 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
图像处理: 五种 插值法
在一维空间中,最近点插值就相当于四舍五入取整。在二维图像中,像素点的坐标都是整数,该方法就是选取离目标点最近的点。
JNingWei
2018/09/27
4.6K0
图像处理: 五种 插值法
双线性插值 一文全讲解
大家好,又见面了,我是你们的朋友全栈君。 1、原理 在图像的仿射变换中,很多地方需要用到插值运算,常见的插值运算包括最邻近插值,双线性插值,双三次插值,兰索思插值等方法,OpenCV提供了很多方法,其中,双线性插值由于折中的插值效果和运算速度,运用比较广泛。   越是简单的模型越适合用来举例子,我们就举个简单的图像:33 的256级灰度图。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source): 234 38 22 67 44 12 89 65 63   这 个矩阵中,元素坐标
全栈程序员站长
2022/08/22
1.8K0
双线性插值 一文全讲解
图像插值算法和OpenCV框架
最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输出。
致Great
2020/04/24
1.5K0
图像插值算法和OpenCV框架
OpenCV-resize函数「建议收藏」
dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已;
全栈程序员站长
2022/08/01
6970
cv2.resize()
cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst
狼啸风云
2019/09/19
2.9K0
cv2.resize()
OpenCV这么简单为啥不学——1.3、图像缩放resize函数
计算机视觉市场巨大而且持续增长,且这方面没有标准API,如今的计算机视觉软件大概有以下三种:
红目香薰
2023/02/10
9070
OpenCV这么简单为啥不学——1.3、图像缩放resize函数
来聊聊图像插值算法
主要可以分为两类,一类是线性图像插值方法,另一类是非线性图像插值方法,如上图所示。
AI算法修炼营
2020/05/19
2K0
『带你学算法』详解OpenCV中Reszie操作与原理
dst - 目标图像。当参数dsize不为0时,dst的大小为size;否则,它的大小需要根据src的大小,参数fx和fy决定。dst的类型(type)和src图像相同
小宋是呢
2020/12/10
3.6K0
『带你学算法』详解OpenCV中Reszie操作与原理
解决cv2.error: C:\projects\opencv-python\opencv\modules\imgproc\src\resize.cpp:404
当我们使用OpenCV库的​​cv2.resize()​​函数对图像进行缩放操作时,有时候可能会遇到以下错误:​​cv2.error: C:\projects\opencv-python\opencv\modules\imgproc\src\resize.cpp:4044: error: (-215) s​​。这个错误通常是由于函数参数设置不正确引起的。本篇博客将介绍如何解决这个错误。
大盘鸡拌面
2023/10/18
2.8K0
【OpenCV】双线性插值法
定义: 又称双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向上分别进行一次线性插值。 对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1) 其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。
全栈程序员站长
2022/06/28
1.5K0
【OpenCV】双线性插值法
OpenCV极坐标变换函数warpPolar的使用
前阵子在做方案时,得了几张骨钉的图片,骨科耗材批号效期管理一直是比较麻烦的,贴RFID标签成本太高,所以一般考虑还是OCR的识别比较好,因为本身骨钉的字符是按圆印上去的,直接截取图片进行OCR没法识别,需要经过图像处理后再识别,所以这篇就是学习一下OpenCV的极坐标变换函数。
Vaccae
2023/12/14
5550
OpenCV极坐标变换函数warpPolar的使用
最近邻插值、双线性插值、双三次插值
越是简单的模型越适合用来举例子,我们就举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source): 234 38 22 67 44 12 89 65 63
全栈程序员站长
2022/07/01
1.8K0
最近邻插值、双线性插值、双三次插值
10、图像的几何变换——平移、镜像、缩放、旋转、仿射变换 OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(1)OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(2)数字图像
  图像几何变换又称为图像空间变换,它将一副图像中的坐标位置映射到另一幅图像中的新坐标位置。我们学习几何变换就是确定这种空间映射关系,以及映射过程中的变化参数。图像的几何变换改变了像素的空间位置,建立一种原图像像素与变换后图像像素之间的映射关系,通过这种映射关系能够实现下面两种计算:
vv彭
2020/10/27
4.2K0
10、图像的几何变换——平移、镜像、缩放、旋转、仿射变换
    


OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(1)OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(2)数字图像
解决OpenCV Error: Assertion failed (ssize.width > 0 && ssize.height > 0) in cv::re
当我们在使用OpenCV进行图像处理时,有时候会遇到如下错误信息:OpenCV Error: Assertion failed (ssize.width > 0 && ssize.height > 0) in cv::resize, file C:\proj。这个错误通常是由于图像的宽度或高度为0导致的。在本篇文章中,我将介绍一些可能导致这个错误的原因,并提供一些解决方法。
大盘鸡拌面
2023/10/26
1.4K0
【图像处理】详解 最近邻插值、线性插值、双线性插值、双三次插值「建议收藏」
2.1 最近邻插值 (Nearest Neighbor Interpolation) —— 零阶插值法
全栈程序员站长
2022/09/06
22K0
【图像处理】详解 最近邻插值、线性插值、双线性插值、双三次插值「建议收藏」
“大脑”生长系列(四)
void resize(InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolaiton == INTER_LINEAR);
视界音你而不同
2020/04/10
2840
“大脑”生长系列(四)
opencv2.4.13.7的resize函数使用(c++)
C++: void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
chenjx85
2019/05/08
1.3K0
opencv2.4.13.7的resize函数使用(c++)
双线性插值(Bilinear Interpol)原理及应用
在很多神经网络上采样过程中会用到双线性插值,其为基础的图像resize操作。以前一直没时间仔细研究,今天探究并记录一下原理和自己的理解。
全栈程序员站长
2022/09/05
5.1K0
双线性插值(Bilinear Interpol)原理及应用
OpenCV 几何变换-图像缩放
图像的缩放主要用于改变图像的大小,缩放后图像的图像的宽度和高度会发生变化。在图像处理中是一种很基础的几何变换,但是具有很重要的作用,比如:当输入图片尺寸过大时,处理速度会很慢,适当的缩小图像可以在不影响处理效果的同时有效提高代码执行速度。 opencv提供了resize函数实现图片缩放功能,函数原型为:
chaibubble
2022/05/07
4950
OpenCV 几何变换-图像缩放
Python库OpenCV安装、配置、使用教程:图像缩放cv2.resize函数用法详解
在数据科学与计算机视觉的领域中,Python库OpenCV 是最常用的工具之一,无论是处理图像还是视频,都能提供强大的支持。特别是在进行图像缩放时,cv2.resize 函数更是核心利器。不论你是新手入门还是高手进阶,这篇文章将为你详细讲解 如何安装OpenCV、配置环境,以及高效使用cv2.resize函数,实现 .jpg、.png 或 .bmp 等格式图片的快速缩放。
猫头虎
2025/01/10
4430
推荐阅读
相关推荐
图像处理: 五种 插值法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验