SSE圖像算法優化系列十:簡單的一個膚色檢測算法的SSE優化。 SSE圖像算法優化系列八:天然飽和度(Vibrance)算法的模擬實現及其SSE優化(附源碼,可做爲SSE圖像入門,Vibrance算法

  在不少場合須要高效率的膚色檢測代碼,本人經常使用的一個C++版本的代碼以下所示:html

void IM_GetRoughSkinRegion(unsigned char *Src, unsigned char *Skin, int Width, int Height, int Stride)
{
    for (int Y = 0; Y < Height; Y++)
    {
        unsigned char *LinePS = Src + Y * Stride;                    //    源圖的第Y行像素的首地址
        unsigned char *LinePD = Skin + Y * Width;                    //    Skin區域的第Y行像素的首地址    for (int X = 0; X < Width; X++)
        for (int X = 0; X < Width; X++)
        {
            int Blue = LinePS[0], Green = LinePS[1], Red = LinePS[2];
            if (Red >= 60 && Green >= 40 && Blue >= 20 && Red >= Blue && (Red - Green) >= 10 && IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10)
                LinePD[X] = 255;                                    //    全爲膚色部分                                                                            
            else
                LinePD[X] = 16;
            LinePS += 3;                                            //    移到下一個像素        
        }
    }
}

  這段代碼效率的效率已經很高了,對於1080P含有人臉的通常圖像大概也就4.0ms就能處理完,效果嘛對於正常光照和膚色的檢測也還湊合,以下所示。算法

                  

      4.0ms確實已經很快了,不過在不少實時的場合,每幀裏能節省下來1MS對於總體的流暢性都是有好處的,這個算法還有沒有提高速度的空間呢。常規的C語言的方面的優化可能也就是循環展開了吧,實測速度也沒啥大的區別。ide

      那咱們接着來嘗試下SIMD指令會有什麼結果。函數

      在決定使用SIMD以前,我一直在猶豫,由於這個算法自己很簡單的,就是一些條件判斷組合,而SSE很是不適合於作判斷運算,同時普通C語言的&&運算具備短路功能,對於本例,當發現其中之一不符合條件後就直接跳出了循環,再也不進行後面的條件的計算和判斷了,而我代碼裏也已經把簡單的判斷條件放在前面,複雜一點的放在後面了。若是使用SSE去實現一樣的功能,因爲SSE的特性,咱們只能對全部的條件進行判斷,而後把每一個條件判斷的結果進行and操做,這個過程是沒法從中間中斷的(從代碼實現上說,是能夠的,可是那種方式必然更慢)。這種全面判斷的耗時和SSE處理器級別多路並行所帶來的加速孰重孰輕,在沒有實現以前內心確實有點不肯定。post

    既然寫了本文,那必定是已經實現了該算法的SSE版本代碼,咱們來講爲分析下實現的方式和可能用到的函數。 測試

      首先,咱們要把R/G/B份量分別提取到一個SSE變量中,這個咱們在SSE圖像算法優化系列八:天然飽和度(Vibrance)算法的模擬實現及其SSE優化(附源碼,可做爲SSE圖像入門,Vibrance算法也可用於簡單的膚色調整) 一文裏已經有提到了實現。優化

      接着看前面的三個判斷條件   Red >= 60 && Green >= 40 && Blue >= 20 , 咱們須要一個unsigned char類型的比較函數,而SSE只提供了singed char類型的SSE比較函數,這個問題在A few missing SSE intrinsics 一文裏有答案。能夠用以下代碼實現:url

#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
 

      第四個條件Red >= Blue 一樣能夠利用上面這個判斷來實現。spa

      咱們再來看第五個條件(Red - Green) >= 10,若是直接計算Red - Green,則須要把他們轉換爲ushort類型才能知足可能存在的負數的狀況,可是若是使用_mm_subs_epu8這個飽和計算函數,當Red < Green時,Red - Green就被截斷爲0了,這個時候 (Red - Green) >= 10就會返回false了,而若是Red > Green, 則Red - Green的結果就不會發生截斷,就是理想的效果,所以,這個問題解決。code

      最後一個條件IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10,這個也很簡單,先用_mm_max_epu8和_mm_min_epu8得到B/G/R三份量的最大值和最小值,這個時候很明顯max>min,所以有能夠直接使用_mm_subs_epu8函數生產不會截斷的正確結果。

      咱們注意到SSE的比較函數(字節類型的)的返回結果只有0和255這兩種,所以上述的6個判斷條件結果直接進行and操做就能夠得到最後的組合值了,知足全部的條件的像素結果就爲255,而其餘的則爲0。

      在咱們C語言版本的代碼中,不知足條件的像素被設置爲了16或者其餘非零的值,這又怎麼辦呢,一樣的道理,255和其餘數進行or操做仍是255,而0和其餘數進行or操做就會變爲其餘數,所以最後再把上述結果和16這個常數進行or操做就能夠獲得正確的結果了,整理下來,主要代碼以下所示:

Src1 = _mm_loadu_si128((__m128i *)(LinePS + 0));
Src2 = _mm_loadu_si128((__m128i *)(LinePS + 16));
Src3 = _mm_loadu_si128((__m128i *)(LinePS + 32));

Blue = _mm_shuffle_epi8(Src1, _mm_setr_epi8(0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
Blue = _mm_or_si128(Blue, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1)));
Blue = _mm_or_si128(Blue, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13)));

Green = _mm_shuffle_epi8(Src1, _mm_setr_epi8(1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
Green = _mm_or_si128(Green, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1)));
Green = _mm_or_si128(Green, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14)));

Red = _mm_shuffle_epi8(Src1, _mm_setr_epi8(2, 5, 8, 11, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
Red = _mm_or_si128(Red, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1)));
Red = _mm_or_si128(Red, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15)));
            
Max = _mm_max_epu8(_mm_max_epu8(Blue, Green), Red);                                                //    IM_Max(IM_Max(Red, Green), Blue)
Min = _mm_min_epu8(_mm_min_epu8(Blue, Green), Red);                                                //    IM_Min(IM_Min(Red, Green), Blue)
Result = _mm_cmpge_epu8(Blue, _mm_set1_epi8(20));                                                //    Blue >= 20
Result = _mm_and_si128(Result, _mm_cmpge_epu8(Green, _mm_set1_epi8(40)));                        //    Green >= 40
Result = _mm_and_si128(Result, _mm_cmpge_epu8(Red, _mm_set1_epi8(60)));                            //    Red >= 60
Result = _mm_and_si128(Result, _mm_cmpge_epu8(Red, Blue));                                        //  Red >= Blue
Result = _mm_and_si128(Result, _mm_cmpge_epu8(_mm_subs_epu8(Red, Green), _mm_set1_epi8(10)));    //    (Red - Green) >= 10 
Result = _mm_and_si128(Result, _mm_cmpge_epu8(_mm_subs_epu8(Max, Min), _mm_set1_epi8(10)));        //    IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10
Result = _mm_or_si128(Result, _mm_set1_epi8(16));
_mm_storeu_si128((__m128i*)(LinePD + 0), Result);

  循環計算100次的速度測試:

環境

1920*1080 膚色約佔一半圖

1920*1080 全圖膚色

1920*1080 全圖無膚色

標準C語言

 400ms

 550ms

360ms 

SSE優化

 70ms

 70ms

70ms 



 

 

  

     

     能夠看到,雖然SSE優化後的計算量理論上比普通的C語言大不少,可是SSE優化的算法有兩個好處,第一是速度快不少,最大加速比約有8倍了,第二是SSE的計算時間和圖像內容是無關的。

     這個結果令我大爲震驚,看樣子SSE一次性處理16個字節的能力不是蓋的,同時也說明普通的C語言的跳轉也仍是耗時的。

     完整工程的地址:http://files.cnblogs.com/files/Imageshop/GetSkinArea.rar

     結合膚色檢測以及之前研究的積分圖、均方差去噪等算法,我用純SSE寫了一個綜合的MakeUp算法,處理單幀的1080P的圖像用時大概也就在25ms內實現(單核),比純C語言的要快了3到4倍,以下圖所示:

   http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar,這裏是一個我所有用SSE優化的圖像處理的Demo,有興趣的朋友能夠看看。

相關文章
相關標籤/搜索