图像处理算法之变老特效

       用了美颜app,瞬间可以将自己变成脸小、大眼、白肤的女神、男神。那有没有人想看看自己一脸皱纹的样子呢?好奇心大家都应该有,不过没人想变老,虽然这个现实每个人都要面对。app store上有相关应用,曾经在做变老特效时,特意下载几个,当用自己照片体验时,真心做不到从容面对自己变老时的模样。那么变老特效是如何实现的呢?下面简单讲讲我做变老特效时的思路。

       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]);
}

       下面是一些效果图,由于用的皱纹纹理都是同一张,所以变老效果基本一样。算法效果还可以继续改进,不过现在也懒得弄了。

 

       下面是同一张图片,不同变老程度效果对比。

       测试图片来自lfpw_data人脸数据库。不得不说,岁月是把杀猪刀,没有对比,就没有伤害。时光哟,你慢点溜!

 

 

 

  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值