【圖像處理】人臉檢測--皺紋---變老特效

變老特效是如何實現的呢?下面簡單講講我做變老特效時的思路。

       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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章