293 lines
13 KiB
C++
Executable File
293 lines
13 KiB
C++
Executable File
|
|
#pragma once
|
|
|
|
#include <set>
|
|
#include <map>
|
|
#include <cmath>
|
|
#include <opencv2/imgproc.hpp>
|
|
|
|
#include "../vp_primary_infer_node.h"
|
|
#include "../../objects/vp_frame_pose_target.h"
|
|
|
|
|
|
namespace vp_nodes {
|
|
// body keypoints detector using openpose
|
|
// https://github.com/CMU-Perceptual-Computing-Lab/openpose
|
|
class vp_openpose_detector_node: public vp_primary_infer_node
|
|
{
|
|
private:
|
|
float score_threshold;
|
|
// pose type (model type)
|
|
vp_objects::vp_pose_type type;
|
|
|
|
// map indexs for PAFs
|
|
const std::map<vp_objects::vp_pose_type, std::vector<std::pair<int,int>>> mapIdxes_map = {
|
|
{vp_objects::vp_pose_type::body_25, {{0,1}, {14,15}, {22,23}, {16,17}, {18,19}, {24,25}, {26,27}, {6,7}, {2,3}, {4,5}, {8,9}, {10,11}, {12,13}, {30,31}, {32,33}, {36,37}, {34,35},
|
|
{38,39}, {40,41} ,{42,43}, {44,45}, {46,47}, {48,49}, {50,51}}},
|
|
{vp_objects::vp_pose_type::coco, {{12,13}, {20,21}, {14,15}, {16,17}, {22,23}, {24,25}, {0,1},
|
|
{2,3}, {4,5}, {6,7}, {8,9}, {10,11}, {28,29}, {30,31}, {34,35}, {32,33}, {36,37}, {18,19}, {26,27}}},
|
|
{vp_objects::vp_pose_type::mpi_15, {{0,1}, {2,3}, {4,5}, {6,7}, {8,9}, {10,11}, {12,13}, {14,15}, {16,17}, {18,19}, {20,21}, {22,23}, {24,25}, {26,27}}},
|
|
{vp_objects::vp_pose_type::hand, std::vector<std::pair<int,int>>()},
|
|
{vp_objects::vp_pose_type::face, std::vector<std::pair<int,int>>()}
|
|
};
|
|
|
|
// pose pairs for PAFs
|
|
const std::map<vp_objects::vp_pose_type, std::vector<std::pair<int,int>>> posePairs_map = {
|
|
{vp_objects::vp_pose_type::body_25, {{1,8}, {1,2}, {1,5}, {2,3}, {3,4}, {5,6}, {6,7}, {8,9},
|
|
{9,10}, {10,11}, {8,12}, {12,13}, {13,14}, {1,0}, {0,15}, {15,17}, {0,16}, {16,18},
|
|
{14,19}, {19,20}, {14,21}, {11,22}, {22,23}, {11,24}}},
|
|
{vp_objects::vp_pose_type::coco, {{1,2}, {1,5}, {2,3}, {3,4}, {5,6}, {6,7},
|
|
{1,8}, {8,9}, {9,10}, {1,11}, {11,12}, {12,13},
|
|
{1,0}, {0,14}, {14,16}, {0,15}, {15,17}, {2,16},
|
|
{5,17}}},
|
|
{vp_objects::vp_pose_type::mpi_15, {{0,1}, {1,2}, {2,3}, {3,4}, {1,5}, {5,6}, {6,7}, {1,14}, {14,8}, {8,9}, {9,10}, {14,11}, {11,12}, {12,13}, {0, 2}, {0, 5}}},
|
|
{vp_objects::vp_pose_type::hand, std::vector<std::pair<int,int>>()},
|
|
{vp_objects::vp_pose_type::face, std::vector<std::pair<int,int>>()}
|
|
};
|
|
|
|
// points count for each type of pose model
|
|
const std::map<vp_objects::vp_pose_type, int> points_map = {
|
|
{vp_objects::vp_pose_type::body_25, 25},
|
|
{vp_objects::vp_pose_type::coco, 18},
|
|
{vp_objects::vp_pose_type::mpi_15, 15},
|
|
{vp_objects::vp_pose_type::hand, 0},
|
|
{vp_objects::vp_pose_type::face, 0}
|
|
};
|
|
|
|
// for postprocess purpose
|
|
struct ValidPair{
|
|
ValidPair(int aId,int bId,float score) {
|
|
this->aId = aId;
|
|
this->bId = bId;
|
|
this->score = score;
|
|
}
|
|
|
|
int aId;
|
|
int bId;
|
|
float score;
|
|
};
|
|
struct KeyPoint{
|
|
KeyPoint(cv::Point point, float probability) {
|
|
this->id = -1;
|
|
this->point = point;
|
|
this->probability = probability;
|
|
}
|
|
|
|
int id;
|
|
cv::Point point;
|
|
float probability;
|
|
};
|
|
// for postprocess purpose end
|
|
|
|
void getKeyPoints(cv::Mat& probMap, double threshold, std::vector<KeyPoint>& keyPoints) {
|
|
cv::Mat smoothProbMap;
|
|
cv::GaussianBlur(probMap, smoothProbMap, cv::Size( 3, 3 ), 0, 0);
|
|
|
|
cv::Mat maskedProbMap;
|
|
cv::threshold(smoothProbMap, maskedProbMap, threshold, 255, cv::THRESH_BINARY);
|
|
|
|
maskedProbMap.convertTo(maskedProbMap, CV_8U, 1);
|
|
|
|
std::vector<std::vector<cv::Point> > contours;
|
|
cv::findContours(maskedProbMap, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
|
|
|
|
for(int i = 0; i < contours.size();++i) {
|
|
cv::Mat blobMask = cv::Mat::zeros(smoothProbMap.rows, smoothProbMap.cols, smoothProbMap.type());
|
|
cv::fillConvexPoly(blobMask, contours[i], cv::Scalar(1));
|
|
|
|
double maxVal;
|
|
cv::Point maxLoc;
|
|
|
|
cv::minMaxLoc(smoothProbMap.mul(blobMask), 0, &maxVal, 0, &maxLoc);
|
|
keyPoints.push_back(KeyPoint(maxLoc, probMap.at<float>(maxLoc.y, maxLoc.x)));
|
|
}
|
|
}
|
|
|
|
void splitNetOutputBlobToParts(cv::Mat& netOutputBlob, const cv::Size& targetSize, std::vector<cv::Mat>& netOutputParts) {
|
|
int nParts = netOutputBlob.size[0];
|
|
int h = netOutputBlob.size[1];
|
|
int w = netOutputBlob.size[2];
|
|
|
|
for(int i = 0; i< nParts; ++i) {
|
|
cv::Mat part(h, w, CV_32F, netOutputBlob.ptr(i));
|
|
|
|
cv::Mat resizedPart;
|
|
|
|
cv::resize(part, resizedPart, targetSize);
|
|
|
|
netOutputParts.push_back(resizedPart);
|
|
}
|
|
}
|
|
|
|
void populateInterpPoints(const cv::Point& a,const cv::Point& b,int numPoints,std::vector<cv::Point>& interpCoords){
|
|
float xStep = ((float)(b.x - a.x)) / (float)(numPoints - 1);
|
|
float yStep = ((float)(b.y - a.y)) / (float)(numPoints - 1);
|
|
interpCoords.push_back(a);
|
|
|
|
for(int i = 1; i< numPoints - 1; ++i) {
|
|
interpCoords.push_back(cv::Point(a.x + xStep * i, a.y + yStep * i));
|
|
}
|
|
interpCoords.push_back(b);
|
|
}
|
|
|
|
void getValidPairs(const std::vector<cv::Mat>& netOutputParts,
|
|
const std::vector<std::vector<KeyPoint>>& detectedKeypoints,
|
|
std::vector<std::vector<ValidPair>>& validPairs,
|
|
std::set<int>& invalidPairs) {
|
|
|
|
int nInterpSamples = 10;
|
|
float confTh = 0.7;
|
|
|
|
for(int k = 0; k < mapIdxes_map.at(type).size(); ++k) {
|
|
//A->B constitute a limb
|
|
cv::Mat pafA = netOutputParts[mapIdxes_map.at(type)[k].first + points_map.at(type) + 1];
|
|
cv::Mat pafB = netOutputParts[mapIdxes_map.at(type)[k].second + points_map.at(type) + 1];
|
|
|
|
//Find the keypoints for the first and second limb
|
|
const std::vector<KeyPoint>& candA = detectedKeypoints[posePairs_map.at(type)[k].first];
|
|
const std::vector<KeyPoint>& candB = detectedKeypoints[posePairs_map.at(type)[k].second];
|
|
|
|
int nA = candA.size();
|
|
int nB = candB.size();
|
|
|
|
/*
|
|
# If keypoints for the joint-pair is detected
|
|
# check every joint in candA with every joint in candB
|
|
# Calculate the distance vector between the two joints
|
|
# Find the PAF values at a set of interpolated points between the joints
|
|
# Use the above formula to compute a score to mark the connection valid
|
|
*/
|
|
|
|
if(nA != 0 && nB != 0){
|
|
std::vector<ValidPair> localValidPairs;
|
|
|
|
for(int i = 0; i< nA; ++i) {
|
|
int maxJ = -1;
|
|
float maxScore = -1;
|
|
bool found = false;
|
|
|
|
for(int j = 0; j < nB; ++j) {
|
|
std::pair<float, float> distance(candB[j].point.x - candA[i].point.x, candB[j].point.y - candA[i].point.y);
|
|
|
|
float norm = std::sqrt(distance.first * distance.first + distance.second * distance.second);
|
|
|
|
if(!norm) {
|
|
continue;
|
|
}
|
|
|
|
distance.first /= norm;
|
|
distance.second /= norm;
|
|
|
|
//Find p(u)
|
|
std::vector<cv::Point> interpCoords;
|
|
populateInterpPoints(candA[i].point, candB[j].point, nInterpSamples, interpCoords);
|
|
//Find L(p(u))
|
|
std::vector<std::pair<float, float>> pafInterp;
|
|
for(int l = 0; l < interpCoords.size();++l) {
|
|
pafInterp.push_back(
|
|
std::pair<float,float>(
|
|
pafA.at<float>(interpCoords[l].y, interpCoords[l].x),
|
|
pafB.at<float>(interpCoords[l].y, interpCoords[l].x)
|
|
));
|
|
}
|
|
|
|
std::vector<float> pafScores;
|
|
float sumOfPafScores = 0;
|
|
int numOverTh = 0;
|
|
for(int l = 0; l< pafInterp.size(); ++l){
|
|
float score = pafInterp[l].first * distance.first + pafInterp[l].second * distance.second;
|
|
sumOfPafScores += score;
|
|
if(score > score_threshold){
|
|
++numOverTh;
|
|
}
|
|
|
|
pafScores.push_back(score);
|
|
}
|
|
|
|
float avgPafScore = sumOfPafScores / ((float)pafInterp.size());
|
|
|
|
if(((float)numOverTh)/((float)nInterpSamples) > confTh){
|
|
if(avgPafScore > maxScore) {
|
|
maxJ = j;
|
|
maxScore = avgPafScore;
|
|
found = true;
|
|
}
|
|
}
|
|
}/* j */
|
|
|
|
if(found) {
|
|
localValidPairs.push_back(ValidPair(candA[i].id, candB[maxJ].id, maxScore));
|
|
}
|
|
|
|
}/* i */
|
|
|
|
validPairs.push_back(localValidPairs);
|
|
|
|
} else {
|
|
invalidPairs.insert(k);
|
|
validPairs.push_back(std::vector<ValidPair>());
|
|
}
|
|
}/* k */
|
|
}
|
|
|
|
void getPersonwiseKeypoints(const std::vector<std::vector<ValidPair>>& validPairs,
|
|
const std::set<int>& invalidPairs,
|
|
std::vector<std::vector<int>>& personwiseKeypoints) {
|
|
for(int k = 0; k < mapIdxes_map.at(type).size(); ++k) {
|
|
if(invalidPairs.find(k) != invalidPairs.end()) {
|
|
continue;
|
|
}
|
|
|
|
const std::vector<ValidPair>& localValidPairs(validPairs[k]);
|
|
|
|
int indexA(posePairs_map.at(type)[k].first);
|
|
int indexB(posePairs_map.at(type)[k].second);
|
|
|
|
for(int i = 0; i< localValidPairs.size(); ++i) {
|
|
bool found = false;
|
|
int personIdx = -1;
|
|
|
|
for(int j = 0; !found && j < personwiseKeypoints.size();++j) {
|
|
if(indexA < personwiseKeypoints[j].size() &&
|
|
personwiseKeypoints[j][indexA] == localValidPairs[i].aId) {
|
|
personIdx = j;
|
|
found = true;
|
|
}
|
|
}/* j */
|
|
|
|
if(found) {
|
|
personwiseKeypoints[personIdx].at(indexB) = localValidPairs[i].bId;
|
|
}
|
|
else if(k < points_map.at(type) - 1) {
|
|
std::vector<int> lpkp(std::vector<int>(points_map.at(type), -1));
|
|
|
|
lpkp.at(indexA) = localValidPairs[i].aId;
|
|
lpkp.at(indexB) = localValidPairs[i].bId;
|
|
|
|
personwiseKeypoints.push_back(lpkp);
|
|
}
|
|
|
|
}/* i */
|
|
}/* k */
|
|
}
|
|
|
|
protected:
|
|
virtual void postprocess(const std::vector<cv::Mat>& raw_outputs, const std::vector<std::shared_ptr<vp_objects::vp_frame_meta>>& frame_meta_with_batch) override;
|
|
public:
|
|
vp_openpose_detector_node(std::string node_name,
|
|
std::string model_path,
|
|
std::string model_config_path = "",
|
|
std::string labels_path = "",
|
|
int input_width = 368,
|
|
int input_height = 368,
|
|
int batch_size = 1,
|
|
int class_id_offset = 0,
|
|
float score_threshold = 0.1,
|
|
vp_objects::vp_pose_type type = vp_objects::vp_pose_type::coco,
|
|
float scale = 1 / 255.0,
|
|
cv::Scalar mean = cv::Scalar(0),
|
|
cv::Scalar std = cv::Scalar(1),
|
|
bool swap_rb = false);
|
|
~vp_openpose_detector_node();
|
|
};
|
|
} |