抖音短视频中的大眼特效有很多人玩,这篇就讲一下怎么实现。本文为《抖音美颜效果开源实现,从 AI 到美颜全流程讲解》姐妹篇,很多代码和内容都类似,看过的同学可以直接看效果和源码。
下图为演示小姐姐
大眼特效原理的美颜差不多,都是 AI 和计算机图形学的结合
美颜是的基本原理就是深度学习加计算机图形学。深度学习用来人脸检测和人脸关键点检测。计算机图形学用来磨皮,瘦脸和画妆容。一般在 Android 上使用 OpenGLES,IOS 为 Metal 。
我们将使用 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"/>
首先我们先初始化 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())
);
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
https://github.com/jiangzhongbo/TengineKit_Demo_Big_Eyes
https://zhuanlan.zhihu.com/p/164803269
推荐一个 Github 上面免费用的 Android 人脸关键点 SDK,Demo 图中的小姐姐好漂亮
1
leimao 2020-07-29 08:43:48 +08:00 via iPhone 3
我绝对不会为网红花一分钱
|
2
nksky 2020-07-29 08:57:42 +08:00 1
第一张图我看着有点恐怖
|
3
littiefish 2020-07-29 08:59:28 +08:00 via iPhone 4
畸形审美
|
4
40EaE5uJO3Xt1VVa 2020-07-29 08:59:33 +08:00
眼睛大的像 ET 了
|
6
takemeaway 2020-07-29 09:24:36 +08:00
我比较好奇 TengineKit 是全自己研发的算法吗?
还是挺厉害的 |
7
chiaf 2020-07-29 09:28:59 +08:00
太想 ET 了😂
|
8
exmorning OP @takemeaway 是啊,从端侧推理引擎到算法都是自己搞的
|
9
mmrx 2020-07-29 09:31:51 +08:00
哈哈哈哈大眼动图可太恐怖了
感谢楼主分享 |
11
xrr2016 2020-07-29 09:41:29 +08:00
话说战斗天使啥时候出第二部啊?第一部才讲了漫画的一点内容...
|
12
Soar360 2020-07-29 09:43:19 +08:00
死鱼眼的既视感
|
13
nguoidiqua 2020-07-29 09:51:15 +08:00 3
假脸时代。
对于国内各种自拍、网上的美照什么的已经完全无感了,美颜太过了,经常挡一下脸就会看到脸忽然变大一圈大,然后马上又变小了,感觉很无语。 |
14
revalue 2020-07-29 09:58:49 +08:00
哪有人把眼袋放大得那么大啊,混蛋
|
15
ln1225707801 2020-07-29 10:10:18 +08:00 via Android
效果蛮厉害的,就是审美需要加强
|
16
zhangchongjie 2020-07-29 10:12:22 +08:00 via Android
opencv 可以做到吗
|
17
wushigejiajia01 2020-07-29 10:13:24 +08:00
@xrr2016 你参考下阿凡达, 反正没看到片子, 卡梅隆这人是不能信了
|
18
exmorning OP @zhangchongjie 可以的,就是 opencv 性能在 mobile 侧不行
|
19
UnitTest 2020-07-29 10:44:28 +08:00
感谢技术分享,学习了。
就是效果有点 creepy 。。 |
21
buliugu 2020-07-29 13:04:24 +08:00
有点恐怖谷效应
|
22
zhangchongjie 2020-07-29 13:15:24 +08:00 via Android
@exmorning 嗯嗯,没试过,做 web 的看看热闹😂
|
23
exmorning OP @zhangchongjie 有时间,我用 nodejs 包一下
|
24
takemeaway 2020-07-29 13:53:28 +08:00
看起来像 ERT 算法,其实还是做得不错的。 看识别率了
|
25
stnaw 2020-07-29 13:55:22 +08:00
用 shader 和人脸蒙版会更好
|
26
exmorning OP @takemeaway 暴力上 MobileNet
|
28
root8080 2020-07-29 14:41:54 +08:00
小姐姐快秃了 注意保养 😭
|
30
IzayakI 2020-07-30 05:52:17 +08:00
第一张图是铳梦里的阿丽塔吧,漫画好看,电影故事讲得稀烂。
还有,后面的对比 gif,这个大眼好看嘛?莫名其妙的。 |
32
LZSZ 2020-07-30 17:29:47 +08:00
有点像青蛙 原谅我这么说
|
33
vinew 2020-07-30 17:30:43 +08:00
小姐姐可能还需要植发特效,发量渐少了
|
34
GM 2020-07-30 17:36:50 +08:00
一行源码都没有,我是不是理解错“开源”的意思了?
|
35
Rh1 2020-08-01 00:12:23 +08:00 via iPhone
真是什么玩意都可以扯到“国内”“国外”.... 滤镜美颜这种东西在全球都很火好吗?
|
36
falcon05 2020-08-01 00:25:41 +08:00 via iPhone
太吓人了我去
|
37
erek 2020-08-01 18:48:42 +08:00
期待阿丽塔第二部
|
38
CoderGeek 2020-08-04 10:03:49 +08:00
666
|
39
djs 2020-08-04 18:55:43 +08:00
吓死我了。。。那个大眼图
|
40
minglanyu 2020-08-06 09:25:07 +08:00
这小姐姐... 这大眼...
我欣赏不了。 |