OpenCV探索之路(二十七):皮肤检测技术

很久没写博客了,由于最近都忙着赶项目和打比赛==| 好吧,今天我打算写一篇关于使用opencv作皮肤检测的技术总结。那首先列一些如今主流的皮肤检测的方法都有哪些:git

  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

那咱们今天就来一一实现它吧!github

方法一:基于RGB的皮肤检测

根据RGB颜色模型找出定义好的肤色范围内的像素点,范围外的像素点设为黑色。算法

查阅资料后能够知道,前人作了大量研究,肤色在RGB模型下的范围基本知足如下约束:api

在均匀光照下应知足如下判别式:ide

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函数

在侧光拍摄环境下:spa

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

既然判别式已经肯定了,因此按照判别式写程序就很简单了。code

/*基于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;
}

检测效果以下:blog

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

方法二:基于椭圆皮肤模型的皮肤检测

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

/*基于椭圆皮肤模型的皮肤检测*/
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;
}

检测效果:

这种基于肤色椭圆模型的算法的皮肤检测较上面算法在效果上有着较大的提高,基本上改检测的皮肤都检测到了,对光线的抗干扰能力也是比较强的,检测出来的图像都比较干净,背景杂质较少。

法三: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法)

/*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;

}

检测效果:

法四:基于YCrCb颜色空间Cr,Cb范围筛选法

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

/*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;

}

检测效果:

法五:HSV颜色空间H范围筛选法

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

/*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;
}

检测效果:

法六: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,则表示先进行一次腐蚀操做,后进行一次膨胀操做。
/*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;
}

从效果图看来,背景多了不少白色的杂质,单从直观效果来看,貌似还不如我上面写的几个算法,难道跟场景有关系?固然,opencv自带的这个api在皮肤检测上确实至关不错的。

总结

今天花了将近7个小时才撸了这篇文章出来==!这篇文章对各大主流的皮肤检测算法作了个总结和实现。其实说白了,每一个算法的思想都是大同小异的,都是根据总结出来的一些经验,设定皮肤颜色的范围,再将其过滤出来,不一样的只是过滤的过程在不一样的颜色空间下进行而已。咱们能够根据本身的应用场景,适当地修改这些范围,以得到满意的结果。能够改善的方向就是,咱们能够用合适的滤波器或者形态学处理一些噪声,来使得提取出来的皮肤更为干净。

完整代码能够访问个人github来获取~

相关文章
相关标签/搜索