1)获取视频帧
2)将视频帧转换到YCrCb颜色空间,并分割通道
3)基于Cr和Cb两个通道做肤色区域的分割,得到肤色区域二值图像
4)将二值图像分别做膨胀和腐蚀处理,得到前景和背景的标记(marker)图像,应用分水岭算法,得到大块肤色区域的边缘轮廓
5)对4)中得到的边缘轮廓用8向种子算法处理,对不同的肤色区域做了标记,并返回了不同肤色区域的边界范围,这些肤色区域作为人手区域的候选区域
6)将5)中得到的候选区域与准备好的人手模板(Cr通道)进行模板匹配,匹配前先将候选区域缩放到与模板相同的大小;使用的方法是平方差匹配法,得到每个候选区域的匹配值(越小越接近)
7)对5)中得到的候选区域中肤色像素的比例进行统计
8)根据6)与7)中得到的结果对候选区域进行筛选,认为匹配值 <0.02(0为最匹配,1为最不匹配),且肤色区域比例<0.65的区域为人手区域(因为人脸区域一般肤色占比比较高)
9)在输出帧中对确定的人手区域画长方形框做标记
讨论:
1)之所以使用肤色区域比例的筛选方法,是因为基于肤色的情况下,人脸和人手非常容易混淆,经过尝试发现增进模板的数量效果并不好,因此采用了这种方法,但这种方法带来的问题就是人手必须张开(从而降低肤色在候选框中的比例)才能稳定被找到。
2)其实不做模板匹配只统计肤色比例应该也是可以的,但是稳定性会比较差。
3)分水岭算法本站有
其中的模板图片为
2)将视频帧转换到YCrCb颜色空间,并分割通道
3)基于Cr和Cb两个通道做肤色区域的分割,得到肤色区域二值图像
4)将二值图像分别做膨胀和腐蚀处理,得到前景和背景的标记(marker)图像,应用分水岭算法,得到大块肤色区域的边缘轮廓
5)对4)中得到的边缘轮廓用8向种子算法处理,对不同的肤色区域做了标记,并返回了不同肤色区域的边界范围,这些肤色区域作为人手区域的候选区域
6)将5)中得到的候选区域与准备好的人手模板(Cr通道)进行模板匹配,匹配前先将候选区域缩放到与模板相同的大小;使用的方法是平方差匹配法,得到每个候选区域的匹配值(越小越接近)
7)对5)中得到的候选区域中肤色像素的比例进行统计
8)根据6)与7)中得到的结果对候选区域进行筛选,认为匹配值 <0.02(0为最匹配,1为最不匹配),且肤色区域比例<0.65的区域为人手区域(因为人脸区域一般肤色占比比较高)
9)在输出帧中对确定的人手区域画长方形框做标记
讨论:
1)之所以使用肤色区域比例的筛选方法,是因为基于肤色的情况下,人脸和人手非常容易混淆,经过尝试发现增进模板的数量效果并不好,因此采用了这种方法,但这种方法带来的问题就是人手必须张开(从而降低肤色在候选框中的比例)才能稳定被找到。
2)其实不做模板匹配只统计肤色比例应该也是可以的,但是稳定性会比较差。
3)分水岭算法本站有
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>
#include <list>
#include <map>
#include <stack>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
using namespace std;
using namespace cv;
//8邻接种子算法,并返回每块区域的边缘框
void Seed_Filling(const cv::Mat& binImg, cv::Mat& labelImg, int& labelNum, int (&ymin)[20], int(&ymax)[20], int(&xmin)[20], int(&xmax)[20]) //种子填充法
{
if (binImg.empty() ||
binImg.type() != CV_8UC1)
{
return;
}
labelImg.release();
binImg.convertTo(labelImg, CV_32SC1);
int label = 1;
int rows = binImg.rows - 1;
int cols = binImg.cols - 1;
for (int i = 1; i < rows - 1; i++)
{
int* data = labelImg.ptr<int>(i);
for (int j = 1; j < cols - 1; j++)
{
if (data[j] == 1)
{
std::stack<std::pair<int, int>> neighborPixels;
neighborPixels.push(std::pair<int, int>(j, i)); // 像素位置: <j,i>
++label; // 没有重复的团,开始新的标签
ymin[label] = i;
ymax[label] = i;
xmin[label] = j;
xmax[label] = j;
while (!neighborPixels.empty())
{
std::pair<int, int> curPixel = neighborPixels.top(); //如果与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它
int curX = curPixel.first;
int curY = curPixel.second;
labelImg.at<int>(curY,curX) = label;
neighborPixels.pop();
if ((curX>0)&&(curY>0)&&(curX<(cols-1))&&(curY<(rows-1)))
{
if (labelImg.at<int>(curY - 1,curX) == 1) //上
{
neighborPixels.push(std::pair<int, int>(curX, curY - 1));
//ymin[label] = curY - 1;
}
if (labelImg.at<int>( curY + 1,curX) == 1) //下
{
neighborPixels.push(std::pair<int, int>(curX, curY + 1));
if ((curY+1)>ymax[label])
ymax[label] = curY + 1;
}
if (labelImg.at<int>(curY,curX - 1) == 1) //左
{
neighborPixels.push(std::pair<int, int>(curX - 1, curY));
if ((curX - 1)<xmin[label])
xmin[label] = curX - 1;
}
if (labelImg.at<int>(curY,curX + 1) == 1) //右
{
neighborPixels.push(std::pair<int, int>(curX + 1, curY));
if ((curX + 1)>xmax[label])
xmax[label] = curX + 1;
}
if (labelImg.at<int>(curY - 1,curX-1) == 1) //左上
{
neighborPixels.push(std::pair<int, int>(curX - 1, curY - 1));
//ymin[label] = curY - 1;
if ((curX - 1)<xmin[label])
xmin[label] = curX - 1;
}
if (labelImg.at<int>(curY + 1,curX+1) == 1) //右下
{
neighborPixels.push(std::pair<int, int>(curX+1, curY + 1));
if ((curY + 1)>ymax[label])
ymax[label] = curY + 1;
if ((curX + 1)>xmax[label])
xmax[label] = curX + 1;
}
if (labelImg.at<int>( curY + 1,curX - 1) == 1) //左下
{
neighborPixels.push(std::pair<int, int>(curX - 1, curY+1));
if ((curY + 1)>ymax[label])
ymax[label] = curY + 1;
if ((curX - 1)<xmin[label])
xmin[label] = curX - 1;
}
if (labelImg.at<int>( curY - 1,curX + 1) == 1) //右上
{
neighborPixels.push(std::pair<int, int>(curX + 1, curY-1));
//ymin[label] = curY - 1;
if ((curX + 1)>xmax[label])
xmax[label] = curX + 1;
}
}
}
}
}
}
labelNum = label-1;
}
class WatershedSegmenter {
private:
cv::Mat markers;
public:
void setMarkers(const cv::Mat& markerImage) {
// Convert to image of ints
markerImage.convertTo(markers, CV_32S);
}
cv::Mat process(const cv::Mat &image) {
// Apply watershed
cv::watershed(image, markers);
return markers;
}
// Return result in the form of an image
cv::Mat getSegmentation() {
cv::Mat tmp;
// all segment with label higher than 255
// will be assigned value 255
markers.convertTo(tmp, CV_8U);
return tmp;
}
// Return watershed in the form of an image
cv::Mat getWatersheds() {
cv::Mat tmp;
markers.convertTo(tmp, CV_8U,255, 255);
return tmp;
}
};
int main()
{
//设置视频读入,括号里面的数字是摄像头的选择,一般自带的是0
cv::VideoCapture cap(0);
if (!cap.isOpened())
{
return -1;
}
Mat frame;
Mat binImage,tmp;
Mat Y, Cr, Cb;
vector<Mat> channels;
//模板图片,是Cr颜色通道的人手图像截图
Mat tmpl = imread("bwz.jpg",CV_8UC1);
bool stop = false;
while (!stop)
{
//读入视频帧,转换颜色空间,并分割通道
cap >> frame;
cvtColor(frame, binImage, CV_BGR2GRAY);
frame.copyTo(tmp);
cvtColor(tmp, tmp, CV_BGR2YCrCb);
split(tmp, channels);
Cr = channels.at(1);
Cb = channels.at(2);
//肤色检测,输出二值图像
for (int j = 1; j < Cr.rows - 1; j++)
{
uchar* currentCr = Cr.ptr< uchar>(j);
uchar* currentCb = Cb.ptr< uchar>(j);
uchar* current = binImage.ptr< uchar>(j);
for (int i = 1; i < Cb.cols - 1; i++)
{
if ((currentCr[i] > 140) && (currentCr[i] < 170) &&(currentCb[i] > 77) && (currentCb[i] < 123))
current[i] = 255;
else
current[i] = 0;
}
}
//形态学处理
//dilate(binImage, binImage, Mat());
dilate(binImage, binImage, Mat());
//分水岭算法
cv::Mat fg;
cv::erode(binImage, fg, cv::Mat(), cv::Point(-1, -1), 6);
// Identify image pixels without objects
cv::Mat bg;
cv::dilate(binImage, bg, cv::Mat(), cv::Point(-1, -1), 6);
cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);
// Show markers image
cv::Mat markers(binImage.size(), CV_8U, cv::Scalar(0));
markers = fg + bg;
// Create watershed segmentation object
WatershedSegmenter segmenter;
segmenter.setMarkers(markers);
segmenter.process(frame);
Mat waterShed;
waterShed = segmenter.getWatersheds();
//imshow("watershed", waterShed);
//获得区域边框
threshold(waterShed, waterShed, 1, 1, THRESH_BINARY_INV);
//8向种子算法,给边框做标记
Mat labelImg;
int label, ymin[20], ymax[20], xmin[20], xmax[20];
Seed_Filling(waterShed, labelImg, label, ymin, ymax, xmin, xmax);
//根据标记,对每块候选区就行缩放,并与模板比较
Size dsize = Size(tmpl.cols, tmpl.rows);
float simi[20];
for (int i = 0; i < label; i++)
{
simi[i] = 1;
if (((xmax[2 + i] - xmin[2 + i])>50) && ((ymax[2 + i] - ymin[2 + i]) > 50))
{
//rectangle(frame, Point(xmin[2 + i], ymin[2 + i]), Point(xmax[2 + i], ymax[2 + i]), Scalar::all(255), 2, 8, 0);
Mat rROI = Mat(dsize, CV_8UC1);
resize(Cr(Rect(xmin[2 + i], ymin[2 + i], xmax[2 + i] - xmin[2 + i], ymax[2 + i] - ymin[2 + i])), rROI, dsize);
Mat result;
matchTemplate(rROI, tmpl, result, CV_TM_SQDIFF_NORMED);
simi[i] = result.ptr<float>(0)[0];
//cout << simi[i] << endl;
}
}
//统计一下区域中的肤色区域比例
float fuseratio[20];
for (int k = 0; k < label; k++)
{
fuseratio[k] = 1;
if (((xmax[2 + k] - xmin[2 + k])>50) && ((ymax[2 + k] - ymin[2 + k]) > 50))
{
int fusepoint=0;
for (int j = ymin[2+k]; j < ymax[2+k]; j++)
{
uchar* current = binImage.ptr< uchar>(j);
for (int i = xmin[2+k]; i < xmax[2+k]; i++)
{
if (current[i] == 255)
fusepoint += 1;
}
}
fuseratio[k] = float(fusepoint) / ((xmax[2 + k] - xmin[2 + k])*(ymax[2 + k] - ymin[2 + k]));
//cout << fuseratio[k] << endl;
}
}
//给符合阈值条件的位置画框
for (int i = 0; i < label; i++)
{
if ((simi[i]<0.02)&&(fuseratio[i]<0.65))
rectangle(frame, Point(xmin[2 + i], ymin[2 + i]), Point(xmax[2 + i], ymax[2 + i]), Scalar::all(255), 2, 8, 0);
}
imshow("frame", frame);
//processor.writeNextFrame(frame);
imshow("test", binImage);
if (waitKey(1) >= 0)
stop = true;
}
cout << "ss" << endl;
//cv::waitKey();
return 0;
} 其中的模板图片为

收藏的用户(0) X
正在加载信息~
推荐阅读
全志d1s-tina-linux使用lvgl8.x加载png图片文件并显示
最新回复 (0)
站点信息
- 文章2314
- 用户1336
- 访客11812443
每日一句
Let's seek joy in the simple, quiet moments.
让我们在简单宁静的时刻中寻找快乐。
让我们在简单宁静的时刻中寻找快乐。