自动瘦脸与眼睛放大可以算作图像局部扭曲算法的一个应用,其参考文献可以追溯至1993年的一篇博士论文:Interactive Image Warping。这篇论文详细描述了算法原理,并提供了伪码实现,有兴趣的同学自行下载研读。
图像局部扭曲算法有三个:局部缩放(Local Scaling)算法、局部平移(Local Transition)算法和局部旋转(Local Rotation)算法。其中应用局部缩放算法可实现眼睛放大,局部平移算法则可用于实现瘦脸效果。当然,图像局部缩放算法只是眼睛放大算法流程中的最关键的一步,要实现自动眼睛放大算法还需要额外的步骤。简单来说,给一张美女头像,你首先需要应用自动人脸检测技术定位出图像中的眼睛位置;然后基于此位置坐标应用图像局部缩放算法。自动瘦脸算法流程类似,不同之处在于应用人脸检测技术得到人脸轮廓点,由这些轮廓坐标点应用局部平移算法得到瘦脸效果。
人脸检测现在已经是一个很成熟的技术,网上也有很多开放资源,目前我只是算法验证,暂时使用的是face++提供的人脸关键点检测SDK。它可以得到很丰富的人脸特征点,包括眉毛、眼睛、鼻子、嘴巴以及脸部轮廓的各个参数,企业应用可能需要付费授权才能使用,个人验证只要注册获取key就能简单集成。
图像局部缩放算法
待续。。。
至于图像局部缩放算法实现,文献中有伪码描述,这里我给出一个简单的OpenGL Shader可以用于实现眼睛放大(其实也可以缩小),偷懒的同学自取,有问题可以跟我讨论,但我不对此代码导致的bug负责。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
precision
highp
float
;
varying
highp
vec2
textureCoordinate
;
uniform
sampler2D
inputImageTexture
;
uniform
highp
float
scaleRatio
;
// 缩放系数,0无缩放,大于0则放大
uniform
highp
float
radius
;
// 缩放算法的作用域半径
uniform
highp
vec2
leftEyeCenterPosition
;
// 左眼控制点,越远变形越小
uniform
highp
vec2
rightEyeCenterPosition
;
// 右眼控制点
uniform
float
aspectRatio
;
// 所处理图像的宽高比
highp
vec2
warpPositionToUse
(
vec2
centerPostion
,
vec2
currentPosition
,
float
radius
,
float
scaleRatio
,
float
aspectRatio
)
{
vec2
positionToUse
=
currentPosition
;
vec2
currentPositionToUse
=
vec2
(
currentPosition
.
x
,
currentPosition
.
y
*
aspectRatio
+
0.5
-
0.5
*
aspectRatio
)
;
vec2
centerPostionToUse
=
vec2
(
centerPostion
.
x
,
centerPostion
.
y
*
aspectRatio
+
0.5
-
0.5
*
aspectRatio
)
;
float
r
=
distance
(
currentPositionToUse
,
centerPostionToUse
)
;
if
(
r
<
radius
)
{
float
alpha
=
1.0
-
scaleRatio
*
pow
(
r
/
radius
-
1.0
,
2.0
)
;
positionToUse
=
centerPostion
+
alpha
*
(
currentPosition
-
centerPostion
)
;
}
return
positionToUse
;
}
void
main
(
)
{
vec2
positionToUse
=
warpPositionToUse
(
leftEyeCenterPosition
,
textureCoordinate
,
radius
,
scaleRatio
,
aspectRatio
)
;
positionToUse
=
warpPositionToUse
(
rightEyeCenterPosition
,
positionToUse
,
radius
,
scaleRatio
,
aspectRatio
)
;
gl_FragColor
=
texture2D
(
inputImageTexture
,
positionToUse
)
;
}
|
给个测试效果图:
图像局部平移算法
图像局部平移算法还是参见论文,多说无益,在此奉上对应Shader代码给需要的同学,可以实现瘦脸和肥脸。这里需要指定瘦脸的控制点,最多支持MAX_CONTOUR_POINT_COUNT个控制点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
precision
highp
float
;
varying
highp
vec2
textureCoordinate
;
uniform
sampler2D
inputImageTexture
;
uniform
highp
float
radius
;
uniform
highp
float
aspectRatio
;
uniform
float
leftContourPoints
[
MAX_CONTOUR_POINT_COUNT
*
2
]
;
uniform
float
rightContourPoints
[
MAX_CONTOUR_POINT_COUNT
*
2
]
;
uniform
float
deltaArray
[
MAX_CONTOUR_POINT_COUNT
]
;
uniform
int
arraySize
;
highp
vec2
warpPositionToUse
(
vec2
currentPoint
,
vec2
contourPointA
,
vec2
contourPointB
,
float
radius
,
float
delta
,
float
aspectRatio
)
{
vec2
positionToUse
=
currentPoint
;
vec2
currentPointToUse
=
vec2
(
currentPoint
.
x
,
currentPoint
.
y
*
aspectRatio
+
0.5
-
0.5
*
aspectRatio
)
;
vec2
contourPointAToUse
=
vec2
(
contourPointA
.
x
,
contourPointA
.
y
*
aspectRatio
+
0.5
-
0.5
*
aspectRatio
)
;
float
r
=
distance
(
currentPointToUse
,
contourPointAToUse
)
;
if
(
r
<
radius
)
{
vec2
dir
=
normalize
(
contourPointB
-
contourPointA
)
;
float
dist
=
radius
*
radius
-
r
*
r
;
float
alpha
=
dist
/
(
dist
+
(
r
-
delta
)
*
(
r
-
delta
)
)
;
alpha
=
alpha
*
alpha
;
positionToUse
=
positionToUse
-
alpha
*
delta
*
dir
;
}
return
positionToUse
;
}
void
main
(
)
{
vec2
positionToUse
=
textureCoordinate
;
for
(
int
i
=
0
;
i
<
arraySize
;
i
++
)
{
positionToUse
=
warpPositionToUse
(
positionToUse
,
vec2
(
leftContourPoints
[
i
*
2
]
,
leftContourPoints
[
i
*
2
+
1
]
)
,
vec2
(
rightContourPoints
[
i
*
2
]
,
rightContourPoints
[
i
*
2
+
1
]
)
,
radius
,
deltaArray
[
i
]
,
aspectRatio
)
;
positionToUse
=
warpPositionToUse
(
positionToUse
,
vec2
(
rightContourPoints
[
i
*
2
]
,
rightContourPoints
[
i
*
2
+
1
]
)
,
vec2
(
leftContourPoints
[
i
*
2
]
,
leftContourPoints
[
i
*
2
+
1
]
)
,
radius
,
deltaArray
[
i
]
,
aspectRatio
)
;
}
gl_FragColor
=
texture2D
(
inputImageTexture
,
positionToUse
)
;
}
|
同样给个测试效果: