zoukankan      html  css  js  c++  java
  • SSE图像算法优化系列十:简单的一个肤色检测算法的SSE优化。

      在很多场合需要高效率的肤色检测代码,本人常用的一个C++版本的代码如下所示:

    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语言的方面的优化可能也就是循环展开了吧,实测速度也没啥大的区别。

          那我们接着来尝试下SIMD指令会有什么结果。

          在决定使用SIMD之前,我一直在犹豫,因为这个算法本身很简单的,就是一些条件判断组合,而SSE非常不适合于做判断运算,同时普通C语言的&&运算具有短路功能,对于本例,当发现其中之一不符合条件后就直接跳出了循环,不再进行后面的条件的计算和判断了,而我代码里也已经把简单的判断条件放在前面,复杂一点的放在后面了。如果使用SSE去实现同样的功能,由于SSE的特性,我们只能对所有的条件进行判断,然后把每个条件判断的结果进行and操作,这个过程是无法从中间中断的(从代码实现上说,是可以的,但是那种方式必然更慢)。这种全面判断的耗时和SSE处理器级别多路并行所带来的加速孰重孰轻,在没有实现之前心里确实有点不确定。

        既然写了本文,那一定是已经实现了该算法的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 一文里有答案。可以用如下代码实现:

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

          第四个条件Red >= Blue 同样可以利用上面这个判断来实现。

          我们再来看第五个条件(Red - Green) >= 10,如果直接计算Red - Green,则需要把他们转换为ushort类型才能满足可能存在的负数的情况,但是如果使用_mm_subs_epu8这个饱和计算函数,当Red < Green时,Red - Green就被截断为0了,这个时候 (Red - Green) >= 10就会返回false了,而如果Red > Green, 则Red - Green的结果就不会发生截断,就是理想的效果,因此,这个问题解决。

          最后一个条件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,有兴趣的朋友可以看看。

  • 相关阅读:
    StreamSets学习系列之StreamSets是什么?
    [转]How to mouse hover using Blue prism on a web page
    [转]UiPath: How to Capture a Mouse Event on Hover Menus?
    [转]VB.net中 excel 的range方法
    [转]Paste from Excel into C# app, retaining full precision
    [转]uipath team svn
    [转]RPA认证 Developer UIPath Certificate,细说uipath认证学习,Online Quiz和Practical Exam项目详解
    [转]UiPath教程:UiPath及其组件介绍
    [转]Introduction
    [转]How to enable macros in Excel 2016, 2013, and 2010
  • 原文地址:https://www.cnblogs.com/Imageshop/p/7364115.html
Copyright © 2011-2022 走看看