常见的肤色检测算法都是基于统计的检测模型,即对大量肤色样本统计其在相应量上的值(一般是均值)的取值范围。有直接在RGB空间进行的,但一般在由RGB空间转化到其他颜色空间如HSV YCbCr、YCgCb等统计效果较好些。
当我们有了这些统计值后就可以对新来的图片中的肤色进行判别了,这些计算都是相对固定的,网上有好多这些理论方面的介绍,在此不赘述。
我在此结合OpenCV实现在YCbCr空间进行肤色检测,同时给出OpenCV几种不同读取图像数据的操作。
1、YCbCr 椭圆肤色分割之直接读取图像数据
//YCbCr 椭圆肤色分割1 void EllipseSkinSegment1(Mat ColorIm, Mat& SkinBW) { int m = ColorIm.rows; int n = ColorIm.cols; SkinBW = Mat::zeros(m, n, CV_8UC1); Mat YCbCr, Y, Cr, Cb; vector<Mat> channels; cvtColor(ColorIm, YCbCr, CV_BGR2YCrCb);//必须要用CV_BGR2YCrCb,不能用CV_RGB2YCrCb split(YCbCr, channels); Y = channels.at(0); Cr = channels.at(1); Cb = channels.at(2); Y.convertTo(Y, CV_32FC1); Cr.convertTo(Cr, CV_32FC1); Cb.convertTo(Cb, CV_32FC1); float Cx = 109.38, Cy = 152.02; float Ecx = 1.60, Ecy = 2.41; float a = 25.39, b = 14.03; float Theta = 2.53; Mat RotateM = (Mat_<float>(2, 2) << cos(Theta), sin(Theta), -sin(Theta), cos(Theta)); Mat Diff = Mat::zeros(2, 1, CV_32FC1);; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { Diff.at<float>(0, 0) = Cb.at<float>(i, j) - Cx; Diff.at<float>(1, 0) = Cr.at<float>(i, j) - Cy; Mat RotateVal = RotateM*Diff; float x = RotateVal.at<float>(0, 0); float y = RotateVal.at<float>(1, 0); float EllipseV = pow((x - Ecx), 2.0) / (a*a) + pow((y - Ecy), 2.0) / (b*b); if (EllipseV <= 1) { SkinBW.at<uchar>(i, j) = 255; } if (Y.at<float>(i, j) < 80.0) { SkinBW.at<uchar>(i, j) = 0; } } } //imshow("原始肤色区域", SkinBW); //形态学开操作,去掉小的噪点区域 int KnelW = 5; morphologyEx(SkinBW, SkinBW, MORPH_OPEN, Mat(KnelW, KnelW, CV_8U), Point(-1, -1), 1); }
2、YCbCr 椭圆肤色分割之间接(指针)读取图像数据
//YCbCr 椭圆肤色分割2 void EllipseSkinSegment2(Mat ColorIm, Mat& SkinBW) { int m = ColorIm.rows; int n = ColorIm.cols; SkinBW = Mat::zeros(m, n, CV_8UC1); Mat YCbCr, Y, Cr, Cb; vector<Mat> channels; cvtColor(ColorIm, YCbCr, CV_BGR2YCrCb);//必须要用CV_BGR2YCrCb,不能用CV_RGB2YCrCb split(YCbCr, channels); Y = channels.at(0); Cr = channels.at(1); Cb = channels.at(2); Y.convertTo(Y, CV_32FC1); Cr.convertTo(Cr, CV_32FC1); Cb.convertTo(Cb, CV_32FC1); float Cx = 109.38, Cy = 152.02; float Ecx = 1.60, Ecy = 2.41; float a = 25.39, b = 14.03; float Theta = 2.53; Mat RotateM = (Mat_<float>(2, 2) << cos(Theta), sin(Theta), -sin(Theta), cos(Theta)); Mat Diff = Mat::zeros(2, 1, CV_32FC1);; for (int i = 0; i < m; i++) { float* YRow = Y.ptr<float>(i); float* CbRow = Cb.ptr<float>(i); float* CrRow = Cr.ptr<float>(i); uchar* SkinBWRow = SkinBW.ptr<uchar>(i); for (int j = 0; j < n; j++) { Diff.at<float>(0, 0) = CbRow[j] - Cx; Diff.at<float>(1, 0) = CrRow[j] - Cy; Mat RotateVal = RotateM*Diff; float x = RotateVal.at<float>(0, 0); float y = RotateVal.at<float>(1, 0); float EllipseV = pow((x - Ecx), 2.0) / (a*a) + pow((y - Ecy), 2.0) / (b*b); if (EllipseV <= 1) { SkinBWRow[j] = 255; } if (YRow[j] < 80.0) { SkinBWRow[j] = 0; } } } //imshow("原始肤色区域", SkinBW); //形态学开操作,去掉小的噪点区域 int KnelW = 5; morphologyEx(SkinBW, SkinBW, MORPH_OPEN, Mat(KnelW, KnelW, CV_8U), Point(-1, -1), 1); }
主函数及测试:
int main() { string ImagePath = "P7.jpg"; Mat ColorIm = imread(ImagePath); imshow("原画", ColorIm); Mat SkinBW; EllipseSkinSegment1(ColorIm, SkinBW); imshow("肤色二值图", SkinBW); waitKey(0); return 0; }测试结果:
扫描二维码关注公众号,回复:
1052590 查看本文章
1和2具体耗时没有测试,但我实际使用时发现2比1快多了。
其他几种常见肤色检测的:混高合斯以及YCbCg肤色检测,我都用OpenCV实现了一下放在一个SkinSegment.cpp文件中,有需要的可以去下面网上下载:
http://download.csdn.net/detail/lingyunxianhe/9907976