1. 程式人生 > >基於OpenCV的簡易四肢位置識別與動作識別

基於OpenCV的簡易四肢位置識別與動作識別

1.前言

之前接到一個專案,要求參考一個國外的MR專案,在綠幕背景下識別人的四肢位置,並在一定程度上識別動作,以便可以和三維場景互動。
外國同類項目演示
由於可能需要同時識別4人以上,導致可識別的區域會非常寬,所以kinect的方案就不能上,只能用普通攝像頭做。在網上逛了半天也沒有找到合適的方案,所以只能自己動手用OpenCV去實現。

2.實現方案

攝像頭獲取的畫面
攝像頭獲取的畫面大致如上所示。

首先,我們需要對影象進行縮放,由於要保證實時處理,所以適當縮小圖片可以加快處理速度。

    resize(videoFrame, videoFrameResize, Size(480, 270));

對圖片上下進行一個裁剪,因為上下兩塊區域的顏色會受到光照影響,可能不太穩定。

    Rect cutRect = Rect(0, cutTop, videoFrameResize.size().width, videoFrameResize.size().height - cutTop - cutBottom);
    videoFrameResize(cutRect).copyTo(videoDisplay);

這裡的cutTop和cutBottom分別代表上下需要裁剪的寬度

然後我們需要把綠幕背景裁掉,只留下人體部分,在這裡我只是簡單的轉換到HSV色域,然後用顏色特徵去除綠色背景。

    cv::Mat srcCut, srcHSV, srcThreshold;
    cv:
:Rect cutRect = cv::Rect(0, cutTop, src.size().width, src.size().height - cutTop - cutBottom); srcCut = src(cutRect); cvtColor(srcCut, srcHSV, CV_BGR2HSV_FULL); cvtColor(srcCut, srcThreshold, CV_BGR2GRAY); for (int i = 0; i < srcHSV.rows; i++) { cv::Vec3b* HSVpixel = srcHSV.ptr<cv:
:Vec3b>(i); uchar* GrayPixel = srcThreshold.ptr<uchar>(i); for (int j = 0; j < srcHSV.cols; j++) { if (HSVpixel[j][0] > 45 && HSVpixel[j][0] < 137 && HSVpixel[j][1] > 43 && HSVpixel[j][2] > 50) { GrayPixel[j] = 0; } else { GrayPixel[j] = 255; } } } //裁剪綠幕

隨後對去除綠幕背景的影象進行膨脹和腐蝕,保證去除後的人身上不會出現孔洞,影響後面的檢測。

    dilate(srcThreshold, srcThreshold, element5);
    erode(srcThreshold, srcThreshold, element5);

    blur(srcThreshold, srcThreshold, cv::Size(7, 7));

由於我們只需要人體輪廓資訊,顏色對於我們來說沒什麼用,所以進行二值化處理。

        threshold(srcThreshold, srcThreshold, 130, 255, cv::THRESH_BINARY);

之後,我們計算二值化後的人體剪影輪廓

        vector<vector<Point> > contours;
        findContours(cutFrameCopy, contours, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
        std::sort(contours.begin(), contours.end(), sortCountersArea);

判斷每個封閉輪廓的面積,去掉明顯輪廓面積偏小的區域(因為肯定是錯誤的)。

            //刪除面積過小的區域
            vector<vector<Point>>::iterator it;
            for (it = contours.begin(); it != contours.end();)
            {
                if (contourArea(*it) < 500)
                {
                    it = contours.erase(it);
                }
                else
                {
                    it++;
                }
             }

之後對這些輪廓所形成的最小外接矩形進行歸一化處理,降低高矮胖瘦對於後續計算的影響。

    resize(cutFrameRectCopy, cutFrameRectCopy, cv::Size(150.0*PersonRect.width / PersonRect.height, 150.0));

由於可能會存在兩個或以上的人重疊的情況,所以需要判斷每一個輪廓是否為兩人或以上,如果是的話就需要進行分割。(當然這種情況只能處理兩個人有少量重疊的情況,你要玩千手觀音的話我也很絕望啊)

之後我們需要判斷這個輪廓到底是屬於一個人的還是兩個人的。通過分析圖片,發現每個人所代表的剪影會有一定的寬度和麵積,而這個值大致是在一個區間內浮動的。因此如果某塊輪廓的面積接近n個人且輪廓的長度也接近n個人,則表示這個輪廓裡面橫向分佈著n個人,需要進行裁剪。

            resize(cutFrameRectCopy, cutFrameRectCopy, cv::Size(150.0*PersonRect.width / PersonRect.height, 150.0));

            int Area = contourArea(contours[i]) * pow((150.0 / PersonRect.height), 2);
            int PersonNumber = min(Area / 4000.0, cutFrameRectCopy.size().width / 52.5);

這個輪廓裡若包含兩個或以上的人,則尋找切割位置

                //表示當前Rect人數可能多於1人,需要裁剪
                int PersonWidth = cutFrameRectCopy.size().width / PersonNumber; //得到每個人的大概寬度


                vector<cv::Point> heightPoint;  //每個人的重心
                for (int j = 0; j < PersonNumber; j++)
                {
                    //處理每幅圖
                    cv::Rect cutPersonRect(j*PersonWidth, 0, PersonWidth, cutFrameRectCopy.size().height);
                    cv::Mat PersonTemp;
                    cutFrameRectCopy(cutPersonRect).copyTo(PersonTemp); //儲存每個人的臨時影象

                                                                        //算出每個圖片的重心
                    vector<vector<cv::Point>> PersonTempContours;
                    findContours(PersonTemp, PersonTempContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
                    std::sort(PersonTempContours.begin(), PersonTempContours.end(), sortCountersArea);
                    cv::Moments mu = moments(PersonTempContours[0]);
                    if (mu.m00 == 0)
                    {
                        continue;
                    }
                    else
                    {
                        //儲存每張圖片的重心點
                        cv::Point mc = cv::Point(mu.m10 / mu.m00, mu.m01 / mu.m00);
                        heightPoint.push_back(cv::Point(j*PersonWidth + mc.x, mc.y));
                        //circle(cutFrameRectCopy, Point(j*PersonWidth + mc.x, mc.y), 2, Scalar(0, 0, 0));
                    }
                }



                vector<int> cutEdge;
                cutEdge.push_back(0);

                //尋找兩張圖片之間需要切割的地方
                for (int j = 0; j < heightPoint.size() - 1; j++)
                {

                    int minWhiteLength = 9999;
                    int cutX;
                    for (int k = heightPoint[j].x; k < heightPoint[j + 1].x; k++)
                    {
                        int countWhiteLength = 0;
                        for (int l = 0; l < cutFrameRectCopy.size().height; l++)
                        {
                            if (cutFrameRectCopy.at<uchar>(l, k) == 255)
                            {
                                countWhiteLength++;
                            }
                        }
                        if (countWhiteLength < minWhiteLength)
                        {
                            minWhiteLength = countWhiteLength;
                            cutX = k;
                        }
                    }
                    //找到白色最少的地方,切割
                    cutEdge.push_back(cutX);
                }
                cutEdge.push_back(cutFrameRectCopy.size().width - 1);
                //所有邊已被儲存,開始分割影象

之後將圖片分割,得到每個人的剪影圖片

cv::Rect PersonTempRect = cv::Rect(cutEdge[j], 0, cutEdge[j + 1] - cutEdge[j] + 1, 150);                cutFrameRectCopy(PersonTempRect).copyTo(PersonTempMat);

這樣,每個人都被分割成了單張圖片,下面就需要進行識別。
首先,將每個人進行骨架化,這裡選用的是Zhang-Suen骨架化演算法。

    // @brief 對輸入影象進行細化,骨骼化
    // @param src為輸入影象,用cvThreshold函式處理過的8位灰度影象格式,元素中只有0與1,1代表有元素,0代表為空白
    // @param maxIterations限制迭代次數,如果不進行限制,預設為-1,代表不限制迭代次數,直到獲得最終結果
    // @return 為對src細化後的輸出影象,格式與src格式相同,元素中只有0與1,1代表有元素,0代表為空白

    cv::Mat thinImage(const cv::Mat & src, const int maxIterations)
    {
        assert(src.type() == CV_8UC1);

        src /= 255;

        cv::Mat dst;
        int width = src.cols;
        int height = src.rows;
        src.copyTo(dst);
        int count = 0;  //記錄迭代次數  
        while (true)
        {
            count++;
            if (maxIterations != -1 && count > maxIterations) //限制次數並且迭代次數到達  
                break;
            std::vector<uchar *> mFlag; //用於標記需要刪除的點  
                                        //對點標記  
            for (int i = 0; i < height; ++i)
            {
                uchar * p = dst.ptr<uchar>(i);
                for (int j = 0; j < width; ++j)
                {
                    //如果滿足四個條件,進行標記  
                    //  p9 p2 p3  
                    //  p8 p1 p4  
                    //  p7 p6 p5  
                    uchar p1 = p[j];
                    if (p1 != 1) continue;
                    uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
                    uchar p8 = (j == 0) ? 0 : *(p + j - 1);
                    uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
                    uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
                    uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
                    uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
                    uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
                    uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
                    if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
                    {
                        int ap = 0;
                        if (p2 == 0 && p3 == 1) ++ap;
                        if (p3 == 0 && p4 == 1) ++ap;
                        if (p4 == 0 && p5 == 1) ++ap;
                        if (p5 == 0 && p6 == 1) ++ap;
                        if (p6 == 0 && p7 == 1) ++ap;
                        if (p7 == 0 && p8 == 1) ++ap;
                        if (p8 == 0 && p9 == 1) ++ap;
                        if (p9 == 0 && p2 == 1) ++ap;

                        if (ap == 1 && p2 * p4 * p6 == 0 && p4 * p6 * p8 == 0)
                        {
                            //標記  
                            mFlag.push_back(p + j);
                        }
                    }
                }
            }

            //將標記的點刪除  
            for (std::vector<uchar *>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
            {
                **i = 0;
            }

            //直到沒有點滿足,演算法結束  
            if (mFlag.empty())
            {
                break;
            }
            else
            {
                mFlag.clear();//將mFlag清空  
            }

            //對點標記  
            for (int i = 0; i < height; ++i)
            {
                uchar * p = dst.ptr<uchar>(i);
                for (int j = 0; j < width; ++j)
                {
                    //如果滿足四個條件,進行標記  
                    //  p9 p2 p3  
                    //  p8 p1 p4  
                    //  p7 p6 p5  
                    uchar p1 = p[j];
                    if (p1 != 1) continue;
                    uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
                    uchar p8 = (j == 0) ? 0 : *(p + j - 1);
                    uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
                    uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
                    uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
                    uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
                    uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
                    uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);

                    if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
                    {
                        int ap = 0;
                        if (p2 == 0 && p3 == 1) ++ap;
                        if (p3 == 0 && p4 == 1) ++ap;
                        if (p4 == 0 && p5 == 1) ++ap;
                        if (p5 == 0 && p6 == 1) ++ap;
                        if (p6 == 0 && p7 == 1) ++ap;
                        if (p7 == 0 && p8 == 1) ++ap;
                        if (p8 == 0 && p9 == 1) ++ap;
                        if (p9 == 0 && p2 == 1) ++ap;

                        if (ap == 1 && p2 * p4 * p8 == 0 && p2 * p6 * p8 == 0)
                        {
                            //標記  
                            mFlag.push_back(p + j);
                        }
                    }
                }
            }

            //將標記的點刪除  
            for (std::vector<uchar *>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
            {
                **i = 0;
            }

            //直到沒有點滿足,演算法結束  
            if (mFlag.empty())
            {
                break;
            }
            else
            {
                mFlag.clear();//將mFlag清空  
            }
        }
        return dst;
    }

隨後,抹去最左邊一列,最右邊一列和最下邊一行的內容,因為如果骨架化之後的端點落在這三個區域的話可能檢測不到。

for (int k = 0; k < PersonTempThin.size().width; k++)
{
    PersonTempThin.at<uchar>(cv::Point(k, PersonTempThin.size().height - 1)) = 0;
}

for (int k = 0; k < PersonTempThin.size().height; k++)
{
    PersonTempThin.at<uchar>(cv::Point(0, k)) = 0;
}

for (int k = 0; k < PersonTempThin.size().height; k++)
{
    PersonTempThin.at<uchar>(cv::Point(PersonTempThin.size().width - 1, k)) = 0;
}

之後算出端點和交叉點,即為可能的四肢位置。

尋找端點的位置通過opencv的卷積器實現

    vector<cv::Point2f> skeletonEndPoints(cv::Mat &src)
    {
        cv::Mat dst;

        vector<cv::Point2f> endpoints;
        cv::Mat k(3, 3, CV_8UC1);

        k.at<uchar>(0, 0) = 1;
        k.at<uchar>(1, 0) = 1;
        k.at<uchar>(2, 0) = 1;
        k.at<uchar>(0, 1) = 1;
        k.at<uchar>(1, 1) = 10;
        k.at<uchar>(2, 1) = 1;
        k.at<uchar>(0, 2) = 1;
        k.at<uchar>(1, 2) = 1;
        k.at<uchar>(2, 2) = 1;

        dst = src.clone();

        filter2D(dst, dst, CV_8UC1, k);
        for (int i = 0; i < dst.rows; i++)
        {
            for (int j = 1; j < dst.cols; j++)
            {
                if (dst.at<uchar>(i, j) == 11)
                {
                    endpoints.push_back(cv::Point2f(j, i));
                }
            }
        }
        return endpoints;
    }

接著尋找交叉點,這些地方可能是胸部和胯部的位置

    std::vector<cv::Point2f> skeletonBranchPoints(const cv::Mat &thinSrc, unsigned int raudis, unsigned int thresholdMax, unsigned int thresholdMin)
    {
        assert(thinSrc.type() == CV_8UC1);
        cv::Mat dst;
        thinSrc.copyTo(dst);
        filterOver(dst);
        int width = dst.cols;
        int height = dst.rows;
        cv::Mat tmp;
        dst.copyTo(tmp);
        std::vector<cv::Point2f> branchpoints;
        for (int i = 0; i < height; ++i)
        {
            for (int j = 0; j < width; ++j)
            {
                if (*(tmp.data + tmp.step * i + j) == 0)
                {
                    continue;
                }
                int count = 0;
                for (int k = i - raudis; k < i + raudis + 1; k++)
                {
                    for (int l = j - raudis; l < j + raudis + 1; l++)
                    {
                        if (k < 0 || l < 0 || k>height - 1 || l>width - 1)
                        {
                            continue;

                        }
                        else if (*(tmp.data + tmp.step * k + l) == 1)
                        {
                            count++;
                        }
                    }
                }

                if (count > thresholdMax)
                {
                    cv::Point2f point(j, i);
                    branchpoints.push_back(point);
                }
            }
        }
        return branchpoints;
    }

得到的這些點為興趣點,其中有可能會出現錯誤的點,因此要對這些點進行篩選。

    skeleton FromEdgePoints(vector<cv::Point2f> &skeletonEndPoints, vector<cv::Point2f> &skeletonBranchPoints, cv::Point2f Center, cv::Mat &bodyThreshold, vector<cv::Point> contours)
    {

        skeleton skeletonData;
        skeletonData._heart = Center;

        vector<cv::Point2f> test1 = skeletonEndPoints;

        skeletonEndPoints = deleteTooNearPoints(skeletonEndPoints);


        vector<cv::Point2f> test2 = skeletonEndPoints;

        skeletonBranchPoints = deleteTooNearPoints(skeletonBranchPoints);



        cv::Point lowestPoint(0, 0);
        int distance = 0;
        //推算此人的最低點(包括手臂舉起的長度)(可能推算身高沒有實際意義)
        for (int i = 0; i < skeletonEndPoints.size(); i++)
        {
            if (skeletonEndPoints[i].y > distance)
            {
                lowestPoint = skeletonEndPoints[i];
                distance = lowestPoint.y;
            }
        }


        //判斷交叉點的型別
        for (int i = 0; i < skeletonBranchPoints.size(); i++)
        {
            //可能是胸部節點
            if (skeletonBranchPoints[i].y < Center.y)
            {
                if (abs(skeletonData.bodyPoint[BodyData_chest].x - Center.x) > abs(skeletonBranchPoints[i].x - Center.x))   //判斷最有可能的胸部節點
                {
                    skeletonData.bodyPoint[BodyData_chest] = skeletonBranchPoints[i];
                }
            }
            //可能是腹部節點
            else
            {
                //若腹部節點太靠下則拋棄
                if (skeletonBranchPoints[i].y > Center.y + abs(Center.y - lowestPoint.y) * 3.0 / 5.0)
                {
                    skeletonData.bodyPoint[BodyData_hip] = cv::Point(0, 0);
                    continue;
                }
                if (sqrt(pow(skeletonData.bodyPoint[BodyData_hip].x - Center.x,2)+ pow(skeletonData.bodyPoint[BodyData_hip].y - (Center.y*1.3), 2)) > sqrt(pow(skeletonBranchPoints[i].x - Center.x,2)+ pow(skeletonBranchPoints[i].y - (Center.y*1.3), 2)))    //判斷最有可能的腹部節點
                {
                    skeletonData.bodyPoint[BodyData_hip] = skeletonBranchPoints[i];
                }
            }
        }

        vector<cv::Point2f> bodyWide;
        bodyWide = calcBodyWide(bodyThreshold, Center);



        //判斷頭部位置
        if (skeletonData.bodyPoint[BodyData_chest] != cv::Point2f(0, 0))
        {
            //以胸口作為判斷標準
            for (int i = 0; i < skeletonEndPoints.size(); i++)
            {
                if (skeletonEndPoints[i].y > skeletonData.bodyPoint[BodyData_chest].y)
                {
                    //在身體寬度範圍之外,拋棄
                    continue;
                }
                else
                {
                    //找出離身體最近的點
                    if (abs(skeletonEndPoints[i].x - skeletonData.bodyPoint[BodyData_chest].x) < abs(skeletonData.bodyPoint[BodyData_head].x - skeletonData.bodyPoint[BodyData_chest].x))
                    {
                        if (skeletonData.bodyPoint[BodyData_head] != cv::Point2f(0, 0))
                        {
                            if (sqrt(pow(skeletonEndPoints[i].x - skeletonData.bodyPoint[BodyData_chest].x,2)+ pow(skeletonEndPoints[i].y - skeletonData.bodyPoint[BodyData_chest].y, 2)) < sqrt(pow(skeletonData.bodyPoint[BodyData_head].x - skeletonData.bodyPoint[BodyData_chest].x, 2) + pow(skeletonData.bodyPoint[BodyData_head].y - skeletonData.bodyPoint[BodyData_chest].y, 2)))
                            {
                                skeletonData.bodyPoint[BodyData_head] = skeletonEndPoints[i];
                            }
                        }
                        else
                        {
                            skeletonData.bodyPoint[BodyData_head] = skeletonEndPoints[i];
                        }

                    }
                }
            }
        }
        //否則以中心點作為判斷標準(可能不準)
        else
        {
            //以胸口作為判斷標準
            for (int i = 0; i < skeletonEndPoints.size(); i++)
            {
                if (skeletonEndPoints[i].y > Center.y)
                {
                    //在身體寬度範圍之外,拋棄
                    continue;
                }
                else
                {
                    //找出離身體最近的點
                    if (abs(skeletonEndPoints[i].x - Center.x) < abs(skeletonData.bodyPoint[BodyData_head].x - Center.x))
                    {
                        if (skeletonData.bodyPoint[BodyData_head] != cv::Point2f(0, 0))
                        {
                            if (abs(skeletonEndPoints[i].y - Center.y) < abs(skeletonData.bodyPoint[BodyData_head].y - Center.y))
                            {
                                skeletonData.bodyPoint[BodyData_head] = skeletonEndPoints[i];
                            }
                        }
                        else
                        {
                            skeletonData.bodyPoint[BodyData_head] = skeletonEndPoints[i];
                        }
                    }
                }
            }
        }

        //去除已配對的點
        vector<cv::Point2f>::iterator it = find(skeletonEndPoints.begin(), skeletonEndPoints.end(), skeletonData.bodyPoint[BodyData_head]);
        if (it != skeletonEndPoints.end())
            skeletonEndPoints.erase(it);


        //去除可能錯誤的點
        if (skeletonData.bodyPoint[BodyData_head] != cv::Point2f(0, 0))
        {
            for (it = skeletonEndPoints.begin(); it != skeletonEndPoints.end();)
            {
                if (it->x > bodyWide[0].x && it->x < bodyWide[1].x && it->y < Center.y && it->y > skeletonData.bodyPoint[BodyData_head].y)
                {
                    it = skeletonEndPoints.erase(it);
                }
                else
                {
                    it++;
                }
            }
        }



        //判斷手腳(可能有誤判)
        sort(skeletonEndPoints.begin(), skeletonEndPoints.end(), sortX);

        for (int i = 0; i < skeletonEndPoints.size(); i++)
        {
            if (skeletonEndPoints[i].y >(lowestPoint.y + Center.y) / 2.0)
                continue;
            if (skeletonEndPoints[i].x < Center.x)
            {
                skeletonData.bodyPoint[BodyData_leftHand] = skeletonEndPoints[i];
                break;
            }
        }

        it = find(skeletonEndPoints.begin(), skeletonEndPoints.end(), skeletonData.bodyPoint[BodyData_leftHand]);
        if (it != skeletonEndPoints.end())
        {
            skeletonEndPoints.erase(it);
        }


        for (int i = skeletonEndPoints.size() - 1; i >= 0; i--)
        {
            if (skeletonEndPoints[i].y > (lowestPoint.y + Center.y) / 2.0)
                continue;
            if (skeletonEndPoints[i].x > Center.x)
            {
                skeletonData.bodyPoint[BodyData_rightHand] = skeletonEndPoints[i];
                break;
            }
        }

        it = find(skeletonEndPoints.begin(), skeletonEndPoints.end(), skeletonData.bodyPoint[BodyData_rightHand]);
        if (it != skeletonEndPoints.end())
        {
            skeletonEndPoints.erase(it);
        }


        for (int i = 0; i < skeletonEndPoints.size(); i++)
        {
            if (skeletonEndPoints[i].y < (bodyThreshold.size().height + Center.y) / 2.0)
                continue;
            skeletonData.bodyPoint[BodyData_leftFoot] = skeletonEndPoints[i];
            break;
        }


        it = find(skeletonEndPoints.begin(), skeletonEndPoints.end(), skeletonData.bodyPoint[BodyData_leftFoot]);
        if (it != skeletonEndPoints.end())
        {
            skeletonEndPoints.erase(it);
        }


        for (int i = skeletonEndPoints.size() - 1; i >= 0; i--)
        {
            if (skeletonEndPoints[i].y < (bodyThreshold.size().height + Center.y) / 2.0)
                continue;
            skeletonData.bodyPoint[BodyData_rightFoot] = skeletonEndPoints[i];
            break;
        }

        it = find(skeletonEndPoints.begin(), skeletonEndPoints.end(), skeletonData.bodyPoint[BodyData_rightFoot]);
        if (it != skeletonEndPoints.end())
        {
            skeletonEndPoints.erase(it);
        }


        //給出胸部推定值

        if (skeletonData.bodyPoint[BodyData_head] != cv::Point2f(0, 0))
        {
            skeletonData.bodyPoint[BodyData_chest] = cv::Point2f((Center.x + skeletonData.bodyPoint[BodyData_head].x) / 2, (Center.y + skeletonData.bodyPoint[BodyData_head].y) / 2);
        }

        return skeletonData;
    }

這樣我們就得到了單個人的四肢與頭、胸、胯部的位置。

由於之前進行了歸一化,因此這些點只針對單人圖片的位置,因此要進行一次對映,得到這些點在整幅圖上的位置。

//對skeletonData進行處理,換算到全域性單位
if (skeletonData._heart != cv::Point2f(0, 0))
    PersonTempSkeleton._heart = cv::Point(round((skeletonData._heart.x + PersonTempRect.x) / (150.0 / PersonRect.height) + PersonRect.x), round((skeletonData._heart.y + PersonTempRect.y) / (150.0 / PersonRect.height) + PersonRect.y));

for (int k = BodyData_head; k != BodyData_len; k++)
{
    if (skeletonData.bodyPoint[k] != cv::Point2f(0, 0))
        PersonTempSkeleton.bodyPoint[k] = cv::Point(round((skeletonData.bodyPoint[k].x + PersonTempRect.x) / (150.0 / PersonRect.height) + PersonRect.x), round((skeletonData.bodyPoint[k].y + PersonTempRect.y) / (150.0 / PersonRect.height) + PersonRect.y));

}

對每張圖片進行處理,就能得到每個人的四肢與頭部的位置。

關於動作判斷,這裡就不詳細說了,基本思路是建立一個動作滑窗,判斷在滑窗內的姿勢是否屬於某個動作,如果該滑窗內的所有姿勢都屬於同一種類型的動作,則視作當前正在做此動作。

3.結果

最終結果如下所示,可以看到效果還是可以接受的,而且速度可以達到實時
結果