【图像处理】人脸检测--皱纹---变老特效

11 篇文章 1 订阅
4 篇文章 2 订阅

变老特效是如何实现的呢?下面简单讲讲我做变老特效时的思路。

       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

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值