3D手勢識別(一)順/逆時針畫圈判斷

檢測動作:單手指畫圈,需要判斷畫圈方向和圈數。

     

步驟:(1)取得3D圖像序列最前點;

            (2)將最前點投影在2D平面上;

            (3)中值濾波和平滑處理;

            (4)得到2D點集進行線性插值;

            (5)以重心爲中心判斷是否閉環;

            (6)2D點集進行橢圓擬合,判斷是否橢圓、橢圓半徑和橢圓度範圍;

            (7)判斷2D點集方向:順/逆時針。

其中手的識別使用深度學習進行,根據TensorFlow訓練完的模型判斷出圖像中有手且爲單手指,
去掉背景、挖出只含有手的圖像;中值濾波用的OpenCV medianBlur函數,其他主要函數算法如下:


Table of Contents

1、2D點集線性插值算法

2、計算重心

3、閉環判斷

4、橢圓擬合

5、順/逆時針方向判斷


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 &centre
// 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 &centre,
                                                  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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章