肤色识别是数字图像的一个重要课题,现在已经有很多方法解决这个问题,其中不乏很多好的方法,但几乎都有各自的缺陷,很难达到完美,毕竟能否识别成功,识别是否精确取决于很多因素。
我做的是基于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则无法识别偏红的颜色,而综合还是不错的!