首页 > 代码库 > 自适应肤色识别

自适应肤色识别

      肤色识别是数字图像的一个重要课题,现在已经有很多方法解决这个问题,其中不乏很多好的方法,但几乎都有各自的缺陷,很难达到完美,毕竟能否识别成功,识别是否精确取决于很多因素。

     我做的是基于YUV空间和YQI空间的自适应光照的肤色识别,其原理非常简单,可以参考如下资料:

    http://wenku.baidu.com/link?url=m01RY0xYaraGnOmWVSSthhuGZq-yuC_JuvCq9JknxLRaTpLWV9X_KhrF2f4XmnkHHgY8HB0ADy-YKFcoijBxj3KyWU-9YnjqcYlEcYoJdlC

   不过这个文档有个地方有问题,在计算UV的相位角时,它的定义是:

   Angle=arctan(|V|/|U|)

   这样似乎是不对的,但是我在网上查了各种资料,都没有结果,不过根据我自己的推理,计算方法应该如下:

  1.V>0 && U>0  //第一象限

      Angle=arctan(V/U)*180/Pi;

  2.V>0 && U<0//第二象限

     Angle=180-arctan(|V|/|U|)*180/Pi;

  3.V<0 && U<0//第三象限

     Angle=180+arctan(|V|/|U|)*180/Pi;

  4.V<0 && U>0//第四象限

     Angle=360-arctan(|V|/|U|)*180/Pi;

  根据我学的数学知识应该是这样没有错,不过这毕竟只是我个人的推理,若有错误请大家指出!

  

   还有一个问题就是他的限定范围,他认为Angle值应该在[105,150],I值应该在[20,80],但这样得出效果并不怎么理想,有很多漏判和错判,虽然这是不可避免的!但还是可以改进的,这里我的解决办法是利用OpenCV的人脸识别,识别出人脸后再缩放在特定的区域(眼睛下面与嘴的上面那部分),在计算这部分的Angle值与I值得平均值,然后在动态放大

,这样识别效果就会好很多了!


下面是该算法的核心部分


//SkinIdentify.h


typedef unsigned char byte;

class SkinIdentify
{
public:
	SkinIdentify(void);
	virtual ~SkinIdentify(void);
	void Run(byte *pSrcR,byte *pSrcG,byte *pSrcB,int Height,int Width,byte *pDstRGBData,byte *pDstRGBData1,byte *pDstRGBData2,int AngleMin,int AngleMax,int IMin,int IMax,float *pfAngle,float *pfI);
	void RunAgain(int Height,int Width,byte *pDstRGBData,byte *pDstRGBData1,byte *pDstRGBData2,int AngleMin,int AngleMax,int IMin,int IMax,float *pfAngle,float *pfI);
	void CalAvgAI(byte *pSrcR,byte *pSrcG,byte *pSrcB,int Height,int Width,float &AvgA,float &AvgI);
private:
	void GammaAdjust(byte *pGray,float * pGamma,int Height,int Width);
};


//SkinIdentify.cpp


#include "StdAfx.h"
#include "SkinIdentify.h"
#include <cmath>

#define Pi 3.1416

SkinIdentify::SkinIdentify(void)
{
}


SkinIdentify::~SkinIdentify(void)
{
}


void SkinIdentify::Run(byte *pSrcR,byte *pSrcG,byte *pSrcB,int Height,int Width,byte *pDstRGBData,byte *pDstRGBData1,byte *pDstRGBData2,int AngleMin,int AngleMax,int IMin,int IMax,float *pfAngle,float *pfI)
{
	//Gamma矫正
	float *pGammaR=new float[Height*Width];
	float *pGammaG=new float[Height*Width];
	float *pGammaB=new float[Height*Width];
	GammaAdjust(pSrcR,pGammaR,Height,Width);
	GammaAdjust(pSrcG,pGammaG,Height,Width);
	GammaAdjust(pSrcB,pGammaB,Height,Width);
	//YUV与YQI空间的结合判断
	float U,V,Angle,I;
	float *pR=pGammaR,*pG=pGammaG,*pB=pGammaB;
	float Imin=IMin*1.0,Imax=IMax*1.0;
	float Amin=AngleMin*1.0,Amax=AngleMax*1.0;
	for(int i=0;i<Height;i++)
	{
		for(int j=0;j<Width;j++)
		{
			U=(-0.147)*pGammaR[j]-0.289*pGammaG[j]+0.436*pGammaB[j];
			V=0.615*pGammaR[j]-0.515*pGammaG[j]-0.100*pGammaB[j];
			//计算相位角
			if (U==0)
				Angle=0;
			else 
				Angle=atan(abs(V/U));
			if(V>0&&U<0)
				Angle=180-Angle*180/Pi;
			else if(V<0&&U<0)
				Angle=180+Angle*180/Pi;
			else if(V<0&&U>0)
				Angle=360-Angle*180/Pi;
			//计算I值
			I=0.596*pGammaR[j]-0.274*pGammaG[j]-0.322*pGammaB[j];
			pfAngle[j]=Angle;//将Angle值保存,方便改变参数时直接调用RunAgain函数
			pfI[j]=I;       //同上
			//YUV空间的效果
			if (Angle>=Amin && Angle <=Amax)
			{
				pDstRGBData[j]=1;
			}
			else
			{
				pDstRGBData[j]=0;
			}
			//YQI空间的效果
			if(I>=Imin&&I<=Imax) 
			{
				pDstRGBData1[j]=1;
			}
			else
			{
				pDstRGBData1[j]=0;
			}
			//结合的效果
			if(Angle>=Amin && Angle <=Amax &&I>=Imin&&I<=Imax)
			{
				pDstRGBData2[j]=1;
			}
			else
			{
				pDstRGBData2[j]=0;
			}
		}
		pSrcR+=Width;
		pSrcG+=Width;
		pSrcB+=Width;
		pGammaR+=Width;
		pGammaG+=Width;
		pGammaB+=Width;
		pfAngle+=Width;
		pfI+=Width;
		pDstRGBData+=Width;
		pDstRGBData1+=Width;
		pDstRGBData2+=Width;
	}
		delete[] pR;
	//	pGammaR=NULL;
		delete[] pG;
		//pGammaG=NULL;
		delete [] pB;
	//	pGammaB=NULL;

}

void SkinIdentify::GammaAdjust(byte *pGray,float * pGamma,int Height,int Width)
{
	float a=0.5;
	float x0=80.0,x1=175.0,x,y,cosx,Gammax;
	for (int i=0;i<Height;i++)
	{
		for(int j=0;j<Width;j++)
		{
			x=(float)pGray[j];
			if(x>=0.0&&x<=x0)
			{
				y=Pi*x/(2.0*x0);
			}
			else if(x>x0&&x<=x1)
			{
				y=Pi/2.0;
			}
			else
			{
				y=Pi-(Pi*(255.0-x))/(2.0*(255-x1));
			}
			cosx=cos(y);
			Gammax=1.0+a*cosx;
			pGamma[i*Width+j]=255*pow(((x*1.0)/255),1.0/Gammax);
		}
		pGray+=Width;
	}
	
}
void SkinIdentify::RunAgain(int Height,int Width,byte *pDstRGBData,byte *pDstRGBData1,byte *pDstRGBData2,int AngleMin,int AngleMax,int IMin,int IMax,float *pfAngle,float *pfI)
{
	//要调用这个函数必须先调用Run函数得到图片的相位角值与I值才行
	//而计算方法与Run函数几乎一致
	float Angle,I;
	float Imin=IMin*1.0,Imax=IMax*1.0;
	float Amin=AngleMin*1.0,Amax=AngleMax*1.0;
	for(int i=0;i<Height;i++)
	{
		for(int j=0;j<Width;j++)
		{
			Angle=pfAngle[j];
			I=pfI[j];
			if (Angle>=Amin && Angle <=Amax)
			{
				pDstRGBData[j]=1;
			}
			else
			{
				pDstRGBData[j]=0;
			}
			if(I>=Imin&&I<=Imax) 
			{
				pDstRGBData1[j]=1;
			}
			else
			{
				pDstRGBData1[j]=0;
			}
			if(Angle>=Amin && Angle <=Amax &&I>=Imin&&I<=Imax)
			{
				pDstRGBData2[j]=1;
			}
			else
			{
				pDstRGBData2[j]=0;
			}
		}
		pfAngle+=Width;
		pfI+=Width;
		pDstRGBData+=Width;
		pDstRGBData1+=Width;
		pDstRGBData2+=Width;
	}
}

void SkinIdentify::CalAvgAI(byte *pSrcR,byte *pSrcG,byte *pSrcB,int Height,int Width,float &AvgA,float &AvgI)
{
	//此函数用于对人脸识别锁定的区域计算其平均相位角值与I值
	//计算方法与Run函数几乎一致
    float *pGammaR=new float[Height*Width];
	float *pGammaG=new float[Height*Width];
	float *pGammaB=new float[Height*Width];
	GammaAdjust(pSrcR,pGammaR,Height,Width);
	GammaAdjust(pSrcG,pGammaG,Height,Width);
	GammaAdjust(pSrcB,pGammaB,Height,Width);
    float U,V,Angle,I;
	float *pR=pGammaR,*pG=pGammaG,*pB=pGammaB;
	for(int i=0;i<Height;i++)
	{
		for(int j=0;j<Width;j++)
		{
			U=(-0.147)*pGammaR[j]-0.289*pGammaG[j]+0.436*pGammaB[j];
			V=0.615*pGammaR[j]-0.515*pGammaG[j]-0.100*pGammaB[j];
			if (U==0)
				Angle=0;
			else 
				Angle=atan(abs(V/U));
			if(V>0&&U<0)
				Angle=180-Angle*180/Pi;
			else if(V<0&&U<0)
				Angle=180+Angle*180/Pi;
			else if(V<0&&U>0)
				Angle=360-Angle*180/Pi;
			I=0.596*pGammaR[j]-0.274*pGammaG[j]-0.322*pGammaB[j];
			AvgI+=I;
			AvgA+=Angle;
			if(Angle<1)
				int a=1;
		}
		pGammaR+=Width;
		pGammaG+=Width;
		pGammaB+=Width;
	}
	AvgA/=(Height*Width);
	AvgI/=(Height*Width);
    delete[] pR;
	delete[] pG;
	delete [] pB;
}

而利用MFC实现的可视界面和OpenCV实现的人脸识别部分我就不附上来了,有兴趣可以和我联系!


下面是识别效果

//原图

//YUV空间效果


YQI空间的效果


//结合效果



其实这幅图效果并不好,不过也能看到YUV与YQI的各自缺陷,YUV无法识别棕黑色,YQI则无法识别偏红的颜色,而综合还是不错的!


自适应肤色识别