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

  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

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

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

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

查阅资料后可以知道,前人做了大量研究,肤色在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范围来判定皮肤的算法太受光线的影响了,鲁棒性确实不好。

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

经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到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来获取~

OpenCV探索之路(二十七):皮肤检测技术的更多相关文章

  1. OpenCV颜色转换和皮肤检测

    本笔记重点记录OpenCV中的颜色转换和利用色彩空间的特性进行皮肤检测 颜色转换 实现原理 之所以要引入色调/饱和度/亮度的色彩空间概念,是因为人们喜欢凭直觉分辨各种颜色,而它与这种方式吻合.实际上, ...

  2. OpenCV探索之路(十七):Mat和IplImage访问每个像素的方法总结

    在opencv的编程中,遍历访问图像元素是经常遇到的操作,掌握其方法非常重要,无论是Mat类的像素访问,还是IplImage结构体的访问的方法,都必须扎实掌握,毕竟,图像处理本质上就是对像素的各种操作 ...

  3. OpenCV探索之路(十七):Mat和IplImage访问像素的方法总结

    在opencv的编程中,遍历访问图像元素是经常遇到的操作,掌握其方法非常重要,无论是Mat类的像素访问,还是IplImage结构体的访问的方法,都必须扎实掌握,毕竟,图像处理本质上就是对像素的各种操作 ...

  4. OpenCV探索之路(二十四)图像拼接和图像融合技术

    图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要. 再举一个身边的例子吧,你用你的手 ...

  5. OpenCV探索之路(十六):图像矫正技术深入探讨

    刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...

  6. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)

    原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序) 基于HTTP-GET的元数据发布方式与基于WS-MEX原理类似,但是ServiceMetad ...

  7. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)

    原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序) 通过<如何将一个服务发布成WSDL[编程篇]>的介绍我们知道了如何可以通过编程或者配 ...

  8. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇]

    原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇] 对于WCF服务端元数据架构体系来说,通过MetadataExporter将服务的终结点导出成MetadataSet(参考< ...

  9. 基于深度学习的病毒检测技术无需沙箱环境,直接将样本文件转换为二维图片,进而应用改造后的卷积神经网络 Inception V4 进行训练和检测

    话题 3: 基于深度学习的二进制恶意样本检测 分享主题:全球正在经历一场由科技驱动的数字化转型,传统技术已经不能适应病毒数量飞速增长的发展态势.而基于沙箱的检测方案无法满足 APT 攻击的检测需求,也 ...

随机推荐

  1. python concurrent.futures

    python因为其全局解释器锁GIL而无法通过线程实现真正的平行计算.这个论断我们不展开,但是有个概念我们要说明,IO密集型 vs. 计算密集型. IO密集型:读取文件,读取网络套接字频繁. 计算密集 ...

  2. dotweb框架之旅 [三] - 常用对象-HttpServer

    dotweb属于一个Web框架,希望通过框架行为,帮助开发人员快速构建Web应用,提升开发效率,减少不必要的代码臃肿. dotweb包含以下几个常用对象: App(dotweb) App容器,为Web ...

  3. webpack-dev-server配置指南(使用webpack3.0)

    最近正在研究webpack,听说webpack可以自己搭建一个小型的服务器(使用过vue-cli的朋友应该都见识到过),所以迫不及待的想要尝试一下.不过,在实际操作中发现,用webpack搭建服务器仍 ...

  4. break和continue 的区别

    区别 break和continue都可在循环语句里面使用,也都可以控制外层的循环.但是continue只能在循环语句里面使用,break也可以使用在switch语句里面. break具体作用在循环语句 ...

  5. typescript 的 polyfill 学习1-Class 继承篇

    Class 继承 js 是多范式的编程语言,同样也是支持面向对象编程的,类 是面向对象中是很重要的概念. 区别于传统的java,c#基于模板的类,js是基于原型的. 类继承一般是通过原型链的方式来实现 ...

  6. WPF 圖表控件 MetroChart

    Torsten Mandelkow MetroChart包括以下: ColumnChart(ClusteredColumnChart,StackedColumnChart,StackedColumnC ...

  7. 如何在 UWP 使用 wpf 的 Trigger

    本文需要告诉大家,如何使用 Behaviors 做出 WPF 的 Trigger ,需要知道 UWP 不支持 WPF 的 Trigger . 安装 Behaviors 请使用 Nuget 安装,可以输 ...

  8. spring框架应用系列一:annotation-config自动装配

    本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7716678.html 解决问题 通过spring XML配置文件, ...

  9. 【ASP.NET MVC 学习笔记】- 18 Bundle(捆绑)

    本文参考:http://www.cnblogs.com/willick/p/3438272.html 1.捆绑(Bundle),一个在 View 和 Layout 中用于组织优化浏览器请求的 CSS ...

  10. time函数获取时间与本地时间不一致

    修改php.ini,将“date.timezone”项修改为“date.timezone = PRC”. 大陆内地可用的值是:Asia/Chongqing ,Asia/Shanghai ,Asia/U ...