Opencv学习笔记(十二)基于椭圆模型的人体肤色检测

前言:

这里需要首先介绍一下一种颜色空间叫做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 <opencv2\opencv.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;

vector<Mat> 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<uchar>(i);
   Vec3b *p2 = (Vec3b*)YCrCbMat.ptr<Vec3b>(i);
   for (int j = 0; j < src.cols; j++)
   {
    //颜色判断
    if (skinMat.at<uchar>(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<vector<Point> > contours;
  vector<vector<Point> > tempcontours;
  vector<Vec4i> 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<uchar>(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<Moments> mu(tempcontours.size());
  vector<Point2f>  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;
}

效果:

在这里插入图片描述

这里注明一下,由于项目需要在形态学滤波的时候定义的结构元素较大,可能会滤去必要的信息,如不需要可以将滤波的代码删去。

如果有哪位代佬知道更好,愿意的话可以跟我分享一下,非常感谢!!!

希望对读者有所帮助,喜欢的话可以关注一下我的公众号,我会把学习笔记发在上面,大家可以一起共同学习!

在这里插入图片描述
Alt

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rosen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值