图像缩放--双线性内插法及其python实现(图文代码)

news/2024/7/21 5:43:20 标签: 图像处理, python

双线性内插法

#数字图像处理
我将最近学数字图像处理,写的一些代码放到了github 中保存,有机会一起学习。有错误和需要补充的地方欢迎评论。

理论简介

双线性插值是图像内插缩放的一种方法。
简单来说,双线性插值即对于目标像素进行两个方向上的线性插值。

比如,首先在x上进行一次线性插值得到两个点,即上图得到的 R 1 R_1 R1 R 2 R_2 R2。再在y方向上进行一次线性插值,由 R 1 R_1 R1 R 2 R_2 R2 得到目标点 P P P

故双线性插值需要四个最近邻像素点(原像素点,即已知灰度值),即上图中的 Q 11 , Q 12 , Q 21 , Q 22 Q_{11},Q_{12},Q_{21},Q_{22} Q11,Q12,Q21,Q22

与最近邻内插法相比较

同样是上图,若求转换后的目标点 P P P 的灰度值,我们只需要在原图像中找最接近的像素,将其值赋给 P P P即可,上图的话即将 Q 12 Q_{12} Q12 赋值给 P P P

引用《数字图像处理-第三版-冈萨雷斯》书上:

假设一副大小为 500x500 的像素的图像要放大 1.5 倍到 750x750 像素。一种简单的放大方法是创建一个假象的 750x750 网格, 它与原始图像有相同的间隔,然和将其收缩,使它准确地与原图像匹配。显然,收缩后的 750x750 网格的像素间隔要小于原图像的像素间隔。
为了对覆盖的每一个点赋以灰度值,我们再原图像中寻找最接近的像素,并把该像素的灰度赋给 750x750 网格中的新像素。

这里不对最近邻内插法做详细讨论。但在这里引出冈萨雷斯这本书上这段话是为了方便理解这种“网格”的思想。: 上图中, Q 11 , Q 12 , Q 21 , Q 22 Q_{11},Q_{12},Q_{21},Q_{22} Q11,Q12,Q21,Q22 即原图像上相邻的 2x2 的4个像素点,对于缩放后的新的图像上的一个像素点,它也会在如上图 P P P点 那般位于一个网格之中。

因此能找到四个最近邻点,再借助这四个点即可进行双线性内插法。

线性插值公式

如第一段所说,双线性内插即进行了两次线性插值,所有我们需要先知道线性插值。以下图为例:

因为
y − y 0 x − x 0 = y 1 − y 0 x 1 − x 0 \frac{y-y_0}{x-x_0} = \frac{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 = \frac{x_1 - x}{x_1 - x_0}y_0 + \frac{x - x_0}{x_1 - x_0}y_1 y=x1x0x1xy0+x1x0xx0y1

所以双线性即在两个方向上都进行一次线性插值即可,只是在第一个由四个点 Q 11 , Q 12 , Q 21 , Q 22 Q_{11},Q_{12},Q_{21},Q_{22} Q11,Q12,Q21,Q22 上计算两个点,第二次由两个点线性插值算出目标点即可。

双线性插值公式


还是这个图(此图来源于网络), Q 11 , Q 12 , Q 21 , Q 22 Q_{11},Q_{12},Q_{21},Q_{22} Q11,Q12,Q21,Q22为近邻点, P P P 为待求点。
设算子 f ( ⋅ ) f(\cdot) f() 代表该像素原图像的灰度值。 Q i j Q_{ij} Qij的坐标为: ( x i , y j ) (x_i,y_j) (xi,yj)

则第一次线性插值计算 R 1 R_1 R1 R 2 R_2 R2
(也用 R 1 R_1 R1 R 2 R_2 R2分布代表他们的线性插值得到的灰度值)
R 1 = x 2 − x x 2 − x 1 f ( Q 11 ) + x − x 1 x 2 − x 1 f ( Q 21 ) R 2 = x 2 − x x 2 − x 1 f ( Q 12 ) + x − x 1 x 2 − x 1 f ( Q 22 ) R_1 = \frac{x2 -x}{x2-x1}f(Q_{11}) + \frac{x-x_1}{x_2-x_1}f(Q_{21}) \\ R_2 = \frac{x2 -x}{x2-x1}f(Q_{12}) + \frac{x-x_1}{x_2-x_1}f(Q_{22}) \\ R1=x2x1x2xf(Q11)+x2x1xx1f(Q21)R2=x2x1x2xf(Q12)+x2x1xx1f(Q22)
第二次插值:
P = y 2 − y y 2 − y 1 R 1 + y − y 1 y 2 − y 1 R 2 P = \frac{y2-y}{y2-y1}R_1 + \frac{y-y1}{y2-y1}R_2 P=y2y1y2yR1+y2y1yy1R2

还记得提到过: Q 11 , Q 12 , Q 21 , Q 22 Q_{11},Q_{12},Q_{21},Q_{22} Q11,Q12,Q21,Q22为近邻点吗,所以上面各式的分母 y 2 − y 1 , x 2 − x 1 y_2 -y_1,x_2-x_1 y2y1,x2x1都等于 1 1 1

于是最终得到:
P = f ( Q 11 ) ( x 2 − x ) ( y 2 − y ) + f ( Q 21 ) ( x − x 1 ) ( y 2 − y ) + f ( Q 12 ) ( x 2 − x ) ( y − y 1 ) + f ( Q 22 ) ( x − x 1 ) ( y − y 1 ) P = f(Q_{11})(x_2-x)(y_2-y) + \\ f(Q_{21})(x-x_1)(y_2-y)+\\ f(Q_{12})(x_2-x)(y-y_1)+\\ f(Q_{22})(x-x_1)(y-y_1) P=f(Q11)(x2x)(y2y)+f(Q21)(xx1)(y2y)+f(Q12)(x2x)(yy1)+f(Q22)(xx1)(yy1)

Tips

源图像和目标图像几何中心的对齐

方法:在计算源图像的虚拟浮点坐标的时候,
一般情况左上角对齐:
   s r c X = d s t X ∗ ( s r c W i d t h / d s t W i d t h ) srcX=dstX * (srcWidth/dstWidth) srcX=dstX(srcWidth/dstWidth) ,
   s r c Y = d s t Y ∗ ( s r c H e i g h t / d s t H e i g h t ) srcY = dstY * (srcHeight/dstHeight) srcY=dstY(srcHeight/dstHeight)
中心对齐:
   S r c X = ( d s t X + 0.5 ) ∗ ( s r c W i d t h / d s t W i d t h ) − 0.5 SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5 SrcX=(dstX+0.5)(srcWidth/dstWidth)0.5
   S r c Y = ( d s t Y + 0.5 ) ∗ ( s r c H e i g h t / d s t H e i g h t ) − 0.5 SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5 SrcY=(dstY+0.5)(srcHeight/dstHeight)0.5

我在代码中都有实现,但目前没有看出有明显的效果,可能需要用更大的图片试试。(通过在类初始化中定义参数 align = 'left', 该参数默认为 center

过程中遇到的问题

按照中心对其的思路写的时候,发现在 1、3、5、7倍缩放的时候,会发生意外。体现在 1倍缩放得到一个全黑的图像,缩放失败。3倍缩放时得到一个相比于原图较暗的图像,5倍缩放得到一个相比于原图较暗但比3倍时亮的图像,7倍时同理。

经过查看 src_i 等数值变换,发现在中心对齐法中 ,可能会出现 src_i (即上面公式中的 S r c X 、 S r c Y SrcX、SrcY SrcXSrcY)值为一个整数的情况,使其在向上取整和向下取整得到一个相同的值。

例如对 235x233 的图像,经过三倍缩放,在 d s t i = 115 dst_i = 115 dsti=115 时,经过中心对齐的公式,计算出的 s r c i = 38 src_i = 38 srci=38
( 115 + 0.5 ) ∗ 235 235 ∗ 3 − 0.5 = 38 (115+0.5)* \frac{235}{235*3} -0.5 = 38 (115+0.5)23532350.5=38

为解决这个问题,我使计算出的 src_i += 0.001 ,再进行向上和向下取整。 OK,虽然比较偷懒加取巧,但确实解决了这个问题。

ps: 更合理的解决方案,应该是对于得到的整数坐标,直接将对应的原像素坐标赋值给新点即可,即对于这种点不采用双线性变换,直接恒等映射,这样保证了信息不丢失。 这里先插个旗,之后再看我有时间改没。

python_91">基于 numpy 的python代码实现

纯手打,实测可用。

python"># 引入必要的包
import numpy as np
import matplotlib.image as mpimg    # 用于读取
import matplotlib.pyplot as plt     # 用于显示
import logging

from math import ceil



# 双线性插值法
class BilinearInterpolation(object):
    def __init__(self,
                 w_rate: float,     # w 的缩放率
                 h_rate: float,     # h 的缩放率
                 *,
                 align='center'
                 ):
        if align not in ['center', 'left']:
            logging.exception(f'{align} is not a valid align parameter')
            align = 'center'
        self.align = align
        self.w_rate = w_rate
        self.h_rate = h_rate
        pass

    def set_rate(self,
                 w_rate: float,     # w 的缩放率
                 h_rate: float      # h 的缩放率
                 ):
        self.w_rate = w_rate
        self.h_rate = h_rate


    # 由变换后的像素坐标得到原图像的坐标    针对高
    def get_src_h(self, dst_i,source_h,goal_h) -> float:
        if self.align == 'left':
            # 左上角对齐
            src_i = float(dst_i * (source_h/goal_h))
        elif self.align == 'center':
            # 将两个图像的几何中心重合。
            src_i = float((dst_i + 0.5) * (source_h/goal_h) - 0.5)
        src_i += 0.001
        src_i = max(0.0, src_i)
        src_i = min(float(source_h - 1), src_i)
        return src_i
        pass
    # 由变换后的像素坐标得到原图像的坐标    针对宽
    def get_src_w(self, dst_j,source_w,goal_w) -> float:
        if self.align == 'left':
            # 左上角对齐
            src_j = float(dst_j * (source_w/goal_w))
        elif self.align == 'center':
            # 将两个图像的几何中心重合。
            src_j = float((dst_j + 0.5) * (source_w/goal_w) - 0.5)
        src_j += 0.001
        src_j = max(0.0, src_j)
        src_j = min((source_w - 1), src_j)
        return src_j
        pass

    def transform(self, img):

        source_h, source_w, source_c = img.shape  # (235, 234, 3)
        goal_h, goal_w = round(
            source_h * self.h_rate), round(source_w * self.w_rate)
        new_img = np.zeros((goal_h, goal_w, source_c), dtype=np.uint8)

        # print the goal image's shape
        # print(new_img.shape[0], new_img.shape[1])

        # i --> h ,  j --> w
        # x --> w:j  y --> h:i
        for i in range(new_img.shape[0]):       # h
            src_i = self.get_src_h(i,source_h,goal_h)
            for j in range(new_img.shape[1]):
                src_j = self.get_src_w(j,source_w,goal_w)
                i2 = ceil(src_i)
                i1 = int(src_i)
                j2 = ceil(src_j)
                j1 = int(src_j)
                # i 对应 y , j 对应 x
                # x 对应 j , y 对应 i
                x2_x = j2 - src_j
                x_x1 = src_j - j1
                y2_y = i2 - src_i
                y_y1 = src_i - i1
                # print(i,j,src_i,i1,i2,src_j,j1,j2)
                # f(Q_xy) 对应 img[y,x] 即 img[i,j]
                new_img[i, j] = img[i1, j1]*x2_x*y2_y + img[i1, j2] * \
                    x_x1*y2_y + img[i2, j1]*x2_x*y_y1 + img[i2, j2]*x_x1*y_y1

        return new_img

        pass
    pass

# 读取图片并显示
pic1 = mpimg.imread('./hw1_picture1.jpg')
pic2 = mpimg.imread('./hw1_picture2.jpg')
pic3 = mpimg.imread('./hw1_picture3.jpg')

print(pic1.shape)
# Show original image --- hw1_picture1.jpg
plt.imshow(pic1)
plt.axis('off')
plt.show()


# 0.5 缩放
BI = BilinearInterpolation(0.5,0.5)

new_pic1_half =  BI.transform(pic1)
plt.imshow(new_pic1_half)
plt.axis('off')
plt.show()

new_pic1_half =  BI.transform(pic2)
plt.imshow(new_pic1_half)
plt.axis('off')
plt.show()

new_pic1_half =  BI.transform(pic3)
plt.imshow(new_pic1_half)
plt.axis('off')
plt.show()

#3倍缩放

BI = BilinearInterpolation(3,3)

new_pic =  BI.transform(pic1)
plt.imshow(new_pic)
plt.axis('off')
plt.show()

new_pic =  BI.transform(pic2)
plt.imshow(new_pic)
plt.axis('off')
plt.show()

new_pic =  BI.transform(pic3)
plt.imshow(new_pic)
plt.axis('off')
plt.show()

代码的运行速度较慢,看了下其他大牛给出的优化方法以及opencv的优化方法,由于时间关系没有深入探究,这里先mark住,之后需要的时候再详细探究。
以下图片来自深入理解双线性插值算法_Activewaste-程序员秘密 - 程序员秘密

end

记录学习,关于代码和理论,若有错误欢迎指正、补充!♥

参考:

  • 最近邻插值和双线性插值原理 - 知乎
  • 深入理解双线性插值算法_Activewaste-程序员秘密 - 程序员秘密

http://www.niftyadmin.cn/n/1329164.html

相关文章

图像增强 -- 直方图均衡化及其python实现

直方图均衡化及其python实现 #数字图像处理 文章目录直方图均衡化及其python实现展示效果理论简介什么是直方图均衡化流程图:代码:这里贴一下 我最近数字图像学习阶段写的代码 – github 仓库。有机会一起学习。 展示效果 先上一下均衡化的效果&#…

机器学习:决策树 -- 学习笔记

参考文章 决策树算法原理(上) - 刘建平Pinard - 博客园决策树算法原理(下) - 刘建平Pinard - 博客园李航《统计学习方法》第二版 决策树算法 决策树学习算法包含: 特征选择决策树生成决策树的剪枝 下面会分别对三个部分进行一定的总结,参考资料主要…

scikit-learn 中决策树模型-参数说明、注解

目录scikit-learn 中决策树算法类库介绍重要参数criterion 特征选择标准对于分类决策树:关于基尼指数对于回归决策树以 squared_error 为例splitter 特征划分点选择标准max_depth 最大深度max_features 划分时考虑的最大特征数min_samples_split 叶子节点允许拆分的…

蓝桥杯 2019-A-修改数组(C++ 并查集解法)

题目 第一版 哈希表暴力模拟&#xff08;超时&#xff09; #include <bits/stdc.h>#include <iostream>using namespace std;int main() {// map<int, int> index;bool index[1000002] {0}; int n;cin >> n;vector<int> num(n, 0);for (int i …

verilog 时刻卡死,$time 不递增解决方案

#verilog #问题记录 #已解决 verilog 时刻卡死&#xff0c;$time 不递增解决方案 先说结论&#xff0c; 在写testbench 时&#xff0c;需要注意在一些 always 中加上延时&#xff0c;避免某个 always 不间断的一直仿真运行&#xff0c;导致仿真时刻卡住&#xff0c;时刻不递增…

HDLBits 刷题笔记 - Exams_ece241 2013 q8 - HDLBits

HDLBits 刷题笔记 - Exams_ece241 2013 q8 - HDLBits 题目原文&#xff1a;Exams/ece241 2013 q8 - HDLBits 题意 Implement a Mealy-type finite state machine that recognizes the sequence “101” on an input signal named x. Your FSM should have an output signal, …

verilog - signed 符号数与无符号

#verilog #FPGA verilog - signed 符号数与无符号数计算 文章目录verilog - signed 符号数与无符号数计算原码 、反码 、 补码的关系verilog 中的 signed不同的扩位方式结论主要参考&#xff1a; Verilog 中signed和$signed()的用法_长弓的坚持的博客-CSDN博客_&#xff04;s…

聊聊几句吧

程序员菜鸟一个&#xff0c;以前读书的时候感觉都没怎么认真思考过&#xff0c;当程序员有没有啥目标&#xff0c;都是上课学习考试&#xff0c;也没有花心思想想这份职业应该怎么做让自己变得更好。现在一晃&#xff0c;毕业也4年了&#xff0c;感觉也没啥积累沉淀&#xff0c…