美颜相关 APP 可以说是现在手机上的必备的软件,例如抖音,快手,拍出的“照骗”和视频不加美颜效果,估计没有人敢传到网上。很多人一直好奇美颜类 APP 是如何开发出来的。本文就大致讲一下在 Android 上如何实现实时修改唇色效果。
下图的红唇效果就是想实现的功能
美颜是的基本原理就是深度学习加计算机图形学。深度学习用来人脸检测和人脸关键点检测。计算机图形学用来磨皮,瘦脸和画妆容。一般在 Android 上使用 OpenGLES,IOS 为 Metal 。由于计算机图形学概念较多和复杂,本文中用 Android 的 Canvas 替代。
我们将使用 TengineKit 来实现实时大红唇效果。
免费移动端实时人脸 212 关键点 SDK 。是一个易于集成的人脸检测和人脸关键点 SDK 。它可以在各种手机上以非常低的延迟运行。
https://github.com/OAID/TengineKit
Project 中的 build.gradle 添加
repositories {
...
mavenCentral()
...
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
主 Module 中的 build.gradle 添加
dependencies {
...
implementation 'com.tengine.android:tenginekit:1.0.5'
...
}
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
相对于上篇用摄像头来做效果,本文用 gif 图来代替摄像头的输入的视频流,如果想用摄像头实现,可以参考:
用开源 212 点人脸关键点实现 Android 人脸实时打码 https://zhuanlan.zhihu.com/p/161038093
首先我们先初始化 TengineKit:
com.tenginekit.Face.init(getBaseContext(),
AndroidConfig.create()
.setNormalMode()
.openFunc(AndroidConfig.Func.Detect)
.openFunc(AndroidConfig.Func.Landmark)
.setInputImageFormat(AndroidConfig.ImageFormat.RGBA)
.setInputImageSize(facingGif.getGifWidth(), facingGif.getGifHeight())
.setOutputImageSize(facingGif.getGifWidth(), facingGif.getGifHeight())
);
Path getMouthLandmarks(FaceLandmarkInfo fi){
Path outPath = new Path();
outPath.moveTo(fi.landmarks.get(180).X,fi.landmarks.get(180).Y);
for(int i = 180; i < 189; i++){
outPath.lineTo(
fi.landmarks.get(i).X,
fi.landmarks.get(i).Y
);
}
for(int i = 204; i >= 196; i--){
outPath.lineTo(
fi.landmarks.get(i).X,
fi.landmarks.get(i).Y
);
}
outPath.close();
Path inPath = new Path();
inPath.moveTo(fi.landmarks.get(180).X,fi.landmarks.get(180).Y);
for(int i = 195; i >= 188; i--){
inPath.lineTo(
fi.landmarks.get(i).X,
fi.landmarks.get(i).Y
);
}
for(int i = 204; i <= 211; i++){
inPath.lineTo(
fi.landmarks.get(i).X,
fi.landmarks.get(i).Y
);
}
outPath.op(inPath, Path.Op.DIFFERENCE);
return outPath;
}
public static void drawLipPerfect(Canvas canvas, Path lipPath, int color, int alpha) {
//most 70% alpha
if (alpha > 80) {
alpha = (int) (alpha * 0.9f + 0.5f);
}
alpha = (int) (Color.alpha(color) * ((float) alpha / 255)) << 24;
color = alphaColor(color, alpha);
final PointF position = new PointF();
float blur_radius = 5;
Bitmap mask = createMask(lipPath, color, blur_radius, position);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
canvas.drawBitmap(mask, position.x, position.y, paint);
}
此代码来源于 https://github.com/DingProg/Makeup
传过来的 bitmap 为 RGB_565,需要转为标准的 RGBA 格式
facingGif.setOnFrameAvailable(new GifImageView.OnFrameAvailable() {
@Override
public Bitmap onFrameAvailable(Bitmap bitmap) {
// bitmap RGB_565
Bitmap out_bitmap = Bitmap.createBitmap(
facingGif.getGifWidth(),
facingGif.getGifHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(out_bitmap);
canvas.drawBitmap(bitmap, 0, 0, null);
bitmap.recycle();
byte[] bytes = bitmap2Bytes(out_bitmap);
Face.FaceDetect faceDetect = com.tenginekit.Face.detect(bytes);
if(faceDetect.getFaceCount() > 0){
faceLandmarks = faceDetect.landmark2d();
if(faceLandmarks != null){
for (int i = 0; i < faceLandmarks.size(); i++) {
Path m_p = getMouthLandmarks(faceLandmarks.get(i));
LipDraw.drawLipPerfect(canvas, m_p, Color.WHITE, 100);
}
}
}
return out_bitmap;
}
});
有兴趣的同学可以在当前项目的基础上面深化,具体可以参考
https://github.com/DingProg/Makeup
更进一步想尝试商业级的美颜效果可以参考
https://github.com/CainKernel/CainCamera
https://github.com/jiangzhongbo/TengineKit_Demo_Face_Beauty
https://zhuanlan.zhihu.com/p/163604590
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.