跳转至

在大佬的博客看到这篇文章:https://www.cnblogs.com/Imageshop/p/3264238.html ,关于一个肤色检测的算法,很感兴趣,所以打算以自己的方式总结和C++实现一下这个算法。

算法来源

https://files-cdn.cnblogs.com/files/Imageshop/Adaptive_skin_color_modeling_using_the_skin_locus.pdf https://files-cdn.cnblogs.com/files/Imageshop/A_novel_method_for_detecting_lips_eyes_and_faces_in_real_time.PDF https://wenku.baidu.com/view/afb818f14693daef5ef73d11.html

算法原理

上面的第一篇论文首次提出了二次多项式混合模型,然后第2篇论文对此算法进行了改进。最后得出的模型由2个二次多项式和一个圆方程构成,这就是混合的意思。方程为:

l_{upper}(r)=-1.3767r^2+1.0743r+0.1452 (1) l_{lower}(r)=-0.776r^2+0.5601r+0.1766(2) W(r, g)=(r-0.33)^2+(g-0.33)^2 (3)

在以上3个方程的基础上,肤色检测可以通过以下规则实现:

R_1: g>l_{lower}(r) And g < l_{upper}(r) R_2:W(r, g)>=0.0004 R_3:R>G>B R_4:R-G>=45

上式中,小写r,g,b(未涉及)为对R/G/B(byte类型的数据,0-255)进行归一化后的数据,即:

r=R/(R+G+B) g=G/(R+G+B) b=B/(R+G+B)

代码实现

实际上有了上述式子据可以轻易实现这个算法了,问题是如果直接这样计算的话,会有大量的浮点数运算,会大大影响程序的执行速度,所以有必要做一些优化,最常用的优化手段就是将浮点数运算尽量变成整数运算。

首先来看R_1g>l_{lower}(r) g>-0.776r^2+0.5601r+0.1766 => G/sum>-0.776\times (R/Sum)^2+0.5601\times (R/sum)+0.1766

不等式两边同乘Sum \times Sum=> G\times Sum>-0.776\times R^2+0.5601\times R \times Sum + 0.1766 \times Sum \times Sum

不等式两边同乘10000=> 10000\times G\times Sum>-7760 \times R^2 + 5601\times R \times Sum + 1766 \times Sum \times Sum

然后R_1的第二个条件同理,就不说了。 最后对于R_2

W(r, g)>0.0004 => (r-0.33)^2+(g-0.33)^2>=0.0004 => (R/Sum-0.33)^2+(G/sum-0.33)^2>=0.0004

不等式两边同乘Sum\times Sum=> (R-0.33Sum)^2+(G-0.33Sum)^2>=0.0004\times Sum\times Sum

不等式2边同时乘以156=> (156R-52Sum)^2+(256-52Sum)^2>=0.0624\times Sum\times Sum

这里注意最后156*0.33=51.48,这里为了速度牺牲了一丢丢精度,向上取整为52。等式右边的0.0624和1/16=0.0625十分接近,所以我们右边的0.0624可以看成1/16,除法用移位来表示会更快。 OK,我们现在用几乎可以忽略不计的精度损失换取了我们的代码中没有浮点数运算,接下来就开始实现代码吧。

另外一个Trick,就是判断顺序的问题,我们可以先判断简单的,再判断复杂的,这样有助于减少计算量。

Mat SkinDetection(Mat src) {
    int row = src.rows;
    int col = src.cols;
    int channels = src.channels();
    Mat dst(row, col, CV_8UC3);
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            for (int k = 0; k < channels; k++) {
                dst.at<Vec3b>(i, j)[k] = src.at<Vec3b>(i, j)[k];
                //dst.at<Vec3b>(i, j)[k] = 0;
            }
            int B = src.at<Vec3b>(i, j)[0];
            int G = src.at<Vec3b>(i, j)[1];
            int R = src.at<Vec3b>(i, j)[2];
            if (R - G >= 45) {
                if (G > B) {
                    int Sum = R + G + B;
                    int T1 = 156 * R - 52 * Sum;
                    int T2 = 156 * G - 52 * Sum;
                    if (T1 * T1 + T2 * T2 >= (Sum * Sum) >> 4) {
                        T1 = 10000 * G * Sum;
                        int Lower = -7760 * R * R + 5601 * R * Sum + 1766 * Sum * Sum;
                        if (T1 > Lower) {
                            int Upper = -13767 * R * R + 10743 * R * Sum + 1452 * Sum * Sum;
                            if (T1 < Upper) {
                                for (int k = 0; k < channels; k++) {
                                    dst.at<Vec3b>(i, j)[k] = 255;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return dst;
}

效果

原图:

在这里插入图片描述在这里插入图片描述

这里我是把不是肤色的像素点设置为0了,如果设为原图的像素点得到的结果为:

在这里插入图片描述

我后面又测试了一些图片,发现很多图片都只能识别小部分肤色,存在大量肤色区域丢失的情况,所以这个算法大概是不能实用的。如果想准确的做肤色检测,还是上深度学习吧。但是希望这篇文章表现出的优化技巧可以帮助到你。

参考博客: https://www.cnblogs.com/Imageshop/p/3264238.html