Android磨皮算法的实现 renderScript实现表面模糊

  renderScript实现简单的图片处理效果,,这一篇继续介绍一些常用的图片处理算法。

待处理图片

Android磨皮算法的实现 renderScript实现表面模糊_第1张图片

实现模糊效果:

Android磨皮算法的实现 renderScript实现表面模糊_第2张图片

这个效果是我看过各种实例中用过最多的一种,或许是由于模糊效果的常用性,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;iif(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,下面标识模糊力度。
下面用这套算法进行试验: 
第一张对比图:
 
  
 
  
第二张对比图:
 
 

第三张对比图:

 Android磨皮算法的实现 renderScript实现表面模糊_第3张图片


总体来说实现了表面模糊。查阅资料发现商用的磨皮算法要分多次处理,表面模糊是其中最重要的一步保边滤波的一种实现方式,一般流程为:

1,保边滤波

2,肤色检测

3,图像融合

4,锐化

完整磨皮流程及其他的保边滤波实现以后有机会再说。


项目源码地址:[email protected]:gediseer/RenderScript.git

 

你可能感兴趣的:(renderScript)