常见的肤色检测算法都是基于统计的检测模型,即对大量肤色样本统计其在相应量上的值(一般是均值)的取值范围。有直接在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;
}
测试结果:
1和2具体耗时没有测试,但我实际使用时发现2比1快多了。
其他几种常见肤色检测的:混高合斯以及YCbCg肤色检测,我都用OpenCV实现了一下放在一个SkinSegment.cpp文件中,有需要的可以去下面网上下载:
http://download.csdn.net/detail/lingyunxianhe/9907976