前言:
這裏需要首先介紹一下一種顏色空間叫做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;
}
效果:
這裏註明一下,由於項目需要在形態學濾波的時候定義的結構元素較大,可能會濾去必要的信息,如不需要可以將濾波的代碼刪去。
如果有哪位代佬知道更好,願意的話可以跟我分享一下,非常感謝!!!
希望對讀者有所幫助,喜歡的話可以關注一下我的公衆號,我會把學習筆記發在上面,大家可以一起共同學習!