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

2020-07-29 08:40:52 +08:00
 exmorning

大眼特效

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

下图为演示小姐姐

大眼特效原理

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

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

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

人脸检测 & 人脸关键点

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

我们将使用 TengineKit 来实现大眼特效。

TengineKit

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

TengineKit 效果图

实现大眼特效

配置 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;
        }
    });

效果对比

建议

有兴趣的同学可以在当前项目的基础上面深化,具体可以参考
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 到美颜全流程讲解

11450 次点击
所在节点    程序员
40 条回复
leimao
2020-07-29 08:43:48 +08:00
我绝对不会为网红花一分钱
nksky
2020-07-29 08:57:42 +08:00
第一张图我看着有点恐怖
littiefish
2020-07-29 08:59:28 +08:00
畸形审美
yanzhiling2001
2020-07-29 08:59:33 +08:00
眼睛大的像 ET 了
mugglezzz
2020-07-29 09:18:15 +08:00
@nksky #2 第一张图是 阿丽塔 啦
takemeaway
2020-07-29 09:24:36 +08:00
我比较好奇 TengineKit 是全自己研发的算法吗?
还是挺厉害的
chiaf
2020-07-29 09:28:59 +08:00
太想 ET 了😂
exmorning
2020-07-29 09:31:16 +08:00
@takemeaway 是啊,从端侧推理引擎到算法都是自己搞的
mmrx
2020-07-29 09:31:51 +08:00
哈哈哈哈大眼动图可太恐怖了

感谢楼主分享
exmorning
2020-07-29 09:33:07 +08:00
@mmrx 特地做大了点,为了展示效果
xrr2016
2020-07-29 09:41:29 +08:00
话说战斗天使啥时候出第二部啊?第一部才讲了漫画的一点内容...
Soar360
2020-07-29 09:43:19 +08:00
死鱼眼的既视感
nguoidiqua
2020-07-29 09:51:15 +08:00
假脸时代。

对于国内各种自拍、网上的美照什么的已经完全无感了,美颜太过了,经常挡一下脸就会看到脸忽然变大一圈大,然后马上又变小了,感觉很无语。
revalue
2020-07-29 09:58:49 +08:00
哪有人把眼袋放大得那么大啊,混蛋
ln1225707801
2020-07-29 10:10:18 +08:00
效果蛮厉害的,就是审美需要加强
zhangchongjie
2020-07-29 10:12:22 +08:00
opencv 可以做到吗
wushigejiajia01
2020-07-29 10:13:24 +08:00
@xrr2016 你参考下阿凡达, 反正没看到片子, 卡梅隆这人是不能信了
exmorning
2020-07-29 10:39:33 +08:00
@zhangchongjie 可以的,就是 opencv 性能在 mobile 侧不行
UnitTest
2020-07-29 10:44:28 +08:00
感谢技术分享,学习了。
就是效果有点 creepy 。。
exmorning
2020-07-29 11:06:10 +08:00
@UnitTest 为了效果牺牲了颜值~

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/693915

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX