传统的皮肤检测算法有通常基于颜色空间、光谱特征和肤色反射模型等方法。简单介绍如下:
- 基于颜色空间的阈值肤色识别:这种方法通常使用RGB、HSV或YCrCb等颜色空间,并根据肤色在这些颜色空间中的分布特点设置阈值进行肤色区域的筛选。例如,在HSV颜色空间中,可以通过设置色相(H)的范围来筛选肤色区域。
- 基于光谱特征的肤色识别:这种方法利用肤色在光谱上的特殊性质进行识别。由于皮肤对不同波长的光线吸收和反射的特性,可以在特定的光谱范围内检测肤色。
- 基于肤色反射模型的肤色识别:这种方法通过建立肤色反射模型来描述皮肤表面的反射特性,并根据模型参数进行肤色区域的识别。这种方法通常需要更多的计算资源和复杂的算法实现。
在工程使用上,基于颜色空间的阈值肤色检测算法是一种常用的皮肤检测方法。这种方法利用颜色空间中的颜色分布特点,通过设定一系列阈值来筛选出图像中的肤色区域。以下是这类方法的原理步骤
1. 颜色空间选择
首先,需要选择一个合适的颜色空间。常用的颜色空间包括RGB、HSV和YCrCb等。RGB是最常用的颜色空间,但它对光照和阴影的变化比较敏感,因此可能不是最佳的选择。HSV颜色空间对光照和阴影的变化有一定的鲁棒性,因此常用于肤色检测。YCrCb颜色空间是JPEG图像压缩中常用的颜色空间,它与人眼对颜色的感知更接近,并且在肤色检测方面也有较好的效果。
2. 设定阈值
在选择了合适的颜色空间后,需要设定一系列阈值来筛选出肤色区域。这些阈值通常基于肤色在颜色空间中的分布特点来设定。例如,在HSV颜色空间中,可以设定色相(H)的范围为0到17度,饱和度(S)的范围为15到170,亮度(V)的范围为0到255。在YCrCb颜色空间中,可以设定Cr和Cb的范围来筛选出肤色区域。这里列举以下场景的判别式范围
1
2
3
4
5
|
//hsv: 0<=H<=17 and 15<=S<=170 and 0<=V<=255
//Ycrcb: 0<=Y<=255 and 135<=Cr<=180 and 85<=Cb<=135
//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
// OR
// R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B
|
3. 肤色区域筛选
设定好阈值后,就可以对图像中的每个像素进行肤色检测了。对于每个像素,根据其颜色值在颜色空间中的位置,判断其是否满足设定的阈值条件。如果满足条件,则认为该像素属于肤色区域,否则不属于肤色区域。
4. 后处理
经过上述步骤后,可以得到一个二值化的图像,其中肤色区域被标记为1,非肤色区域被标记为0。为了得到更好的效果,通常需要进行一些后处理操作,如形态学操作、滤波等。
缺点
需要注意的是,基于颜色空间的阈值肤色检测算法虽然简单直观,但也有一些局限性。例如,肤色在不同的光照和阴影条件下会有很大的变化,因此很难设定一个通用的阈值来适应所有情况。此外,不同人的肤色也有很大的差异,这也给肤色检测带来了一定的挑战。因此,其实可以在此基础上做一个小小的改进,通常输入图像的数据格式都是 RGB,所以在做 HSV的判别是否为肤色的同时,增加一个在 RGB的判断结果,两个颜色空间的判断结果取交集作为判断依据,以此提升识别准确率。取交集而非并集的原因是基于颜色空间的阈值肤色检测方法往往是误检的情况较多,而非漏检。
实现
实现非常简单,下面给下一参考实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
int SkinDetectByColorSpace(Image src, Image& dst)
{
if(src.IsEmpty()){return -1;}
dst = Image(src.GetSize(), IMAGE_TYPE::IMAGE_8UC1);
int w = src.GetSize().width;
int h = src.GetSize().height;
auto hsv = src.Convert(CONVERT_TYPE::RGB2HSV);
for(int i = 0; i < h; i++)
{
for(int j = 0; j < w; j++)
{
auto r = src.Ptr<std::uint8_t>(j ,i)[0];
auto g = src.Ptr<std::uint8_t>(j ,i)[1];
auto b = src.Ptr<std::uint8_t>(j ,i)[2];
auto h = hsv.Ptr<std::uint8_t>(j ,i)[0];
auto s = hsv.Ptr<std::uint8_t>(j ,i)[1];
auto v = src.Ptr<std::uint8_t>(j ,i)[2];
if((h < 17 && 15 <= s && s <= 170)
&& ((r > 95 && b > 20 && std::max({r , g, b}) - std::min({r, g, b}) > 15 && std::abs(r -g ) > 15 && r > g && r > b)
|| (r > 220 && g > 210 && b > 170 && std::abs(r - g) <= 15 && r > b && g > b)))
{
//is skin
dst.Ptr<std::uint8_t>(j, i)[0] = 255;
dst.Ptr<std::uint8_t>(j, i)[1] = 255;
dst.Ptr<std::uint8_t>(j, i)[2] = 255;
}else
{
dst.Ptr<std::uint8_t>(j, i)[0] = 0;
dst.Ptr<std::uint8_t>(j, i)[1] = 0;
dst.Ptr<std::uint8_t>(j, i)[2] = 0;
}
}
}
return 0;
}
|
完整实现https://github.com/mangoeffect/mvk-nodes/blob/master/src/modules/filter/skin_detect.cpp