6种肤色检测方法的原理及实现(opencv, C++)

Mr Qin
分类:机器视觉
个人专栏:图像处理
发布时间 2021.06.18
阅读数 2024
评论数 0
0 简介:
本博文首发csdn链接:https://blog.csdn.net/qinqinxiansheng/article/details/112795823
  本文主要介绍了多种基于颜色空间的肤色检测算法,并给出了C++的代码实现。方法包括以下六种:

1.基于HSV颜色空间的阈值肤色识别
2.基于RGB颜色空间的阈值肤色识别
3.基于YCbCr颜色空间的阈值肤色识别
4.基于YCbCr颜色空间和椭圆皮肤模型的皮肤识别
5.基于YCbCr颜色空间的Otsu阈值肤色识别
6.OpenCV自带的函数
1 原理介绍:
  随着人脸检测技术,表情识别及手势识别等技术的快速发展,肤色应用领域日趋增多。肤色检测技术常用的方法有基于颜色空间,光谱特征及肤色反射模型等方法,下面主要介绍和对比基于HSV、RGB、YCrCb颜色空间的肤色检测技术。

1.1基于HSV颜色空间的阈值肤色识别
  HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法,其包含色相(H)、饱和度(S)、名度(V)三个基本属性。在HSV颜色空间中,一般通过色相值对肤色区域进行筛选:2<H<13。

1.2基于RGB颜色空间的阈值肤色识别
  在RGB彩色模型中,每种颜色都以红色(R)、绿色(G)和蓝色(B)光谱成分表示,用RGB颜色空间对物体的颜色的描述,很符合人们的直观感受,但是肤色受亮度的影响较大,需要用复杂的约束才能很好的将皮肤区域筛选出来。查阅资料后可知,前人做了大量研究,肤色在RGB模型下的范围基本满足以下约束:

(R,G,B)>(95,40,20)(R,G,B)>(95,40,20)

max(R,G,B)-minmax(R,G,B)>15max(R,G,B)−minmax(R,G,B)>15

|R-G|>15∣R−G∣>15

R>GR>G

R>BR>B

1.3基于YCbCr颜色空间的阈值肤色识别
  肤色检测领域经常用到YCbCr颜色空间,其中Y代表亮度,Cr代表光源中的红色分量,Cb代表光源中的蓝色分量。在YCbCr颜色空间中,一般通过Cr、Cb值进行皮肤的检测,有关资料显示,正常黄种人肤色基本满足以下约束:

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

1.5基于YCbCr颜色空间的Otsu阈值肤色识别
  该方法是对方法四的改进,首先将RGB图像转换到YCrCb颜色空间中,提取Cr分量的图像,然后对Cr做二值化阈值分割处理(Otsu法),避免的人为设定阈值的步骤,使得算法具有更强的鲁棒性,能适应更多变化的环境。
  为了选择更加适合本次实验任务的肤色检测方法,对五种不同的识别方案进行的实验测试,实验结果表明,各种方法都能很好的完成人脸和手臂的识别任务,考虑到HSV空间H阈值筛选算法简单、鲁棒性强的特点,接下来使用其进行肤色检测。

(a)待处理图像 (b) HSV空间H筛选  © RGB空间筛选

© YCrCb空间Cr与Cb筛选   (d) YCbCr空间椭圆模型筛选 (e) YCbCr空间Otsu阈值筛选
图 1  不同肤色检测方法的识别结果
2 代码实现(C++,opencv2.4.9,vs2013)
#include
#include
#include <opencv2\opencv.hpp>
#include <stdio.h>
using namespace cv;
using namespace std;

//-----各颜色空间的参数-------------------------------------//
int minH = 2, maxH = 13;//肤色分割阈值白天建议3 14
int minR = 95, minG = 40, minB = 20, max_min = 15, absR_G= 10;
int minCr = 138, maxCr = 243, minCb = 77, maxCb = 127;
int ecl_x = 113, ecl_y = 156, leng_x = 24, leng_y = 23, ang =43;

Mat frame, frameH, frameHSV, frameYCrCb; //不同颜色空间下的图片
Mat RIOframe, RIOresult; //二值化得到的图像,识别出的皮肤区域,最终结果,将结果显示在原图
vector RGBchannels, HSVchannels; //RGB通道分离,HSV通道分离

//-----6种肤色识别方法-------------------------------------//
void hand_HSV();
void hand_RGB();
void hand_YCbCr_ellipse();//椭圆模型
void hand_YCbCr();
void hand_YCbCr_Otsu();
void hand_opencv();

int main()
{
while (true)
{
frame = imread(“test.jpg”);
resize(frame, frame, Size(frame.cols0.1, frame.rows0.1));//降采样
namedWindow(“原始图片”, CV_WINDOW_NORMAL);
imshow(“原始图片”,frame);

    //--------------------------6种肤色检测方法------------------------------------//
    //hand_HSV();
    //hand_RGB();
    hand_YCbCr_ellipse();//椭圆模型
    //hand_YCbCr();
    //hand_YCbCr_Otsu();
    //hand_opencv();
    namedWindow("肤色分割之后的图片", CV_WINDOW_NORMAL);
    imshow("肤色分割之后的图片", RIOframe);// 显示肤色分割之后的图片
    RIOframe.setTo(0);
    waitKey(1);
}
system("pause");
return 0;

}

//-----6种肤色识别方法-------------------------------------//
void hand_HSV()
{
cvtColor(frame, frameHSV, CV_BGR2HSV);//在opencv中,其默认的颜色制式排列是BGR而非RGB
split(frameHSV, HSVchannels);//分离后, channels[0]对应H, channels[1]对应S, channels[2]对应
frameH = HSVchannels[0];
inRange(frameH, Scalar(minH), Scalar(maxH), RIOresult);
frame.copyTo(RIOframe, RIOresult);
};

void hand_RGB()
{

//imshow("4.肤色分割之后的图片", frame);
Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));
for (int i = 0; i < frame.rows; i++)
 {
    for (int j = 0; j < frame.cols;j++)
    {
        int r,g,b;
        r = frame.at<cv::Vec3b>(i,j)[2];
        g = frame.at<cv::Vec3b>(i,j)[1];
        b = frame.at<cv::Vec3b>(i,j)[0];

        if( r>minR && g>minG && b>minB && max(max (r,g),b) - min(min (r,g),b)> max_min && abs(r-g)>absR_G && r>g && r>b)
        //if( r>95 && g>40 && b>20 && max(max (r,g),b) - min(min (r,g),b)> 15 && abs(r-g)>15 && r>g && r>b )
        {
            tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
        }
        else if( r>220 && g>210 &&b>170 && abs(r-g)<=15 && r>b && g<b)
        {
            tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
        }
    }

  };
 RIOresult = tempresult.clone();
 frame.copyTo(RIOframe, tempresult);

};

//椭圆模型
void hand_YCbCr_ellipse()
{
/椭圆皮肤模型/
Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1);
//ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 23.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);
ellipse(skinCrCbHist, Point(ecl_x, ecl_y), Size(leng_x, leng_y), ang, 0.0, 360.0, Scalar(255, 255, 255), -1);

cvtColor(frame, frameYCrCb , CV_BGR2YCrCb);
Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));
for (int i = 0; i < frame.rows; i++)
 {
    for (int j = 0; j < frame.cols;j++)
    {
        int y, cr, cb;
        y = frameYCrCb.at<cv::Vec3b>(i,j)[0];
        cr = frameYCrCb.at<cv::Vec3b>(i,j)[1];
        cb = frameYCrCb.at<cv::Vec3b>(i,j)[2];

        if( skinCrCbHist.at<uchar>(cr,cb) >0 )
        {
         tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
        }
    }
  };
RIOresult = tempresult.clone();
frame.copyTo(RIOframe, tempresult);

};

void hand_YCbCr()
{
cvtColor(frame, frameYCrCb , CV_BGR2YCrCb);
Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));
inRange(frameYCrCb, Scalar(0,minCr,minCb), Scalar(255, maxCr, maxCb), RIOresult);
frame.copyTo(RIOframe, RIOresult);
};

void hand_YCbCr_Otsu()
{
cvtColor(frame, frameYCrCb , CV_BGR2YCrCb);
Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));

Mat detect;
vector<Mat> channels;
split(frameYCrCb, channels);
RIOresult = channels[1];
threshold(RIOresult, RIOresult, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

frame.copyTo(RIOframe, RIOresult);

};

void hand_opencv()
{
IplImage *frame1;
frame1 = &IplImage(frame); //Mat -> IplImage
CvAdaptiveSkinDetector filter(1, CvAdaptiveSkinDetector::MORPHING_METHOD_ERODE_DILATE);

IplImage *maskImg = cvCreateImage(cvSize(frame.cols, frame.rows), IPL_DEPTH_8U, 1);
IplImage *skinImg = cvCreateImage(cvSize(frame.cols, frame.rows), IPL_DEPTH_8U, 3);
cvZero(skinImg);
filter.process(frame1, maskImg);    // process the frame
cvCopy(frame1, skinImg, maskImg);
Mat tmp(skinImg);  //IplImage -> Mat
RIOresult= tmp.clone();
cvReleaseImage(&skinImg);
cvReleaseImage(&maskImg);

frame.copyTo(RIOframe, RIOresult);

};

GitHub 加速计划 / opencv31 / opencv
77.34 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:27 天前 )
e1fec156 features2d: fixed out of bounds access in SIFT 5 天前
63087396 - 5 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐