您的位置:首页 > 运维架构

Opencv之人脸肤色检测总结

2014-04-12 12:04 435 查看
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;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.二次多项式模型

在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;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》里面的反向投影,原理如下:

使用 模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤

对测试图像中的每个像素 (

),获取色调数据并找到该色调(

)在直方图中的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",<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);
}


效果图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: