# 几种肤色分割算法（OpenCV实现）

在手势识别和人脸识别中，肤色分割是非常重要的，特将几种肤色分割方法总结了一下，将代码贴出。

ps:有部分代码非原创，若有侵权，修改。

包括在rgb、rg空间上进行分割，以及大津分割法在多个颜色空间上的实现。

#include "highgui.h"
#include "cv.h"
#include<iostream>
using namespace std;
// skin region location using rgb limitation
void SkinRGB(IplImage* rgb,IplImage* _dst)
{
assert(rgb->nChannels==3&& _dst->nChannels==3);

static const int R=2;
static const int G=1;
static const int B=0;

IplImage* dst=cvCreateImage(cvGetSize(_dst),8,3);
cvZero(dst);

for (int h=0; h<rgb->height; h++)
{
unsigned char* prgb=(unsigned char*)rgb->imageData+h*rgb->widthStep;
unsigned char* pdst=(unsigned char*)dst->imageData+h*dst->widthStep;
for (int w=0; w<rgb->width; w++)
{
if ((prgb[R]>95 && prgb[G]>40 && prgb[B]>20 &&
prgb[R]-prgb[B]>15 && prgb[R]-prgb[G]>15)||//uniform illumination
(prgb[R]>200 && prgb[G]>210 && prgb[B]>170 &&
abs(prgb[R]-prgb[B])<=15 && prgb[R]>prgb[B]&& prgb[G]>prgb[B])//lateral illumination
)
{
memcpy(pdst,prgb,3);
}
prgb+=3;
pdst+=3;
}
}
cvCopyImage(dst,_dst);
cvReleaseImage(&dst);
}
// skin detection in rg space
void cvSkinRG(IplImage* rgb,IplImage* gray)
{
assert(rgb->nChannels==3&&gray->nChannels==1);

const int R=2;
const int G=1;
const int B=0;

double Aup=-1.8423;
double Bup=1.5294;
double Cup=0.0422;
double Bdown=0.6066;
double Cdown=0.1766;
for (int h=0; h<rgb->height; h++)
{
unsigned char* pGray=(unsigned char*)gray->imageData+h*gray->widthStep;
unsigned char* pRGB=(unsigned char* )rgb->imageData+h*rgb->widthStep;
for (int w=0; w<rgb->width; w++)
{
int s=pRGB[R]+pRGB[G]+pRGB[B];
double r=(double)pRGB[R]/s;
double g=(double)pRGB[G]/s;
double Gup=Aup*r*r+Bup*r+Cup;
double Wr=(r-0.33)*(r-0.33)+(g-0.33)*(g-0.33);
if (g<Gup && g>Gdown && Wr>0.004)
{
*pGray=255;
}
else
{
*pGray=0;
}
pGray++;
pRGB+=3;
}
}

}
// implementation of otsu algorithm
// author: onezeros#yahoo.cn
// reference: Rafael C. Gonzalez. Digital Image Processing Using MATLAB
void cvThresholdOtsu(IplImage* src, IplImage* dst)
{
int height=src->height;
int width=src->width;

//histogram
float histogram[256]= {0};
for(int i=0; i<height; i++)
{
unsigned char* p=(unsigned char*)src->imageData+src->widthStep*i;
for(int j=0; j<width; j++)
{
histogram[*p++]++;
}
}
//normalize histogram
int size=height*width;
for(int i=0; i<256; i++)
{
histogram[i]=histogram[i]/size;
}

//average pixel value
float avgValue=0;
for(int i=0; i<256; i++)
{
avgValue+=i*histogram[i];
}

int threshold;
float maxVariance=0;
float w=0,u=0;
for(int i=0; i<256; i++)
{
w+=histogram[i];
u+=i*histogram[i];

float t=avgValue*w-u;
float variance=t*t/(w*(1-w));
if(variance>maxVariance)
{
maxVariance=variance;
threshold=i;
}
}

cvThreshold(src,dst,threshold,255,CV_THRESH_BINARY);
}

void cvSkinOtsu(IplImage* src, IplImage* dst)//yCbCr
{
assert(dst->nChannels==1&& src->nChannels==3);

IplImage* ycrcb=cvCreateImage(cvGetSize(src),8,3);
IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,ycrcb,CV_BGR2YCrCb);
cvSplit(ycrcb,0,cr,0,0);

cvThresholdOtsu(cr,cr);
cvCopyImage(cr,dst);
cvReleaseImage(&cr);
cvReleaseImage(&ycrcb);
}

void cvSkinOtsu_H(IplImage* src, IplImage* dst)//H通道上做分割，找到区域后再映射到S通道上继续做分割
{
assert(dst->nChannels==1&& src->nChannels==3);

IplImage* HSV=cvCreateImage(cvGetSize(src),8,3);
IplImage* H=cvCreateImage(cvGetSize(src),8,1);
IplImage* S=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,HSV,CV_BGR2HSV);
cvSplit(HSV,H,S,0,0);

cvThresholdOtsu(H,S);
cvCopyImage(S,dst);
cvReleaseImage(&H);
cvReleaseImage(&HSV);
}

void cvSkinYUV(IplImage* src,IplImage* dst)
{
IplImage* ycrcb=cvCreateImage(cvGetSize(src),8,3);
//IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
//IplImage* cb=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,ycrcb,CV_BGR2YCrCb);
//cvSplit(ycrcb,0,cr,cb,0);

static const int Cb=2;
static const int Cr=1;
static const int Y=0;

//IplImage* dst=cvCreateImage(cvGetSize(_dst),8,3);
cvZero(dst);

for (int h=0; h<src->height; h++)
{
unsigned char* pycrcb=(unsigned char*)ycrcb->imageData+h*ycrcb->widthStep;
unsigned char* psrc=(unsigned char*)src->imageData+h*src->widthStep;
unsigned char* pdst=(unsigned char*)dst->imageData+h*dst->widthStep;
for (int w=0; w<src->width; w++)
{
if (pycrcb[Cr]>=133&&pycrcb[Cr]<=173&&pycrcb[Cb]>=77&&pycrcb[Cb]<=127)
{
memcpy(pdst,psrc,3);
}
pycrcb+=3;
psrc+=3;
pdst+=3;
}
}
//cvCopyImage(dst,_dst);
//cvReleaseImage(&dst);
}

void cvSkinHSV(IplImage* src,IplImage* dst)
{
IplImage* hsv=cvCreateImage(cvGetSize(src),8,3);
//IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
//IplImage* cb=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,hsv,CV_BGR2HSV);
//cvSplit(ycrcb,0,cr,cb,0);

static const int V=2;
static const int S=1;
static const int H=0;

//IplImage* dst=cvCreateImage(cvGetSize(_dst),8,3);
cvZero(dst);

for (int h=0; h<src->height; h++)
{
unsigned char* phsv=(unsigned char*)hsv->imageData+h*hsv->widthStep;
unsigned char* psrc=(unsigned char*)src->imageData+h*src->widthStep;
unsigned char* pdst=(unsigned char*)dst->imageData+h*dst->widthStep;
for (int w=0; w<src->width; w++)
{
if (phsv[H]>=7&&phsv[H]<=29)
{
memcpy(pdst,psrc,3);
}
phsv+=3;
psrc+=3;
pdst+=3;
}
}
//cvCopyImage(dst,_dst);
//cvReleaseImage(&dst);
}

void Skin_HSV_2(IplImage *initial,IplImage *distinction)
{
CvScalar Avg,Sdv;
IplImage *temp = cvCreateImage(cvGetSize(initial),8,3);
cvCvtColor(initial,temp,CV_BGR2HSV);
IplImage *h_img = cvCreateImage(cvGetSize(initial),8,1);
IplImage *s_img = cvCreateImage(cvGetSize(initial),8,1);
IplImage *v_img = cvCreateImage(cvGetSize(initial),8,1);

double h_avg,s_avg,v_avg;
double h_sdv,s_sdv,v_sdv;

cvAvgSdv(temp,&Avg,&Sdv,0);

h_avg = Avg.val[0];
s_avg = Avg.val[1];
v_avg = Avg.val[2];
//cout<<h_avg<<'\n'<<s_img<<'\n'<<v_img;
h_sdv = Sdv.val[0];
s_sdv = Sdv.val[1];
v_sdv = Sdv.val[2];

cvSplit(temp, h_img, s_img, v_img, 0);
/*cvShowImage("h_img1",h_img);
cvShowImage("s_img1",s_img);
cvShowImage("v_img1",v_img);*/
int value = 0;
for(int i= 0;i< h_img->height;i++)
{
for(int j = 0; j< h_img->width; j++)
{
value = cvGetReal2D(h_img, i, j);
if((value<(h_avg+h_sdv)) && (value>(h_avg-h_sdv)) )
{
*(h_img->imageData+i*h_img->widthStep+j) = 0;
}
else
{
*(h_img->imageData+i*h_img->widthStep+j) = 255;
}
}
}

for(int i= 0;i< s_img->height;i++)
{
for(int j = 0; j< s_img->width; j++)
{
value = cvGetReal2D(s_img, i, j);
if((value<(s_avg+0.5*s_sdv))&&(value>(s_avg-0.5*s_sdv)) )
{
*(s_img->imageData+i*s_img->widthStep+j) = 0;
}
else
{
*(s_img->imageData+i*s_img->widthStep+j) = 255;
}
}
}

for(int i= 0;i< v_img->height;i++)
{
for(int j = 0; j< v_img->width; j++)
{
value = cvGetReal2D(v_img, i, j);
if((value<(v_avg+v_sdv)) && (value>(v_avg-v_sdv)))
{
*(v_img->imageData+i*v_img->widthStep+j) = 0;
}
else

{
*(v_img->imageData+i*v_img->widthStep+j) = 255;
}

}
}
cvShowImage("h_img",h_img);
/*cvShowImage("s_img",s_img);
cvShowImage("v_img",v_img);*/
cvAnd(h_img,s_img,distinction);
//cvAnd(v_img,distinction,distinction);

/*cvShowImage("distinction",distinction);*/

/*cvReleaseImage(&temp);*/
cvReleaseImage(&h_img);
cvReleaseImage(&s_img);
cvReleaseImage(&v_img);
}

void main()
{

IplImage* dstRGB=cvCreateImage(cvGetSize(img),8,3);
IplImage* dstRG=cvCreateImage(cvGetSize(img),8,1);
IplImage* dst_crotsu=cvCreateImage(cvGetSize(img),8,1);
IplImage* dst_YUV=cvCreateImage(cvGetSize(img),8,3);
IplImage* dst_HSV=cvCreateImage(cvGetSize(img),8,3);
IplImage* dst_crotsu_H=cvCreateImage(cvGetSize(img),8,1);

IplImage* dst_HSV_2=cvCreateImage(cvGetSize(img),8,1);

cvNamedWindow("inputimage", CV_WINDOW_AUTOSIZE);
cvShowImage("inputimage", img);
cvWaitKey(0);

SkinRGB(img,dstRGB);
cvNamedWindow("SkinRGB", CV_WINDOW_AUTOSIZE);
cvShowImage("SkinRGB", dstRGB);
///cvWaitKey(0);
cvSkinRG(img,dstRG);
cvNamedWindow("cvSkinRG", CV_WINDOW_AUTOSIZE);
cvShowImage("cvSkinRG", dstRG);
//cvWaitKey(0);
cvSkinOtsu(img,dst_crotsu);
cvNamedWindow("cvSkinOtsu", CV_WINDOW_AUTOSIZE);
cvShowImage("cvSkinOtsu", dst_crotsu);

cvSkinOtsu_H(img,dst_crotsu_H);
cvNamedWindow("cvSkinOtsu_H", CV_WINDOW_AUTOSIZE);
cvShowImage("cvSkinOtsu_H", dst_crotsu_H);
//cvWaitKey(0);
cvSkinYUV(img,dst_YUV);
cvNamedWindow("cvSkinYUV", CV_WINDOW_AUTOSIZE);
cvShowImage("cvSkinYUV", dst_YUV);
//cvWaitKey(0);
cvSkinHSV(img,dst_HSV);
cvNamedWindow("cvSkinHSV", CV_WINDOW_AUTOSIZE);
cvShowImage("cvSkinHSV", dst_HSV);

Skin_HSV_2(img, dst_HSV_2);
cvShowImage("cvSkinHSV2", dst_HSV_2);
cvWaitKey(0);

}

经过多次实验，发现在HSV空间上的大津分割较好。

void cvThresholdOtsu(IplImage* src, IplImage* dst)
{
int height=src->height;
int width=src->width;

//histogram
float histogram[256]= {0};
for(int i=0; i<height; i++)
{
unsigned char* p=(unsigned char*)src->imageData+src->widthStep*i;
for(int j=0; j<width; j++)
{
histogram[*p++]++;
}
}
//normalize histogram
int size=height*width;
for(int i=0; i<256; i++)
{
histogram[i]=histogram[i]/size;
}

//average pixel value
float avgValue=0;
for(int i=0; i<256; i++)
{
avgValue+=i*histogram[i];
}

int threshold;
float maxVariance=0;
float w=0,u=0;
for(int i=0; i<256; i++)
{
w+=histogram[i];
u+=i*histogram[i];

float t=avgValue*w-u;
float variance=t*t/(w*(1-w));
if(variance>maxVariance)
{
maxVariance=variance;
threshold=i;
}
}

cvThreshold(src,dst,threshold,255,CV_THRESH_BINARY);
}

void cvSkinOtsu(IplImage* src, IplImage* dst)//yCbCr
{
assert(dst->nChannels==1&& src->nChannels==3);

IplImage* ycrcb=cvCreateImage(cvGetSize(src),8,3);
IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,ycrcb,CV_BGR2YCrCb);
cvSplit(ycrcb,0,cr,0,0);

cvThresholdOtsu(cr,cr);
cvCopyImage(cr,dst);
cvReleaseImage(&cr);
cvReleaseImage(&ycrcb);
}

void cvSkinOtsu_H(IplImage* src, IplImage* dst)//H通道上做分割，找到区域后再映射到S通道上继续做分割
{
assert(dst->nChannels==1&& src->nChannels==3);

IplImage* HSV=cvCreateImage(cvGetSize(src),8,3);
IplImage* H=cvCreateImage(cvGetSize(src),8,1);
IplImage* S=cvCreateImage(cvGetSize(src),8,1);
IplImage* V=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,HSV,CV_BGR2HSV);
cvSplit(HSV,H,S,V,0);
cvThresholdOtsu(S,S);
cvThresholdOtsu(V,V);  //H通道，效果不好，S通道还行
cvAnd(S,V,dst);   //S,V通道与
//cvCopyImage(V,dst);
cvReleaseImage(&H);
cvReleaseImage(&HSV);
}

void Skin_HSV_new(IplImage *img,IplImage *dst)
{
//cvEqualizeHist(img,img); //直方图均衡
cvCvtColor(img,img, CV_BGR2HSV);
for(int i=0;i<img->width;i++)
{
for(int j=0;j<img->height;j++)
{
CvScalar temp=cvGet2D(img,j,i);
int value = (((temp.val[1]+temp.val[2])*1.0)/temp.val[0]);
if(value<8)
{
*(dst->imageData+j*dst->widthStep+i)=0;
}
else
{
*(dst->imageData+j*dst->widthStep+i)=255;
}
//cout<<value<<'\n';
}
}

//cvSmooth(dst,dst,CV_MEDIAN);
/*cvDilate(dst,dst);
cvErode(dst,dst);
cvErode(dst,dst);
cvDilate(dst,dst);

cvDilate(dst,dst);
cvErode(dst,dst);
cvErode(dst,dst);
cvSmooth(dst,dst,CV_MEDIAN);
cvDilate(dst,dst);
cvErode(dst,dst);*/
cvDilate(dst,dst);
cvErode(dst,dst);
//cvSmooth(dst,dst,CV_BILATERAL);
//cvSmooth(dst,dst);
}