这里需要首先介绍一下一种颜色空间叫做YCrCb(YUV)空间:
YCrCb色彩空间,主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽。
其中“Y”表示明亮度,“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。
“U”和“V” 表示的则是色度。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。
一般地,我们在进行肤色检测时常常只需要是用到Cr和Cb两个通道,通过过滤掉Y通道之后,我们的肤色能够达到一个比较好的识别度。
1、用Mat::zeros(src.size(), CV_8UC1);创建一张黑色背景的图像,然后通过ellipse()函数绘制出一个白色实心的椭圆,如下图:
2、接下来我们就可以将待检测图像转换成YCrCb颜色空间,在进行一系列的降噪处理,进而过滤掉一些噪声,然后提取出Cr和Cb通道,再遍历图像,只需得到该像素点的Cr,Cb两个坐标,在上面的椭圆中找到该坐标的值,如果大于0,则为皮肤,反之亦然。
3、对得到的图像进行提取轮廓,再通过轮廓面积筛选,剔除掉误检测的噪声点,即可。
#include
#include
#include
#include
using namespace std;
using namespace cv;
vector imread_model();
int main()
{
Mat src, background, med_src, gray_src, dest, ycrcb_image, gray_diff;
VideoCapture capture(0);
if (!capture.isOpened()) {
printf("could not find the video file...\n");
return -1;
}
//构建椭圆模型
Mat skinMat = Mat::zeros(Size(256, 256), CV_8UC1);
ellipse(skinMat, Point(113, 155.6), Size(26, 22), 43.0, 0.0, 360, Scalar(255, 255, 255), -1);
//定义结构元素
Mat element = getStructuringElement(MORPH_RECT, Size(4, 4), Point(-1, -1));
Mat YCrCbMat;
//加载模板图像轮廓
while (1)
{
capture >> src;
Mat temp = Mat::zeros(src.size(), CV_8UC1);
//颜色空间转换YCrCb
cvtColor(src, YCrCbMat, COLOR_BGR2YCrCb);
//椭圆肤色模型检测
for (int i = 0; i < src.rows; i++)
{
uchar *p1 = (uchar*)temp.ptr(i);
Vec3b *p2 = (Vec3b*)YCrCbMat.ptr(i);
for (int j = 0; j < src.cols; j++)
{
//颜色判断
if (skinMat.at(p2[j][1], p2[j][2]) > 0) p1[j] = 255;
}
}
GaussianBlur(temp, temp, Size(5, 5),0,0);
//形态学闭操作
morphologyEx(temp, temp, MORPH_CLOSE, element);
//定义轮廓参数
vector > contours;
vector > tempcontours;
vector hierarchy;
//查找连通域
//CV_RETR_EXTERNAL:只检测出最外轮廓
findContours(temp, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
Mat tmp(src.size(), CV_8UC1, Scalar::all(0));
//筛选的轮廓
for (int i = 0; i < contours.size(); i++)
{
//判断区域面积
if (fabs(contourArea(contours[i])) > 1000) tempcontours.push_back(contours[i]);
for (int j = 0; j < contours[i].size(); j++) tmp.at(contours[i][j].y, contours[i][j].x) = 255;
}
Mat drawing = Mat::zeros(src.size(), CV_8UC3);
drawContours(drawing, tempcontours, -1, Scalar(255, 255, 255), FILLED);
vector mu(tempcontours.size());
vector mc(tempcontours.size());
Rect rect;
for (int i = 0; i < tempcontours.size(); i++)
{
rect = boundingRect(tempcontours[i]);
mu[i] = moments(tempcontours[i], false); //计算轮廓矩
mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00); //计算轮廓中心
circle(drawing, mc[i], 3, Scalar(0, 0, 255), -1, LINE_AA, 0); //画中心圆
rectangle(drawing, rect,Scalar(0,255,0),2,LINE_AA,0);
}
imshow("result", drawing);
if (waitKey(30) > 0)
{
break;
}
}
capture.release();
return 0;
}
这里注明一下,由于项目需要在形态学滤波的时候定义的结构元素较大,可能会滤去必要的信息,如不需要可以将滤波的代码删去。
如果有哪位代佬知道更好,愿意的话可以跟我分享一下,非常感谢!!!
希望对读者有所帮助,喜欢的话可以关注一下我的公众号,我会把学习笔记发在上面,大家可以一起共同学习!