很久沒寫博客了,由於最近都忙着趕項目和打比賽==| 好吧,今天我打算寫一篇關於使用opencv作皮膚檢測的技術總結。那首先列一些如今主流的皮膚檢測的方法都有哪些:git
那咱們今天就來一一實現它吧!github
根據RGB顏色模型找出定義好的膚色範圍內的像素點,範圍外的像素點設爲黑色。算法
查閱資料後能夠知道,前人作了大量研究,膚色在RGB模型下的範圍基本知足如下約束:api
在均勻光照下應知足如下判別式:ide
R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B函數
在側光拍攝環境下:spa
R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B3d
既然判別式已經肯定了,因此按照判別式寫程序就很簡單了。code
/*基於RGB範圍的皮膚檢測*/ Mat RGB_detect(Mat& img) { /* R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B OR R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B */ Mat detect = img.clone(); detect.setTo(0); if (img.empty() || img.channels() != 3) { return detect; } for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) { uchar *p_detect = detect.ptr<uchar>(i, j); uchar *p_img = img.ptr<uchar>(i, j); if ((p_img[2] > 95 && p_img[1]>40 && p_img[0] > 20 && (MAX(p_img[0], MAX(p_img[1], p_img[2])) - MIN(p_img[0], MIN(p_img[1], p_img[2])) > 15) && abs(p_img[2] - p_img[1]) > 15 && p_img[2] > p_img[1] && p_img[1] > p_img[0]) || (p_img[2] > 200 && p_img[1] > 210 && p_img[0] > 170 && abs(p_img[2] - p_img[1]) <= 15 && p_img[2] > p_img[0] && p_img[1] > p_img[0])) { p_detect[0] = p_img[0]; p_detect[1] = p_img[1]; p_detect[2] = p_img[2]; } } } return detect; }
檢測效果以下:blog
從檢測結果能夠看出,皮膚的檢測效果並很差,首先皮膚檢測的完整性並不高,一些稍微光線很差的區域也無法檢測出皮膚來。第二,這種基於RBG範圍來斷定皮膚的算法太受光線的影響了,魯棒性確實很差。
通過前人學者大量的皮膚統計信息能夠知道,若是將皮膚信息映射到YCrCb空間,則在CrCb二維空間中這些皮膚像素點近似成一個橢圓分佈。所以若是咱們獲得了一個CrCb的橢圓,下次來一個座標(Cr, Cb)咱們只需判斷它是否在橢圓內(包括邊界),若是是,則能夠判斷其爲皮膚,不然就是非皮膚像素點。
/*基於橢圓皮膚模型的皮膚檢測*/ Mat ellipse_detect(Mat& src) { Mat img = src.clone(); Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1); //利用opencv自帶的橢圓生成函數先生成一個膚色橢圓模型 ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 15.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1); Mat ycrcb_image; Mat output_mask = Mat::zeros(img.size(), CV_8UC1); cvtColor(img, ycrcb_image, CV_BGR2YCrCb); //首先轉換成到YCrCb空間 for (int i = 0; i < img.cols; i++) //利用橢圓皮膚模型進行皮膚檢測 for (int j = 0; j < img.rows; j++) { Vec3b ycrcb = ycrcb_image.at<Vec3b>(j, i); if (skinCrCbHist.at<uchar>(ycrcb[1], ycrcb[2]) > 0) //若是該落在皮膚模型橢圓區域內,該點就是皮膚像素點 output_mask.at<uchar>(j, i) = 255; } Mat detect; img.copyTo(detect,output_mask); //返回膚色圖 return detect; }
檢測效果:
這種基於膚色橢圓模型的算法的皮膚檢測較上面算法在效果上有着較大的提高,基本上改檢測的皮膚都檢測到了,對光線的抗干擾能力也是比較強的,檢測出來的圖像都比較乾淨,背景雜質較少。
這裏先簡單介紹YCrCb顏色空間。
YCrCb即YUV,其中「Y」表示明亮度(Luminance或Luma),也就是灰階值;而「U」和「V」 表示的則是色度(Chrominance或Chroma),做用是描述影像色彩及飽和度,用於指定像素的顏色。「亮度」是透過RGB輸入信號來創建的,方法是將RGB信號的特定部分疊加到一塊兒。「色度」則定義了顏色的兩個方面─色調與飽和度,分別用Cr和Cb來表示。其中,Cr反映了RGB輸入信號紅色部分與RGB信號亮度值之間的差別。而Cb反映的是RGB輸入信號藍色部分與RGB信號亮度值之間的差別。
該方法的原理也很簡單:
a.將RGB圖像轉換到YCrCb顏色空間,提取Cr份量圖像
b.對Cr作自二值化閾值分割處理(Otsu法)
/*YCrCb顏色空間Cr份量+Otsu法*/ Mat YCrCb_Otsu_detect(Mat& src) { Mat ycrcb_image; cvtColor(src, ycrcb_image, CV_BGR2YCrCb); //首先轉換成到YCrCb空間 Mat detect; vector<Mat> channels; split(ycrcb_image, channels); Mat output_mask = channels[1]; threshold(output_mask, output_mask, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); src.copyTo(detect, output_mask); return detect; }
檢測效果:
這個方法跟法一其實大同小異,只是顏色空間不一樣而已。據資料顯示,正常黃種人的Cr份量大約在133至173之間,Cb份量大約在77至127之間。你們能夠根據本身項目需求放大或縮小這兩個份量的範圍,會有不一樣的效果。
/*YCrCb顏色空間Cr,Cb範圍篩選法*/ Mat YCrCb_detect(Mat & src) { Mat ycrcb_image; int Cr = 1; int Cb = 2; cvtColor(src, ycrcb_image, CV_BGR2YCrCb); //首先轉換成到YCrCb空間 Mat output_mask = Mat::zeros(src.size(), CV_8UC1); for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { uchar *p_mask = output_mask.ptr<uchar>(i, j); uchar *p_src = ycrcb_image.ptr<uchar>(i, j); if (p_src[Cr] >= 133 && p_src[Cr] <= 173 && p_src[Cb] >= 77 && p_src[Cb] <= 127) { p_mask[0] = 255; } } } Mat detect; src.copyTo(detect, output_mask);; return detect; }
檢測效果:
一樣地,也是在不一樣的顏色空間下采起相應的顏色範圍將皮膚分割出來。
/*HSV顏色空間H範圍篩選法*/ Mat HSV_detector(Mat& src) { Mat hsv_image; int h = 0; int s = 1; int v = 2; cvtColor(src, hsv_image, CV_BGR2HSV); //首先轉換成到YCrCb空間 Mat output_mask = Mat::zeros(src.size(), CV_8UC1); for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { uchar *p_mask = output_mask.ptr<uchar>(i, j); uchar *p_src = hsv_image.ptr<uchar>(i, j); if (p_src[h] >= 0 && p_src[h] <= 20 && p_src[s] >=48 && p_src[v] >=50) { p_mask[0] = 255; } } } Mat detect; src.copyTo(detect, output_mask);; return detect; }
檢測效果:
opencv提供了下面這個好用的皮膚檢測函數:
CvAdaptiveSkinDetector(int samplingDivider = 1, int morphingMethod = MORPHING_METHOD_NONE);
這個函數的第二個參數表示皮膚檢測過程時所採用的圖形學操做方式,其取值有3種可能:
/*opencv自帶膚色檢測類AdaptiveSkinDetector*/ Mat AdaptiveSkinDetector_detect(Mat& src) { IplImage *frame; frame = &IplImage(src); //Mat -> IplImage CvAdaptiveSkinDetector filter(1, CvAdaptiveSkinDetector::MORPHING_METHOD_ERODE_DILATE); IplImage *maskImg = cvCreateImage(cvSize(src.cols, src.rows), IPL_DEPTH_8U, 1); IplImage *skinImg = cvCreateImage(cvSize(src.cols, src.rows), IPL_DEPTH_8U, 3); cvZero(skinImg); filter.process(frame, maskImg); // process the frame cvCopy(frame, skinImg, maskImg); Mat tmp(skinImg); //IplImage -> Mat Mat detect = tmp.clone(); cvReleaseImage(&skinImg); cvReleaseImage(&maskImg); return detect; }
從效果圖看來,背景多了不少白色的雜質,單從直觀效果來看,貌似還不如我上面寫的幾個算法,難道跟場景有關係?固然,opencv自帶的這個api在皮膚檢測上確實至關不錯的。
今天花了將近7個小時才擼了這篇文章出來==!這篇文章對各大主流的皮膚檢測算法作了個總結和實現。其實說白了,每一個算法的思想都是大同小異的,都是根據總結出來的一些經驗,設定皮膚顏色的範圍,再將其過濾出來,不一樣的只是過濾的過程在不一樣的顏色空間下進行而已。咱們能夠根據本身的應用場景,適當地修改這些範圍,以得到滿意的結果。能夠改善的方向就是,咱們能夠用合適的濾波器或者形態學處理一些噪聲,來使得提取出來的皮膚更爲乾淨。
完整代碼能夠訪問個人github來獲取~