序言
本篇文章是代码撸彩妆的第二篇, 主要介绍在Android上怎么进行图片的局部变形,并实现抖音上比较火的大眼,瘦脸,大长腿特效.
在开始之前我们先来回顾上一篇的主要内容.
使用代码画一半的效果如下
public enum Region {
FOUNDATION("粉底"),
BLUSH("腮红"),
LIP("唇彩"),
BROW("眉毛"),
EYE_LASH("睫毛"),
EYE_CONTACT("美瞳"),
EYE_DOUBLE("双眼皮"),
EYE_LINE("眼线"),
EYE_SHADOW("眼影");
private String name;
Region(String name) {
this.name = name;
}
}
使用代码画出各种效果. 上一篇的文章地址 Android:让你的“女神”逆袭,代码撸彩妆(画妆)
上一篇和本篇的代码所在地址一致,都已经托管到github,如果你喜欢,欢迎给一个star,谢谢 https://github.com/DingProg/Makeup
现在开始我们今天的主题,人体(图像)的局部变形,如果要直接看效果的话,可以点击目录快速滑到效果区域.
大眼
效果
实现
图片局部缩放原理
我们知道,图片的放大缩小,是比较容易的事,相应的库已经封装好了,可以直接使用(我们并不需要关注图形放大缩小的插值处理等). 但是图片的局部放大缩小,并没有直接封装好,比如Android里面的bitmap,并没有直接局部处理放大缩小的API.
那我们先来看一下什么是图形的局部缩放?
局部的缩放,我们可以想象成中心点被缩放的比例比较小,而边缘的地方被缩放的比例很小,或者边界区域几乎没有变化,这样就可以达到一种平滑的效果。如果直接只对选中的圆形区域,变化的话,那边缘就变成了断裂式的缩放.
借用1993年的一篇博士论文 Interactive Image Warping 对局部图片进行缩放
其中a为缩放因子,当a=0时,不缩放
代码实现
既然要让眼睛放大,那么我们就把对应的近圆心的点的值️赋给远心点。
按照论文里所提到的思路,进行部分修改,实现如下.
/**
* 眼睛放大算法
* @param bitmap 原来的bitmap
* @param centerPoint 放大中心点
* @param radius 放大半径
* @param sizeLevel 放大力度 [0,4]
* @return 放大眼睛后的图片
*/
public static Bitmap magnifyEye(Bitmap bitmap, Point centerPoint, int radius, float sizeLevel) {
TimeAopUtils.start();
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));
}
}
}
}
TimeAopUtils.end("eye","magnifyEye");
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;
}
其中里面计算缩放前后后的点,使用的是如下图所示的计算规则计算.
有了这个方法,我们借助人脸识别的结果,把眼睛中心部分传入进去就可以实现自动大眼的效果了.
Bitmap magnifyEye = MagnifyEyeUtils.magnifyEye(bitmap,
Objects.requireNonNull(FacePoint.getLeftEyeCenter(faceJson)),
FacePoint.getLeftEyeRadius(faceJson) * 3, 3);
略有不足
- 代码所示部分没有使用插值 (代码直接使用了值替代,而不是