计算机视觉入门之图像处理<三>:图像插值方法

往期文章回顾:
计算机视觉入门之<零>
计算机视觉入门之图像处理<一>:图像处理基础概念
计算机视觉入门之图像处理<二>:图像处理基础概念


前言

本篇文章主要内容包括常见的两种图像像素插值方法:最邻近插值和双线性插值。文章将从理论分析、代码实现等方面对图像插值进行阐述。
日常生活中放大和缩小一张图片仅仅是机械地改变一张图片的尺寸,并未真正改变图片的像素个数,故在图像放大之后可能存在图像模糊,分辨率降低的情况。对于数字图像的处理,图像的放大称为上采样,上采样可以使得图像尺寸变大而分辨率基本保持不变(或提高分辨率)来适应不同的显示设备;图像的缩小称为下采样,下采样还可以使得图像生成相应的缩略图形式。
图像的上采样、下采样均可以通过像素插值来实现(不能简单地认为插值只能用于上采样),不同的操作需要改变相应的参数,如将前后图像尺寸的商取倒数等。

图像插值原理

如上图所示为3*3和7*7的图像,现在要把a图像经上采样放大到b图(这并非b图实际大小,只是方便显示,实际中a图大小仅为b图中红色方框大小),一种最形象的方法就是在原始图像中(3*3)放入一个7*7的虚拟栅格,效果如图所示: 然后通过一定的方法(就是这里所说的图像插值方法),利用已知原始图像的像素值来填充7*7的空白栅格,最后再将这7*7的虚拟栅格映射到实际的7*7图像中,即可实现图像的上采样。

最邻近插值

最邻近插值,即在每一个虚拟栅格处寻找离其最近的原始图像像素点,并将它的灰度值赋值给该栅格上的新像素,当对栅格上的所有点赋值完毕后,将虚拟栅格对应的像素值映射到原来指定的实际大小,以得到放大的图像。

那如何寻找离其最近的原始图像像素点?如上图所示,图中紫色实线将图像分为四部分,以左下部分为例,在左下部分中黄色虚线将其分为四小块:假设已知虚拟栅格的坐标为(x, y),则:

  • H = x%1(取出x的小数部分)
  • V = y%1(取出y的小数部分)

对应的像素值有以下几种情况:

①. H < 0.5, V < 0.5, 此时该点距离原始图像的(0, 0)点最近,则虚拟栅格(x, y)的灰度值为 f(0, 0);
②. H < 0.5, V > 0.5, 此时该点距离原始图像的(0, 1)点最近,则虚拟栅格(x, y)的灰度值为 f(0, 1);
③. H > 0.5, V < 0.5, 此时该点距离原始图像的(1, 0)点最近,则虚拟栅格(x, y)的灰度值为 f(1, 0);
④. H > 0.5, V > 0.5, 此时该点距离原始图像的(1, 1)点最近,则虚拟栅格(x, y)的灰度值为 f(1, 1)。
若H、V恰好等于0.5,可以根据上采样效果和实际情况取相应像素点的灰度值。
核心代码如下:

def resize_func1(img_resize, src_img):                          #定义上采样函数
    for i in range(img_resize.shape[0]):
        for j in range(img_resize.shape[1]):
            for k in range(img_resize.shape[2]):
                
                uw = (src_img.shape[0]-1)/(img_resize.shape[0]-1)#上采样比例,式中减1的目的是让栅格虚拟点均匀分布在原始图像中,下同,效果如上图所示
                vw = (src_img.shape[1]-1)/(img_resize.shape[1]-1)
                
                u = (i * uw) % 1                                 #(i * uw)表示将栅格映射到原始图像,并计算栅格虚拟坐标的小数部分
                v = (j * vw) % 1

                if 0 <= u <= 0.5 and 0 <= v <= 0.5:
                    img_resize[i, j][k] = src_img[int((i * uw)/1),   int((j * vw)/1)][k]
                    
                elif 0 <= u <= 0.5 and v > 0.5:
                    img_resize[i, j][k] = src_img[int((i * uw)/1),   int((j * vw)/1)+1][k]
                    
                elif u > 0.5 and 0 <= v <= 0.5:
                    img_resize[i, j][k] = src_img[int((i * uw)/1)+1, int((j * vw)/1)][k]
                    
                else:
                    img_resize[i, j][k] = src_img[int((i * uw)/1)+1, int((j * vw)/1)+1][k]
    return img_resize

代码改进:采用np.round()函数则取原始图像坐标值(np.round()可能不满足四舍五入的进制规则,因为np.round(2.5)=2,向下取整),核心代码如下:

def resize_func2(img_resize, src_img):
    for i in range(img_resize.shape[0]):
        for j in range(img_resize.shape[1]):
            for k in range(img_resize.shape[2]):
                 img_resize[i, j][k] = src_img[int(np.round(i * ((src_img.shape[0]-1) / (img_resize.shape[0]-1)))),
                                               int(np.round(j * ((src_img.shape[0]-1) / (img_resize.shape[0]-1))))][k]
    return img_resize

主函数代码:

def main():
    re_height, re_width, re_channels = 800, 800, 3  #定义放大图像尺寸
    src_img = cv.imread('lenna.png', 1)             #读取原始图像,“1”表示彩色图像(获取彩色图不加“1”也可以获取),”0“表示灰度图像
    height, width, channels = src_img.shape         #获取原始图像属性
    img_resize = np.zeros([re_height,
                           re_width,
                           re_channels],
                          dtype=src_img.dtype, )    #创建一个空矩阵,注意该矩阵中元素类型需与原始图像保持一致
    result1 = resize_func1(img_resize, src_img)
    result2 = resize_func2(img_resize, src_img)

    cv.imshow('src_img', src_img)
    cv.imshow('result1', result1)
    cv.imshow('result2', result2)
    cv.waitKey(0)
    cv.destroyAllWindows()

if __name__ == '__main__':
    main()

上采样插值效果:

双线性插值

尽管最邻近插值速度快,但此种方法实现的图像在高放大倍数的情况下会产生较为明显的棋盘格效应。一种改进之后的插值方法,采用四个最近邻像素点的灰度进行栅格虚拟点灰度值的拟合,这种方法称为双线性插值。

首先先从二维平面坐标说起,若已知坐标(x0, y0)和(x1, y1),要求计算坐标x对应的y值,因为只有两个点,最简单最直观的方法就是利用两点确定一条直线来预测x对应的y的值,因此可以看作是已知直线方程,给出x计算y值,由图中红色方框公式即可求出。

再考虑数字图像对应的坐标系,如上图所示,X、Y轴对应像素点位置坐标,Z轴对应像素点灰度值。已知a,b,c,d四点的位置坐标和灰度值,在给出e点坐标的情况下计算该点的Z坐标(该像素点的灰度值),类比二维平面下的计算方法,计算一个点的值,先找到两个点;而这两个点又可以通过已知的四个点得到,即:4点->2点->1点。同样,这四个点之间最简单的关系就是线性关系,即把这个四个点连起来。然后根据所给e点坐标,在任意一组对边上找到两个点,这两个点确定的一条直线在对应的e点坐标下取得的z坐标值就是该像素点的灰度值,图中该点像素值为f(i+u, j+v)。

将三维坐标系投影到二维平面坐标系,如上图所示,Q点为已知的四个像素点,P点为栅格虚拟点,要求计算P点灰度值,根据上述分析,可得:
根据二维平面中公式,在X方向上做插值:

同理,在Y方向上做插值:

把Y方向插值等式带入X方向插值等式得:

式中分母部分为1,因为原始图像中每两个相邻像素点间的距离是1。
核心代码如下:

def bilinear_interp_func(img_resize,src):
    for i in range(img_resize.shape[0]):
        for j in range(img_resize.shape[1]):
            for k in range(3):
                img_resize_x = i*((src.shape[0]-1)/(img_resize.shape[0]-1))
                img_resize_y = j*((src.shape[1]-1)/(img_resize.shape[1]-1))
                # u = img_resize_y - np.round(img_resize_y)
                # v = img_resize_x - np.round(img_resize_x)
                # if u > 0:
                #     y1 = int(np.round(img_resize_y ))
                #     y2 = y1 + 1
                # else:
                #     y1 = int(np.round(img_resize_y) - 1)
                #     y1 = np.where(y1 < 0, 0, y1)#判断y1 < 0是为了防止y1变成-1,即img_resize_y=0.3时,y1=0。下面的x1同理
                #     y2 = y1 + 1
                #
                # if v > 0:
                #     x1 = int(np.round(img_resize_x ))
                #     x2 = x1 + 1
                # else:
                #     x1 = int(np.round(img_resize_x) - 1)
                #     x1 = np.where(x1<0, 0, x1)
                #     x2 = x1 + 1
                  """上面注释代码与下面四行代码作用一致"""
                x1 = int(np.floor(img_resize_x))#np.floor向下取整,即取出小数部分
                x2 = min(x1 + 1, src.shape[0]-1)#(x1 + 1)可能大于原始图像的最大x坐标511,当(x1 + 1)>511时,就取511
                y1 = int(np.floor(img_resize_y))
                y2 = min(y1 + 1, src.shape[1]-1)

                t1 = (x2 - img_resize_x) * src[x1, y1, k] + (img_resize_x - x1) * src[x2, y1, k]#代入计算公式
                t2 = (x2 - img_resize_x) * src[x1, y2, k] + (img_resize_x - x1) * src[x2, y2, k]
                img_resize[i, j, k] = np.round((y2 - img_resize_y) * t1 + (img_resize_y - y1) * t2)
    return img_resize

主函数代码如下:

def main():
    src = cv.imread('lenna.png')
    img_resize_height, img_resize_width = 800, 800
    img_resize = np.zeros([img_resize_height, img_resize_width, 3], dtype=src.dtype)
    result = bilinear_interp_func(img_resize, src)

    cv.imshow('result',result)
    cv.imshow('src',src)
    cv.waitKey(0)
    cv.destroyAllWindows()

if __name__ == '__main__':
    main()

实现效果如图所示:

两种插值方法对比

左图为最邻近插值上采样结果,右图为双线性插值结果,明显可以看出,左图存在边缘棋盘化的效应,灰度不连续;右图图像更加光滑,但其计算量也随之增加。

参考文章:[1]数字图像处理:第3版/(美)冈萨雷斯(Gonzalez,R.C),(美)伍兹(Woods, R.E.)著.阮秋琦等译
[2]【NumPy】 之常见运算四舍五入、取整、条件选取(np.around、np.floor、np.ceil、np.where)

[Until:2021年1月2日15时,又是一年,能再次遇见你或许是银河赠我的糖,凡是过往,皆为序章。]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值