好久没写博客了,因为最近都忙着赶项目和打比赛==| 好吧,今天我打算写一篇关于使用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. jquery定时刷新数据

    $(function () { setInterval("startRequest()", 3000); }); function startRequest() { $(" ...

  2. asp.net或javascript判断是否手机访问

    /// <summary> /// 判断手机用户UserAgent /// </summary> /// <returns></returns> pri ...

  3. 使用C#系统服务定时执行操作

    1.新建项目 --> Windows 服务 2.Service1.cs代码 using System; using System.Collections.Generic; using Syste ...

  4. 【网络爬虫入门02】HTTP客户端库Requests的基本原理与基础应用

    [网络爬虫入门02]HTTP客户端库Requests的基本原理与基础应用 广东职业技术学院  欧浩源 1.引言 实现网络爬虫的第一步就是要建立网络连接并向服务器或网页等网络资源发起请求.urllib是 ...

  5. 即时通信系统Openfire分析之八:集群管理

    前言 在第六章<路由表>中,客户端进行会话时,首先要获取对方的Session实例.获取Session实例的方法,是先查找本地路由表,若找不到,则通过路由表中的缓存数据,由定位器获取. 路由 ...

  6. 【框架学习与探究之AOP--Castle DynamicProxy】

    声明 本文欢迎转载,原始地址:http://www.cnblogs.com/DjlNet/p/7603654.html 前言 先说一点废话,在此之前博主也在早期就接触了或者看了些许AOP相关的文章,然 ...

  7. git 合并两个仓库

    我有两个仓库,一个是gitbook在写一本 一个是放在github的垃圾,这个是我想要开个人网站,但是做的还是不行https://github.com/lindexi/lindexi.github.i ...

  8. C#实现局域网内远程开机

    1.远程开机原理 远程开机Wake on LAN(WOL),俗称远程唤醒,远程唤醒的实现主要是向目标主机发送特殊格式的数据包,是AMD公司制作的MagicPacket这套软件以生成网络唤醒所需要的特殊 ...

  9. [Java第一课]环境变量的配置以及eclipse一些常用快捷键

    1.环境变量的配置(这里对xp系统电脑来说:) 首先安装jdk软件. 然后在我的电脑(右键)-->属性-->高级-->环境变量-->系统变量(注意)-->新建(新建两个p ...

  10. appium测试代码nullpoint

    今天写了个简单向上滑动,执行到向上滑动操作,报nullpoint异常,经过各种乱碰终于解决了,现记录一下过程,以备以后参考! 环境背景:java+testng+appium 在@Test下调用 dir ...