1.RGB颜色空间肤色检测
在human skin color clustering for face detection一文中提出如下判别算式:
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;
}
}
}
效果图:
2.二次多项式模型
在Adaptive skin color modeling using the skin locus.pdf 一文中提出如下判别算式:
其中
代码也比较简单:
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;
}
}
}
3.反向投影法肤色检测
这个方法来源《学习opencv》里面的反向投影,原理如下:
使用 模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤
对测试图像中的每个像素 ( ),获取色调数据并找到该色调( )在直方图中的bin的位置。
查询 模型直方图 中对应的bin - - 并读取该bin的数值。
将此数值储存在新的图像中(BackProjection)。 你也可以先归一化 模型直方图 ,这样测试图像的输出就可以在屏幕显示了。
通过对测试图像中的每个像素采用以上步骤.
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);
}
效果图:
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);
}
对于肤色检测后,应该加上一些去噪处理,比如形态学的开运算;对于检测特定目标(人脸),在形态学处理后计算出图像轮廓,有选择性地选择一些轮廓,比如像素面积大于一定阈值来进行限制。
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");
}
效果图:
6.椭圆模型肤色检测
经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到YCrCb空间,则在CrCb二维空间中这些皮肤像素点近似成一个椭圆分布。因此如果我们得到了一个CrCb的椭圆,下次来一个坐标(Cr, Cb)我们只需判断它是否在椭圆内(包括边界),如果是,则可以判断其为皮肤,否则就是非皮肤像素点。
肤色在CrCb坐标系下,类聚图像如下:
该方法与反向投影法比较类似。
代码如下:
#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);
}