基于椭圆皮肤模型的皮肤检测-Opencv

肤色检测主要有以下六种方法:

1、RGB color space
2、Ycrcb之cr分量+otsu阈值化
3、YCrCb中133<=Cr<=173 77<=Cb<=127
4、HSV中 75、基于椭圆皮肤模型的皮肤检测
6、opencv自带肤色检测类AdaptiveSkinDetector
不过经检验,用RGB的方法受光线影响比较大,鲁棒性太低了,所以我们这次就不实验它了,留下一个判别式就好。
此处主要说第五项,其他项请参考 https://blog.csdn.net/qq_22527639/article/details/81501565

基于椭圆皮肤模型的皮肤检测

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

看看这个YCrCb是个啥:
肤色YCbCr颜色空间是一种常用的肤色检测的色彩模型,其中Y代表亮度,Cr代表光源中的红色分量,Cb代表光源中的蓝色分量。人的肤色在外观上的差异是由色度引起的,不同人的肤色分布集中在较小的区域内。肤色的YCbCr颜色空间CbCr平面分布在近似的椭圆区域内,通过判断当前像素点的CbCr是否落在肤色分布的椭圆区域内,就可以很容易地确认当前像素点是否属于肤色。将图像转换到YCbCr空间并且在CbCr平面进行投影,可以采集到肤色的样本点。

将CbCr平面均分为许多小区域,将每个区域的中心点CbCr色度值作为当前区域的特征值,对肤色区域像素值进行遍历,如果当前像素值落在该区域内则替换当前区域特征值。

基于椭圆皮肤模型的皮肤检测-Opencv_第1张图片

这么一看,和HSV模型有点像哈。

基于椭圆皮肤模型的皮肤检测-Opencv_第2张图片

3、YUV和RGB互相转换的公式如下(RGB取值范围均为0-255)︰
 Y = 0.299R + 0.587G + 0.114B
 U = -0.147R - 0.289G + 0.436B
 V = 0.615R - 0.515G - 0.100B
 R = Y + 1.14V
 G = Y - 0.39U - 0.58V
 B = Y + 2.03U

大概知道了,YUV(YCrCb)就是一个单独把亮度分离开来的颜色模型,使用这个颜色模型的话,像肤色不会受到光线亮度而发生改变。

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

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表示绘制椭圆弧线相对该椭圆自己的水平轴的终止角度;后面的参数比较普通就不介绍了。

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

长短半轴的长度,以及角度是怎么得出来的,根据这个图形,大概就可以看出来角度是43~45之间了。这个参数应该是固定的。

#include
#include

using namespace cv;
using namespace std;

void ellipse_detect(Mat &src, Mat& detected_one);
int main(int argc, char** argv)
{
	Mat src = imread("song.jpg");
	if (src.empty()){
		cerr << "could not load image..." << endl;
		return -1;
	}
	namedWindow("Pre Image", CV_WINDOW_AUTOSIZE);
	imshow("Pre Image", src);

	//需要处理一下尺寸
	resize(src, src, Size(src.cols, src.rows));

	//肤色检测
	Mat detectImg;
	ellipse_detect(src, detectImg);

	imshow("肤色检测图", detectImg);

	waitKey(0);
	return 0;
}

void ellipse_detect(Mat &src, Mat& detected_one)
{
	Mat img = src.clone();
	Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1);	//256*256的矩阵,相当于CrCb分量的横纵坐标

	//利用Opencv自带的椭圆生成函数生成一个肤色椭圆模型
	ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 15.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);
	Mat ycrcb_image;

	Mat output_mask = Mat(img.size(), CV_8UC1);
	output_mask = Scalar::all(0);
	cvtColor(img, ycrcb_image, COLOR_BGR2YCrCb);//转换为YCrCb模型
	for (int row = 0; row < ycrcb_image.rows; row++){
		uchar *currentRow = ycrcb_image.ptr(row);//ycrcb_image指针
		uchar *maskRow = output_mask.ptr(row);//mask指针
		for (int col = 0; col < ycrcb_image.cols; col++){
			uchar y, cr, cb;
			y = *currentRow++;
			cr = *currentRow++;
			cb = *currentRow++;

			Vec3b ycrcb = Vec3b(y, cr, cb);
			if (skinCrCbHist.at(cr, cb)>0){
				*maskRow = 255;
			}
			else{
				*maskRow = 0;
			}
			maskRow++;
		}

	}

	img.copyTo(detected_one, output_mask);//返回肤色图
	//表示img和output_mask与操作后赋给detected_one
}

这段代码的大致流程是这样的,首先先设置一个单通道的256*256大小的二维矩阵,全0填充完也就是黑色的了。

Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1);//256*256的矩阵,相当于CrCb分量的横纵坐标。

然后再上面画一个椭圆,具体位置大小角度等是固定的了,这是前人实验总结出来的。

ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 15.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);//画椭圆,-1表示全填充,也就是说这个椭圆是全白的

接下来把获取的图像转为YCrCb颜色模型的图像,由于用这个模型就是为了排除Y(亮度)的影响,所以横纵坐标分别是Cb,Cr,看上面的图就知道了。转完后,遍历转完图像的像素点,看其后两个通道的值,也就是Cb,Cr的值,转到前面的掩模上就是它的横纵坐标,那我们之前根据这个坐标,对应到的位置如果在椭圆里面,之前椭圆是白色的嘛,底是黑色的,那么说明这个点是肤色点了。

也就是在这个图阴影的位置。

我们新建了一个掩模,Mat output_mask = Mat::zeros(img.size(), CV_8UC1);,跟原图一样大小,不过是黑色的。然后如果原图上哪一个点是肤色像素点,那么我们就把掩模上的这个点设为255,之后用copyTo函数的时候,会把这两个图img、output_mask进行掩模运算(与操作)再赋值detected_one过去,就是只显示肤色像素点的图了。

说起掩模,可以去这个地方看:http://www.cnblogs.com/skyfsm/p/6894685.html
 

你可能感兴趣的:(基于椭圆皮肤模型的皮肤检测-Opencv)