博客
关于我
Python OpenCV 图像的双线性插值算法,全网最细致的算法说明
阅读量:454 次
发布时间:2019-03-06

本文共 6535 字,大约阅读时间需要 21 分钟。

转:

Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 42 篇。

该系列文章导航参考:https://blog.csdn.net/hihell/category_10688961.html

Python OpenCV

    • 基础知识铺垫
      • 图像的双线性插值算法
    • 橡皮擦的小节

基础知识铺垫

本篇博客实现双线性插值算法的编写,顺便修改一下 上篇博客 最近邻插值算法最后实现与 OpenCV 提供的内置参数不一致问题。

还有一个问题,是执行速度问题,该问题一并在学习双线性插值算法之后解决。

图像的双线性插值算法

双线性内插值算法是一种比较好的图像缩放算法,它利用了源图像中虚拟点四周四个真实存在的像素值,依据权重来决定目标图中的一个像素值。

先摘抄一些原理性的描述:

对于一个目标像素,通过反向变换可以得到源图像的虚拟坐标,大概率是浮点坐标,格式为(i+u,j+v),其中 ij 为整数部分,uv 为小数部分,取值 [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_0,y_0) (x0,y0) ( x 1 , y 1 ) (x_1,y_1) (x1,y1),要计算 [ x 0 , x 1 ] [x_0,x_1] [x0,x1] 区间内某一位置 x 在直线上的 y 值,公式如下:

y − y 0 x − x 0 = y 1 − y 0 x 1 − x 0 cfrac{y-y_0}{x-x_0}=cfrac{y_1-y_0}{x_1-x_0} xx0yy0=x1x0y1y0

公式进行变形得到:

y = x 1 − x x 1 − x 0 y 0 + x − x 0 x 1 − x 0 y 1 y=cfrac{x_1-x}{x_1-x_0}y_0+cfrac{x-x_0}{x_1-x_0}y_1 y=x1x0x1xy0+x1x0xx0y1

变换之后大概等用 x 0 x_0 x0 x 1 x_1 x1 的距离作为一个权重,用于 y 0 y_0 y0 y 1 y_1 y1 的加权,双线性插值就是在两个方向上做线性插值。

继续看上图,在点 1 与点 2 区间内寻找一点,依据公式可得:

f ( 插 值 点 1 ) ≈ x 2 − x x 2 − x 1 f ( 点 1 ) + x − x 1 x 2 − x 1 f ( 点 2 ) f(插值点1)approxcfrac{x_2-x}{x_2-x_1}f(点1)+cfrac{x-x_1}{x_2-x_1}f(点2) f(1)x2x1x2xf(1)+x2x1xx1f(2) 其中插值点 1 = ( x , y 1 ) (x,y_1) (x,y1)

同样的算法获取插值点 2:

f ( 插 值 点 2 ) ≈ x 2 − x x 2 − x 1 f ( 点 3 ) + x − x 1 x 2 − x 1 f ( 点 4 ) f(插值点2)approxcfrac{x_2-x}{x_2-x_1}f(点3)+cfrac{x-x_1}{x_2-x_1}f(点4) f(2)x2x1x2xf(3)+x2x1xx1f(4) 其中插值点 2 = ( x , y 2 ) (x,y_2) (x,y2)

接下来在 Y 方向进行线性插值计算:

f ( P ) ≈ y 2 − y y 2 − y 1 f ( 插 值 点 1 ) + y − y 1 y 2 − y 1 f ( 插 值 点 2 ) f(P)approxcfrac{y_2-y}{y_2-y_1}f(插值点1)+cfrac{y-y_1}{y_2-y_1}f(插值点2) f(P)y2y1y2yf(1)+y2y1yy1f(2)

将上述式子展开,就可以得到最后的结果了,这个没多少难度,写的时候与看的时候都仔细点就好:

f ( x , y ) ≈ f ( 点 1 ) ( x 2 − x ) ( y 2 − y ) ( x 2 − x 1 ) ( y 2 − y 1 ) + f ( 点 2 ) ( x − x 1 ) ( y 2 − y ) ( x 2 − x 1 ) ( y 2 − y 1 ) + f ( 点 3 ) ( x 2 − x ) ( y − y 1 ) ( x 2 − x 1 ) ( y 2 − y 1 ) + f ( 点 4 ) ( x − x 1 ) ( y − y 1 ) ( x 2 − x 1 ) ( y 2 − y 1 ) f(x,y)approxcfrac{f(点1)(x_2-x)(y_2-y)}{(x_2-x_1)(y_2-y_1)}+cfrac{f(点2)(x-x_1)(y_2-y)}{(x_2-x_1)(y_2-y_1)}+cfrac{f(点3)(x_2-x)(y-y_1)}{(x_2-x_1)(y_2-y_1)}+cfrac{f(点4)(x-x_1)(y-y_1)}{(x_2-x_1)(y_2-y_1)} f(x,y)(x2x1)(y2y1)f(1)(x2x)(y2y)+(x2x1)(y2y1)f(2)(xx1)(y2y)+(x2x1)(y2y1)f(3)(x2x)(yy1)+(x2x1)(y2y1)f(4)(xx1)(yy1)

该式子可以进一步的简化,因为两个相邻点插值是 1,所以简化如下:

f ( x , y ) ≈ f ( 点 1 ) ( x 2 − x ) ( y 2 − y ) + f ( 点 2 ) ( x − x 1 ) ( y 2 − y ) + f ( 点 3 ) ( x 2 − x ) ( y − y 1 ) + f ( 点 4 ) ( x − x 1 ) ( y − y 1 ) f(x,y)approx f(点1)(x_2-x)(y_2-y)+f(点2)(x-x_1)(y_2-y)+f(点3)(x_2-x)(y-y_1)+f(点4)(x-x_1)(y-y_1) f(x,y)f(1)(x2x)(y2y)+f(2)(xx1)(y2y)+f(3)(x2x)(yy1)+f(4)(xx1)(yy1)
在将所有点的坐标带入
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 ) f(x,y)approx 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) f(x,y)f(x1,y1)(x2x)(y2y)+f(x2,y1)(xx1)(y2y)+f(x1,y2)(x2x)(yy1)+f(x2,y2)(xx1)(yy1)
将 (x,y) 替换成最开始的写法 (i+u,j+v) ,其他的坐标分别为 点 1~点 4 分别为:(i,j)(i+1,j)(i,j+1)(i+1,j+1) ,带入上述公式,变化结果如所示:
f ( i + u , j + v ) ≈ f ( i , j ) ( i + 1 − ( i + u ) ) ( j + 1 − ( j + v ) ) + f ( i + 1 , j ) ( i + u − i ) ( j + 1 − ( j + v ) ) + f ( i , j + 1 ) ( i + 1 − ( i + u ) ) ( j + v − j ) + f ( i + 1 , j + 1 ) ( i + u − i ) ( j + v − j ) f(i+u,j+v)approx f(i,j)(i+1-(i+u))(j+1-(j+v))+f(i+1,j)(i+u-i)(j+1-(j+v))+f(i,j+1)(i+1-(i+u))(j+v-j)+f(i+1,j+1)(i+u-i)(j+v-j) f(i+u,j+v)f(i,j)(i+1(i+u))(j+1(j+v))+f(i+1,j)(i+ui)(j+1(j+v))+f(i,j+1)(i+1(i+u))(j+vj)+f(i+1,j+1)(i+ui)(j+vj)

别晕,估计这是全网最清晰的转换方式了:

f ( i + u , j + v ) ≈ f ( i , j ) ( 1 − u ) ( 1 − v ) + f ( i + 1 , j ) u ( 1 − v ) + f ( i , j + 1 ) ( 1 − u ) v + f ( i + 1 , j + 1 ) u v f(i+u,j+v)approx f(i,j)(1-u)(1-v)+f(i+1,j)u(1-v)+f(i,j+1)(1-u)v+f(i+1,j+1)uv f(i+u,j+v)f(i,j)(1u)(1v)+f(i+1,j)u(1v)+f(i,j+1)(1u)v+f(i+1,j+1)uv
到这里就与本篇博客最开始的公式呼应上了。

所以通过目标图像反推出来的一点,可以通过四个点的坐标进行计算,每个坐标前面的叫做权重,假设存在这样一个像素坐标为 (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 cv2import numpy as npimport timedef 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 dstif __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 个小时你有所收获,我们下篇博客见~

相关阅读


技术专栏

  1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
  2. Python 爬虫小课,精彩 9 讲

今天是持续写作的第 84 / 100 天。

如果你想跟博主建立亲密关系,可以关注同名公众号 梦想橡皮擦,近距离接触一个逗趣的互联网高级网虫。
博主 ID:梦想橡皮擦,希望大家点赞评论收藏

转:

你可能感兴趣的文章
Unable to execute dex: Multiple dex files
查看>>
Java多线程
查看>>
Unity监听日记
查看>>
AndroidStudio跳到错误位置
查看>>
木马开发的基本理论基础(五)
查看>>
openssl服务器证书操作
查看>>
expect 模拟交互 ftp 上传文件到指定目录下
查看>>
linux系统下双屏显示
查看>>
PDF.js —— vue项目中使用pdf.js显示pdf文件(流)
查看>>
我用wxPython搭建GUI量化系统之最小架构的运行
查看>>
我用wxPython搭建GUI量化系统之多只股票走势对比界面
查看>>
我用wxPython搭建GUI量化系统之财务选股工具添加日历和排序
查看>>
selenium+python之切换窗口
查看>>
重载和重写的区别:
查看>>
搭建Vue项目步骤
查看>>
账号转账演示事务
查看>>
idea创建工程时错误提醒的是architectCatalog=internal
查看>>
SpringBoot找不到@EnableRety注解
查看>>
简易计算器案例
查看>>
在Vue中使用样式——使用内联样式
查看>>