几种肤色分割算法(OpenCV实现)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_26671711/article/details/52123866

几种肤色分割算法(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 Adown=-0.7279;
    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 Gdown=Adown*r*r+Bdown*r+Cdown;
            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* 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; 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);
}

此外,还有一种在HSV空间上判决的方法,通过(S+V)/H 的值进行判决的,是在一篇论文上看到的,然而,后来没有找到出处。我根据需要对阈值进行了变化,效果在特定情况下比较好,不具有普遍性。

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);
}
总之,这篇文作为总结。



展开阅读全文

没有更多推荐了,返回首页