抖音快手 APP"大眼特效"开源实现,换了一个甜美系小姐姐做效果演示
时间: 2020-08-21来源:V2EX
前景提要
大眼特效
抖音短视频中的大眼特效有很多人玩,这篇就讲一下怎么实现。本文为《 抖音美颜效果开源实现,从 AI 到美颜全流程讲解 》姐妹篇, 很多代码和内容都类似,看过的同学可以直接看效果和源码。

下图为演示小姐姐

大眼特效原理
大眼特效原理的美颜差不多,都是 AI 和计算机图形学的结合 美颜是的基本原理就是深度学习加计算机图形学。深度学习用来人脸检测和人脸关键点检测。计算机图形学用来磨皮,瘦脸和画妆容。一般在 Android 上使用 OpenGLES,IOS 为 Metal 。
来源 抖音美颜效果开源实现,从 AI 到美颜全流程讲解
人脸检测 & 人脸关键点 人脸检测指的是对图片或者视频流中的人脸进行检测,并定位到图片中的人脸。 人脸关键点检测是对人脸中五官和脸的轮廓进行关键点定位,一般情况下它紧接在人脸检测后。

我们将使用 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: 选用 normal 处理模式 打开人脸检测和人脸关键点功能 设置图片流格式为 RGBA 设置输入图片流的宽高,此处为 gif 图的预览宽高 设置输出图片流的宽高,此处为 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
参考 TengineKit - Free, Fast, Easy, Real-Time FaceDetection & FaceLandmark SDK on Mobile. Makeup - 让你的“女神”逆袭,代码撸彩妆(画妆) 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 到美颜全流程讲解

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

热门排行