Opencv之人脸肤色检测总结

1.RGB颜色空间肤色检测

 在human skin color clustering for face detection一文中提出如下判别算式:

Opencv之人脸肤色检测总结_第1张图片

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;j95&&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;
		}
	}
}
效果图:

Opencv之人脸肤色检测总结_第2张图片


2.二次多项式模型

在Adaptive skin color modeling using the skin locus.pdf  一文中提出如下判别算式:

Opencv之人脸肤色检测总结_第3张图片

其中          

代码也比较简单:

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;jRdown && wr>0.004)
				p_dst[j*width+i]=255;
		}
	}
}

效果图:

Opencv之人脸肤色检测总结_第4张图片


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",back_proj);
		
		 char c=cvWaitKey(33);
		 if(c==27) break;
	 }
	 
	 cvWaitKey(0);
}
效果图:
Opencv之人脸肤色检测总结_第5张图片

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;jmax_variance) 
		{  
            max_variance=variance;  
            threshold=i;  
        }  
	}

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

	cvReleaseImage(&temp);
}

效果图:

Opencv之人脸肤色检测总结_第6张图片


对于肤色检测后,应该加上一些去噪处理,比如形态学的开运算;对于检测特定目标(人脸),在形态学处理后计算出图像轮廓,有选择性地选择一些轮廓,比如像素面积大于一定阈值来进行限制。


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 
#include 
#include 
#include 
#include 
#include

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");
}


效果图:

Opencv之人脸肤色检测总结_第7张图片


6.椭圆模型肤色检测

经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到YCrCb空间,则在CrCb二维空间中这些皮肤像素点近似成一个椭圆分布。因此如果我们得到了一个CrCb的椭圆,下次来一个坐标(Cr, Cb)我们只需判断它是否在椭圆内(包括边界),如果是,则可以判断其为皮肤,否则就是非皮肤像素点。

肤色在CrCb坐标系下,类聚图像如下:

Opencv之人脸肤色检测总结_第8张图片

该方法与反向投影法比较类似。

代码如下:

#include 
#include 
#include 

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;iheight;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;jwidth;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);
}

效果图:

Opencv之人脸肤色检测总结_第9张图片

你可能感兴趣的:(Opencv)