您的位置:首页 > 其它

自适应肤色识别

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

     我做的是基于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则无法识别偏红的颜色,而综合还是不错的!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息