http://blog.csdn.net/laviewpbt/article/details/10027907
由于CSDN博客和博客园的编辑方面有不一致的地方,导致文中部分图片错位,为不影响浏览效果,建议点击打开链接。
由于能力有限,算法层面的东西自己去创新的很少,很多都是从现有的论文中学习,然后实践的。
本文涉及的很多算法,在网络上也有不少同类型的文章,但是肯定的一点就是,很多都是不配代码的,或者所附带的代码都是象征性的,速度慢,不优雅,不具有实用价值,本文努力解决这些问题。
文中各算法出现的顺序并不代表算法的优越性,仅仅是作者随机排布的而已。
1、二次多项式混合模型
二次多项式混合模型首先有SORIANO提出,此后CHIANG对此进行了改进。改进后的模型由两个R-G平面的二次多项式和一个圆方程构成:
在以上三个方程的基础上,肤色区域可以通过一下规则实现:

上述算法的参考论文:Adaptive skin color modeling using the skin locus.pdf
A novel method for detecting lips,eyes and faces in real time
以及百度文库相关文章:基于混合肤色模型的快速人脸检测算法
上式中,小写r,g,b(未涉及)为对R/G/B(byte类型的数据,0-255)进行归一化后的数据,即:
如上所示,算法中涉及到了不少的浮点运算,以及大量的乘法,如果按照源汁原味的来编写代码,程序的效率可想而知。因此,我们着手于算法的优化。
首先,我们来看四个判断条件,由于判断条件是不分先后,需要同时满足的地方才是区域,因此应该把简单的判断条件放在最前面判断。
首先看如果符合了判断条件R4,条件R3中的R>G肯定是已经成立的,则只需要判断G是否大于B,这是优化手段1。
然后我们来看R2的优化,为方便表达,我们这里令Sum=R+G+B,将判断条件R2展开:
上式子最后一步同时乘以156, 理论上说156×0.33=51.48,不应该取52的,不过这个0.33本来就是个经验数据,谁说不能是1/3呢。
到此,我们看到在式子的最右侧还有个浮点数0.0624,如果不消除该数据,算法速度依旧会有大的影响,常常研究移位的朋友肯定对0.0625这个数字很熟悉,1/16=0.0625,不是吗,懂了吗,还不懂,看代码吧(这里的式子很多都是经验公式,因此,稍微修改一些参数对结果基本无影响)。
上述这样做的目的,无非是将浮点数的运算全部转换为整数的运算。
最后来看式R1的优化,R1实际上也是两个条件,把他分开来,分别称为R11及R12,对于R11,同样展开:
现在大部分的PC都还是32位的系统,因此,使用32位的整数类数据类型速度是最快的,因此,如果上述放大系数的取夺就必须主要使得计算式两边的值都在int.MinValue和 int.MaxValue之间,比如上式,>号左侧算式的肯能最大取值为10000×255×765,是小于int.MaxValue所能表达的范围的,因此放大系数是合理的。
对于R12的展开我想应该不需要我在去贴出来了吧。
算法部分参考代码:
for (Y = 0; Y < Height; Y++) { Pointer = Scan0 + Y * Stride; SkinP = SkinScan0 + Y * SkinStride; for (X = 0; X < Width; X++) { *SkinP = 0; // 非皮肤区域为黑色 Blue = *Pointer; Green = *(Pointer + 1); Red = *(Pointer + 2); if (Red - Green >= 45) // 符合条件R4 { if (Green > Blue) // 符合条件R3 { Sum = Red + Green + Blue; T1 = 156 * Red - 52 * Sum; T2 = 156 * Green - 52 * Sum; if (T1 * T1 + T2 * T2 >= (Sum * Sum) >> 4) // 符合条件R2,在32位系统要尽量避免用long类型数据, { T1 = 10000 * Green * Sum; Lower = - 7760 * Red * Red + 5601 * Red * Sum + 1766 * Sum * Sum; // 把这里的公用的乘法提取出来基本没啥优化的效果 if (T1 > Lower) // 符合条件R11 { Upper = - 13767 * Red * Red + 10743 * Red * Sum + 1452 * Sum * Sum ; if (T1 < Upper) // 符合条件R12 { *SkinP = 255; } } } } } Pointer += 3; SkinP++; }
本人特喜欢优化,特别是代码层面的优化,比如上述的 Lower = 5601 * Red * Sum + 1766 * Sum * Sum 这句,偶尔我写成Lower =- Red * Red * 7760+ 5601 * Red * Sum + 1766 * Sum * Sum 这样,然后没事的时候我反汇编了两种写法有什么不同,结果如下:
- Lower =-7760 * Red * Red+ 5601 * Red * Sum + 1766 * Sum * Sum ; // 把这里的公用的乘法提取出来基本没啥优化的效果
- 00000118 imul ebx,ecx,0FFFFE1B0h
- 0000011e imul ebx,ecx
- 00000121 imul eax,ecx,15E1h
- 00000127 imul eax,esi
- 0000012a add ebx,eax
- 0000012c imul eax,esi,6E6h
- 00000132 imul eax,esi
- 00000135 add ebx,eax
- Lower = -Red * Red * 7760 * +5601 * Red * Sum + 1766 * Sum * Sum; // 把这里的公用的乘法提取出来基本没啥优化的效果
- 00000118 mov ebx,ecx
- 0000011a neg ebx
- 0000011c imul ebx,ecx
- 0000011f imul ebx,ebx,1E50h
- 00000125 imul ebx,ebx,15E1h
- 0000012b imul ebx,ecx
- 0000012e imul ebx,esi
- 00000131 imul eax,esi,6E6h
- 00000137 imul eax,esi
- 0000013a add ebx,eax
可见多了两条汇编语句的。可能这个优化举在这里不合适,因为有个系数-7760,一般谁都不会像上面写,但是如果系数是-1,那就比一定了,比如如果是-Red+Blue 和Blue-Red那就有着截然不同的意义了。
这个算法的皮肤检测效果还是很不错的,那原文中的图像来举例如下:
原图 梦版图 合成图
然后贴一张别人博客上的照片的例子(一群帅哥和美女):
检测结果:
由于是有选择性的执行,因此程序执行的速度其实和图像的内容有关,同样一副大小的图像,如果皮肤部分站的比例越大,执行的时间可能就会越长,就上述这幅800*600的图像来说,在我I3的笔记本上仅用了4ms就得到了结果,因此速度是相当的快的。
测试工程随后附上。
*****************************基本上我不提供源代码,但是我会尽量用文字把对应的算法描述清楚或提供参考文档**************************
*******************************因为靠自己的努力和实践写出来的效果才真正是自己的东西,人一定要靠自己****************************
***************************作者: laviewpbt 时间: 2013.8.17 联系QQ: 33184777 转载请保留本行信息*************************