Python OpenCV 图像的双线性插值算法,全网最细致的算法说明
Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 42 篇。
基础知识铺垫
本篇博客实现双线性插值算法的编写,顺便修改一下 上篇博客 最近邻插值算法最后实现与 OpenCV 提供的内置参数不一致问题。还有一个问题,是执行速度问题,该问题一并在学习双线性插值算法之后解决。
图像的双线性插值算法
双线性内插值算法是一种比较好的图像缩放算法,它利用了源图像中虚拟点四周四个真实存在的像素值,依据权重来决定目标图中的一个像素值。
先摘抄一些原理性的描述:对于一个目标像素,通过反向变换可以得到源图像的虚拟坐标,大概率是浮点坐标,格式为
(i+u,j+v)
,其中
i
、
j
为整数部分,
u
、
v
为小数部分,取值
[0,1)
,这时在源图像中
(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)
这一步的变换被省略了很多内容,橡皮擦也是查阅了很多资料,接下来为你补充上。先画一张辅助理解的图~
首先在 X 方向上进行两次线性插值计算,然后在 Y 方向上进行一次插值计算。
在计算之前,又要补充知识了,叫做线性插值,已知数据 ( x 0 , y 0 ) 和 ( x 1 , y 1 ) ,要计算 [ x 0 , x 1 ] 区间内某一位置 x 在直线上的 y 值,公式如下:
公式进行变形得到:
变换之后大概等用 x 0 和 x 1 的距离作为一个权重,用于 y 0 和 y 1 的加权,双线性插值就是在两个方向上做线性插值。
继续看上图,在点 1 与点 2 区间内寻找一点,依据公式可得:
f
(
插
值
点
1
)
≈
x
2
−
x
1
x
2
−
x
f
(
点
1
)
+
x
2
−
x
1
x
−
x
1
f
(
点
2
)
其中插值点 1 =
(
x
,
y
1
)
同样的算法获取插值点 2:
f
(
插
值
点
2
)
≈
x
2
−
x
1
x
2
−
x
f
(
点
3
)
+
x
2
−
x
1
x
−
x
1
f
(
点
4
)
其中插值点 2 =
(
x
,
y
2
)
接下来在 Y 方向进行线性插值计算:
将上述式子展开,就可以得到最后的结果了,这个没多少难度,写的时候与看的时候都仔细点就好:
该式子可以进一步的简化,因为两个相邻点插值是 1,所以简化如下:
在将所有点的坐标带入
f
(
x
,
y
)
≈
f
(
x
1
,
y
1
)
(
x
2
−
x
)
(
y
2
−
y
)
+
f
(
x
2
,
y
1
)
(
x
−
x
1
)
(
y
2
−
y
)
+
f
(
x
1
,
y
2
)
(
x
2
−
x
)
(
y
−
y
1
)
+
f
(
x
2
,
y
2
)
(
x
−
x
1
)
(
y
−
y
1
)
将 (x,y) 替换成最开始的写法
(i+u,j+v)
,其他的坐标分别为 点 1~点 4 分别为:
(i,j)
、
(i+1,j)
、
(i,j+1)
、
(i+1,j+1)
,带入上述公式,变化结果如所示:
别晕,估计这是全网最清晰的转换方式了:
到这里就与本篇博客最开始的公式呼应上了。
所以通过目标图像反推出来的一点,可以通过四个点的坐标进行计算,每个坐标前面的叫做权重,假设存在这样一个像素坐标为
(1,1)
,反推在源图中得到的坐标是
(0.75,0.75)
,由于图像中不可能存在浮点坐标,所以获取周围四个坐标分别是
(0,0)(0,1)(1,0)(1,1)
,由于
(0.75,0.75)
距离
(1,1)
最近,所以
(1,1)
点对该像素颜色作用最大,相应的
(1,1)
点对应的点是
f(i+1,i+1)
,该变量前面的系数权重为
0.75*0.75
,结果最大,这个说明是通过真实的数据去说明。
拿到计算方式之后,就可以通过代码实现双线性插值算法了。
先通过内置的缩放函数,测试一下运行时间:
if __name__ == '__main__':
src = cv2.imread('./t.png')
start = time.time()
dst = cv2.resize(src, (600, 600))
print('内置函数运行时间:%f' % (time.time() - start))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
得到的时间为
内置函数运行时间:0.002000
,非常快。
接下来就是自写函数验证了,代码的说明我写在了注释中,你可以研究一下,注意公式的运用
import cv2
import numpy as np
import time
def resize_demo(src, new_size):
# 目标图像宽高
dst_h, dst_w = new_size
# 源图像宽高
src_h, src_w = src.shape[:2]
# 如果图像大小一致,直接复制返回即可
if src_h == dst_h and src_w == dst_w:
return src.copy()
# 计算缩放比例
scale_x = float(src_w) / dst_w
scale_y = float(src_h) / dst_h
# 遍历目标图像
dst = np.zeros((dst_h, dst_w, 3), dtype=np.uint8)
# return dst
# 对通道进行循环
# for n in range(3):
# 对 height 循环
for dst_y in range(dst_h):
# 对 width 循环
for dst_x in range(dst_w):
# 目标在源上的坐标
src_x = dst_x * scale_x
src_y = dst_y * scale_y
# 计算在源图上 4 个近邻点的位置
# i,j
i = int(np.floor(src_x))
j = int(np.floor(src_y))
u = src_x-i
v = src_y-j
if j == src_h-1:
j = src_h-2
if i == src_w-1:
i = src_h-2
# 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)
dst[dst_y, dst_x] = (1-u)*(1-v)*src[j, i]+u*(1-v) * \
src[j+1, i] + (1-u)*v*src[j, i+1]+u*v*src[j+1, i+1]
# dst[dst_y, dst_x] = 0.25*src[j, i]+0.25 * \
# src[j+1, i] + 0.25*src[j, i+1]+0.25*src[j+1, i+1]
# dst[dst_y,dst_x,n] = 255
return dst
if __name__ == '__main__':
src = cv2.imread('./t.png')
start = time.time()
dst = resize_demo(src, (500, 600))
print('自写函数运行时间:%f' % (time.time() - start))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
代码运行消耗了
2s
多,确实比较费时间。
橡皮擦的小节
希望今天的 1 个小时你有所收获,我们下篇博客见~
博主 ID:梦想橡皮擦,希望大家<font color="red">点赞</font>、<font color="red">评论</font>、<font color="red">收藏</font>。
版权声明: 本文为 InfoQ 作者【梦想橡皮擦】的原创文章。
原文链接:【 http://xie.infoq.cn/article/ab8ea2f5fb41ab89a53e369b4 】。文章转载请联系作者。
梦想橡皮擦
爬虫 100 例作者,蓝桥签约作者,博客专家 2021.02.06 加入
6 年产品经理+教学经验,3 年互联网项目管理经验; 互联网资深爱好者; 沉迷各种技术无法自拔,导致年龄被困在 25 岁;