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,有兴趣的朋友能够看看。

相关文章
相关标签/搜索