通过RS实现美颜功能
前言
最近在做一个相机APP,通过OpenGL实现了实时美颜功能,但是网上很难找到通过RS实现美颜的例子,因为相机预览通过OpenGL实现美颜后,用户点击拍照后的数据依然是原始数据,并没有美颜,所以我通过同样的算法用RS实现了一遍。有需要的同学可以参考一下
Java调用代码
public static Bitmap beauty(Context context, Bitmap inBitmap) {
// Creates a RS context.
RenderScript mRS = RenderScript.create(context);
// Creates the input Allocation and copies all Bitmap contents into it.
Allocation inAllocation = Allocation.createFromBitmap(mRS, inBitmap);
// Defines the output Type, which will be a RGBA pixel.
// The Allocation will be composed by four unsigned chars (0-255) for each pixel,
// so that R-G-B-A values can be stored.
// It is necessary to use a Type-based approach whenever there is a multi-dimensional sizing (X,Y).
int bitmapWidth = inBitmap.getWidth();
int bitmapHeight = inBitmap.getHeight();
Type.Builder outType = new Type.Builder(mRS, Element.RGBA_8888(mRS)).setX(bitmapWidth).setY(bitmapHeight);
// Creates the output Allocation wherein to store the conversion result.
Allocation outAllocation = Allocation.createTyped(mRS, outType.create(), Allocation.USAGE_SCRIPT);
// Creates the conversion script wrapper.
ScriptC_beauty processScript = new ScriptC_beauty(mRS);
// Binds the inAllocation variable with the actual Allocation.
processScript.set_inAllocation(inAllocation);
// Performs the conversion. RS kernel will use outAllocation size for its iterations.
processScript.forEach_beauty(outAllocation);
// Creates output Bitmap, matching input one size.
Bitmap outBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, inBitmap.getConfig());
// Copy calculation result to the output Bitmap.
outAllocation.copyTo(outBitmap);
mRS.destroy();
return outBitmap;
}
RenderScript代码
// Needed directive for RS to work
#pragma version(1)
// Change java_package_name directive to match your Activity's package path
#pragma rs java_package_name(com.dong.opencamera)
rs_allocation inAllocation;
static const float beautyParam = 0.5f;
static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f};
static float hardLight(float color) {
if (color <= 0.5) {
color = color * color * 2.0;
} else {
color = 1.0 - ((1.0 - color) * (1.0 - color) * 2.0);
}
return color;
}
uchar4 __attribute__((kernel)) beauty(uint32_t x, uint32_t y) {
const uint32_t imageWidth = rsAllocationGetDimX(inAllocation);
const uint32_t imageHeight = rsAllocationGetDimY(inAllocation);
float4 in = rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x, y));
//process border
if(x < 10 || y < 10 || x >= (imageWidth - 10) || y >= (imageHeight - 10)) {
return rsPackColorTo8888(in);
}
// 对绿色通道进行模糊操作
float4 sampleColor = in.g * 20.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x, y - 10));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x, y + 10));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 10, y));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 10, y));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 5, y - 8));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 5, y + 8));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 5, y + 8));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 5, y - 8));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 8, y - 5));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 8, y - 5));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 8, y + 5));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 8, y - 5));
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x, y - 6)) * 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x, y + 6)) * 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 6, y)) * 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 6, y)) * 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 4, y - 4)) * 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x - 4, y + 4))* 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 4, y - 4)) * 2.0;
sampleColor += rsUnpackColor8888(rsGetElementAt_uchar4(inAllocation, x + 4, y + 4))* 2.0;
sampleColor = sampleColor / 48.0;
float highPass = in.g - sampleColor.g + 0.5;
highPass = clamp(highPass, 0.0, 1.0);
for (int i = 0; i < 5; i++) {
highPass = hardLight(highPass);
}
highPass = clamp(highPass, 0.0, 1.0);
float luminance = dot(in, weight);
float alpha = pow(luminance, beautyParam);
alpha = clamp(alpha, 0.0, 1.0);
float3 offset = (in.rgb - (float3){highPass, highPass, highPass}) * alpha * 0.1;
float3 smoothColor = in.rgb + offset;
smoothColor.r = clamp(smoothColor.r, 0.0, 1.0);
smoothColor.g = clamp(smoothColor.g, 0.0, 1.0);
smoothColor.b = clamp(smoothColor.b, 0.0, 1.0);
// 线性混合
float4 out;
out.r = mix(smoothColor.r, max(in.r,smoothColor.r), alpha);
out.g = mix(smoothColor.g, max(in.g,smoothColor.g), alpha);
out.b = mix(smoothColor.b, max(in.b,smoothColor.b), alpha);
out.a = in.a;
return rsPackColorTo8888(out);
}
相机美颜的原理
1.取出绿色通道,对绿色通道进行模糊处理,例如高斯模糊,得到模糊后的值sampleColor
2.用原图绿色通道值减去sampleColor,加上0.5(即128),1+2两个步骤即PS中的高反差保留
3.对上述结果值进行3-5次强光处理,此步骤可以使得噪声更加突出
4.计算原图的灰度值,公式为0.299R + 0.587G + 0.114*B
5.将灰度值作为阈值,用来排除非皮肤部分,根据灰度值计算,将原图与1-3后的结果图合成
6.对混合后结果增加亮度
7.以灰度值作为透明度将原图与混合后结果进行滤色、柔光等混合,并调节饱和度