1. 程式人生 > >Opencv之人臉膚色檢測總結

Opencv之人臉膚色檢測總結

1.RGB顏色空間膚色檢測


opencv程式碼非常簡單:

void SkinRGB(IplImage* src,IplImage* dst)
{
	//RGB顏色空間
	//均勻照明:R>95,G>40,B>20,R-B>15,R-G>15
	//側向照明:R>200,G>210,B>170,R-B<=15,R>B,G>B
		
	int height=src->height,width=src->width,channel=src->nChannels,step=src->widthStep;
	int b=0,g=1,r=2;
	cvZero(dst);
	unsigned char* p_src=(unsigned char*)src->imageData;
	unsigned char* p_dst=(unsigned char*)dst->imageData;
		
	for(int j=0;j<height;j++)
	{
		for(int i=0;i<width;i++)
		{
			if((p_src[j*step+i*channel+r]>95&&p_src[j*step+i*channel+g]>40&&p_src[j*step+i*channel+b]>20&&
				(p_src[j*step+i*channel+r]-p_src[j*step+i*channel+b])>15&&(p_src[j*step+i*channel+r]-p_src[j*step+i*channel+g])>15)||
				(p_src[j*step+i*channel+r]>200&&p_src[j*step+i*channel+g]>210&&p_src[j*step+i*channel+b]>170&&
				(p_src[j*step+i*channel+r]-p_src[j*step+i*channel+b])<=15&&p_src[j*step+i*channel+r]>p_src[j*step+i*channel+b]&&
				p_src[j*step+i*channel+g]>p_src[j*step+i*channel+b]))
				p_dst[j*width+i]=255;
		}
	}
}
效果圖:


2.二次多項式模型


其中

程式碼也比較簡單:

void cvSkinRG(IplImage* src,IplImage* dst)
{
	//二項式混合模型
	//來源:Adaptive skin color modeling using the skin locus 

	int b=0,g=1,r=2;
	double Aup=-1.8423,Bup=1.5294,Cup=0.0422,Adown=-0.7279,Bdown=0.6066,Cdown=0.1766;
	double r_ratio=0.0,g_ratio=0.0,sum=0.0,Rup=0.0,Rdown=0.0,wr=0.0;

	int height=src->height,width=src->width,channel=src->nChannels,step=src->widthStep;
	if(dst==NULL)
		dst=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,1);
	cvZero(dst);

	unsigned char* p_src=(unsigned char*)src->imageData;
	unsigned char* p_dst=(unsigned char*)dst->imageData;

	for(int j=0;j<height;j++)
	{
		for(int i=0;i<width;i++)
		{
			sum=p_src[j*step+i*channel+r]+p_src[j*step+i*channel+g]+p_src[j*step+i*channel+b];
			r_ratio=p_src[j*step+i*channel+r]/sum;
			g_ratio=p_src[j*step+i*channel+g]/sum;
			Rup=Aup*r_ratio*r_ratio+Bup*r_ratio+Cup;
			Rdown=Adown*r_ratio*r_ratio+Bdown*r_ratio+Cdown;
			wr=(r_ratio-0.33)*(r_ratio-0.33)+(g_ratio-0.33)*(g_ratio-0.33);
			if(g_ratio<Rup && g_ratio>Rdown && wr>0.004)
				p_dst[j*width+i]=255;
		}
	}
}

效果圖:


3.反向投影法膚色檢測

這個方法來源《學習opencv》裡面的反向投影,原理如下:

使用 模型直方圖 (代表手掌的面板色調) 來檢測測試影象中的面板區域。以下是檢測的步驟

  1. 對測試影象中的每個畫素 ( p(i,j) ),獲取色調資料並找到該色調( ( h_{i,j}, s_{i,j} ) )在直方圖中的bin的位置。

  2. 查詢 模型直方圖 中對應的bin - ( h_{i,j}, s_{i,j} ) - 並讀取該bin的數值。

  3. 將此數值儲存在新的影象中(BackProjection)。 你也可以先歸一化 模型直方圖 ,這樣測試影象的輸出就可以在螢幕顯示了。

  4. 通過對測試影象中的每個畫素採用以上步驟.

也可以參考:反向投影  其實現程式碼:
int main()
{
    /**********************************\
	*           back Project           *
	\**********************************/
    
     //一維直方圖
     //獲取直方圖模型
     IplImage* src=cvLoadImage("base.jpg",1);//下載base影象,base影象為目標顏色,即包含人臉顏色的影象
	 IplImage* hsv_src=cvCreateImage(cvGetSize(src),8,3);//建立一幅與src同樣大小3通道的hsv影象
	 cvCvtColor(src,hsv_src,CV_RGB2HSV);//將src從rgb顏色空間轉換到hsv顏色空間
	 IplImage* h_plane=cvCreateImage(cvGetSize(src),8,1);//建立一幅與src同樣大小單通道的h影象
	 cvSplit(hsv_src,h_plane,0,0,0);//取hsv影象中h分量
	 
	 // 計算base的h分量直方圖
	 //-------------------------------------------------------------
	 int dims=1;
	 int size[]={180};
	 float h_range[]={0,180};
	 float* ranges[]={h_range};
	 IplImage* hsv_test=NULL;
	 IplImage* h_test_plane=NULL;
	 IplImage* back_proj=NULL;
	 IplImage* img_dst=NULL;

	 CvHistogram* hist=cvCreateHist(dims,size,CV_HIST_ARRAY,ranges,1);
	 cvCalcHist(&h_plane,hist,0,0);
	 //---------------------------------------------------------------
	 IplImage* img_src=NULL;
	 IplImage* test=NULL;
	 CvCapture* capture=cvCreateCameraCapture(0);
	 cvNamedWindow("Input Video",1);
	 cvNamedWindow("Skin Detect",1);

	 while(1)
	 {
		 img_src=cvQueryFrame(capture);
		 if(!img_src) break;
		 cvShowImage("Input Video",img_src);

		 if(test==NULL)
		 {
			 test=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
		 }
		 cvZero(test);
		 cvCopy(img_src,test);
		 if(hsv_test==NULL)
			 hsv_test=cvCreateImage(cvGetSize(test),8,3);
		 cvZero(hsv_test);
		 cvCvtColor(test,hsv_test,CV_RGB2HSV);//轉換到hsv空間
		 if(h_test_plane==NULL)
			 h_test_plane=cvCreateImage(cvGetSize(test),8,1);
		 cvZero(h_test_plane);
		 cvSplit(hsv_test,h_test_plane,0,0,0);//取h分量
		 if(back_proj==NULL)
			 back_proj=cvCreateImage(cvGetSize(test),8,1);//用於存取膚色檢測結果
		 cvZero(back_proj);

		 cvCalcBackProject(&h_test_plane,back_proj,hist);//反向投影計算投影
		 cvThreshold(back_proj,back_proj,250,255,CV_THRESH_BINARY); 
		 cvShowImage("Skin Detect",<span style="font-family: sans-serif;">back_proj</span>);
		
		 char c=cvWaitKey(33);
		 if(c==27) break;
	 }
	 
	 cvWaitKey(0);
}
效果圖:

4.YCrCb顏色空間Cr分量+Ostu法

這個方法暫時沒有找到論文來源,參考別人的部落格,原理很簡單:

a.將RGB影象轉換到YCrCb顏色空間,提取Cr分量影象

b.對Cr做自適應二值化處理(Ostu法)

程式碼:

void cvSkinOtsu(IplImage* src, IplImage* dst)
{
	//Cr自適應閾值法
	//

	IplImage* img_ycrcb=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,3);
	IplImage* img_cr=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,1);

	cvCvtColor(src,img_ycrcb,CV_BGR2YCrCb);
	cvSplit(img_ycrcb,0,img_cr,0,0);
	cvThresholdOtsu(img_cr,img_cr);
	cvCopy(img_cr,dst);

	cvReleaseImage(&img_ycrcb);
	cvReleaseImage(&img_cr);
}

Ostu法:

void cvThresholdOtsu(IplImage* src, IplImage* dst)
{
	int height=src->height,width=src->width,threshold=0;
	double histogram[256]={0};
	double average=0.0,max_variance=0.0,w=0.0,u=0.0;
	IplImage* temp=cvCreateImage(cvGetSize(src),src->depth,1);
	if(src->nChannels!=1)
		cvCvtColor(src,temp,CV_BGR2GRAY);
	else
		cvCopy(src,temp);

	unsigned char* p_temp=(unsigned char*)temp->imageData;

	//計算灰度直方圖
	//
	for(int j=0;j<height;j++)
	{
		for(int i=0;i<width;i++)
		{
			histogram[p_temp[j*width+i]]++;
		}
	}
	for(int i=0;i<256;i++)
		histogram[i]=histogram[i]/(double)(height*width);

	//計算平局值
	for(int i=0;i<256;i++)
		average+=i*histogram[i];

	for(int i=0;i<256;i++)
	{
		w+=histogram[i];
		u+=i*histogram[i];

		double t=average*w-u;  
        double variance=t*t/(w*(1-w));  
        if(variance>max_variance) 
		{  
            max_variance=variance;  
            threshold=i;  
        }  
	}

	cvThreshold(temp,dst,threshold,255,CV_THRESH_BINARY);

	cvReleaseImage(&temp);
}

效果圖:


對於膚色檢測後,應該加上一些去噪處理,比如形態學的開運算;對於檢測特定目標(人臉),在形態學處理後計算出影象輪廓,有選擇性地選擇一些輪廓,比如像素面積大於一定閾值來進行限制。

5.opencv自帶膚色檢測類AdaptiveSkinDetector

CvAdaptiveSkinDetector類中的2個比較重要的函式:

CvAdaptiveSkinDetector(int samplingDivider = 1, int morphingMethod = MORPHING_METHOD_NONE);

該函式為類的建構函式,其中引數1表示的是樣本取樣的間隔,預設情況下為1,即表示不進行降取樣;引數2為圖形學操作方式,即對用面板檢測後的影象進行圖形學操作。其取值有3種可能,如果為MORPHING_METHOD_ERODE,則表示只進行一次腐蝕操作;如果為MORPHING_METHOD_ERODE_ERODE,則表示連續進行2次腐蝕操作;如果為MORPHING_METHOD_ERODE_DILATE,則表示先進行一次腐蝕操作,後進行一次膨脹操作。 

virtual void process(IplImage *inputBGRImage, IplImage *outputHueMask);

該函式為面板檢測的核心函式,引數1為需要進行面板檢測的輸入影象;引數2為輸出面板的掩膜影象,如果其值為1,代表該畫素為面板,否則當其為0時,代表為非面板。另外需要注意的是,這個函式只有opencv的c版本的,因為CvAdaptiveSkinDetector這個類放在opencv原始碼裡的contrib目錄裡,即表示比較新的但不成熟的演算法

程式碼:

#include <opencv\cv.h>
#include <opencv\highgui.h>
#include <contrib\contrib.hpp>
#include <core\core.hpp>
#include <imgproc\imgproc.hpp>
#include<time.h>

int main()
{
	CvCapture* capture=cvCreateCameraCapture(0);
	cvNamedWindow("Input Video",1);
	cvNamedWindow("Output Video",1);

	IplImage* img_src=NULL;
	IplImage* input_img=NULL;
	IplImage* output_mask=NULL;
	IplImage* output_img=NULL;

	clock_t start,finish;
	double duration;

	CvAdaptiveSkinDetector skin_detector(1,CvAdaptiveSkinDetector::MORPHING_METHOD_ERODE_DILATE);    //定義膚色檢測運算元

	while(1)
	{
		img_src=cvQueryFrame(capture);
		if(!img_src) break;
		cvShowImage("Input Video",img_src);
		if(input_img==NULL)
		{
			input_img=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
		}
		cvCopy(img_src,input_img);

		output_img=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
		cvZero(output_img);

		if(output_mask==NULL)
		{
			output_mask=cvCreateImage(cvGetSize(img_src),img_src->depth,1);
		}

		//膚色檢測
		//
		start=clock();
		skin_detector.process(input_img,output_mask);
		finish=clock();
		duration=(double)(finish-start)/CLOCKS_PER_SEC;
		printf("elapsed time :%.0f 毫秒\n",duration*1000); 

		cvCopy(img_src,output_img,output_mask);
		cvShowImage("Output Video",output_img);

		char c=cvWaitKey(33);
		if(c==27)break;
	}

	cvReleaseCapture(&capture);
	cvDestroyWindow("Video");
}

效果圖:


6.橢圓模型膚色檢測

經過前人學者大量的面板統計資訊可以知道,如果將面板資訊對映到YCrCb空間,則在CrCb二維空間中這些面板畫素點近似成一個橢圓分佈。因此如果我們得到了一個CrCb的橢圓,下次來一個座標(Cr, Cb)我們只需判斷它是否在橢圓內(包括邊界),如果是,則可以判斷其為面板,否則就是非面板畫素點。

膚色在CrCb座標系下,類聚影象如下:


該方法與反向投影法比較類似。

程式碼如下:

#include <opencv\cv.h>
#include <opencv\highgui.h>
#include <time.h>

int main()
{
	//膚色橢圓
	IplImage* skin_ellipse=cvCreateImage(cvSize(256,256),IPL_DEPTH_8U,1);
	cvZero(skin_ellipse);
	cvEllipse(skin_ellipse, cvPoint(113, 155.6), cvSize(23.4, 15.2), 43.0, 0.0, 360.0, cvScalar(255, 255, 255), -1);  

	CvCapture* capture=cvCreateCameraCapture(0);
	IplImage* img_src=NULL;
	IplImage* img_ycrcb=NULL;
	IplImage* output_mask=NULL;
	IplImage* img_dst=NULL;
	CvMemStorage* storage=cvCreateMemStorage(0);
	CvSeq* first_contour;
	bool create_bool=false;
	
	cvNamedWindow("Input Video",1);
	cvNamedWindow("Skin Detect",1);
	
	clock_t start,finish;
	double duration;

	while(1)
	{
		img_src=cvQueryFrame(capture);
		if(!img_src) break;
		cvShowImage("Input Video",img_src);

		if(!create_bool)
		{
			img_ycrcb=cvCreateImage(cvGetSize(img_src),IPL_DEPTH_8U,3);
			img_dst=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
			output_mask=cvCreateImage(cvGetSize(img_src),IPL_DEPTH_8U,1);
			create_bool=true;
		}	
		cvZero(img_dst);
		cvZero(output_mask);
		
		start=clock();
		cvCvtColor(img_src,img_ycrcb,CV_BGR2YCrCb);
		unsigned char* p_ellipse=(unsigned char*)skin_ellipse->imageData;
		for(int i=0;i<img_ycrcb->height;i++)
		{
			unsigned char* p_mask=(unsigned char*)(output_mask->imageData+i*output_mask->widthStep);
			unsigned char* p_ycrcb=(unsigned char*)(img_ycrcb->imageData+i*img_ycrcb->widthStep);
			for(int j=0;j<img_ycrcb->width;j++)
			{
				if(p_ellipse[p_ycrcb[j*3+1]*skin_ellipse->widthStep+p_ycrcb[j*3+2]] > 0)  
                    p_mask[j] = 255;  
			}
		}

		finish=clock();
		duration=(double)(finish-start)/CLOCKS_PER_SEC;
		printf("elapsed time :%.0f 毫秒\n",duration*1000); 

		cvCopy(img_src,img_dst,output_mask);
		cvShowImage("Skin Detect",img_dst);

		char c=cvWaitKey(33);
		if(c==27) break;
	}

	cvReleaseImage(&img_src);
	cvReleaseCapture(&capture);
	cvReleaseImage(&img_ycrcb);
	cvReleaseImage(&output_mask);
	cvDestroyWindow("Skin Detect");
	cvDestroyWindow("Input Video");
	cvWaitKey(0);
}

效果圖: