V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
exmorning
V2EX  ›  程序员

抖音美颜效果开源实现,从 AI 到美颜全流程讲解

  •  
  •   exmorning · 2020-07-27 12:22:37 +08:00 · 6871 次点击
    这是一个创建于 1585 天前的主题,其中的信息可能已经有所发展或是发生改变。

    美颜和短视频

    美颜相关 APP 可以说是现在手机上的必备的软件,例如抖音,快手,拍出的“照骗”和视频不加美颜效果,估计没有人敢传到网上。很多人一直好奇美颜类 APP 是如何开发出来的。本文就大致讲一下在 Android 上如何实现实时修改唇色效果。

    下图的红唇效果就是想实现的功能
    demo1

    美颜原理

    美颜是的基本原理就是深度学习加计算机图形学。深度学习用来人脸检测和人脸关键点检测。计算机图形学用来磨皮,瘦脸和画妆容。一般在 Android 上使用 OpenGLES,IOS 为 Metal 。由于计算机图形学概念较多和复杂,本文中用 Android 的 Canvas 替代。

    人脸检测 & 人脸关键点

    1. 人脸检测指的是对图片或者视频流中的人脸进行检测,并定位到图片中的人脸。
    2. 人脸关键点检测是对人脸中五官和脸的轮廓进行关键点定位,一般情况下它紧接在人脸检测后。

    face landmarks

    我们将使用 TengineKit 来实现实时大红唇效果。

    TengineKit

    免费移动端实时人脸 212 关键点 SDK 。是一个易于集成的人脸检测和人脸关键点 SDK 。它可以在各种手机上以非常低的延迟运行。
    https://github.com/OAID/TengineKit

    TengineKit 效果图

    demo1

    实现口红效果

    配置 Gradle

    Project 中的 build.gradle 添加

        repositories {
            ...
            mavenCentral()
            ...
        }
    
        allprojects {
            repositories {
                ...
                mavenCentral()
                ...
            }
        }
    

    主 Module 中的 build.gradle 添加

        dependencies {
            ...
            implementation 'com.tengine.android:tenginekit:1.0.5'
            ...
        }
    

    配置 manifests

        <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

    处理 Gif 传过来的图片流

    首先我们先初始化 TengineKit:

    1. 选用 normal 处理模式
    2. 打开人脸检测和人脸关键点功能
    3. 设置图片流格式为 RGBA
    4. 设置输入图片流的宽高,此处为 gif 图的预览宽高
    5. 设置输出图片流的宽高,此处为 GifImageView 的宽高,此处和 gif 一致,所以用 gif 图的宽高代替
        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;
            }
        });
    

    效果对比

    demo demo

    建议

    有兴趣的同学可以在当前项目的基础上面深化,具体可以参考
    https://github.com/DingProg/Makeup

    更进一步想尝试商业级的美颜效果可以参考
    https://github.com/CainKernel/CainCamera

    参考

    1. TengineKit - Free, Fast, Easy, Real-Time FaceDetection & FaceLandmark SDK on Mobile.

    2. Makeup - 让你的“女神”逆袭,代码撸彩妆(画妆)

    3. CainCamera - CainCamera is an Android Project to learn about development of beauty camera, image and short video

    源码

    https://github.com/jiangzhongbo/TengineKit_Demo_Face_Beauty

    知乎

    https://zhuanlan.zhihu.com/p/163604590

    系列

    https://v2ex.com/t/690842
    https://v2ex.com/t/687534

    19 条回复    2020-08-11 13:10:24 +08:00
    KalaSearch
        1
    KalaSearch  
       2020-07-27 12:27:12 +08:00
    做得挺棒的,感谢 lz 分享

    这个 gif 选得也好 :D
    96412hj
        2
    96412hj  
       2020-07-27 12:55:01 +08:00
    美颜和人脸识别技术是不是有点类似?
    exmorning
        3
    exmorning  
    OP
       2020-07-27 13:04:14 +08:00
    exmorning
        4
    exmorning  
    OP
       2020-07-27 13:04:49 +08:00
    @96412hj 差不多,人脸识别还要一步人脸特征值检测
    dying4death
        5
    dying4death  
       2020-07-27 13:49:33 +08:00
    男人看的颜值和女人看的颜值什么意思
    exmorning
        6
    exmorning  
    OP
       2020-07-27 14:04:36 +08:00
    @dying4death 男性审美和女性审美不一样,所以打分也不一致,不过这个算法做的有问题,作为附带功能带上去
    glumess
        7
    glumess  
       2020-07-27 14:14:33 +08:00
    大佬优秀,感谢分享,请问文章可以在公众号 《音视频开发进阶》中进行转载吗?
    exmorning
        8
    exmorning  
    OP
       2020-07-27 14:17:27 +08:00
    @glumess 可以的,注明来源(知乎地址)就行
    Veneris
        9
    Veneris  
       2020-07-27 14:55:40 +08:00
    感谢分享,蛮清晰的文章。
    exmorning
        10
    exmorning  
    OP
       2020-07-27 15:27:34 +08:00
    @Veneris ;-)
    rayhy
        11
    rayhy  
       2020-07-28 10:40:49 +08:00 via Android
    请问楼主,这些算法在桌面端或者 Web 端实现有什么辅助用的基础库吗?就是不太想从底层写起,比 opencv 再高一个层次的。
    exmorning
        12
    exmorning  
    OP
       2020-07-28 11:53:23 +08:00
    @rayhy web 用 three.js+tf.js
    dream4ever
        13
    dream4ever  
       2020-07-28 17:10:44 +08:00
    这个妹子不错,哈哈
    exmorning
        14
    exmorning  
    OP
       2020-07-28 17:19:19 +08:00
    @dream4ever 欢迎骚扰~
    dream4ever
        15
    dream4ever  
       2020-07-29 18:36:41 +08:00
    @exmorning 咦,莫非你就是动图里的妹子?
    AmberJiang
        16
    AmberJiang  
       2020-07-31 15:32:47 +08:00 via iPad
    卧槽 一开始看这妹子 我还以为是王鸥
    🤣🤣
    duanxiaoyu
        17
    duanxiaoyu  
       2020-08-10 18:53:25 +08:00
    学会了,去哪找妹子来测试
    firefox12
        18
    firefox12  
       2020-08-11 13:08:48 +08:00
    大佬牛逼, 但是如果我现在想从零开始 实现这些 该如何入门,目前 你描述的 只不过是 用个库 调调 api,没有核心技术。
    firefox12
        19
    firefox12  
       2020-08-11 13:10:24 +08:00
    还有你的简书都挂了, 能不能把文章放到 github 上?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1743 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 16:41 · PVG 00:41 · LAX 08:41 · JFK 11:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.