由於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 轉載請保留本行資訊*************************