由於能力有限,算法層面的東西自己去創新的很少,很多都是從現有的論文中學習,然后實踐的。
本文涉及的很多算法,在網絡上也有不少同類型的文章,但是肯定的一點就是,很多都是不配代碼的,或者所附帶的代碼都是象征性的,速度慢,不優雅,不具有實用價值,本文努力解決這些問題。
文中各算法出現的順序並不代表算法的優越性,僅僅是作者隨機排布的而已。
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 轉載請保留本行信息*************************