圖片高斯模糊效果簡單優化

       模糊背景現在已經很流行了,固定圖片的模糊可以讓設計師處理,那麼動態模糊,或者每個用戶個人主頁用自己頭像的圖片模糊做背景,這就需要我們程序員通過代碼來實現了,實現的方案在github上已經有比較完善和得到大家公認的開源項目:

BlurEffectForAndroidDesignBlurry 。前者主要通過判斷系統版本號,大於API17的時候調系統的API renderscript處理,否則採用Java實現的方式對一個bitmap對象進行高斯模糊,把該模塊封裝成一個工具類。後者則進一步封裝一個功能強大,使用簡單的庫,有點類似UIL , Volley ,Picasso這些圖片加載庫,在內部已經封裝了線程池,通過現場對圖片進行高斯模糊,並且通過傳入具體view對象,來直接在回調結果裏把高斯模糊後的bitmap加載到你需要的view控件上,主要用法如下:

Blurry.with(context).radius(25).sampling(2).onto((ViewGroup) rootView);
Blurry.with(context).capture(view).into(imageView);
 

      對於第二種在github過千star的項目,不用過多介紹,直接拿下來用,基本上也沒多大問題了,現在主要對第一種方式,結合最近做的一個小項目來分享一點經驗,也算是自己的學習筆記。項目的需求是獲取一張相冊的圖片,加載到界面上,並且在界面上有一個拖動的滑條,動態來改變模糊半徑值,結果顯示出來。


       首先是renderscript使用有API版本的限制,必須API 17(4.2)或者以上才能用,以前項目通過第一種方式(BlurEffectForAndroidDesign)把Blur.class工具類拿過來用的時候,發現個別機器達到API 17但並不能正常使用renderscript的方法,當時沒想太多,直接把判斷去了,全部統一用Java的實現方式,確實可以把當時的測試機都全部正常跑通了。最近又新的需求搞這塊,但發現Java的實現方式性能不佳,處理一個圖片往往耗時很嚴重(機器配置不錯的情況下),尤其大圖和模糊半徑大的時候,耗時5-6秒到10秒都有的,以前都是很快的,耗時都是幾十到一百多毫秒,所以沒有留意這個問題。其實原因只是以前這個需求調用這個Blur工具類的地方只有用戶個人主頁的頭像,而當時頭像的尺寸比較小,所以處理起來非常快。而現在是選擇相冊的圖片直接處理,這個差別就非常大了,照片都是長寬幾千,千萬級別像素的圖片,跟以前長寬只有幾百像素的頭像相比,像素點只要相冊圖片的百分之一,時間自然就大大縮小了。


     這裏穿插一下高斯模糊的原理,簡單介紹一下(別的地方copy一個簡單介紹過來);

"模糊"的算法有很多種,其中有一種叫做"高斯模糊"(Gaussian Blur)。它將正態分佈(又名"高斯分佈")用於圖像處理。"高斯模糊"的算法,本質上它是一種數據平滑技術(data smoothing),適用於多個場合,圖像處理恰好提供了一個直觀的應用實例。


    所謂"模糊",可以理解成每一個像素都取周邊像素的平均值。"中間點"取"周圍點"的平均值,就會變成1。在數值上,這是一種"平滑化"。在圖形上,就相當於產生"模糊"效果,"中間點"失去細節。模糊半徑越大,圖像就越模糊。從數值角度看,就是數值越平滑。簡單來說,高斯的基本原理或者說核心思想就是每個點,取周邊的點的均值,達到平滑效果,實際上實現和算法,涉及到權重等問題,這裏就不展開了,這就是爲什麼同樣的圖片,不同實現方法性能差異很大。


   瞭解了高斯模糊的基本原理,至少有兩點是可以肯定的是,一是圖片越大(指的是長和寬或者說像素點數量)越耗時;二是,模糊半徑越大越耗時,因爲模糊半徑越大,意味着取周邊的點更多,計算的時間也是越多,而且這個增加不是線性的,據說是指數上升,簡單理解就是,如上圖,模糊半徑1PX的時候,計算每個點取周邊8個點,但模糊半徑2PX的時候,需求取周邊24個點(大概理解如此,沒有實際驗證具體數值)。實際測試打印的執行時間也是符合這兩點。


      回到我們的需求,要加載大圖,而且還是動態改變模糊半徑顯示到UI上,那麼就要求我們對圖片的模糊處理耗時控制到一定範圍,比如200毫秒以內,每200毫秒刷新UI,加載新的模糊後的圖片,時間越短越流暢平滑,當然對高斯處理的耗時也越嚴格了。首先是方法的選擇,對比發現同樣的圖片和同樣模糊半徑下,renderscript的性能要遠遠高於Java實現的那個方法,renderscript的源碼沒有去細看,沒法兼容到4.2系統以下那肯定不是簡單的純Java實現的方式,必然是有依賴到底層的API的。在這裏有兩種方式處理兼容問題:一是判斷系統版本,高版本調renderscript,低版本還是調Java的方法;二是直接加入support v8的包。


      手上的機器都是5.0+的,性能也不差,包括MX5和谷歌親兒子,當即使用性能不錯的renderscript去處理大圖或者模糊半徑比較大的時候,仍然非常耗時(耗時超過500毫秒就別想滑動來在UI上實現"實時"顯示模糊效果了,延遲非常明顯,加入每次處理都耗時500毫秒,用戶第一次滑動到半徑5的位置,半秒後滑動到半徑10的位置,這時候才顯示出模糊半徑5的圖片,滯後非常明顯的,200毫秒以內還能接受,真正實時的話,估計難以實現了。。。只能壓縮模糊處理的時間,把延遲滯後時間不斷縮短來達到效果);另外一個問題就是renderscript只支持0-25PX像素的模糊,不支持25像素以上的模糊處理。


 解決方案其實也非常簡單,那就是縮小圖片。 平時我們對圖片縮小,必然會帶來很明顯的清晰度的損失,當高斯模糊本身的目的就是要實現模糊的效果,因此實際上的效果差別不大,幾乎可以忽略。

例如需求:圖片原圖大小500*1000,進行10個像素的高斯模糊,顯示到500*1000的view上。

方法1:不縮放,直接原圖進行10像素模糊處理,加載出來。

方法2:圖片長寬縮小到原來一半,5個像素模糊,放大處理後的250*500的圖到view上。

實測的效果就是,顯示效果差別非常小,但耗時差別非常大。 方法2不但需要模糊的像素點縮小到原來的25%(長寬減半),而且模糊半徑也10縮小到5了,長寬縮小一半,模糊半徑也應該縮小一半,這個還是很好理解的。半徑10變成5,每個點計算均值的參與的周邊的像素點,又10*10下降到5*5(粗略算法姑且這樣認爲),整個時間就大大縮小了。


對於另外一個問題renderscript不支持25像素以上的模糊,解決方案同樣是縮小圖片,如果需求50像素的模糊,圖片長寬縮小一半,再直接進行25像素模糊,放大回去效果跟原圖直接50像素模糊幾乎一樣的,時間就天差地別了。


簡單總結來說,無非就是大概知道高斯模糊的原理是對像素點取周邊像素計算均值達到平滑效果,像素點越多(長寬越大)越耗時,模糊半徑越大也越耗時,那麼解決方案就是縮小圖片的長寬或者模糊半徑。而恰好,縮小模糊半徑也是需要對應縮小對應的圖片。因此實際上對耗時非常嚴格的時候,可以通過循環判斷模糊半徑R>X的時候,不斷縮小圖片來實現,至於X取值25還是更小,根據項目實際場景來決定,實測效果就是X取10真的比25要快得多。。。


應用到實際項目需要注意的地方:

1.針對我上面的需求,滑條可以滑動到模糊半徑R=0,1,2,3等半徑小的值的情況,也就是需要顯示完全清晰或者非常清晰(模糊半徑1,2,3)的情況,因爲需要控制好圖片不能縮小得太小,或者乾脆判斷R<3的時候,不縮小圖片,因爲R很小的時候,速度還是很快的。如果業務需求R是固定值,比如10或者更大,那圖片可以縮小好幾倍都幾乎沒有效果上的影響。


2.imageview加載了模糊半徑=10的bitmap1後,間隔200毫秒再加載模糊半徑=20的bitmap2的過渡問題。可以用TransitionDrawable來實現平滑過渡,順便學了一個新的API哈,高手忽略吧,當學習筆記。。。但需要注意的是TransitionDrawable的內存溢出問題,這裏不多說了,貼兩個鏈接,大家看看就明白了。



上一個效果圖,壓縮的厲害失真了。。。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章