整体思路:
1.过滤噪声
2.由于RGB颜色的离散性转换为HSV通道
3.对HSV空间进行量化,得到2值图像,亮的部分为手的形状
4.去除杂点造成的伪轮廓,留下手的真实轮廓
5.对凸出点连线
6.最高点到底部中点的连线即为手指方向
//部分代码:
将MFC实现部分给省略了,给出了完整的opencv部分代码,可以参考实现。
int main()
{
cv::VideoCapture cap(0);
if (!cap.isOpened())
{
return -1;
}
//
cv::Mat frame;
cv::Mat frameHSV;
//
std::vector< std::vector<cv::Point> > OriginalContours;//轮廓
std::vector< cv::Vec4i > hierarchy; // 轮廓的结构信息
//
std::vector< std::vector<cv::Point> > FinalContours;// 筛选后的轮廓
std::vector< cv::Point > hull; // 凸包络的点集
//
CString Str;
int i, j;
int Width = cap.get(CV_CAP_PROP_FRAME_WIDTH), Height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
float fHeight = ((float)2 / 3 - (float)1 / 7) * Height;
//
while (0 == pDlg->m_bStopFlag)
{
cap >> frame;
if (frame.empty())
{
break;
}
//左右翻转
cv::flip(frame, frame, 1);
//中值滤波,用了这个下面的其他滤波全部异常
//medianBlur(frame, frame, 10);
//高斯滤波
cv::GaussianBlur(frame, frame, cv::Size(7, 7), 1.5, 1.5);
//通道转换
cv::cvtColor(frame, frameHSV, CV_BGR2HSV);
//imshow("frameHSV", frameHSV);
/*
S = 符号整型 U = 无符号整型 F = 浮点型
E.g.:
CV_8UC1 是指一个8位无符号整型单通道矩阵,
CV_32FC2是指一个32位浮点型双通道矩阵
CV_8UC1 CV_8SC1 CV_16U C1 CV_16SC1
CV_8UC2 CV_8SC2 CV_16UC2 CV_16SC2
CV_8UC3 CV_8SC3 CV_16UC3 CV_16SC3
CV_8UC4 CV_8SC4 CV_16UC4 CV_16SC4
CV_32SC1 CV_32FC1 CV_64FC1
CV_32SC2 CV_32FC2 CV_64FC2
CV_32SC3 CV_32FC3 CV_64FC3
CV_32SC4 CV_32FC4 CV_64FC4
*/
// 对HSV空间进行量化,得到2值图像,亮的部分为手的形状
cv::Mat mask(frame.rows, frame.cols, CV_8UC1);
//inRange(frameHSV, cv::Scalar(0, 30, 30), cv::Scalar(40, 170, 256), mask);
inRange(frameHSV, cv::Scalar(5, 30, 30), cv::Scalar(40, 170, 256), mask);
//
// 腐蚀:去除小亮点 膨胀:连接区块
cv::erode(mask, mask, cv::Mat(5, 5, CV_8U), cv::Point(-1, -1), 1);
//cv::dilate(mask, mask, cv::Mat(5, 5, CV_8U), cv::Point(-1, -1), 2);
//imshow("frameHSV", frameHSV);
//imshow("mask", mask);
OriginalContours.clear();
hierarchy.clear();
FinalContours.clear();
// 得到手的轮廓
cv::findContours(mask, OriginalContours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// 去除伪轮廓
for (i = 0; i < OriginalContours.size(); i++)
{
if (fabs(cv::contourArea(cv::Mat(OriginalContours[i]))) > 25000)
{
FinalContours.push_back(OriginalContours[i]);
}
}
// 画轮廓
cv::drawContours(frame, FinalContours, -1, cv::Scalar(0, 0, 255), 3);
// 得到轮廓的凸包络
int hullcount;
int iNumOfContours = FinalContours.size();
cv::Point top;
for (j = 0; j < iNumOfContours; j++)
{
convexHull(cv::Mat(FinalContours[j]), hull, true);
hullcount = (int)hull.size();
//
for (i = 0; i<hullcount - 1; i++)
{
cv::line(frame, hull[i + 1], hull[i], cv::Scalar(255, 0, 0), 2, CV_AA);
}
cv::line(frame, hull[hullcount - 1], hull[0], cv::Scalar(255, 0, 0), 2, CV_AA);
//画点
GetTopPoint(hull, hullcount, top);
cv::line(frame, top, top, cv::Scalar(255, 0, 255), 18, CV_AA);
cv::line(frame, top, cv::Point(Width/2, Height), cv::Scalar(0, 255, 255), 2, CV_AA);
//
if (1 == iNumOfContours)
{
pDlg->m_FingerPosition.bChanged = 1;
pDlg->m_FingerPosition.top = ((float)top.y - (float)Height / 7) / fHeight;
pDlg->m_FingerPosition.left = (float)top.x / (float)Width;
}
}
//
cv::line(frame, cv::Point(0, Height / 7), cv::Point(Width, Height / 7), cv::Scalar(255, 255, 255), 2, CV_AA);
cv::line(frame, cv::Point(0, 2 * Height / 3), cv::Point(Width, 2 * Height / 3), cv::Scalar(255, 255, 255), 2, CV_AA);
imshow("frame", frame);
cv::waitKey(33);
}
return 0;
}
实验效果图