在图像处理中,插值算法应用广泛,但本渣能力有限,只能阐述基于图像缩放的简单插值算法
首先定义一个3*3的256级灰度图像(三通道的彩色图像与之类似,只不过要在每个通道都计算一次)
原图像的像素矩阵为:
[ 234 38 22 67 44 12 89 65 63 ] (1) \left[ \begin{matrix} 234 & 38 & 22 \\ 67 & 44 & 12 \\ 89& 65 & 63 \end{matrix}\right] \tag{1} ⎣⎡2346789384465221263⎦⎤(1)
在图像处理中常用的坐标系定义方式为:
x从左到右,从0开始;y从上到下,从0开始
如果把原图像放大为4*4大小的目标图像,则初始矩阵为:
[ x x x x x x x x x x x x x x x x ] (2) \left[ \begin{matrix} x & x & x&x \\ x & x & x&x \\ x& x& x&x\\ x&x&x&x \end{matrix}\right] \tag{2} ⎣⎢⎢⎡xxxxxxxxxxxxxxxx⎦⎥⎥⎤(2)
首先确定目标图像最上角的像素值,是通过四舍五入的方法求取目标图像的坐标对应的原图像的坐标,然后原图像在该点的像素值直接作为目标图像在该点的像素值,进而把该矩阵中的每一个元素求解出来。具体四舍五入的方法以及像素求取公式如下:
f ( d s t X , d s t Y ) = h ( d s t X s r c W i d t h d s t W i d t h , d s t Y s r c H e i g h t d s t H e i g h t ) \begin{array}{c} f(dst_{X}, dst_{Y}) = h(\frac{dst_{X}src_{Width}} {dst_{Width}}, \frac{dst_{Y}src_{Height}} {dst_{Height}}) \end{array} f(dstX,dstY)=h(dstWidthdstXsrcWidth,dstHeightdstYsrcHeight)
f ( 0 , 0 ) = h ( 0 , 0 ) f ( 0 , 1 ) = h ( 0 , 0.75 ) = h ( 0 , 1 ) f ( 0 , 2 ) = h ( 0 , 1.50 ) = h ( 0 , 2 ) f ( 0 , 3 ) = h ( 0 , 2.25 ) = h ( 0 , 2 ) . . . \begin{array}{c} f(0,0)=h(0,0) \ f(0,1)=h(0,0.75)=h(0,1) \ f(0,2)=h(0,1.50)=h(0,2) \ f(0,3)=h(0,2.25)=h(0,2) \ ...\ \end{array} f(0,0)=h(0,0) f(0,1)=h(0,0.75)=h(0,1) f(0,2)=h(0,1.50)=h(0,2) f(0,3)=h(0,2.25)=h(0,2) ...
最终结果为:
[ 234 38 22 22 67 44 12 12 89 65 63 63 89 65 63 63 ] (3) \left[ \begin{matrix} 234 & 38 & 22&22\\ 67& 44 & 12&12 \\ 89& 65& 63&63\\ 89&65&63&63 \end{matrix}\right] \tag{3} ⎣⎢⎢⎡234678989384465652212636322126363⎦⎥⎥⎤(3)
注:计算机里的图像是数字图像,像素是其最小单位,不会出现小数,所以必须将小数变成整数,这里采用四舍五入的整数近似。
总结:这是一种最基本的插值方法,效果不是最好的,对于放大的图像有很严重的失真,其根本原因就是直接采取四舍五入的方法得到目标图像的坐标。
**前言:**相对于最近邻插值算法,它充分利用了原图像中虚拟点(即通过目标图像直接求出来的原图像的坐标),四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最近邻插值要好很多。
算法:
对于一个目标像素,设置坐标通过反向变换得到的浮点坐标 ( i + u , j + v ) \begin{array}{l}(i+u,j+v)\end{array} (i+u,j+v),其中(i,j)为浮点坐标的整数部分,u,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 ) v f ( i , j + 1 ) + u ( 1 − v ) f ( i = 1 , j ) + u v f ( i + 1 , j + 1 ) \begin{array}{l} 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) \end{array} 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)
从上面的表达式很容易发现,如果有一个虚拟点为(0.75,0.75),它离(1,1)更近,那么(1,1)点对应的像素值所起的决定性作用更大一些,uv=0.750.75,权重更大,而(0,0)离(0.75,0.75)远,故(1-u)(1-v)=0.250.25,权值较小。
虚拟点的计算公式为:
( d s t X , d s t Y ) = ( d s t X s r c W i d t h d s t W i d t h , d s t Y s r c H e i g h t d s t H e i g h t ) \begin{array}{c} (dst_{X}, dst_{Y}) = (\frac{dst_{X}src_{Width}} {dst_{Width}}, \frac{dst_{Y}src_{Height}} {dst_{Height}}) \end{array} (dstX,dstY)=(dstWidthdstXsrcWidth,dstHeightdstYsrcHeight)
而对于双线性插值公式的作用与上面直接求解的方法殊途同归,是连续公式的离散化应用,具体的原理可参考这里
至此,双线性插值法的核心介绍完毕,但是单纯按照上文实现的插值算法只能勉强完成插值的功能,速度和效果都不会理想,在具体代码实现的时候有些小技巧。参考OpenCV源码以及网上博客整理如下两点:
源图像和目标图像几何中心的对齐。
将浮点运算转换成整数运算
由于笔者水平有限,该部分内容尚未完全理解,故给出参考博客
分别将原图像的宽高变为原来的0.5倍和1.5倍,观察输出结果
import cv2
img = cv2.imread('D:/Mechine_learning_data/lena512.bmp',cv2.IMREAD_GRAYSCALE)
img1 = cv2.resize(img,dsize=None,fx=0.5,fy=0.5,interpolation = cv2.INTER_NEAREST)
img2 = cv2.resize(img,dsize=None,fx=0.5,fy=0.5,interpolation = cv2.INTER_LINEAR)
img3 = cv2.resize(img,dsize=None,fx=1.5,fy=1.5,interpolation = cv2.INTER_NEAREST)
img4 = cv2.resize(img,dsize=None,fx=1.5,fy=1.5,interpolation = cv2.INTER_LINEAR)
img5 = cv2.resize(img,dsize=None,fx=1.5,fy=1.5,interpolation = cv2.INTER_CUBIC)
print('Smaller_image_nearest:',img1.shape)
print('Smaller_image_linear:',img2.shape)
print('Bigger_image_neraest:',img3.shape)
print('Bigger_image_linear:',img4.shape)
print('Bigger_image_CUBIC:',img5.shape)
print('Original Dimensions:',img.shape)
输出结果为:
Original Dimensions: (512, 512)
Smaller_image_nearest: (256, 256)
Smaller_image_linear: (256, 256)
Bigger_image_neraest: (768, 768)
Bigger_image_linear: (768, 768)
Bigger_image_CUBIC: (768, 768)
进一步显示图像信息,由于我们对比这几种方法的效果只是在图像放大的时候更明显,因为放大图像才需要插入新值,所以只对比放大图像的信息:
cv2.imshow("Bigger_neraest",img3)
cv2.imshow("Bigger_linear",img4)
cv2.imshow("Bigger_CUBIC",img5)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果分别为:
注:最后一种方法是基于4x4像素邻域的3次插值法,它的原理与双线性内插值算法相似。但是效果会更好一些,所以拿来做个比较。
通过观察可以发现,最近邻插值的放大图像,它的边缘特征以及很明显的出现了失真现象,即帽子的边沿出现像素块,效果是最差的,而4*4像素邻域的3次插值法效果更好一些,但是算法也相对更复杂,所以经常用到的便是双线性内插值算法
The End,Opencv的插值算法还有很多,读者可以自行阅读这篇博客