本文主要是介绍3D手势识别(一)顺/逆时针画圈判断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
场景:前装摄像头
检测动作:单手指画圈,需要判断画圈方向和圈数。
步骤: (1)取得3D图像序列最前点;
(2)将最前点投影在2D平面上;
(3)中值滤波和平滑处理;
(4)得到2D点集进行线性插值;
(5)以重心为中心判断是否闭环;
(6)2D点集进行椭圆拟合,判断是否椭圆、椭圆半径和椭圆度范围;
(7) 2D点集进行圆拟合,计算半径均值和标准差;
(8)判断2D点集方向:顺/逆时针。
其中手的识别使用深度学习进行,根据TensorFlow训练完的模型判断出图像中有手且为单手指,
去掉背景、挖出只含有手的图像;中值滤波用的OpenCV medianBlur函数,其他主要函数算法如下:
Table of Contents
1、2D点集线性插值算法
2、计算重心
3、闭环判断
4、椭圆拟合
5、圆拟合
6、顺/逆时针方向判断
1、2D点集线性插值算法
//************************************
// Description: 插值,根据两点间距离线性插值,两点间距离越大插值越多,距离小于min_dis则不插值返回原值
// Method: InterpCurve
// FullName: InterpCurve
// Access: private
// Parameter: 插值前二维点集 std::vector<cv::Point2f> &data
// Parameter: 插值后二维点集 std::vector<cv::Point2f> &result
// Parameter: 最小插值距离 float min_dis
// Returns:
// Author:
// Date: 2018/08/28
// History:
//************************************
void InterpCurve(std::vector<cv::Point2f> &data, std::vector<cv::Point2f> &result, float min_dis) {result.clear();result.reserve(data.size());result.push_back(data.at(0));for (size_t i = 1; i < data.size(); i++) {float dis = Distance(data.at(i - 1), data.at(i));float num = dis / min_dis + 1;float step = 1.0f / num;float s = 0;while (s <= 1.0) {result.emplace_back(cv::Point2f((1 - s) * data.at(i - 1).x + s * data.at(i).x,(1.0f - s) * data.at(i - 1).y + s * data.at(i).y));s += step;}}// vecp::print(result, "result");
}
2、计算重心
3D点计算方法类似。
//************************************
// Description: 将输入的point2d点集求和平均求重心
// Method: CalcCentre
// FullName: CalcCentre
// Access: private
// Parameter: 二维点集 const std::vector<cv::Point2f> &point2ds
// Returns: 重心坐标 cv::Point2f
// Author:
// Date: 2018/08/28
// History:
//************************************
cv::Point2f CalcCentre(const std::vector<cv::Point2f> &point2ds) {float x = 0, y = 0;for (auto &pt: point2ds) {x += pt.x;y += pt.y;}return cv::Point2f(x / point2ds.size(), y / point2ds.size());
}
3、闭环判断
//************************************
// Description: 将输入的point2d点集判断是否闭环
// Method: FindOneCircleFromUnformInCircle
// FullName: FindOneCircleFromUnformInCircle
// Access: private
// Parameter: 二维点集 const std::vector<cv::Point2f> &point2ds
// Parameter: 判断闭环坐标中心 const cv::Point2f ¢re
// Parameter: 最大无点角度0-360 float max_angle
// Parameter: 划分区域数量 int part_count
// Returns: bool
// Author:
// Date: 2018/08/28
// History:
//************************************
bool FindOneCircleFromUnformInCircle(const std::vector<cv::Point2f> &point2ds,const cv::Point2f ¢re,float max_angle, int part_count) {double each_path_dog = 2.0f * M_PI / part_count;//cout<<"each_path_dog:"<<each_path_dog<<" ";std::vector<int> counts(part_count, 0);int full_part_counts = 0;for (auto &pt: point2ds) {float alpha = atan2Ex(pt.x - centre.x, pt.y - centre.y);auto idx = static_cast<size_t>(alpha / each_path_dog);counts.at(idx)++;}//每个区域大于5个点算作填满for (auto &count: counts) {if (count > 5)full_part_counts++;}double dao_each_path_dog = 1.0 / each_path_dog;//填满区域大于需要填满的区域则返回truereturn full_part_counts >= int(DegToRad(360.0 - max_angle) * dao_each_path_dog);
}
4、椭圆拟合
使用了OpenCV的fitEllipseEx函数。
//************************************
// Description: 将输入的point2d点集做椭圆拟合
// Method: IsCircleFromEllipse
// FullName: IsCircleFromEllipse
// Access: private
// Parameter: 二维点集 const std::vector<cv::Point2f> &point2ds
// Parameter: 最小半径 float min_radius
// Parameter: 最大椭圆度(两半径比值) float min_ovality
// Parameter: 最大非拟合点 float min_error
// Returns: bool
// Author:
// Date: 2018/08/28
// History:
//************************************
bool IsCircleFromEllipse(const std::vector<cv::Point2f> &point2ds,float min_radius, float min_ovality, float min_error,cv::RotatedRect &ellipse) {//OpenCV椭圆拟合,要求至少有六个点输入float error = fitEllipseEx(point2ds, ellipse);float a = ellipse.size.width;float b = ellipse.size.height;//printf("a %f, b %f, ovality %f, error %f\n", a, b, a/b, error);return !(a < min_radius || b < min_radius || a / b < min_ovality || a / b > 1.0 / min_ovality ||error > min_error);
}
5、圆拟合
以重心为圆心设置半径均值和标准差阈值:
/************************************Description: 将输入的point2d点集做圆拟合,根据圆心计算半径均值和标准差Method: IsCircelFromDevFullName: IsCircelFromDevAccess: privateParameter: 最小半径均值 float min_radiusParameter: 最大半径标准差 float max_devReturns: boolAuthor: Date: 2018/10/17History:
************************************/
bool CircleAction::IsCircelFromDev(const std::vector<cv::Point2f> &point2ds, float min_radius, float max_dev) {cv::Point2f centre = CalcCentre(point2ds);std::vector<float> radius(point2ds.size(), 0.0f);for (size_t i = 0; i < point2ds.size(); i++) {radius.at(i) = Distance(centre, point2ds.at(i));}cv::Mat mean, dev;cv::meanStdDev(radius, mean, dev);double mean_radius = mean.at<double>(0, 0), dev_radius = dev.at<double>(0, 0);//std::cout << "radius mean---dev: " <<mean_radius << "dev_radius: " << dev_radius << std::endl;return (mean_radius > min_radius && dev_radius < max_dev);
}
6、顺/逆时针方向判断
//************************************
// Description: 将输入的point2d点集判断顺/逆时针方向
// Method: IsClockwiseFromCross
// FullName: IsClockwiseFromCross
// Access: private
// Parameter: 二维点集 const std::vector<cv::Point2f> &point2ds
// Returns: bool
// Author:
// Date: 2018/08/28
// History:
//************************************
bool IsClockwiseFromCross(const std::vector<cv::Point2f> &point2ds) {int positive = 0, negative = 0;for (size_t i = 1; i < point2ds.size() - 1; i++) {//由i,i-1,i+1得到局部方向int status = WhichClockWise(point2ds.at(i - 1), point2ds.at(i), point2ds.at(i + 1));if (status == -1)negative++;else if (status == 1)positive++;}// if (abs(positive - negative) < 5)// std::cout << "[warning]: the clockwise may be wrong" << std::endl;//按正、反方向数量判断返回多的反向return (positive >= negative);
}
//************************************
// Description: 将输入的point2d点判断局部方向
// Method: WhichClockWise
// FullName: WhichClockWise
// Access: private
// Parameter: 二维点a cv::Point2f a
// Parameter: 二维点b cv::Point2f b
// Parameter: 二维点c cv::Point2f c
// Returns: int
// Author:
// Date: 2018/08/28
// History:
//************************************
int WhichClockWise(cv::Point2f a, cv::Point2f b, cv::Point2f c) {double triangle_area = a.x * b.y - a.y * b.x + a.y * c.x - a.x * c.y + b.x * c.y - c.x * b.y;if (triangle_area < 0) return -1;else if (triangle_area > 0) return 1;return 0;
}
这篇关于3D手势识别(一)顺/逆时针画圈判断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!