在手势识别和人脸识别中,肤色分割是非常重要的,特将几种肤色分割方法总结了一下,将代码贴出。
ps:有部分代码非原创,若有侵权,修改。
包括在rgb、rg空间上进行分割,以及大津分割法在多个颜色空间上的实现。
#include "highgui.h"
#include "cv.h"
#include
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; hheight; 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; wwidth; 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 Adown=-0.7279;
double Bdown=0.6066;
double Cdown=0.1766;
for (int h=0; hheight; 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; wwidth; 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 Gdown=Adown*r*r+Bdown*r+Cdown;
double Wr=(r-0.33)*(r-0.33)+(g-0.33)*(g-0.33);
if (gGdown && 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; iimageData+src->widthStep*i;
for(int j=0; jmaxVariance)
{
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; hheight; 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; wwidth; 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; hheight; 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; wwidth; 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<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* img= cvLoadImage("original.jpg"); //随便放一张jpg图片在D盘或另行设置目录
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; iimageData+src->widthStep*i;
for(int j=0; jmaxVariance)
{
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);
}
此外,还有一种在HSV空间上判决的方法,通过(S+V)/H 的值进行判决的,是在一篇论文上看到的,然而,后来没有找到出处。我根据需要对阈值进行了变化,效果在特定情况下比较好,不具有普遍性。
void Skin_HSV_new(IplImage *img,IplImage *dst)
{
//cvEqualizeHist(img,img); //直方图均衡
cvCvtColor(img,img, CV_BGR2HSV);
for(int i=0;iwidth;i++)
{
for(int j=0;jheight;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<
总之,这篇文作为总结。