变老特效是如何实现的呢?下面简单讲讲我做变老特效时的思路。
1. 面部皱纹纹理。变老后的皱纹并不是实时绘制,而是需要先准备一系列皱纹纹理,然后通过纹理转移算法将皱纹纹理转移至正常面部纹理上。
2. 由于照片尺寸不同,即使相同照片尺寸,检测的人脸尺寸也不会相同,而皱纹纹理尺寸是固定的,所以需要对皱纹纹理做合适的缩放才可以,这个简单,正常的图片缩放算法即可。
3. 由于检测的人脸姿态多变,有的头正颈直,有的歪头,有的仰头,而皱纹纹理都是头正颈直的,所以还需要对皱纹纹理做合适的变形,而非旋转,旋转的话匹配精度不够。本文采用的是移动最小二乘法图像变形(mls)算法。
4. 由于涉及到mls变形算法,所以需要控制点。本文采用的是提取人脸68个特征点,由于标准68个特征点主要分布在脸颊、眉毛、眼睛、鼻子、嘴巴,为了保证变形更精准,所以又计算增补了部分额头控制点,共计78个特征点作为变形控制点。
5. 由于照片亮度与皱纹纹理亮度会有一些差异,所以还需要修正皱纹纹理亮度,本文采用的方法比较简单,先计算检测到的人脸亮度均值,然后采用gamma校正皱纹纹理亮度。其他一些细节,可以参考代码。
下面是算法主流程代码:
void FacialAging(BMPINFO *pSrcBitmap, BMPINFO *pTexBitmap, MLS_Point *src_points, MLS_Point *mls_points, MLS_Point *tex_points, int pt_count, float strength)
{
// 缩放纹理
float ratio = 0.0f;
ScaleTexture(pTexBitmap, src_points, tex_points, &ratio);
// 变换控制点及变形点
memcpy(mls_points, src_points, pt_count*sizeof(MLS_Point));
TransformPQ(mls_points, tex_points, pTexBitmap->lWidth, pTexBitmap->lHeight, ratio, pt_count);
// 计算源/纹理图像脸部区域
Face_Rect src_face_rect, tex_face_rect;
CompFaceRect(&src_face_rect, src_points);
CompFaceRect(&tex_face_rect, mls_points);
if (!CheckFaceRect(pSrcBitmap, &src_face_rect))
{
return;
}
// 图像变形
ImageWarp_MLS(pTexBitmap, tex_points, mls_points, pt_count);
// 提取脸部区域
BMPINFO src_facebmp = { 0 }, tex_facebmp = { 0 }, dst_facebmp = { 0 };
src_facebmp.dwPixelFormat = BMPFORMAT_RGB32_R8G8B8A8;
src_facebmp.lWidth = src_face_rect.width;
src_facebmp.lHeight = src_face_rect.height;
src_facebmp.lPitch[0] = src_facebmp.lWidth * 4;
src_facebmp.pPlane[0] = (uchar*)malloc(src_facebmp.lPitch[0] * src_facebmp.lHeight);
dst_facebmp.dwPixelFormat = BMPFORMAT_RGB32_R8G8B8A8;
dst_facebmp.lWidth = src_face_rect.width;
dst_facebmp.lHeight = src_face_rect.height;
dst_facebmp.lPitch[0] = dst_facebmp.lWidth * 4;
dst_facebmp.pPlane[0] = (uchar*)malloc(dst_facebmp.lPitch[0] * dst_facebmp.lHeight);
tex_facebmp.dwPixelFormat = BMPFORMAT_RGB32_R8G8B8A8;
tex_facebmp.lWidth = tex_face_rect.width;
tex_facebmp.lHeight = tex_face_rect.height;
tex_facebmp.lPitch[0] = tex_facebmp.lWidth * 4;
tex_facebmp.pPlane[0] = (uchar*)malloc(tex_facebmp.lPitch[0] * tex_facebmp.lHeight);
ExtractFaceRect(pSrcBitmap, &src_facebmp, &src_face_rect);
ExtractFaceRect(pTexBitmap, &tex_facebmp, &tex_face_rect);
float posx = 0.0f, posy = 0.0f;
uchar red = 0, green = 0, blue = 0;
uchar *pSrcData = dst_facebmp.pPlane[0];
for (int i = 0; i < dst_facebmp.lHeight; i++)
{
posy = ((float)i / dst_facebmp.lHeight)*tex_facebmp.lHeight;
for (int j = 0; j < dst_facebmp.lWidth; j++, pSrcData += 4)
{
posx = ((float)j / dst_facebmp.lWidth)*tex_facebmp.lWidth;
BilinearInterRGB(tex_facebmp.pPlane[0], posx, posy, tex_facebmp.lWidth, tex_facebmp.lHeight, &blue, &green, &red);
pSrcData[AXJ_BLUE] = blue;
pSrcData[AXJ_GREEN] = green;
pSrcData[AXJ_RED] = red;
}
}
// 修正皱纹纹理亮度
float sum = 0.0f, mean = 0.0f;
int size = src_facebmp.lWidth*src_facebmp.lHeight;
uchar luminance = 0;
pSrcData = src_facebmp.pPlane[0];
for (int i = 0; i < size; i++, pSrcData += 4)
{
luminance = (pSrcData[0] + pSrcData[1] + pSrcData[2]) / 3;
sum += luminance;
}
mean = sum / size;
//
free(src_facebmp.pPlane[0]);
free(tex_facebmp.pPlane[0]);
free(dst_facebmp.pPlane[0]);
}
下面是一些效果图,由于用的皱纹纹理都是同一张,所以变老效果基本一样。算法效果还可以继续改进,不过现在也懒得弄了。
https://blog.csdn.net/grafx/article/details/70473685