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

抖音快手 APP"大眼特效"开源实现,换了一个甜美系小姐姐做效果演示

  •  
  •   exmorning · 7 天前 · 7559 次点击

    大眼特效

    抖音短视频中的大眼特效有很多人玩,这篇就讲一下怎么实现。本文为《抖音美颜效果开源实现,从 AI 到美颜全流程讲解》姐妹篇,很多代码和内容都类似,看过的同学可以直接看效果和源码。

    demo1

    下图为演示小姐姐

    demo1

    大眼特效原理

    大眼特效原理的美颜差不多,都是 AI 和计算机图形学的结合

    美颜是的基本原理就是深度学习加计算机图形学。深度学习用来人脸检测和人脸关键点检测。计算机图形学用来磨皮,瘦脸和画妆容。一般在 Android 上使用 OpenGLES,IOS 为 Metal 。

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

    人脸检测 & 人脸关键点

    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 传过来的图片流

    首先我们先初始化 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())
        );
    

    通过关键点得眼睛的中心点

        Point getLeftEyeCenter(FaceLandmarkInfo fi){
            FaceLandmarkPoint p1 = fi.landmarks.get(105);
            FaceLandmarkPoint p2 = fi.landmarks.get(113);
            return new Point((int)((p1.X + p2.X) / 2), (int)((p1.Y + p2.Y) / 2));
        }
    
        Point getRightEyeCenter(FaceLandmarkInfo fi){
            FaceLandmarkPoint p1 = fi.landmarks.get(121);
            FaceLandmarkPoint p2 = fi.landmarks.get(129);
            return new Point((int)((p1.X + p2.X) / 2), (int)((p1.Y + p2.Y) / 2));
        }
    

    眼睛放大算法

    public class MagnifyEyeUtils {
        /**
         *  眼睛放大算法
         * @param bitmap      原来的 bitmap
         * @param centerPoint 放大中心点
         * @param radius      放大半径
         * @param sizeLevel    放大力度  [0,4]
         * @return 放大眼睛后的图片
         */
        public static Bitmap magnifyEye(Bitmap bitmap, Point centerPoint, int radius, float sizeLevel) {
            Bitmap dstBitmap = bitmap.copy(Bitmap.Config.RGB_565, true);
            int left = centerPoint.x - radius < 0 ? 0 : centerPoint.x - radius;
            int top = centerPoint.y - radius < 0 ? 0 : centerPoint.y - radius;
            int right = centerPoint.x + radius > bitmap.getWidth() ? bitmap.getWidth() - 1 : centerPoint.x + radius;
            int bottom = centerPoint.y + radius > bitmap.getHeight() ? bitmap.getHeight() - 1 : centerPoint.y + radius;
            int powRadius = radius * radius;
    
            int offsetX, offsetY, powDistance, powOffsetX, powOffsetY;
    
            int disX, disY;
    
            //当为负数时,为缩小
            float strength = (5 + sizeLevel * 2) / 10;
    
            for (int i = top; i <= bottom; i++) {
                offsetY = i - centerPoint.y;
                for (int j = left; j <= right; j++) {
                    offsetX = j - centerPoint.x;
                    powOffsetX = offsetX * offsetX;
                    powOffsetY = offsetY * offsetY;
                    powDistance = powOffsetX + powOffsetY;
    
                    if (powDistance <= powRadius) {
                        double distance = Math.sqrt(powDistance);
                        double sinA = offsetX / distance;
                        double cosA = offsetY / distance;
    
                        double scaleFactor = distance / radius - 1;
                        scaleFactor = (1 - scaleFactor * scaleFactor * (distance / radius) * strength);
    
                        distance = distance * scaleFactor;
                        disY = (int) (distance * cosA + centerPoint.y + 0.5);
                        disY = checkY(disY, bitmap);
                        disX = (int) (distance * sinA + centerPoint.x + 0.5);
                        disX = checkX(disX, bitmap);
                        //中心点不做处理
                        if (!(j == centerPoint.x && i == centerPoint.y)) {
                            dstBitmap.setPixel(j, i, bitmap.getPixel(disX, disY));
                            //dstBitmap.setPixel(j, i, Color.WHITE);
                        }
                    }
                }
            }
            return dstBitmap;
        }
    
        private static int checkY(int disY, Bitmap bitmap) {
            if (disY < 0) {
                disY = 0;
            } else if (disY >= bitmap.getHeight()) {
                disY = bitmap.getHeight() - 1;
            }
            return disY;
        }
    
        private static int checkX(int disX, Bitmap bitmap) {
            if (disX < 0) {
                disX = 0;
            } else if (disX >= bitmap.getWidth()) {
                disX = bitmap.getWidth() - 1;
            }
            return disX;
        }
    
    }
    

    此代码来源于 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++) {
                            FaceLandmarkInfo fi = faceLandmarks.get(i);
                            out_bitmap = MagnifyEyeUtils.magnifyEye(out_bitmap, getLeftEyeCenter(fi), 40, 4);
                            out_bitmap = MagnifyEyeUtils.magnifyEye(out_bitmap, getRightEyeCenter(fi), 40, 4);
                        }
                    }
                }
                return out_bitmap;
            }
        });
    

    效果对比

    demo demo

    建议

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

    参考

    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_Big_Eyes

    知乎

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

    V2EX 系列

    推荐一个 Github 上面免费用的 Android 人脸关键点 SDK,Demo 图中的小姐姐好漂亮

    尝试 AI 人脸关键点算法实现一下 Android 人脸匿名功能

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

    39 条回复    2020-08-04 18:55:43 +08:00
    leimao
        1
    leimao   7 天前 via iPhone   ❤️ 3
    我绝对不会为网红花一分钱
    nksky
        2
    nksky   7 天前   ❤️ 1
    第一张图我看着有点恐怖
    littiefish
        3
    littiefish   7 天前 via iPhone   ❤️ 2
    畸形审美
    yanzhiling2001
        4
    yanzhiling2001   7 天前
    眼睛大的像 ET 了
    mugglezzz
        5
    mugglezzz   7 天前
    @nksky #2 第一张图是 阿丽塔 啦
    takemeaway
        6
    takemeaway   7 天前
    我比较好奇 TengineKit 是全自己研发的算法吗?
    还是挺厉害的
    chiaf
        7
    chiaf   7 天前
    太想 ET 了😂
    exmorning
        8
    exmorning   7 天前
    @takemeaway 是啊,从端侧推理引擎到算法都是自己搞的
    mmrx
        9
    mmrx   7 天前
    哈哈哈哈大眼动图可太恐怖了

    感谢楼主分享
    exmorning
        10
    exmorning   7 天前
    @mmrx 特地做大了点,为了展示效果
    xrr2016
        11
    xrr2016   7 天前
    话说战斗天使啥时候出第二部啊?第一部才讲了漫画的一点内容...
    Soar360
        12
    Soar360   7 天前
    死鱼眼的既视感
    nguoidiqua
        13
    nguoidiqua   7 天前   ❤️ 3
    假脸时代。

    对于国内各种自拍、网上的美照什么的已经完全无感了,美颜太过了,经常挡一下脸就会看到脸忽然变大一圈大,然后马上又变小了,感觉很无语。
    revalue
        14
    revalue   7 天前
    哪有人把眼袋放大得那么大啊,混蛋
    ln1225707801
        15
    ln1225707801   7 天前 via Android
    效果蛮厉害的,就是审美需要加强
    zhangchongjie
        16
    zhangchongjie   7 天前 via Android
    opencv 可以做到吗
    wushigejiajia01
        17
    wushigejiajia01   7 天前
    @xrr2016 你参考下阿凡达, 反正没看到片子, 卡梅隆这人是不能信了
    exmorning
        18
    exmorning   7 天前
    @zhangchongjie 可以的,就是 opencv 性能在 mobile 侧不行
    UnitTest
        19
    UnitTest   7 天前
    感谢技术分享,学习了。
    就是效果有点 creepy 。。
    exmorning
        20
    exmorning   7 天前
    @UnitTest 为了效果牺牲了颜值~
    buliugu
        21
    buliugu   7 天前
    有点恐怖谷效应
    zhangchongjie
        22
    zhangchongjie   7 天前 via Android
    @exmorning 嗯嗯,没试过,做 web 的看看热闹😂
    exmorning
        23
    exmorning   7 天前
    @zhangchongjie 有时间,我用 nodejs 包一下
    takemeaway
        24
    takemeaway   7 天前
    看起来像 ERT 算法,其实还是做得不错的。 看识别率了
    stnaw
        25
    stnaw   7 天前
    用 shader 和人脸蒙版会更好
    exmorning
        26
    exmorning   7 天前
    @takemeaway 暴力上 MobileNet
    exmorning
        27
    exmorning   7 天前
    @stnaw 这样就是不是入门级的文章了,上 canvas 为了让初学者能快速玩起来
    root8080
        28
    root8080   7 天前
    小姐姐快秃了 注意保养 😭
    exmorning
        29
    exmorning   7 天前
    @root8080 我会转告的😭
    IzayakI
        30
    IzayakI   6 天前
    第一张图是铳梦里的阿丽塔吧,漫画好看,电影故事讲得稀烂。


    还有,后面的对比 gif,这个大眼好看嘛?莫名其妙的。
    exmorning
        31
    exmorning   6 天前
    @IzayakI 为了看出效果,特地做大了些
    LZSZ
        32
    LZSZ   6 天前
    有点像青蛙 原谅我这么说
    vinew
        33
    vinew   6 天前
    小姐姐可能还需要植发特效,发量渐少了
    GM
        34
    GM   6 天前
    一行源码都没有,我是不是理解错“开源”的意思了?
    Rh1
        35
    Rh1   5 天前 via iPhone
    真是什么玩意都可以扯到“国内”“国外”.... 滤镜美颜这种东西在全球都很火好吗?
    falcon05
        36
    falcon05   5 天前 via iPhone
    太吓人了我去
    erek
        37
    erek   4 天前
    期待阿丽塔第二部
    CoderGeek
        38
    CoderGeek   1 天前
    666
    djs
        39
    djs   1 天前
    吓死我了。。。那个大眼图
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1093 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 21:43 · PVG 05:43 · LAX 14:43 · JFK 17:43
    ♥ Do have faith in what you're doing.