檢測動作:單手指畫圈,需要判斷畫圈方向和圈數。
步驟:(1)取得3D圖像序列最前點;
(2)將最前點投影在2D平面上;
(3)中值濾波和平滑處理;
(4)得到2D點集進行線性插值;
(5)以重心爲中心判斷是否閉環;
(6)2D點集進行橢圓擬合,判斷是否橢圓、橢圓半徑和橢圓度範圍;
(7)判斷2D點集方向:順/逆時針。
其中手的識別使用深度學習進行,根據TensorFlow訓練完的模型判斷出圖像中有手且爲單手指,
去掉背景、挖出只含有手的圖像;中值濾波用的OpenCV medianBlur函數,其他主要函數算法如下:
Table of Contents
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
CircleActionImpl::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 CircleActionImpl::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
CircleActionImpl::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;
//填滿區域大於需要填滿的區域則返回true
return 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 CircleActionImpl::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: IsClockwiseFromCross
// FullName: IsClockwiseFromCross
// Access: private
// Parameter: 二維點集 const std::vector<cv::Point2f> &point2ds
// Returns: bool
// Author:
// Date: 2018/08/28
// History:
//************************************
bool CircleActionImpl::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 CircleActionImpl::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;
}