Python下圖片的高斯模糊化的優化


資源下載

#本文PDF版下載

Python下圖片的高斯模糊化的優化(或者單擊我博客園右上角的github小標,找到lab102的W6目錄下即可)

#本文代碼下載

高斯模糊(一維)優化代碼(和本文方法集合部分相同)

前言

在上一篇博文中,我們介紹了高斯模糊的算法,在文末的時候點到了當時我們的方法模糊一張很小的圖要2.82s的時間,這對於很多需要高斯模糊需求的場景而言有着很大的局限性,以及在上一篇介紹的方法中,對於邊緣的問題直接不進行處理了,這導致模糊半徑大的時候圖片四周有很大一塊原圖區,所以在本文中就會對這兩個問題的解決方法進行解決.並且將對在上一篇文章中提到的隨着半徑增加模糊時間是以什么增長方式進行的問題進行簡單的闡釋.

歸納一下本篇所要分享的內容:

 

  •  解決邊緣未模糊問題
  •  高斯模糊速度優化
  •  解釋隨模糊半徑增加所需時間關於平方增長還是線性增長的問題

     上一篇博文>>Python下嘗試實現圖片的高斯模糊化 (傳送門)

 

邊緣問題的解決方案

上次文末用的例子中模糊半徑R和標准差σ都取了3,我們可以通過圖片可以看到很明顯的邊緣沒有被處理,經過我們在上一篇博文的介紹,我們現在都應該知道每個像素點的灰度值或者RGB三通道值都是通過周圍像素得到的,那么邊緣處的像素點該怎么辦呢?它們周圍至少有一邊缺少了參考點,這個時候我們怎么對它們(邊緣)進行模糊化呢?

不處理邊緣的效果

 

         四周未模糊

忽略超出部分影響的方法

首先我們先來觀察一下對於邊緣像素而言,下圖深灰色的為圖像的邊角一部分,而模糊半徑為2,需要模糊所需的像素矩陣范圍我們這里用淺灰色(邊緣外部加上了斜線)來表示:

邊緣忽略處理

 

我們可以看到,以最右下角的像素為例,這個例子中模糊半徑為2,它所需要的矩陣范圍內只有9個格子可以給它參考,而剩下的16個格子都分布在了圖像的外面.那么我們在邊上的格子不可取的時候,我們能做到的就是先用存在的9個格子的像素對它邊緣的像素進行模糊,其他邊緣處的處理方法也是一樣,所以我們先來用代碼來看一下模糊的效果(PS:由於完整代碼過長,所以本篇博文除了最終代碼集合外,只展示對應部分的代碼)

def newrgb(ma,nr,ng,nb,r):#生成新的像素rgb矩陣
    timer[2] = clock() newr = np.empty((sizepic[0],sizepic[1])) newg = np.empty((sizepic[0],sizepic[1])) newb = np.empty((sizepic[0],sizepic[1])) for i in range(0,sizepic[0]): for j in range(0,sizepic[1]): o = 0 for x in range(i-r,i+r+1): p = 0 for y in range(j-r,j+r+1): if x<0 or x >sizepic[0]-1 or y<0 or y>sizepic[1]-1:   #此處是對邊緣的判斷
                        pass
                    else: newr[i][j] += nr[x][y]*ma[o][p] newg[i][j] += ng[x][y]*ma[o][p] newb[i][j] += nb[x][y]*ma[o][p] p += 1 o += 1            
    print("已經計算出新的三通道矩陣,所花時間為{:.3f}s".format(timer[2])) return newr,newg,newb

優化后的效果圖:

處理前后對比

 

             經過邊緣優化與優化前的對比圖

通過上述方法對邊緣進行優化后我們可以發現邊框確實有了模糊的效果,但是畢竟只處理了一部分內部像素,把外部的像素都忽略了,所以整體邊框偏暗,有類似黑邊的存在.那么我們怎么才能夠做到將黑邊去掉呢?這里我們來看看鏡像擴充的方法.

鏡像擴充

(鏡像擴充這個名字是因為我覺得挺像的所以這樣子叫,如果有和其他專業術語重合請跳過這個名字)我之所以叫這種方法鏡像擴充,是因為由於我們的邊緣的參考矩陣超過了圖片范圍,並且忽略超出部分的上種方法也存在了局限性會留有小黑邊(最邊上的參數缺少過多).在這里我們用的這種方法就是把缺少的點都補上來避免黑邊的出現.具體的方法是通過邊緣的點在邊緣外界補上關於邊緣點對稱的點的RGB值/灰度值,就像是里面的數字和邊緣鏡像對稱一樣.而補多少由模糊半徑決定,示意圖如下圖所示:

鏡像擴充示意

 

                第一步填充頂底                                                 第二步填充左右

  • 左圖中我們可以看到頂部(第一行)50117是第三行的數字關於第二行(圖片上邊緣)對稱得到的,同理下邊緣也是.
  • 右圖中我們可以看到最左列0501171是第三列關於第二列(圖片左邊緣)對稱得到的,最右列也是同理
  • (由於四個角上對於中心值而言影響最小,故雖然不是很准確但影響不大)

通過這種方法我們可以有效的避免 上一種方法因為無視邊緣超出部分的影響而造成的邊緣偏暗有黑邊的現象.

我們用代碼來驗證一下我們的這種方法(為了清晰,四周的添加方法單獨列出)

def getrgb(path,r):#得到圖像中各個點像素的RGB三通道值
    timer[0]=clock() pd = p.open(path) sizepic[0] = pd.size[0] sizepic[1] = pd.size[1] nr = np.zeros((sizepic[0],sizepic[1])) ng = np.zeros((sizepic[0],sizepic[1])) nb = np.zeros((sizepic[0],sizepic[1])) for i in range(0,sizepic[0]): for j in range(0,sizepic[1]): nr[i][j] = pd.getpixel((i,j))[0] ng[i][j] = pd.getpixel((i,j))[1] nb[i][j] = pd.getpixel((i,j))[2] #鏡像擴充
    for i in range(1,r+1):#頂部
        nxr = nr[i*2-1] nxg = ng[i*2-1] nxb = nb[i*2-1] nr = np.insert(nr,0,values = nxr ,axis = 0) ng = np.insert(ng,0,values = nxg ,axis = 0) nb = np.insert(nb,0,values = nxb ,axis = 0) for i in range(sizepic[0]+r-1,sizepic[0]-1,-1):#底部
        nxr = nr[i] nxg = ng[i] nxb = nb[i] nr = np.insert(nr,(sizepic[0]+r-1)*2-i,values = nxr ,axis = 0) ng = np.insert(ng,(sizepic[0]+r-1)*2-i,values = nxg ,axis = 0) nb = np.insert(nb,(sizepic[0]+r-1)*2-i,values = nxb ,axis = 0) for i in range(1,r+1):#左側
        nxr = nr[:,i*2-1] nxg = ng[:,i*2-1] nxb = nb[:,i*2-1] nr = np.insert(nr,0,values = nxr ,axis = 1) ng = np.insert(ng,0,values = nxg ,axis = 1) nb = np.insert(nb,0,values = nxb ,axis = 1) for i in range(sizepic[1]+r-1,sizepic[1]-1,-1):#右側
        nxr = nr[:,i] nxg = ng[:,i] nxb = nb[:,i] nr = np.insert(nr,(sizepic[1]+r-1)*2-i,values = nxr ,axis = 1) ng = np.insert(ng,(sizepic[1]+r-1)*2-i,values = nxg ,axis = 1) nb = np.insert(nb,(sizepic[1]+r-1)*2-i,values = nxb ,axis = 1) print("已經得到所有像素的R,G,B的值,所花時間為{:.3f}s".format(clock()-timer[0])) return nr,ng,nb

通過鏡像擴充優化邊緣后的效果圖:

鏡像填充的效果

 

邊緣模糊的總結

  • 忽略超出部分影響
  • 鏡像擴充

這兩種方法,第二種比較適合使用,因為這樣邊緣不會存在黑邊(暗區),兩種法都是有意義的.第一種雖然有黑邊,但是黑邊部分並非是純黑色,所以作為圖片之間的分界也具有一定美觀性並且減少了邊緣部分的大量計算也可以提升模糊速度.這兩種方法具體使用視實際情況而定.

高斯模糊速度優化

我們在上一節的嘗試中,我們最終可以看到最終,我們對實驗室LOGO(一個120x120大小的圖片)進行高斯模糊時,在模糊半徑為3時我們所用的時間2.82s.這個時間太久了,我們用過PS的都知道它的高斯模糊幾乎是瞬時完成的.相比而言,我們上一節使用的方法過於低效,所以在下文中我們對於如何優化模糊速度展開嘗試.

圖片壓縮->模糊->放大

我們可以通過實驗發現發現當圖片壓縮->放大時圖片會丟失細節;而我們的高斯模糊也是一樣,當圖片經過高斯模糊后,細節會丟失,所以對於這兩者而言,都會使圖片失去細節,那么我們來看看實際上這兩者產生效果有沒有大的區別吧.

壓縮對比

 

       直接模糊                         縮小模糊后放大

這里我們的縮小模糊后放大的方法長寬都先縮小為原來的一半,相同的模糊半徑我們可以發現右邊的模糊感更強,即模糊半徑相對更大.所以我么可以知道縮小的比例和模糊半徑的選定有關.(但是這種方法對於時間的縮減有巨大貢獻)直接模糊所花的時間為4.805s,而先縮小(甚至得到的是一個更大模糊的半徑)的圖片,我們只用了2.2s就模糊完畢,縮短了一半多的時間.只是我們需要探索模糊半徑和縮小比例之間的關系.

高斯函數的可分離性

可分離性的意義

 

由於高斯函數具有可分離性所以可以將高斯函數寫作可分離的形式,可以采用可分離濾波的方式來實現模糊加速.分離濾波-指將多維的卷積化成多個一維卷積.所以這里我們要使用的是二維的高斯函數,所以可以分離為:

  • 先沿水平方向對圖像的灰度/RGB卷積一次
  • 對卷積完的矩陣再對鉛垂方向卷積

我們可以發現,一維的高斯函數如下:

一維高斯函數公式

這里x||y指的是x或者y(要分別對x,y進行卷積)
上述一維函數可以對每一個(x,y)坐標進行計算(需要鏡像擴充),所以可以排除邊緣的干擾.(這里由於使用了兩次卷積來代替二維卷積,也大大提高了卷集的速度(在最后會提到速度的變化))

分離性應用於高斯模糊

改進代碼如下:

def matcombine(r,g,b,rd): #模糊矩陣
    summat = 0 timer[1] = clock() ma = np.zeros(2*rd+1) for i in range(0,2*rd+1): ma[i] = (1/(((2*PI)**0.5)*rd))*math.e**(-((i-rd)**2)/(2*(rd**2))) summat += ma[i] ma[0::1] /= summat print("已經計算出高斯函數矩陣,所花時間為{:.3f}s".format(clock()-timer[1])) timer[1] = clock()-timer[1] #blur
    ner,neg,neb = np.zeros_like(r),np.zeros_like(g),np.zeros_like(b) u,p,q = 0,0,0 #y向模糊
    timer[2] = clock() for i in range(rd+1,sizepic[0]+rd-1): for j in range(rd+1,sizepic[1]+rd-1): u += r[j-rd:j+rd+1:1,i]*ma[0::1] p += g[j-rd:j+rd+1:1,i]*ma[0::1] q += b[j-rd:j+rd+1:1,i]*ma[0::1] ner[j][i],neg[j][i],neb[j][i] = u.sum(0),p.sum(0),q.sum(0) u,p,q = 0,0,0 #x向模糊
    for i in range(rd+1,sizepic[0]+rd-1): for j in range(rd+1,sizepic[1]+rd-1): u += ner[i,j-rd:j+rd+1:1]*ma[0::1] p += neg[i,j-rd:j+rd+1:1]*ma[0::1] q += neb[i,j-rd:j+rd+1:1]*ma[0::1] ner[i][j] = u.sum(0) neg[i][j] = p.sum(0) neb[i][j] = q.sum(0) u,p,q = 0,0,0 print("已經完成生成,所花時間為{:.3f}s".format(clock()-timer[2])) timer[2] = clock()-timer[2] return ner,neg,neb

優化結果的截圖:

優化后截圖

優化效果:(半徑為10)

一維高斯優化效果

                半徑為10的優化效果

全文方法總結集合

全文代碼展示(這里不集成先縮小->模糊->放大的方式)

#一維高斯模糊 -xlxw
from PIL import Image as p import numpy as np from time import clock import math #define
sizepic = [0,0] timer = [0,0,0,0] PI = math.pi def getrgb(path,r):#得到圖像中各個點像素的RGB三通道值
    timer[0] = clock() pd = p.open(path) sizepic[0],sizepic[1] = pd.size[0],pd.size[1] nr = np.zeros((sizepic[0],sizepic[1])) ng = np.zeros((sizepic[0],sizepic[1])) nb = np.zeros((sizepic[0],sizepic[1])) for i in range(0,sizepic[0]): for j in range(0,sizepic[1]): nr[i][j] = pd.getpixel((i,j))[0] ng[i][j] = pd.getpixel((i,j))[1] nb[i][j] = pd.getpixel((i,j))[2] #鏡像擴充
    for i in range(1,r+1):#頂部
        nxr = nr[i*2-1] nxg = ng[i*2-1] nxb = nb[i*2-1] nr = np.insert(nr,0,values = nxr ,axis = 0) ng = np.insert(ng,0,values = nxg ,axis = 0) nb = np.insert(nb,0,values = nxb ,axis = 0) for i in range(sizepic[0]+r-1,sizepic[0]-1,-1):#底部
        nxr = nr[i] nxg = ng[i] nxb = nb[i] nr = np.insert(nr,(sizepic[0]+r-1)*2-i,values = nxr ,axis = 0) ng = np.insert(ng,(sizepic[0]+r-1)*2-i,values = nxg ,axis = 0) nb = np.insert(nb,(sizepic[0]+r-1)*2-i,values = nxb ,axis = 0) for i in range(1,r+1):#左側
        nxr = nr[:,i*2-1] nxg = ng[:,i*2-1] nxb = nb[:,i*2-1] nr = np.insert(nr,0,values = nxr ,axis = 1) ng = np.insert(ng,0,values = nxg ,axis = 1) nb = np.insert(nb,0,values = nxb ,axis = 1) for i in range(sizepic[1]+r-1,sizepic[1]-1,-1):#右側
        nxr = nr[:,i] nxg = ng[:,i] nxb = nb[:,i] nr = np.insert(nr,(sizepic[1]+r-1)*2-i,values = nxr ,axis = 1) ng = np.insert(ng,(sizepic[1]+r-1)*2-i,values = nxg ,axis = 1) nb = np.insert(nb,(sizepic[1]+r-1)*2-i,values = nxb ,axis = 1) print("已經得到所有像素的R,G,B的值,所花時間為{:.3f}s".format(clock()-timer[0])) timer[0] = clock()-timer[0] return nr,ng,nb def matcombine(r,g,b,rd): #模糊矩陣
    summat = 0 timer[1] = clock() ma = np.zeros(2*rd+1) for i in range(0,2*rd+1): ma[i] = (1/(((2*PI)**0.5)*rd))*math.e**(-((i-rd)**2)/(2*(rd**2))) summat += ma[i] ma[0::1] /= summat print("已經計算出高斯函數矩陣,所花時間為{:.3f}s".format(clock()-timer[1])) timer[1] = clock()-timer[1] #blur
    ner,neg,neb = np.zeros_like(r),np.zeros_like(g),np.zeros_like(b) u,p,q = 0,0,0 #y向模糊
    timer[2] = clock() for i in range(rd+1,sizepic[0]+rd-1): for j in range(rd+1,sizepic[1]+rd-1): u += r[j-rd:j+rd+1:1,i]*ma[0::1] p += g[j-rd:j+rd+1:1,i]*ma[0::1] q += b[j-rd:j+rd+1:1,i]*ma[0::1] ner[j][i],neg[j][i],neb[j][i] = u.sum(0),p.sum(0),q.sum(0) u,p,q = 0,0,0 #x向模糊
    for i in range(rd+1,sizepic[0]+rd-1): for j in range(rd+1,sizepic[1]+rd-1): u += ner[i,j-rd:j+rd+1:1]*ma[0::1] p += neg[i,j-rd:j+rd+1:1]*ma[0::1] q += neb[i,j-rd:j+rd+1:1]*ma[0::1] ner[i][j] = u.sum(0) neg[i][j] = p.sum(0) neb[i][j] = q.sum(0) u,p,q = 0,0,0 print("已經完成模糊,所花時間為{:.3f}s".format(clock()-timer[2])) timer[2] = clock()-timer[2] return ner,neg,neb def cpic(r,g,b,path,rd):#圖片輸出
    timer[3] = clock() pd = p.new("RGB",(sizepic[0]-rd-1,sizepic[1]-rd-1)) for i in range(rd+1,sizepic[0]): for j in range(rd+1,sizepic[1]): pd.putpixel((i-rd-1,j-rd-1),(int(r[i][j]),int(g[i][j]),int(b[i][j]))) print("已經完成生成,所花時間為{:.3f}s".format(clock() - timer[3])) timer[3] = clock()-timer[3] print("正在導出圖片..") pd.save("blurred.jpg") def main(): rd = eval(input("請輸入模糊的半徑:")) path = input("請輸入圖片的地址(包括后綴):") nr,ng,nb = getrgb(path,rd) nr,ng,nb = matcombine(nr,ng,nb,rd) cpic(nr,ng,nb,path,rd) print("{} - >> {}".format(path.split('/')[-1],"blurred.jpg")) print("總計耗時:{:.3f}s,感謝您的使用.".format(timer[0]+timer[1]+timer[2]+timer[3])) main()

闡釋高斯模糊運算時間的增長方式

會出現兩種增長方式就是因為前文中用到的用到的兩種情況(二維高斯函數和一維高斯函數)(這里不考慮逼近得到的圖像)
首先我們來分析二維高斯函數:

  • 模糊的半徑為x,那么每個像素點需要運算的次數為(2Xx+1)^2次,那么設圖像大小為aXb,則aXbX(2Xx+1)^2次運算

那么一維函數會是怎么樣的結果,讓我們看下面:

  • 模糊的半徑為x,那么每個像素點需要運算的次數為(2Xx+1)次,那么設圖像大小為aXb,則進行了aXbX(2Xx+1)次運算

模糊方法比較

           二維高斯函數運算次數圖像                                          一維高斯函數運算次數圖像

 

 

 其他的快速高斯模糊的辦法

當模糊半徑為一很大的數值時,我們可以發現高斯二維函數對於離中心點越遠的權重越小,所以當到達了某種數量級后可以忽略后方范圍的點對於中心像素點的影響

  • 通過傅立葉變換與高斯模糊函數結合能大量減少卷積的時間
  • 通過多次均值模糊來達到高斯模糊的效果
  • 可參考文獻:[Fastest Gaussian Blur (in linear time)]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM