postprocess_body_pose.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /*************************************************************************
  2. * Copyright (C) [2021] by Cambricon, Inc. All rights reserved
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  13. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  15. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. * THE SOFTWARE.
  19. *************************************************************************/
  20. #include <opencv2/opencv.hpp>
  21. #include <algorithm>
  22. #include <array>
  23. #include <cmath>
  24. #include <cstring>
  25. #include <memory>
  26. #include <string>
  27. #include <tuple>
  28. #include <utility>
  29. #include <vector>
  30. #include "cnstream_frame_va.hpp"
  31. #include "cns_openpose.hpp"
  32. #include "postproc.hpp"
  33. void RemapKeypoints(cns_openpose::Keypoints* keypoints, int src_w, int src_h, int dst_w, int dst_h) {
  34. float scaling_factor = std::min(1.0f * src_w / dst_w, 1.0f * src_h / dst_h);
  35. const int scaled_w = scaling_factor * dst_w;
  36. const int scaled_h = scaling_factor * dst_h;
  37. for (auto& points : *keypoints) {
  38. for (auto& point : points) {
  39. point.x -= (src_w - scaled_w) / 2.0f;
  40. point.y -= (src_h - scaled_h) / 2.0f;
  41. point.x = std::floor(1.0f * point.x / scaled_w * dst_w);
  42. point.y = std::floor(1.0f * point.y / scaled_w * dst_w);
  43. }
  44. }
  45. }
  46. /**
  47. * @brief Post process for body pose
  48. */
  49. template <int knKeypoints, int knLimbs>
  50. class PostprocPose : public cnstream::Postproc {
  51. static constexpr int knHeatmaps = knKeypoints + knLimbs * 2;
  52. public:
  53. virtual ~PostprocPose() {}
  54. using Heatmaps = std::array<cv::Mat, knHeatmaps>;
  55. int Execute(const std::vector<float*>& net_outputs, const std::shared_ptr<edk::ModelLoader>& model,
  56. const cnstream::CNFrameInfoPtr& package) override;
  57. protected:
  58. virtual const std::array<std::pair<int, int>, knLimbs>& GetHeatmapIndexs() = 0;
  59. virtual const std::array<std::pair<int, int>, knLimbs>& GetLimbEndpointPairs() = 0;
  60. private:
  61. Heatmaps GetHeatmaps(float* net_output, const std::shared_ptr<edk::ModelLoader>& model);
  62. cns_openpose::Keypoints GetKeypoints(const Heatmaps& heatmaps);
  63. cns_openpose::Limbs GetLimbs(const Heatmaps& heatmaps, const cns_openpose::Keypoints& keypoints);
  64. }; // class PostprocPose
  65. template<int knKeypoints, int knLimbs>
  66. int PostprocPose<knKeypoints, knLimbs>::Execute(const std::vector<float*>& net_outputs,
  67. const std::shared_ptr<edk::ModelLoader>& model,
  68. const cnstream::CNFrameInfoPtr& package) {
  69. // model output in NCHW order. see parameter named data_order in Inferencer module.
  70. if (model->OutputShape(0).C() != knHeatmaps)
  71. LOGF(POSTPROC_POSE) << "The number of heatmaps in model mismatched.";
  72. auto frame = package->collection.Get<cnstream::CNDataFramePtr>(cnstream::kCNDataFrameTag);
  73. auto heatmaps = GetHeatmaps(net_outputs[0], model);
  74. auto keypoints = GetKeypoints(heatmaps);
  75. auto total_limbs = GetLimbs(heatmaps, keypoints);
  76. RemapKeypoints(&keypoints, model->InputShape(0).W(), model->InputShape(0).H(), frame->width, frame->height);
  77. package->collection.Add(cns_openpose::kPoseKeypointsTag, keypoints);
  78. package->collection.Add(cns_openpose::kPoseLimbsTag, total_limbs);
  79. return 0;
  80. }
  81. template <int knKeypoints, int knLimbs>
  82. typename PostprocPose<knKeypoints, knLimbs>::Heatmaps
  83. PostprocPose<knKeypoints, knLimbs>::GetHeatmaps(float* net_output, const std::shared_ptr<edk::ModelLoader>& model) {
  84. Heatmaps heatmaps;
  85. const int src_w = model->OutputShape(0).W();
  86. const int src_h = model->OutputShape(0).H();
  87. const int src_heatmap_len = src_w * src_h;
  88. const cv::Size dst_size(model->InputShape(0).W(), model->InputShape(0).H());
  89. for (int i = 0; i < knHeatmaps; ++i) {
  90. cv::Mat src(src_h, src_w, CV_32FC1, net_output + i * src_heatmap_len);
  91. cv::Mat dst;
  92. cv::resize(src, dst, dst_size, cv::INTER_CUBIC);
  93. heatmaps[i] = std::move(dst);
  94. }
  95. return heatmaps;
  96. }
  97. template <int knKeypoints, int knLimbs>
  98. cns_openpose::Keypoints
  99. PostprocPose<knKeypoints, knLimbs>::GetKeypoints(const Heatmaps& heatmaps) {
  100. cns_openpose::Keypoints keypoints(knKeypoints - 1); // ignore background
  101. for (int i = 0; i < knKeypoints - 1; ++i) {
  102. static constexpr double kThreshold = 0.1;
  103. cv::Mat confidence_map = heatmaps[i];
  104. // image binaryzation
  105. cv::Mat smooth;
  106. cv::GaussianBlur(confidence_map, smooth, cv::Size(3, 3), 0, 0);
  107. cv::Mat binary;
  108. cv::threshold(smooth, binary, kThreshold, 255, cv::THRESH_BINARY);
  109. binary.convertTo(binary, CV_8UC1);
  110. // find contours
  111. std::vector<std::vector<cv::Point>> contours;
  112. cv::findContours(binary, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  113. // local maximum
  114. std::vector<cv::Point> points;
  115. for (const auto& contour : contours) {
  116. cv::Mat mask = cv::Mat::zeros(binary.rows, binary.cols, smooth.type());
  117. cv::fillConvexPoly(mask, contour, cv::Scalar(1));
  118. cv::Point max_loc;
  119. cv::minMaxLoc(smooth.mul(mask), NULL, NULL, NULL, &max_loc);
  120. points.push_back(max_loc);
  121. }
  122. keypoints[i] = std::move(points);
  123. }
  124. return keypoints;
  125. }
  126. static
  127. std::vector<cv::Point> Sampling(cv::Point first_end, const cv::Point& second_end, int nsamples) {
  128. cv::Point distance = second_end - first_end;
  129. float x_step = 1.0 * distance.x / (nsamples - 1);
  130. float y_step = 1.0 * distance.y / (nsamples - 1);
  131. std::vector<cv::Point> samples;
  132. samples.push_back(first_end);
  133. for (int i = 1; i < nsamples - 1; ++i) {
  134. samples.emplace_back(cv::Point(std::round(first_end.x + x_step * i), std::round(first_end.y + y_step * i)));
  135. }
  136. samples.push_back(second_end);
  137. return samples;
  138. }
  139. template <int knKeypoints, int knLimbs>
  140. cns_openpose::Limbs
  141. PostprocPose<knKeypoints, knLimbs>::GetLimbs(const Heatmaps& heatmaps, const cns_openpose::Keypoints& keypoints) {
  142. static constexpr int knSamples = 10; // number of samples between tow points
  143. static constexpr float kPafThreshold = 0.1;
  144. static constexpr float kSamplesMatchThreshold = 0.7;
  145. cns_openpose::Limbs total_limbs;
  146. for (int limb_idx = 0; limb_idx < knLimbs; ++limb_idx) {
  147. static constexpr int kPafHeatmapsOffset = knKeypoints;
  148. int first_ends_idx = GetLimbEndpointPairs()[limb_idx].first;
  149. int second_ends_idx = GetLimbEndpointPairs()[limb_idx].second;
  150. const cv::Mat& paf_first = heatmaps[kPafHeatmapsOffset + GetHeatmapIndexs()[limb_idx].first];
  151. const cv::Mat& paf_second = heatmaps[kPafHeatmapsOffset + GetHeatmapIndexs()[limb_idx].second];
  152. // both ends of the limb
  153. const std::vector<cv::Point>& first_ends = keypoints[first_ends_idx];
  154. const std::vector<cv::Point>& second_ends = keypoints[second_ends_idx];
  155. const int knFirstEnds = static_cast<int>(first_ends.size());
  156. const int knSecondEnds = static_cast<int>(second_ends.size());
  157. std::vector<std::pair<cv::Point, cv::Point>> limbs;
  158. // stores second endpoint status (selected or not), selected by which first endpoint and the score.
  159. // 0: selected or not, 1: index in limbs, 2: limb score
  160. std::vector<std::tuple<bool, size_t, float>>
  161. second_end_selected_status(knSecondEnds, std::make_tuple(false, -1, -1.0f));
  162. // find max score between first ends and second ends
  163. for (int first_end_idx = 0; first_end_idx < knFirstEnds; ++first_end_idx) {
  164. int selected_second_end_idx = -1;
  165. float max_score = -1;
  166. const cv::Point& first_end = first_ends[first_end_idx];
  167. for (int second_end_idx = 0; second_end_idx < knSecondEnds; ++second_end_idx) {
  168. const cv::Point& second_end = second_ends[second_end_idx];
  169. std::pair<float, float> distance = std::make_pair(second_end.x - first_end.x, second_end.y - first_end.y);
  170. float norm2 = std::sqrt(distance.first * distance.first +
  171. distance.second * distance.second);
  172. distance.first /= norm2;
  173. distance.second /= norm2;
  174. // p(u)
  175. std::vector<cv::Point> sample_points = Sampling(first_end, second_end, knSamples);
  176. // L(p(u))
  177. float sum_of_sample_score = 0;
  178. int num_over_threshold = 0;
  179. for (int sample_idx = 0; sample_idx < knSamples; ++sample_idx) {
  180. const auto& sample_point = sample_points[sample_idx];
  181. float paf_first_value = paf_first.at<float>(sample_point);
  182. float paf_second_value = paf_second.at<float>(sample_point);
  183. float score = paf_first_value * distance.first + paf_second_value * distance.second;
  184. if (score > kPafThreshold) {
  185. ++num_over_threshold;
  186. sum_of_sample_score += score;
  187. }
  188. } // for samples
  189. if (1.0f * num_over_threshold / knSamples > kSamplesMatchThreshold) {
  190. float avg_score = sum_of_sample_score / sample_points.size();
  191. if (avg_score > max_score) {
  192. const auto& selected_status = second_end_selected_status[second_end_idx];
  193. if (std::get<0>(selected_status) &&
  194. std::get<2>(selected_status) > avg_score) {
  195. // selected by other first endpoint and pre-score greater than current score
  196. continue;
  197. }
  198. selected_second_end_idx = second_end_idx;
  199. max_score = avg_score;
  200. }
  201. } // if kSamplesMatchThreshold
  202. } // for second ends
  203. if (-1 != selected_second_end_idx) {
  204. auto& selected_status = second_end_selected_status[selected_second_end_idx];
  205. // found best matchs second end, positions in keypoints vector
  206. limbs.emplace_back(std::make_pair(cv::Point(first_ends_idx, first_end_idx),
  207. cv::Point(second_ends_idx, selected_second_end_idx)));
  208. if (std::get<0>(selected_status)) {
  209. // selected by other first endpoint, but current score greater than pre-score. remove limb
  210. limbs.erase(limbs.begin() + std::get<1>(selected_status));
  211. }
  212. // save second endpoint selected status
  213. std::get<0>(selected_status) = true;
  214. std::get<1>(selected_status) = limbs.size() - 1;
  215. std::get<2>(selected_status) = max_score;
  216. }
  217. } // for first ends
  218. total_limbs.push_back(std::move(limbs));
  219. } // for keypoints
  220. return total_limbs;
  221. }
  222. static constexpr int knBody25Keypoints = 26; // 25 keypoints + 1 background
  223. static constexpr int knBody25Limbs = 26;
  224. class PostprocBody25Pose : public PostprocPose<knBody25Keypoints, knBody25Limbs> {
  225. public:
  226. ~PostprocBody25Pose() = default;
  227. private:
  228. const std::array<std::pair<int, int>, knBody25Limbs>& GetHeatmapIndexs() override {
  229. static constexpr std::array<std::pair<int, int>, knBody25Limbs> kBody25HeatmapIndexs {
  230. std::make_pair(0, 1), {14, 15}, {22, 23}, {16, 17}, {18, 19}, {24, 25},
  231. {26, 27}, {6, 7}, {2, 3}, {4, 5}, {8, 9}, {10, 11}, {12, 13},
  232. {30, 31}, {32, 33}, {36, 37}, {34, 35}, {38, 39}, {20, 21},
  233. {28, 29}, {40, 41}, {42, 43}, {44, 45}, {46, 47}, {48, 49}, {50, 51}
  234. };
  235. return kBody25HeatmapIndexs;
  236. }
  237. const std::array<std::pair<int, int>, knBody25Limbs>& GetLimbEndpointPairs() override {
  238. static constexpr std::array<std::pair<int, int>, knBody25Limbs> kBody25LimbEndpointPairs {
  239. std::make_pair(1, 8), {1, 2}, {1, 5}, {2, 3}, {3, 4}, {5, 6}, {6, 7},
  240. {8, 9}, {9, 10}, {10, 11}, {8, 12}, {12, 13}, {13, 14},
  241. {1, 0}, {0, 15}, {15, 17}, {0, 16}, {16, 18}, {2, 17},
  242. {5, 18}, {14, 19}, {19, 20}, {14, 21}, {11, 22}, {22, 23}, {11, 24}
  243. };
  244. return kBody25LimbEndpointPairs;
  245. }
  246. DECLARE_REFLEX_OBJECT_EX(PostprocBody25Pose, cnstream::Postproc)
  247. }; // class PostprocBody25Pose
  248. IMPLEMENT_REFLEX_OBJECT_EX(PostprocBody25Pose, cnstream::Postproc)
  249. static constexpr int knCOCOKeypoints = 19; // 18 keypoints + 1 background
  250. static constexpr int knCOCOLimbs = 19;
  251. class PostprocCOCOPose : public PostprocPose<knCOCOKeypoints, knCOCOLimbs> {
  252. public:
  253. ~PostprocCOCOPose() = default;
  254. private:
  255. const std::array<std::pair<int, int>, knCOCOLimbs>& GetHeatmapIndexs() override {
  256. static constexpr std::array<std::pair<int, int>, knCOCOLimbs> kCOCOHeatmapIndexs {
  257. std::make_pair(12, 13), {20, 21}, {14, 15}, {16, 17}, {22, 23},
  258. {24, 25}, {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {28, 29},
  259. {30, 31}, {34, 35}, {32, 33}, {36, 37}, {18, 19}, {26, 27}
  260. };
  261. return kCOCOHeatmapIndexs;
  262. }
  263. const std::array<std::pair<int, int>, knCOCOLimbs>& GetLimbEndpointPairs() override {
  264. static constexpr std::array<std::pair<int, int>, knCOCOLimbs> kCOCOLimbEndpointPairs {
  265. std::make_pair(1, 2), {1, 5}, {2, 3}, {3, 4}, {5, 6},
  266. {6, 7}, {1, 8}, {8, 9}, {9, 10}, {1, 11}, {11, 12}, {12, 13},
  267. {1, 0}, {0, 14}, {14, 16}, {0, 15}, {15, 17}, {2, 16}, {5, 17}
  268. };
  269. return kCOCOLimbEndpointPairs;
  270. }
  271. DECLARE_REFLEX_OBJECT_EX(PostprocCOCOPose, cnstream::Postproc)
  272. }; // class PostprocCOCOPose
  273. IMPLEMENT_REFLEX_OBJECT_EX(PostprocCOCOPose, cnstream::Postproc)