肤色检测

方法一:基于YCrCb颜色空间Cr,Cb范围筛选法


思路如下:

1)将RGB模型转换为YCbCr模型

2)阈值分割:据资料显示,正常黄种人的Cr分量大约在133至173之间,Cb分量大约在77至127之间。大家可以根据自己项目需求放大或缩小

下面放上源代码:

#include<opencv2\opencv.hpp>
#include<vector>
#include<iostream>
using namespace cv;

int main()
{
	Mat srcImage = imread("hand.jpg");
	if (!srcImage.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	imshow("srcImage", srcImage);

	Mat result, tmp;
	Mat Y, Cr, Cb;
	std::vector<Mat> channels;//定义一些Mat的变量用来存储Y Cr Cb的变量

	srcImage.copyTo(tmp);//将原图拷贝一份到tmp中
	cvtColor(tmp, tmp, CV_BGR2YCrCb);//转换到YCrCb空间
	split(tmp, channels);//通道分离的图存在channels中
	Y = channels.at(0);
	Cr = channels.at(1);
	Cb = channels.at(2);

	result = Mat::zeros(srcImage.size(), CV_8UC1);
	for (int i = 0; i < result.rows; i++)
	{
		//各个图首行的指针
		uchar* currentCr = Cr.ptr< uchar>(i);
		uchar* currentCb = Cb.ptr< uchar>(i);
		uchar* current = result.ptr< uchar>(i);
		for (int j = 0; j < result.cols; j++)
		{
			/*
			据资料显示,正常黄种人的Cr分量大约在133至173之间,
			Cb分量大约在77至127之间。大家可以根据自己项目需求放大或缩小这两个分量的范围,会有不同的效果。
			*/
			if ((currentCr[j] > 133) && (currentCr[j] < 173) && (currentCb[j] > 77) && (currentCb[j] < 127))
				current[j] = 255;
			else
				current[i] = 0;
		}
	}
	imshow("result", result);
	waitKey(0);
	return 0;
}


原图:



效果图:

扫描二维码关注公众号,回复: 1039464 查看本文章


这里有一条黑线不知道是啥情况!!!


方法二:基于椭圆皮肤模型的皮肤检测

皮肤检测----肤色椭圆模型

        皮肤模型中有单高斯,混合高斯,贝叶斯模型和椭圆模型等。经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到YCrCb空间,则在CrCb二维空间中这些皮肤像素点近似成一个椭圆分布。因此如果我们得到了一个CrCb的椭圆,下次来一个坐标(Cr, Cb)我们只需判断它是否在椭圆内(包括边界),如果是,则可以判断其为皮肤,否则就是非皮肤像素点。

 

        肤色区域的颜色与亮度成非线性函数关系,在低亮度条件下,YCbCr 空间中色度的聚类性会随Y 呈非线性变换降低。为了使肤色聚类不受亮度Y 的影响并将YCbCr 颜色空间中的色度Cb、Cr进行非线性变换,在研究YCbCr 颜色空间的肤色聚类情况的基础上,去掉高光阴影部分(即 Y 的最大最小值),YCbCr 空间色度非线性变换过程中,用 Cb·· Y 、 Cr·· Y 表示肤色区域的中轴线,肤色区域的宽度分别用 Vcb、Vcr 表示。

        即将图像转化到YCbCr 空间并且在CbCr平面进行投影,因此我们采集了肤色的样本点,将其投影到此平面,并且投影后,我们进行了相应的非线性变换K-L变换

图1.1 椭圆模板示例

进而形成的的统计椭圆模型

  说它比较有趣是因为我们是用一副图像来存储上面的椭圆的,而不是直接采用椭圆数学方程。该图像是二值图像,即椭圆区域内部为白色,其它地方为黑色。所以当其需要判断其它像素点时,只需将该像素点转换成Cr,Cb两个坐标,然后在上面的椭圆中找到该坐标的值,如果非0,则为皮肤,反之亦然。

  在实际代码中,该椭圆是采用绘画函数绘制到图片上的,一句代码而已:

ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 15.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);

 

void ellipse(Mat& img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar&color, int thickness=1, int lineType=8, int shift=0)

  该函数是用来在指定图片上绘制椭圆弧线的。

  参数image为需要绘制椭圆的图像;

  参数center是该椭圆的中心点坐标;

  参数axes是该椭圆的长半轴和短半轴;

  参数angle是该椭圆和水平方向上的旋转夹角;

  参数startAngle表示绘制椭圆弧线相对该椭圆自己的水平轴的起始角度;

  参数endAngel表示绘制椭圆弧线相对该椭圆自己的水平轴的终止角度;

  后面的参数比较普通就不介绍了。

  绘制椭圆曲线的示意图如下所示:


放上源代码:

#include<opencv2\opencv.hpp>
#include<vector>
#include<iostream>
using namespace cv;

int main()
{
	Mat srcImage = imread("hand.jpg");
	if (!srcImage.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	imshow("srcImage", srcImage);

	//构建椭圆模型
	Mat tmp=srcImage.clone();//克隆一张原图

	Mat SkinMat = Mat::zeros(Size(256, 256), CV_8UC1);
	ellipse(SkinMat, Point(113, 115.6), Size(23.4, 15.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);
	Mat YcrcbMat, resultMat;//创建一张ycrcb颜色空间图,与结果图
	resultMat = Mat::zeros(tmp.size(), CV_8UC1);
	//将颜色空间转换到ycrcb
	cvtColor(tmp, YcrcbMat, CV_BGR2YCrCb);
	for (int i = 0; i < resultMat.rows; i++)
	{
		uchar *presult = resultMat.ptr<uchar>(i);
		Vec3b *ycrcb = YcrcbMat.ptr<Vec3b>(i);
		for (int j = 0; j < resultMat.cols; j++)
		{
			//颜色判断
			if (SkinMat.at<uchar>(ycrcb[j][1], ycrcb[j][2]) > 0)
				presult[j] = 255;
		}
	}
	imshow("result", resultMat);
	waitKey(0);
	return 0;
}



猜你喜欢

转载自blog.csdn.net/linqianbi/article/details/79155823