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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章