1. 程式人生 > >人像美妝---妝容遷移演算法研究(Makeup transfer)

人像美妝---妝容遷移演算法研究(Makeup transfer)

對於人像美妝演算法,現在的美妝相機、玩美彩妝之類的app已經做的比較成熟了,但是具體演算法,基本網路上是杳無可查,今天本人介紹一種自動的人像美妝演算法----(Makeup Transfer)妝容遷移

妝容遷移相關的論文不多,有如下幾篇:

1.Example-Based cosmetic transfer

2.Makeup Transfer using Multi-example

3.A new digtial face makeup method

4.An automatic framework for example vased virtual makeup

本人主要介紹第一篇《Example-Based cosmetic transfer》,論文效果圖如下:


注意:A和A*是兩張輸入,B是原始影象,B*是根據A*遷移過來的妝容效果圖;

本文的演算法流程如下:

1,Face warp

若要講A*的妝容遷移到B上,文中有幾個條件:

①背景單一;

②膚色相近;

這兩個條件也是為了最後的效果更加自然;

首先,Face warp的過程需要有人臉特徵點,因此,這一步之前需要進行人臉檢測和點位對齊,拿到A*和B的特徵點;

然後,根據變形演算法講A*和B的特徵點對齊,從而將A*變形到與B一致;

變形演算法有以下幾種(個人總結):

①最小二乘MLS變形演算法:Image Deformation Using Moving Least Squares.

詳細介紹參考部落格:http://blog.csdn.net/hjimce/article/details/46550001

②基於線的變形演算法:As-Rigid-As-Possible Shape Manipulation.

詳細介紹參考部落格:http://blog.csdn.net/hjimce/article/details/45766321

③三角網格仿射變換

給出這一步的效果圖如下:


左邊是原圖B(為避免 侵權,眼睛做了處理),中間是妝容圖A*,右邊是Face warp之後的效果圖(我這裡採用的是三角網格變形);

2,Cosmetic Map計算

文中介紹的重點也就是這一步,計算Cosmetic Map,即CP,其實演算法很簡單,公式如下:

Cp = ap / ap*

ap:妝容影象A*對應的原圖A

ap*:妝容影象A*

就這麼一個簡單的公式,就可以化腐朽為神奇。

文中所給CP效果圖如下:


3,Makeup transfer

得到了CP之後,我們就可以來進行妝容遷移了,具體演算法如下:


4,Others

實際上論文中還介紹一些其他內容,這裡我沒有寫出,因為我的重點是妝容遷移,所以就主要提取了這塊內容。

論文中實際上還進行了Freckle remove雀斑去除,眉毛眼睛紋理細節提取等等,如下所示:


這些內容,我這裡不關心,實際上,就是為了讓最後的效果更加自然更加逼真;

以上整個過程就是這篇論文的核心演算法;

現在,好東東才剛剛開始:

本人對這篇論文提出一下幾個問題,實際上也是應用中的缺陷:

①,論文要求三個輸入(A, A*, B),一個輸出B*

這一點,實際應用中就有很大限制,一般而言,我們能拿到A*,也就是好看的妝容效果圖,然後想對自己的照片B進行化妝,這個邏輯中是沒有未化妝的原圖A的。

②,論文中要求膚色相近,背景單一

這一點,普適性太低,很難應用;

③,按照論文的邏輯,嘴巴區域是閉合的,無法適應於各種大笑等開口的情況,或者是效果太差;

介於以上三點,本人對演算法進行了改進:

①,根據A*,對A進行估計,估算得到A,這樣就只要求使用者輸入一張好看的效果圖,即可對自己的自拍照等進行妝容遷移了;

②,根據人臉特徵點,獲取A*中的膚色特徵,構建精準的人臉Mask,去除背景,這樣就避免了背景的影響,同時,進行膚色轉換,將A*中的膚色轉換到B*中去,從而避免膚色差異過大造成的影響;

③獲取A*中的脣色特徵,對B進行脣色轉換,即將A*的膚色和脣色遷移到B*中去,從而使用於各種大笑等開口場景;

根據上述三點,本人改進演算法,得到如下的結果:


原圖B


三個目標妝容B*

上述三個妝容B*分別對應的效果圖如下:


在給一組測試圖:


以上效果本人做了化妝程度自適應,所以沒有出現很飽滿的豔妝,這樣是為了看起來更自然一點。

注意:本人使用的測試圖來自美顏相機和網際網路,若有侵權敬請告知。

本人提供簡單的程式碼呼叫如下:

      private void pictureBox4_Click(object sender, EventArgs e)
      {
          if (pictureBox1.Image != null)
          {
              Graphics g = Graphics.FromImage(curBitmap);
              int[] eyePoints = { 
                                 173, 370, 177, 441, 191, 509, 212, 577, 236, 640,
                                 267, 698, 303, 748, 347, 793, 399, 826, 465, 837,
                                 526, 825, 575, 792, 611, 748, 643, 699, 671, 643,
                                 695, 578, 714, 507, 727, 435, 728, 364, 214, 316,
                                 245, 284, 285, 273, 328, 274, 370, 281, 402, 308,
                                 363, 309, 325, 304, 287, 302, 251, 307, 513, 307,
                                 544, 282, 583, 275, 623, 274, 660, 284, 688, 313,
                                 654, 306, 620, 302, 585, 304, 549, 309, 269, 390,
                                 282, 373, 300, 364, 323, 361, 347, 366, 365, 380,
                                 378, 401, 360, 406, 342, 410, 321, 412, 300, 408,
                                 283, 401, 533, 399, 544, 378, 562, 365, 585, 359,
                                 607, 362, 625, 371, 638, 386, 624, 398, 608, 406,
                                 588, 410, 567, 408, 550, 404, 424, 394, 424, 453,
                                 417, 512, 386, 542, 398, 580, 446, 588, 480, 588,
                                 528, 576, 536, 539, 506, 511, 494, 452, 490, 393,
                                 363, 653, 394, 643, 429, 637, 462, 642, 495, 636,
                                 527, 643, 557, 654, 535, 687, 506, 713, 461, 726,
                                 415, 715, 384, 688, 373, 656, 417, 657, 462, 661,
                                 504, 657, 546, 657, 505, 676, 460, 686, 414, 676,
                                 322, 389, 586, 387, 457, 392, 461, 502, 463, 554,
                                 463, 588 };

              eyePoints[2 * 50 + 1] -= 2;
              eyePoints[2 * 49 + 1] -= 3;
              eyePoints[2 * 48 + 1] -= 4;
              eyePoints[2 * 47 + 1] -= 3;
              eyePoints[2 * 46 + 1] -= 2;

              eyePoints[2 * 52 + 1] -= 1;
              eyePoints[2 * 61 + 1] -= 2;
              eyePoints[2 * 60 + 1] -= 3;
              eyePoints[2 * 59 + 1] -= 2;
              eyePoints[2 * 58 + 1] -= 1;
              for (int i = 0; i < 101; i++)
              {
                  g.DrawRectangle(new Pen(Color.Red, 1), new Rectangle(eyePoints[2 * i] - 1, eyePoints[2 * i + 1] - 1, 2, 2));
              }
              g.Dispose();
              DateTime start = DateTime.Now;
              curBitmap = ip.SoftSkin(srcBitmap, new Bitmap(startPath + "\\MakeUp\\MAP.png"), null, skinRatio, 30);

              curBitmap = ip.MKMakeupTransfer(curBitmap, new Bitmap(Application.StartupPath + "\\M3.JPG"), curFacePoints, eyePoints);
              DateTime end = DateTime.Now;
                  label1.Text = "TC: " + (end - start).ToString();
              pictureBox1.Image = curBitmap;
          }
      }
 [DllImport("TestDemo_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.None, ExactSpelling = true)]
        private static extern int IN_Pic_MakeupTransfer(byte* srcData, int width, int height, int stride, int[] srcFacePointsAll, byte* maskData, int mWidth, int mHeight, int mStride, int[] mKeyPointsAll);
        public Bitmap MKMakeupTransfer(Bitmap src, Bitmap mask, int[] srcFacePointsAll, int[] mskFacePointsAll)
        {
            Bitmap a = new Bitmap(src);
            int w = a.Width;
            int h = a.Height;
            BitmapData srcData = a.LockBits(new Rectangle(0, 0, a.Width, a.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            BitmapData mskData = mask.LockBits(new Rectangle(0, 0, mask.Width, mask.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            IN_Pic_MakeupTransfer((byte*)srcData.Scan0, w, h, srcData.Stride, srcFacePointsAll,(byte*)mskData.Scan0, mask.Width, mask.Height, mskData.Stride,  mskFacePointsAll);
            a.UnlockBits(srcData);
            mask.UnlockBits(mskData);
            return a;
        }
最後,給一個測試DEMO點選開啟連結

如果問題,請聯絡QQ:1358009172