肤色检测算法

由于能力有限,算法层面的东西自己去创新的很少,很多都是从现有的论文中学习,然后实践的。

首先列一些现在主流的皮肤检测的方法都有哪些:

  1. RGB color space
  2. Ycrcb之cr分量+otsu阈值化
  3. YCrCb中133<=Cr<=173 77<=Cb<=127
  4. HSV中 7<H<20 28<S<256 50<V<256
  5. 基于椭圆皮肤模型的皮肤检测
  6. opencv自带肤色检测类AdaptiveSkinDetector

每个算法的思想都是大同小异的,都是根据总结出来的一些经验,设定皮肤颜色的范围,再将其过滤出来,不同的只是过滤的过程在不同的颜色空间下进行而已。我们可以根据自己的应用场景,适当地修改这些范围,以获得满意的结果。可以改善的方向就是,我们可以用合适的滤波器或者形态学处理一些噪声,来使得提取出来的皮肤更为干净。 

1.基于RGB颜色空间的简单阈值肤色识别

      在human skin color clustering for face detection一文中提出如下简单的判别算式:

      R>95 And G>40 And B>20 And R>G And R>B And Max(R,G,B)-Min(R,G,B)>15 And Abs(R-G)>15 

肤色在RGB模型下的范围基本满足以下约束:

在均匀光照下应满足以下判别式:

R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B

在侧光拍摄环境下:

R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B

算法非常之简单,同样主要把复杂的判断条件放到后面去判断,能有效的降低程序的执行时间,参考代码:

/*基于RGB范围的皮肤检测*/
Mat RGB_detect(Mat& img)
{
    /*
        R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B
            OR
        R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B
    */
    Mat detect = img.clone();
    detect.setTo(0);
    if (img.empty() || img.channels() != 3)
    {
        return detect;
    }

    for (int i = 0; i < img.rows; i++)
    {
        for (int j = 0; j < img.cols; j++)
        {
            uchar *p_detect = detect.ptr<uchar>(i, j);
            uchar *p_img = img.ptr<uchar>(i, j);
            if ((p_img[2] > 95 && p_img[1]>40 && p_img[0] > 20 &&
                (MAX(p_img[0], MAX(p_img[1], p_img[2])) - MIN(p_img[0], MIN(p_img[1], p_img[2])) > 15) &&
                abs(p_img[2] - p_img[1]) > 15 && p_img[2] > p_img[1] && p_img[1] > p_img[0]) ||
                (p_img[2] > 200 && p_img[1] > 210 && p_img[0] > 170 && abs(p_img[2] - p_img[1]) <= 15 &&
                p_img[2] > p_img[0] &&  p_img[1] > p_img[0]))
            {
                p_detect[0] = p_img[0];
                p_detect[1] = p_img[1];
                p_detect[2] = p_img[2];
            }

         
        }

    }
    return detect;
}

效果如下:

从检测结果可以看出,皮肤的检测效果并不好,首先皮肤检测的完整性并不高,一些稍微光线不好的区域也没法检测出皮肤来。第二,这种基于RBG范围来判定皮肤的算法太受光线的影响了,鲁棒性确实不好。

2.基于椭圆皮肤模型的皮肤检测

经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到YCrCb空间,则在CrCb二维空间中这些皮肤像素点近似成一个椭圆分布。因此如果我们得到了一个CrCb的椭圆,下次来一个坐标(Cr, Cb)我们只需判断它是否在椭圆内(包括边界),如果是,则可以判断其为皮肤,否则就是非皮肤像素点。

这种基于肤色椭圆模型的算法的皮肤检测较上面算法在效果上有着较大的提升。

3.YCrCb颜色空间Cr分量+Otsu法阈值分割

这里先简单介绍YCrCb颜色空间。
YCrCb即YUV,其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。

该方法的原理也很简单:

a.将RGB图像转换到YCrCb颜色空间,提取Cr分量图像

b.对Cr做自二值化阈值分割处理(Otsu法)

效果:

可以看出这种方法效果也还不错。

4.基于YCrCb颜色空间Cr,Cb范围筛选法

这个方法跟法一其实大同小异,只是颜色空间不同而已。据资料显示,正常黄种人的Cr分量大约在133至173之间,Cb分量大约在77至127之间。大家可以根据自己项目需求放大或缩小这两个分量的范围,会有不同的效果。

5.HSV颜色空间H范围筛选法

同样地,也是在不同的颜色空间下采取相应的颜色范围将皮肤分割出来。

6.opencv自带肤色检测类AdaptiveSkinDetector

opencv提供了下面这个好用的皮肤检测函数:

CvAdaptiveSkinDetector(int samplingDivider = 1, int morphingMethod = MORPHING_METHOD_NONE);

这个函数的第二个参数表示皮肤检测过程时所采用的图形学操作方式,其取值有3种可能:

  • 如果为MORPHING_METHOD_ERODE,则表示只进行一次腐蚀操作;
  • 如果为MORPHING_METHOD_ERODE_ERODE,则表示连续进行2次腐蚀操作;
  • 如果为MORPHING_METHOD_ERODE_DILATE,则表示先进行一次腐蚀操作,后进行一次膨胀操作。

从效果图看来,背景多了很多白色的杂质,单从直观效果来看,貌似还不如上面写的几个算法。

完整算法代码如下:



#include "stdafx.h"

#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cvaux.h>
#include <highgui.h>

using namespace cv;
#if 1
class skin_detector
{
public:
    /*基于RGB范围的皮肤检测*/
    Mat RGB_detect(Mat& img)
    {
        /*
            R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B
                OR
            R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B
        */
        Mat detect = img.clone();
        detect.setTo(0);
        if (img.empty() || img.channels() != 3)
        {
            return detect;
        }
        for (int i = 0; i < img.rows; i++)
        {
            for (int j = 0; j < img.cols; j++)
            {
                uchar *p_detect = detect.ptr<uchar>(i, j);
                uchar *p_img = img.ptr<uchar>(i, j);
                if ((p_img[2] > 95 && p_img[1]>40 && p_img[0] > 20 &&
                    (MAX(p_img[0], MAX(p_img[1], p_img[2])) - MIN(p_img[0], MIN(p_img[1], p_img[2])) > 15) &&
                    abs(p_img[2] - p_img[1]) > 15 && p_img[2] > p_img[1] && p_img[1] > p_img[0]) ||
                    (p_img[2] > 200 && p_img[1] > 210 && p_img[0] > 170 && abs(p_img[2] - p_img[1]) <= 15 &&
                    p_img[2] > p_img[0] &&  p_img[1] > p_img[0]))
                {
                    p_detect[0] = p_img[0];
                    p_detect[1] = p_img[1];
                    p_detect[2] = p_img[2];
                }

            }

        }
        return detect;
    }

    /*HSV颜色空间H范围筛选法*/
    Mat HSV_detector(Mat& src)
    {
        Mat hsv_image;
        int h = 0;
        int s = 1;
        int v = 2;
        cvtColor(src, hsv_image, CV_BGR2HSV); //首先转换成到YCrCb空间
        Mat output_mask = Mat::zeros(src.size(), CV_8UC1);
        for (int i = 0; i < src.rows; i++)
        {
            for (int j = 0; j < src.cols; j++)
            {
                uchar *p_mask = output_mask.ptr<uchar>(i, j);
                uchar *p_src = hsv_image.ptr<uchar>(i, j);
                if (p_src[h] >= 0 && p_src[h] <= 20 && p_src[s] >=48 && p_src[v] >=50)
                {
                    p_mask[0] = 255;
                }
            }
        }
        Mat detect;
        src.copyTo(detect, output_mask);;
        return detect;
    }

    /*YCrCb颜色空间Cr,Cb范围筛选法*/
    Mat YCrCb_detect(Mat & src)
    {
        Mat ycrcb_image;
        int Cr = 1;
        int Cb = 2;
        cvtColor(src, ycrcb_image, CV_BGR2YCrCb); //首先转换成到YCrCb空间
        Mat output_mask = Mat::zeros(src.size(), CV_8UC1);
        for (int i = 0; i < src.rows; i++)
        {
            for (int j = 0; j < src.cols; j++)
            {
                uchar *p_mask = output_mask.ptr<uchar>(i, j);
                uchar *p_src = ycrcb_image.ptr<uchar>(i, j);
                if (p_src[Cr] >= 133 && p_src[Cr] <= 173 && p_src[Cb] >= 77 && p_src[Cb] <= 127)
                {
                    p_mask[0] = 255;
                }
            }
        }
        Mat detect;
        src.copyTo(detect, output_mask);;
        return detect;

    }

    /*YCrCb颜色空间Cr分量+Otsu法*/
    Mat YCrCb_Otsu_detect(Mat& src)
    {
        Mat ycrcb_image;
        cvtColor(src, ycrcb_image, CV_BGR2YCrCb); //首先转换成到YCrCb空间
        Mat detect;
        vector<Mat> channels;
        split(ycrcb_image, channels);
        Mat output_mask = channels[1];
        threshold(output_mask, output_mask, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
        src.copyTo(detect, output_mask);
        return detect;

    }

    /*基于椭圆皮肤模型的皮肤检测*/
    Mat ellipse_detect(Mat& src)
    {
        Mat img = src.clone();
        Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1);
        //利用opencv自带的椭圆生成函数先生成一个肤色椭圆模型
        ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 15.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);
        Mat ycrcb_image;
        Mat output_mask = Mat::zeros(img.size(), CV_8UC1);
        cvtColor(img, ycrcb_image, CV_BGR2YCrCb); //首先转换成到YCrCb空间
        for (int i = 0; i < img.cols; i++)   //利用椭圆皮肤模型进行皮肤检测
            for (int j = 0; j < img.rows; j++) 
            {
                Vec3b ycrcb = ycrcb_image.at<Vec3b>(j, i);
                if (skinCrCbHist.at<uchar>(ycrcb[1], ycrcb[2]) > 0)   //如果该落在皮肤模型椭圆区域内,该点就是皮肤像素点
                    output_mask.at<uchar>(j, i) = 255;
            }

        Mat detect;
        img.copyTo(detect,output_mask);  //返回肤色图
        return detect;
    }

    /*opencv自带肤色检测类AdaptiveSkinDetector*/
    Mat AdaptiveSkinDetector_detect(Mat& src)
    {
        IplImage *frame;
        frame = &IplImage(src);  //Mat -> IplImage
        CvAdaptiveSkinDetector filter(1, CvAdaptiveSkinDetector::MORPHING_METHOD_ERODE_DILATE);

        IplImage *maskImg = cvCreateImage(cvSize(src.cols, src.rows), IPL_DEPTH_8U, 1);
        IplImage *skinImg = cvCreateImage(cvSize(src.cols, src.rows), IPL_DEPTH_8U, 3);
        cvZero(skinImg);
        filter.process(frame, maskImg);    // process the frame
        cvCopy(frame, skinImg, maskImg);
        Mat tmp(skinImg);  //IplImage -> Mat
        Mat detect = tmp.clone();
        cvReleaseImage(&skinImg);
        cvReleaseImage(&maskImg);
        return detect;
    }
};
#endif

测试代码:


int main(int argc, char** argv)
{

    VideoCapture cap(0);
    if (!cap.isOpened())
    {
        printf("fail to open camera!\n");
        return -1;
    }
    Mat frame;
    skin_detector detector;
    
    while (1)
    {
        cap >> frame;
        if (frame.empty())
        {
            continue;
        }
        Mat skin = detector.AdaptiveSkinDetector_detect(frame);//AdaptiveSkinDetector_detect
        imshow("AdaptiveSkinDetector capture skin", skin);
        imshow("capture src",frame);

        if (waitKey(1) == 27)
        {
            break;
        }
    }

    return 0;
}

 文章参考:

1.https://www.cnblogs.com/Imageshop/p/3264238.html

2.https://www.cnblogs.com/skyfsm/p/7868877.html

猜你喜欢

转载自blog.csdn.net/Gavinmiaoc/article/details/84034183