基于YCbCr的肤色检测OpenCV实现

           常见的肤色检测算法都是基于统计的检测模型,即对大量肤色样本统计其在相应量上的值(一般是均值)的取值范围。有直接在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 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

猜你喜欢

转载自blog.csdn.net/lingyunxianhe/article/details/75907070