深度学习之双线性插值(Bilinear interpolation)
1. 什么是插值
Interpolation is a method of constructing new data points within the range of a discrete set of known data points. Image interpolation refers to the“guess”of intensity values at missing locations.
圖片放大是圖像處理中的一個(gè)特別基礎(chǔ)的操作。在幾乎每一個(gè)圖片相關(guān)的項(xiàng)目中,從傳統(tǒng)圖像處理到深度學(xué)習(xí),都有應(yīng)用。生活里,和朋友通過微信傳張圖片,從圖片發(fā)出,到朋友收到圖片,查看圖片,都會(huì)數(shù)次的的改變圖像的尺寸,從而用到這個(gè)算法。但是很少關(guān)注這個(gè)算法的實(shí)現(xiàn)細(xì)節(jié):插值算法是如何工作的。
簡(jiǎn)單來說,插值指利用已知的點(diǎn)來“猜”未知的點(diǎn),圖像領(lǐng)域插值常用在修改圖像尺寸的過程,由舊的圖像矩陣中的點(diǎn)計(jì)算新圖像矩陣中的點(diǎn)并插入,不同的計(jì)算過程就是不同的插值算法。
2.常用的插值算法
插值算法有很多種,常見的插值運(yùn)算包括:
- 最近鄰法(Nearest Interpolation):計(jì)算速度最快,但是效果最差。
- 雙線性插值(Bilinear Interpolation):雙線性插值是用原圖像中4(2*2)個(gè)點(diǎn)計(jì)算新圖像中1個(gè)點(diǎn),效果略遜于雙三次插值,速度比雙三次插值快,屬于一種平衡美,在很多框架中屬于默認(rèn)算法。由于折中的插值效果和運(yùn)算速度,運(yùn)用比較廣泛。
- 雙三次插值(Bicubic interpolation):雙三次插值是用原圖像中16(4*4)個(gè)點(diǎn)計(jì)算新圖像中1個(gè)點(diǎn),效果比較好,但是計(jì)算代價(jià)過大。
3.最近鄰法(Nearest Interpolation)
越是簡(jiǎn)單的模型越適合用來舉例子,我們就舉個(gè)簡(jiǎn)單的圖像:3?33*33?3 的256級(jí)灰度圖。假如圖像的象素矩陣如下圖所示(這個(gè)原始圖把它叫做源圖,Source):
234 38 22 67 44 12 89 65 63這個(gè)矩陣中,元素坐標(biāo)(x,y)是這樣確定的,x從左到右,從0開始,y從上到下,也是從零開始,這是圖象處理中最常用的坐標(biāo)系。
如果想把這副圖放大為 4?44*44?4 大小的圖像,那么該怎么做呢?那么第一步肯定想到的是先把 4?44*44?4 的矩陣先畫出來再說,好了矩陣畫出來了,如下所示,當(dāng)然,矩陣的每個(gè)像素都是未知數(shù),等待著我們?nèi)ヌ畛?#xff08;這個(gè)將要被填充的圖的叫做目標(biāo)圖,Destination):
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?然后要往這個(gè)空的矩陣?yán)锩嫣钪盗?#xff0c;要填的值從哪里來來呢?是從源圖中來,好,先填寫目標(biāo)圖最左上角的象素,坐標(biāo)為(0,0),那么該坐標(biāo)對(duì)應(yīng)源圖中的坐標(biāo)可以由如下公式得出
srcX=dstX?(srcWidth/dstWidth),srcY=dstY?(srcHeight/dstHeight)srcX=dstX* (srcWidth/dstWidth) ,srcY = dstY * (srcHeight/dstHeight)srcX=dstX?(srcWidth/dstWidth),srcY=dstY?(srcHeight/dstHeight)
好了,套用公式,就可以找到對(duì)應(yīng)的原圖的坐標(biāo)了 (0?(3/4),0?(3/4))=>(0?0.75,0?0.75)=>(0,0)(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0)(0?(3/4),0?(3/4))=>(0?0.75,0?0.75)=>(0,0),找到了源圖的對(duì)應(yīng)坐標(biāo),就可以把源圖中坐標(biāo)為(0,0)處的234象素值填進(jìn)去目標(biāo)圖的(0,0)這個(gè)位置了。
接下來,如法炮制,尋找目標(biāo)圖中坐標(biāo)為(1,0)的象素對(duì)應(yīng)源圖中的坐標(biāo),套用公式:
(1?0.75,0?0.75)=>(0.75,0)(1*0.75,0*0.75)=>(0.75,0)(1?0.75,0?0.75)=>(0.75,0) 結(jié)果發(fā)現(xiàn),得到的坐標(biāo)里面竟然有小數(shù),這可怎么辦?計(jì)算機(jī)里的圖像可是數(shù)字圖像,象素就是最小單位了,象素的坐標(biāo)都是整數(shù),從來沒有小數(shù)坐標(biāo)。這時(shí)候采用的一種策略就是采用四舍五入的方法(也可以采用直接舍掉小數(shù)位的方法),把非整數(shù)坐標(biāo)轉(zhuǎn)換成整數(shù),好,那么按照四舍五入的方法就得到坐標(biāo)(1,0),完整的運(yùn)算過程就是這樣的:(1?0.75,0?0.75)=>(0.75,0)=>(1,0)(1*0.75,0*0.75)=>(0.75,0)=>(1,0)(1?0.75,0?0.75)=>(0.75,0)=>(1,0) 那么就可以再填一個(gè)象素到目標(biāo)矩陣中了,同樣是把源圖中坐標(biāo)為(1,0)處的像素值38填入目標(biāo)圖中的坐標(biāo)。
依次填完每個(gè)象素,一幅放大后的圖像就誕生了,像素矩陣如下所示:
234 38 22 22 67 44 12 12 89 65 63 63 89 65 63 63這種放大圖像的方法叫做最臨近插值算法,這是一種最基本、最簡(jiǎn)單的圖像縮放算法,效果也是最不好的,放大后的圖像有很嚴(yán)重的馬賽克,縮小后的圖像有很嚴(yán)重的失真;效果不好的根源就是其簡(jiǎn)單的最臨近插值方法引入了嚴(yán)重的圖像失真,比如,當(dāng)由目標(biāo)圖的坐標(biāo)反推得到的源圖的的坐標(biāo)是一個(gè)浮點(diǎn)數(shù)的時(shí)候,采用了四舍五入的方法,直接采用了和這個(gè)浮點(diǎn)數(shù)最接近的象素的值,這種方法是很不科學(xué)的,當(dāng)推得坐標(biāo)值為 0.75的時(shí)候,不應(yīng)該就簡(jiǎn)單的取為1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目標(biāo)象素值其實(shí)應(yīng)該根據(jù)這個(gè)源圖中虛擬的點(diǎn)四周的四個(gè)真實(shí)的點(diǎn)來按照一定的規(guī)律計(jì)算出來的,這樣才能達(dá)到更好的縮放效果。
4 雙線性插值
雙線型內(nèi)插值算法就是一種比較好的圖像縮放算法,它充分的利用了源圖中虛擬點(diǎn)四周的四個(gè)真實(shí)存在的像素值來共同決定目標(biāo)圖中的一個(gè)像素值,因此縮放效果比簡(jiǎn)單的最鄰近插值要好很多。 雙線性內(nèi)插值算法描述如下:
對(duì)于一個(gè)目的像素,設(shè)置坐標(biāo)通過反向變換得到的浮點(diǎn)坐標(biāo)為(i+u,j+v) (其中i、j均為浮點(diǎn)坐標(biāo)的整數(shù)部分,u、v為浮點(diǎn)坐標(biāo)的小數(shù)部分,是取值[0,1)區(qū)間的浮點(diǎn)數(shù)),則這個(gè)像素得值 f(i+u,j+v) 可由原圖像中坐標(biāo)為 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所對(duì)應(yīng)的周圍四個(gè)像素的值決定,即: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)
其中f(i,j)表示源圖像(i,j)處的的像素值,以此類推。
比如,象剛才的例子,現(xiàn)在假如目標(biāo)圖的象素坐標(biāo)為(1,1),那么反推得到的對(duì)應(yīng)于源圖的坐標(biāo)是(0.75,0.75), 這其實(shí)只是一個(gè)概念上的虛擬象素,實(shí)際在源圖中并不存在這樣一個(gè)象素,那么目標(biāo)圖的象素(1,1)的取值不能夠由這個(gè)虛擬象素來決定,而只能由源圖的這四個(gè)象素共同決定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)離(1,1)要更近一些,那么(1,1)所起的決定作用更大一些,這從公式1中的系數(shù)uv=0.75×0.75就可以體現(xiàn)出來,而(0.75,0.75)離(0,0)最遠(yuǎn),所以(0,0)所起的決定作用就要小一些,公式中系數(shù)為(1-u)(1-v)=0.25×0.25也體現(xiàn)出了這一特點(diǎn)。
計(jì)算方法
首先,在X方向上進(jìn)行兩次線性插值計(jì)算,然后在Y方向上進(jìn)行一次插值計(jì)算。
在圖像處理的時(shí)候,我們先根據(jù)
srcX=dstX?(srcWidth/dstWidth),srcX=dstX* (srcWidth/dstWidth) ,srcX=dstX?(srcWidth/dstWidth),
srcY=dstY?(srcHeight/dstHeight)srcY = dstY * (srcHeight/dstHeight) srcY=dstY?(srcHeight/dstHeight)
來計(jì)算目標(biāo)像素在源圖像中的位置,這里計(jì)算的srcX和srcY一般都是浮點(diǎn)數(shù),比如f(1.2, 3.4)這個(gè)像素點(diǎn)是虛擬存在的,先找到與它臨近的四個(gè)實(shí)際存在的像素點(diǎn) (1,3) (2,3) (1,4) (2,4) 寫成f(i+u,j+v)的形式,則u=0.2,v=0.4, i=1, j=3 在沿著X方向差插值時(shí),f(R1)=u(f(Q21)-f(Q11))+f(Q11) 沿著Y方向同理計(jì)算。 或者,直接整理一步計(jì)算,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) 。
5. 加速以及優(yōu)化策略
單純按照上文實(shí)現(xiàn)的插值算法只能勉強(qiáng)完成插值的功能,速度和效果都不會(huì)理想,在具體代碼實(shí)現(xiàn)的時(shí)候有些小技巧。參考OpenCV源碼以及網(wǎng)上博客整理如下兩點(diǎn):
- 源圖像和目標(biāo)圖像幾何中心的對(duì)齊。
- 將浮點(diǎn)運(yùn)算轉(zhuǎn)換成整數(shù)運(yùn)算
5.1 源圖像和目標(biāo)圖像幾何中心的對(duì)齊
方法:在計(jì)算源圖像的虛擬浮點(diǎn)坐標(biāo)的時(shí)候,一般情況:
srcX=dstX?(srcWidth/dstWidth),srcY=dstY?(srcHeight/dstHeight)srcX=dstX* (srcWidth/dstWidth) ,srcY = dstY * (srcHeight/dstHeight) srcX=dstX?(srcWidth/dstWidth),srcY=dstY?(srcHeight/dstHeight)
中心對(duì)齊(OpenCV也是如此):
SrcX=(dstX+0.5)?(srcWidth/dstWidth)?0.5,SrcY=(dstY+0.5)?(srcHeight/dstHeight)?0.5SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5,SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5SrcX=(dstX+0.5)?(srcWidth/dstWidth)?0.5,SrcY=(dstY+0.5)?(srcHeight/dstHeight)?0.5
問題
下面這張圖是以右上角為坐標(biāo)系原點(diǎn),我們可以發(fā)現(xiàn)最右面的點(diǎn)都會(huì)有概率直接復(fù)制到目標(biāo)圖像中(至少原點(diǎn)肯定是這樣),而且就算不不和原圖像中的點(diǎn)重合,也相當(dāng)于進(jìn)行了1次單線性插值。這樣如果我們采用不用的坐標(biāo)系產(chǎn)生的結(jié)果是不一樣的,而且無論我們采用什么坐標(biāo)系,最左側(cè)和最右側(cè)(最上側(cè)和最下側(cè))的點(diǎn)是不“公平的”,這是第一個(gè)問題。
整體的圖像相對(duì)位置會(huì)發(fā)生變化看下面這張圖,左側(cè)是原圖像(33),右側(cè)是目標(biāo)圖像(55),原圖像的幾何中心點(diǎn)是(1, 1),目標(biāo)圖像的幾何中心點(diǎn)是(2, 2),根據(jù)對(duì)應(yīng)關(guān)系,目標(biāo)圖像的幾何中心點(diǎn)對(duì)應(yīng)的原圖像的位置是(1.2, 1.2),如圖所示,那么問題來了,目標(biāo)圖像的原點(diǎn)(0, 0)點(diǎn)和原始圖像的原點(diǎn)是重合的,但是目標(biāo)圖像的幾何中心點(diǎn)相對(duì)于原始圖像的幾何中心點(diǎn)偏右下,那么整體圖像的位置會(huì)發(fā)生偏移,為什么這樣說,其實(shí)圖像是由1個(gè)個(gè)的像素點(diǎn)組成,單純說1個(gè)像素點(diǎn)是沒有太大的意義的,1個(gè)像素點(diǎn)跟相鄰像素點(diǎn)的值的漸變或者突變形成圖像顏色的漸變或者邊界,所以參與計(jì)算的點(diǎn)相對(duì)都往右下偏移會(huì)產(chǎn)生相對(duì)的位置信息損失。這是第二個(gè)問題。
解決
將公式變形,srcX=dstX?(srcWidth/dstWidth)+0.5?(srcWidth/dstWidth?1)srcX=dstX* (srcWidth/dstWidth)+0.5*(srcWidth/dstWidth-1)srcX=dstX?(srcWidth/dstWidth)+0.5?(srcWidth/dstWidth?1) 相當(dāng)于我們?cè)谠嫉母↑c(diǎn)坐標(biāo)上加上了 0.5?(srcWidth/dstWidth?1)0.5*(srcWidth/dstWidth-1)0.5?(srcWidth/dstWidth?1) 這樣一個(gè)控制因子,這項(xiàng)的符號(hào)可正可負(fù),與 srcWidth/dstWidthsrcWidth/dstWidthsrcWidth/dstWidth 的比值也就是當(dāng)前插值是擴(kuò)大還是縮小圖像有關(guān),有什么作用呢?看一個(gè)例子:假設(shè)源圖像是 3?33*33?3,中心點(diǎn)坐標(biāo)(1,1)目標(biāo)圖像是 9?99*99?9,中心點(diǎn)坐標(biāo)(4,4),我們?cè)谶M(jìn)行插值映射的時(shí)候,盡可能希望均勻的用到源圖像的像素信息,最直觀的就是(4,4)映射到(1,1)現(xiàn)在直接計(jì)算 srcX=4?3/9=1.3333!=1srcX=4*3/9=1.3333!=1srcX=4?3/9=1.3333!=1,也就是我們?cè)诓逯档臅r(shí)候所利用的像素集中在圖像的右下方,而不是均勻分布整個(gè)圖像?,F(xiàn)在考慮中心點(diǎn)對(duì)齊,srcX=(4+0.5)*3/9-0.5=1,剛好滿足我們的要求。
5.2 將浮點(diǎn)運(yùn)算轉(zhuǎn)換成整數(shù)運(yùn)算
參考圖像處理界雙線性插值算法的優(yōu)化
直接進(jìn)行計(jì)算的話,由于計(jì)算的srcX和srcY 都是浮點(diǎn)數(shù),后續(xù)會(huì)進(jìn)行大量的乘法,而圖像數(shù)據(jù)量又大,速度不會(huì)理想,解決思路是:浮點(diǎn)運(yùn)算→→整數(shù)運(yùn)算→→”<<左右移按位運(yùn)算”。
放大的主要對(duì)象是u,v這些浮點(diǎn)數(shù),OpenCV選擇的放大倍數(shù)是2048“如何取這個(gè)合適的放大倍數(shù)呢,要從三個(gè)方面考慮,第一:精度問題,如果這個(gè)數(shù)取得過小,那么經(jīng)過計(jì)算后可能會(huì)導(dǎo)致結(jié)果出現(xiàn)較大的誤差。第二,這個(gè)數(shù)不能太大,太大會(huì)導(dǎo)致計(jì)算過程超過長(zhǎng)整形所能表達(dá)的范圍。第三:速度考慮。假如放大倍數(shù)取為12,那么算式在最后的結(jié)果中應(yīng)該需要除以12*12=144,但是如果取為16,則最后的除數(shù)為16*16=256,這個(gè)數(shù)字好,我們可以用右移來實(shí)現(xiàn),而右移要比普通的整除快多了?!蔽覀兝米笠?1位操作就可以達(dá)到放大目的。
6. 代碼
uchar* dataDst = matDst1.data;int stepDst = matDst1.step;uchar* dataSrc = matSrc.data;int stepSrc = matSrc.step;int iWidthSrc = matSrc.cols;int iHiehgtSrc = matSrc.rows;for (int j = 0; j < matDst1.rows; ++j){float fy = (float)((j + 0.5) * scale_y - 0.5);int sy = cvFloor(fy);fy -= sy;sy = std::min(sy, iHiehgtSrc - 2);sy = std::max(0, sy);short cbufy[2];cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);cbufy[1] = 2048 - cbufy[0];for (int i = 0; i < matDst1.cols; ++i){float fx = (float)((i + 0.5) * scale_x - 0.5);int sx = cvFloor(fx);fx -= sx;if (sx < 0) {fx = 0, sx = 0;}if (sx >= iWidthSrc - 1) {fx = 0, sx = iWidthSrc - 2;}short cbufx[2];cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);cbufx[1] = 2048 - cbufx[0];for (int k = 0; k < matSrc.channels(); ++k){*(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] + *(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] + *(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] + *(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22;}}}cv::imwrite("linear_1.jpg", matDst1);cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1);cv::imwrite("linear_2.jpg", matDst2);參考:
- OpenCV ——雙線性插值(Bilinear interpolation)
- https://blog.csdn.net/qq_37577735/article/details/80041586
總結(jié)
以上是生活随笔為你收集整理的深度学习之双线性插值(Bilinear interpolation)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 溢出植入型木马(后门)的原型实现 作者:
- 下一篇: Java 正则表达式,正则表达式匹配a标