Android磨皮算法的實現 renderScript實現表面模糊

  renderScript實現簡單的圖片處理效果,,這一篇繼續介紹一些常用的圖片處理算法。

待處理圖片


實現模糊效果:


這個效果是我看過各種實例中用過最多的一種,或許是由於模糊效果的常用性,Android提供了封裝好的api可以直接調用:

Bitmap overlay = BitmapFactory.decodeResource(getResources(),R.drawable.scene);
RenderScript rs = RenderScript.create(MainActivity.this);
Allocation overlayAlloc = Allocation.createFromBitmap(rs, overlay);//解析bitmap到allocation
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs,overlayAlloc.getElement());//創建模糊效果工具類,相當於BlurRS
blur.setInput(overlayAlloc);
blur.setRadius(6);//設置模糊力度
blur.forEach(overlayAlloc);//調用腳本中的核心算法
overlayAlloc.copyTo(overlay);//輸出結果到bitmap
view.setBackground(new BitmapDrawable(getResources(),overlay));
rs.destroy();

 
系統提供的模糊算法在調用上與我們寫的其他圖像處理算法很像,也就是說通過api調用可以省去編寫rs腳本的麻煩。Rs腳本和之前的文章寫法差不多,也就是算法上對當前像素的取值要參考以當前像素爲中心,以r爲半徑的區域內所有像素點的平均值.
平均值是怎麼計算出來的呢?最簡單的一種,直接算術平均,把r區域內的所有臨近點加起來一除,得到的就是中間點的像素值,這樣最終效果較差,因爲圖像大多是連續的,越靠近區域中間像素關聯越大,理應對中心像素產生更大的影響,越遠離區域中心,對中心點的影響應該越小,因此,應該採用加權平均更爲合理。
有一種加權平均參照高斯分佈,以x軸爲距中心點距離,y軸爲權重,可以用二維高斯函數來建立數學模型:
 
 
                                               (圖片來自百度圖片)
 
公式:



那麼在實際的計算中,將r區域內的像素點座標x,y代入公式即可得到其應賦予的權值。
這種模糊算法就叫做高斯模糊。

模糊算法的具體實現比較多,網上有不少博客,這裏就不再重現。
 
觀察上面的模糊算法讓人像臉部雀斑淡化,和膚色接近,有一種磨皮的效果,如果在處理圖像的時候只處理這些較爲平坦的區域,如臉部,額頭,而對眼部,眉毛等細節多的地方保留,是不是就可以實現磨皮效果了呢?
我們在設計模糊算法時,可以對r區域內的不同位置的像素點賦予不同的權值(高斯模糊),那麼要想保留細節,是否可以根據當前中心點與周圍像素點顏色的差值賦予周圍像素點不同大小的權重呢,根據細節多的地方模糊效果小,細節少的地方模糊效果大的原則,我們爲與中心像素點顏色差值大的像素點賦予低權值,對於中心點顏色差異小的像素點賦予高權值:
臉部:因爲是平坦區域,周圍顏色相差小,權值賦值大,對中心點顏色影響大,經過計算後該區域顏色逐漸趨於一致
眼部睫毛:是崎嶇區域,睫毛與周圍皮膚色差極大,給周圍像素點的權值極小,基本上不會對中心區域(睫毛黑)造成影響,因此黑色得以保留。
以此建立數學模型:
 
設中心點顏色值爲X,模糊區域半徑爲r,周圍像素點爲X(i),模糊力度爲Y,那麼中心像素點計算結果是:
 
(圖片來自網絡)

因爲每個像素點有四個分量,a,r,g,b分別代表透明度,紅,綠,藍,因此要對四個分量分別應用上面的公式。
可以看到,該方法複雜度與r有關,且成平方關係,因此耗時還是挺長的。以後有優化的餘地。
 
保邊模糊算法rs腳本:
 
#pragma version(1)

// The java_package_name directive needs to use your Activity's package path
#pragma rs java_package_name(com.example.administrator.rs)

// Store the input allocation
rs_allocation inputAllocation;

int radius=0;
float weight=0;//uchar4 result=0;

uchar4 __attribute__((kernel)) magnify(uchar4 in, int x, int y) {

int total=(2*radius+1)*(2*radius+1);
float Rdenominator=0;
float Gdenominator=0;
float Bdenominator=0;
float Adenominator=0;
float Rmolecular=0;
float Gmolecular=0;
float Bmolecular=0;
float Amolecular=0;
int dx=-radius;
int dy=-radius-1;

uchar4 cur= rsGetElementAt_uchar4(inputAllocation, x, y);

for(int i=0;i<total;i++){
if(i%(2*radius+1)==0){
dx=-radius;
dy++;
}
 
uchar4 sam= rsGetElementAt_uchar4(inputAllocation, x+dx, y+dy);

float rRatio=fmax((float)0,1-abs(sam.r-cur.r)/weight);
float gRatio=fmax((float)0,1-abs(sam.g-cur.g)/weight);
float bRatio=fmax((float)0,1-abs(sam.b-cur.b)/weight);
float aRatio=fmax((float)0,1-abs(sam.a-cur.a)/weight);

Rmolecular+=(rRatio*sam.r);
Gmolecular+=(gRatio*sam.g);
Bmolecular+=(bRatio*sam.b);
Amolecular+=(aRatio*sam.a);

Rdenominator+=rRatio;
Gdenominator+=gRatio;
Bdenominator+=bRatio;
Adenominator+=aRatio;

dx++;
}//for

result.r=min((float)255,Rmolecular/Rdenominator);
result.g=min((float)255,Gmolecular/Gdenominator);
result.b=min((float)255,Bmolecular/Bdenominator);
result.a=min((float)255,Amolecular/Adenominator);;
return result;
}
void init(){
}
 
中間的兩個seekBar上邊標識r,下面標識模糊力度。
下面用這套算法進行試驗: 
第一張對比圖:


第二張對比圖:
 
 

第三張對比圖:

 


總體來說實現了表面模糊。查閱資料發現商用的磨皮算法要分多次處理,表面模糊是其中最重要的一步保邊濾波的一種實現方式,一般流程爲:

1,保邊濾波

2,膚色檢測

3,圖像融合

4,銳化

完整磨皮流程及其他的保邊濾波實現以後有機會再說。


項目源碼地址:[email protected]:gediseer/RenderScript.git

 

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