小白入门计算机视觉(三) : 图像基本处理----图像采样常用的插值方法


我们在用计算机对图像进行处理时,计算机保存的图像都是一个一个的像素点,所以又称为数字图像。而这一过程由图像的取样与量化来完成,所以我们先来了解一下什么是取样和量化

取样和量化

取样

取样就是要用多少点来描述一幅图像,取样结果质量的高低就是用图像的分辨率来衡量的,一般我们说图像的像素是M*N的,这就是取样

量化

量化是指要使用多大范围的数值来表示图像采样之后的一个点,比如我们常见的用0-255来表示一个点色调或者采用0-1来表示一个点的色调

总的来说就是数字化坐标值称为取样,数字化幅度值称为量化

上采样和下采样

上采样(upsampling)就是放大图像(或称为图像插值(interpolating))的主要目的 是放大原图像,从而可以显示在更高分辨率的显示设备上
下采样(subsampled)就是缩小图像(或称为降采样(downsampled))的主要目的 有两个:
1、使得图像符合显示区域的大小;
2、生成对应图像的缩略图

这里说明一下,这里说的放大和缩小图像是放大或缩小原图像,或者说就是增加或者减少图像的像素点,不是我们日常见得那种点击放大图片的操作哈

采样方法

上采样:

上采样原理几乎都是通过内插值的方法,就是在原来图像基础上在像素点点之间插入合适的新的像素点。

下采样:

下采样原理就是如果一幅图像尺寸为MxN,对其进行s倍下采样(即图像缩小s倍),即得到(M/s)x(N/s)尺寸的得分辨率图像,当然s应该是M和N的公约数才行,如果考虑的是矩阵形式的图像,就是把原始图像s*s窗口内的图像变成一个像素,也就是每间隔几个像素点取一次。
我们常见的马赛克效果,专业的处理就是用下采样的方式实现的,而非是用方格图片覆盖在原图片上

常用的插值方法

无论是上采样还是下采样,采样的方式都几乎是以插值的方法实现的,我就先分享几个最基础的吧

最邻近插值 (The nearest interpolation)

最近邻插值我们也说零阶插值,最邻近插值算法简而言之就是令变换后像素的灰度值等于距它最近的输入像素的灰度值,这个是最简单的插值方法。
设i+u, j+v(i, j为正整数, u, v为大于零小于1的小数,下同)为待求象素坐标,则待求象素灰 度的值 f(i+u, j+v) 如下图所示:

在这里插入图片描述
如果(i+u, j+v)落在A区,即u<0.5, v<0.5,则将左上角象素的灰度值赋给待求象素,同理,落在B区则赋予右上角的象素灰度值,落在C区则赋予左下角象素的灰度值,落在D区则赋予右下角象素的灰度值。

我们先说一下u,v的值怎么确定的吧,我举个例子,假如现在我有4*4的图像 img_src,每个点灰度值如下图所示:

在这里插入图片描述

现在我要把他变成5*5的图像 img_dst,那么图像的高度和宽度的放大比例都是4:5

在这里插入图片描述
我们的是要在原图像中判断放大后的图像像素点应该离哪个坐标更接近,所以要将放大后的图像坐标映射到原图像里面。我们假设对应到原图像的坐标为 (x,y),这里的坐标可能会是小数,所以后面我们需要再做一个四舍五入的计算。这里就拿第一行的坐标进行计算:
( x 0 , y 0 ) = i m g d s t ( 0 , 0 ) 5 / 4 = ( 0 , 0 ) (x_0,y_0)=\frac{ img_{dst}(0,0)}{5/4}=(0,0) x0y0=5/4imgdst(0,0)=(0,0)
( x 1 , y 0 ) = i m g d s t ( 1 , 0 ) 5 / 4 = ( 0.8 , 0 ) (x_1,y_0)=\frac{ img_{dst}(1,0)}{5/4}=(0.8,0) x1y0=5/4imgdst(1,0)=(0.8,0)
( x 2 , y 0 ) = i m g d s t ( 2 , 0 ) 5 / 4 = ( 1.6 , 0 ) (x_2,y_0)=\frac{ img_{dst}(2,0)}{5/4}=(1.6,0) x2y0=5/4imgdst(2,0)=(1.6,0)
( x 3 , y 0 ) = i m g d s t ( 3 , 0 ) 5 / 4 = ( 2.4 , 0 ) (x_3,y_0)=\frac{ img_{dst}(3,0)}{5/4}=(2.4,0) x3y0=5/4imgdst(3,0)=(2.4,0)
( x 4 , y 0 ) = i m g d s t ( 4 , 0 ) 5 / 4 = ( 3.2 , 0 ) (x_4,y_0)=\frac{ img_{dst}(4,0)}{5/4}=(3.2,0) x4y0=5/4imgdst(4,0)=(3.2,0)
好了,看到这里,相信都已经明白上面所说的u和v的值怎么来的吧
进行四舍五入后,第一行的坐标就变为:
(0,0),(1,0),(2,0),(2,0),(3,0)
然后我们再把原图像的对应坐标的像素点的色调值赋值给放大后的图像,那么放大后的图像的第一行像素点如下图所示:

在这里插入图片描述
原理搞明白了我们来看看代码是怎么实现的,还是先拿我女神照片吧

在这里插入图片描述

我们先用opencv看看原图的大小吧

import cv2
import numpy as np
img = cv2.imread("data.jpg")
size = img.shape[:2]
print(size)

'''
output:
(185, 270)
'''

我们现在把他放大到300*300的图像

#创建一个空的三通道图像数组
dst_img = np.zeros( shape = ( 300, 300, 3 ), dtype = np.uint8 )
#循环遍历放大后的图像的每个坐标映射到原图像中
for i in range( 0, 300 ):
        for j in range( 0, 300 ):
            row = ( i / 300 ) * size[0]
            col = ( j / 300 ) * size[1]
            #四舍五入
            dst_row = round ( row )
            dst_col = round( col )
            dst_img[i][j] = img[dst_row][dst_col]

这样就完成了图像的放大操作,我们看看效果吧
完整代码

import cv2
import numpy as np
img = cv2.imread("data.jpg")
size = img.shape[:2]
print(img)
#创建一个空的三通道图像数组
dst_img = np.zeros( shape = ( 300, 300, 3 ), dtype = np.uint8 )
#循环遍历放大后的图像的每个坐标映射到原图像中
for i in range( 0, 300 ):
        for j in range( 0, 300 ):
            row = ( i / 300 ) * size[0]
            col = ( j / 300 ) * size[1]
            #四舍五入
            dst_row = round ( row )
            dst_col = round( col )
            dst_img[i][j] = img[dst_row][dst_col]

print(dst_img.shape)
cv2.imshow("img",img)
cv2.imshow("dst_img",dst_img)
cv2.waitKey(0)
'''
output:
(185, 270, 3)
(300, 300, 3)
'''

在这里插入图片描述
我们可以看到放大后的图片质量不太好。效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,可以看到上面(x_2,y_0)的坐标值是(1.6,0)的时候,不应该就简单的取为2,既然是1.6,比2要小0.4 ,比1要大0.6,那么在灰度值骤变的地方就会产生明显的锯齿。合理的处理方式应该是目标象素值根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果,所以我就又顺便学习了一下双线型内插值算法。
双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多

为了更容易理解双线性插值,我们先看一下单线性插值方法

单线性插值

什么是线性呢,我直接举例说明吧
假如现在有一组数据,[0,2,4,6,8,10],现在我要在这几个数据之前各插一个数,[0,1,2,3,4,5,6,7,8,9,10],新插入的数和原来的数都在一条直线上,我们就称为这几个数据是线性的,是不是很像高中的等差数列呢?我们假设y=f(x)是线性关系,那么函数图像如下图所示:

在这里插入图片描述
已知坐标 (x0, y0) 与 (x1, y1)的值,我们这里假设坐标(x,y)中的x已经确定了,我们现在要确定y的值,上图中我画出来的两个三角形的斜率是相同的,那么就可以得到下面的等式:
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
同理可以得出:
x = y 1 − y y 1 − y 0 x 0 + y − y 0 y 1 − y 0 x 1 x = \frac{y_1-y}{y_1-y_0}x_0+\frac{y-y_0}{y_1-y_0}x_1 x=y1y0y1yx0+y1y0yy0x1

双线性插值

双线性插值就是两个方向上都做线性插值,因为图像处理时候,坐标是个二维的,也就是我们要对列和行同时做线性插值,还是放坐标系里看吧,如下图所示:
在这里插入图片描述
坐标系里 P(x,y) 是我们要插入的点的坐标,假设 f(x,y)代表的是在(x,y)这个点的灰度值,现在有Q11(x1,y1),Q12(x1,y2),Q21(x2,y1),Q22(x2,y2)这四个点,我们要在这四个点中间插一个点P,那么在x方向做两次插值操作(R1和R2),然后在y轴方向上做一次插值操作(P),坐标按照上面的公式就是
在R1处坐标为(x, y1)
f ( R 1 ) ≈ x 2 − x x 2 − x 1 f ( Q 11 ) + x 1 − x x 2 − x 1 f ( Q 21 ) f(R_1)≈\frac{x_2-x}{x_2-x_1}f(Q_{11})+\frac{x_1-x}{x_2-x_1}f(Q_{21}) f(R1)x2x1x2xf(Q11)+x2x1x1xf(Q21)
在R2处坐标为(x, y2)
f ( R 2 ) ≈ x 2 − x x 2 − x 1 f ( Q 12 ) + x 1 − x x 2 − x 1 f ( Q 22 ) f(R_2)≈\frac{x_2-x}{x_2-x_1}f(Q_{12})+\frac{x_1-x}{x_2-x_1}f(Q_{22}) f(R2)x2x1x2xf(Q12)+x2x1x1xf(Q22)
然后在y轴方向上做一次插值
f ( P ) ≈ y 2 − y y 2 − y 1 f ( R 1 ) + y 1 − y y 2 − y 1 f ( R 2 ) f(P)≈\frac{y_2-y}{y_2-y_1}f(R_1)+\frac{y_1-y}{y_2-y_1}f(R_2) f(P)y2y1y2yf(R1)+y2y1y1yf(R2)
现在将这三次插值操作合并一下,因为我们在计算的时候都是计算邻近的点,之间间隔都是1,所以
x 2 − x 1 = 1 x_2-x_1 = 1 x2x1=1
y 2 − y 1 = 1 y_2-y_1 = 1 y2y1=1
那么上面所有的分母就都是1了,则
f ( P ) ≈ ( y 2 − y ) ( ( x 2 − x ) f ( Q 11 ) + ( x 2 − x ) f ( Q 21 ) ) + ( y 1 − y ) ( ( x 2 − x ) f ( Q 12 ) + ( x 2 − x ) f ( Q 22 ) ) f(P)≈(y_2-y)((x_2-x)f(Q_{11})+(x_2-x)f(Q_{21}))+(y_1-y)((x_2-x)f(Q_{12})+(x_2-x)f(Q_{22})) f(P)(y2y)((x2x)f(Q11)+(x2x)f(Q21))+(y1y)((x2x)f(Q12)+(x2x)f(Q22))
我们再变换一下,更容易看
f ( P ) ≈ ( y 2 − y ) ( x 2 − x ) f ( Q 11 ) + ( y 2 − y ) ( x 2 − x ) f ( Q 21 ) + ( y 1 − y ) ( x 2 − x ) f ( Q 12 ) + ( y 1 − y ) ( x 2 − x ) f ( Q 22 ) f(P)≈(y_2-y)(x_2-x)f(Q_{11})+(y_2-y)(x_2-x)f(Q_{21})+(y_1-y)(x_2-x)f(Q_{12})+(y_1-y)(x_2-x)f(Q_{22}) f(P)(y2y)(x2x)f(Q11)+(y2y)(x2x)f(Q21)+(y1y)(x2x)f(Q12)+(y1y)(x2x)f(Q22)
双线性插值的数学理论差不多就是这样了,但是这里我要强调一下,在双线性插值过程中还有个比较重要的问题,就是中心对齐

中心对齐

关于这个问题我们先举个例子吧,假如现在有3 * 3的源图像,现在我想把他放大成9 * 9的图像,那么目标图像的中心点(4,4)应该是映射到源图像的(1,1)上才对,我们先按照一般的公式计算一下插值的坐标
( s r c X , s r c Y ) = ( 4 ∗ 3 9 , 4 ∗ 3 9 ) = ( 1.3333 , 1.3333 ) (srcX,srcY)=(\frac{4*3}{9},\frac{4*3}{9})=(1.3333,1.3333) (srcXsrcY)=(943,943)=(1.3333,1.3333)
这时候我们插入的点用到的更多的是源图像右下角的信息,那么放大后的图像也就和源图像会有较大的差别。我们现在试着将原点从源图像的插值区间的像素网格的中心开始计算,也就是(srcX+0.5,srcY+0.5),由于这个点是虚拟的,算完之后要还原,所以我们还需要再减去0.5,我们看看结果
( s r c X , s r c Y ) = ( ( 4 + 0.5 ) ∗ 3 9 − 0.5 , ( 4 + 0.5 ) ∗ 3 9 − 0.5 ) = ( 1 , 1 ) (srcX,srcY)=(\frac{(4+0.5)*3}{9}-0.5,\frac{(4+0.5)*3}{9}-0.5)=(1,1) (srcXsrcY)=(9(4+0.5)30.5,9(4+0.5)30.5)=(1,1)
这样就刚好满足我们的要求了
我们现在看看代码怎么实现吧

完整代码

import numpy as np
import cv2

'''
python implementation of bilinear interpolation
'''


def bilinear_interpolation(img, out_dim):
    src_h, src_w, channel = img.shape
    dst_h, dst_w = out_dim[1], out_dim[0]
    print("src_h, src_w = ", src_h, src_w)
    print("dst_h, dst_w = ", dst_h, dst_w)
    if src_h == dst_h and src_w == dst_w:
        return img.copy()
    dst_img = np.zeros((dst_h, dst_w, 3), dtype=np.uint8)
    scale_x, scale_y = float(src_w) / dst_w, float(src_h) / dst_h
    #对三个通道都要计算
    for i in range(3):
        for dst_y in range(dst_h):
            for dst_x in range(dst_w):
                # 几何中心对齐
                src_x = (dst_x + 0.5) * scale_x - 0.5
                src_y = (dst_y + 0.5) * scale_y - 0.5

                # 确定最近邻的四个像素坐标
                src_x0 = int(np.floor(src_x))
                src_x1 = min(src_x0 + 1, src_w - 1)
                src_y0 = int(np.floor(src_y))
                src_y1 = min(src_y0 + 1, src_h - 1)
                # 计算公式
                temp0 = (src_x1 - src_x) * img[src_y0, src_x0, i] + (src_x - src_x0) * img[src_y0, src_x1, i]
                temp1 = (src_x1 - src_x) * img[src_y1, src_x0, i] + (src_x - src_x0) * img[src_y1, src_x1, i]
                dst_img[dst_y, dst_x, i] = int((src_y1 - src_y) * temp0 + (src_y - src_y0) * temp1)

    return dst_img


if __name__ == '__main__':
    img = cv2.imread('data.jpg')
    dst = bilinear_interpolation(img, (300, 300))
    cv2.imshow('bilinear interp', dst)
    cv2.waitKey()

看一下放大后的图片效果,和最近邻插值放大的图片做个对比
在这里插入图片描述在这里插入图片描述
可以看到明显左边的用双线性插值算法放大后的图片更清晰

插值方法总结

除了这两个基础的插值算法,还有其他很多的插值算法:

“Inverse Distance to a Power(反距离加权插值法)”、
“Kriging(克里金插值法)”、
“Minimum Curvature(最小曲率)”、
“Modified Shepard’s Method(改进谢别德法)”、
“Natural Neighbor(自然邻点插值法)”、
“Polynomial Regression(多元回归法)”、
“Radial Basis Function(径向基函数法)”、
“Triangulation with Linear Interpolation(线性插值三角网法)”、
“Moving Average(移动平均法)”、
“Local Polynomial(局部多项式法)”

我就先不介绍了,学不动了,以后有机会用到了再说吧,学会双线性插值,以后就可以自己自定义放大自己的照片了呢,就不用担心放大后图片不清晰了

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值