OpenPoseAdapter.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * ArmarX is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * ArmarX is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * @package visionx::MonocularOpenPoseEstimation
17  * @author Fabian Peller <fabian.peller@kit.edu>
18  * @date 2020
19  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20  * GNU General Public License
21  */
22 
23 // Header
24 #include "OpenPoseAdapter.h"
25 
26 // OpenPose
27 #include <openpose/pose/poseParameters.hpp>
28 #include <openpose/pose/poseParametersRender.hpp>
29 
30 // IVT
31 #include <Image/ImageProcessor.h>
32 #include <Image/IplImageAdaptor.h>
33 #include <Image/PrimitivesDrawer.h>
34 
35 // ArmarX
40 
41 using namespace visionx;
42 using namespace armarx;
43 
44 namespace
45 {
46  // This is a workaround because in OpenPose 1.4, this function was called `intRound`, whereas
47  // in OpenPose 1.5.1 it was renamed to `positiveIntRound`. This function is now defined in this
48  // implementation as long as we use several OpenPose versions. As soon as we move to 1.5.1 we
49  // can remove this and use 'op::positiveIntRound` instead.
50  template <typename T>
51  inline int
52  positiveIntRound(const T a)
53  {
54  return int(a + 0.5f);
55  }
56 } // namespace
57 
58 OpenPoseAdapter::OpenPoseAdapter(const OpenPoseSettings& settings) : settings(settings)
59 {
60  setupOpenPoseEnvironment();
61 }
62 
63 // Initialize all variables to interact with openpose
64 void
65 OpenPoseAdapter::setupOpenPoseEnvironment()
66 {
67  std::lock_guard l(op_running_mutex);
68 
69  TIMING_START(setupOpenPose);
70  ARMARX_INFO_S << "setting up open pose environment";
71 
72  auto op_model_pose_conv = ConvertToOPCustomType(settings.model_pose);
73  poseModel = op::flagsToPoseModel(op_model_pose_conv);
74 
75  auto op_net_resolution_conv = ConvertToOPCustomType(settings.net_resolution);
76  auto op_output_resolution_conv = ConvertToOPCustomType(settings.output_resolution);
77 
78  const op::Point<int> netInputSize = op::flagsToPoint(op_net_resolution_conv, "-1x368");
79  const op::Point<int> outputSize = op::flagsToPoint(op_output_resolution_conv, "-1x-1");
80 
81 #if (OPENPOSE_VERSION_MAJOR > 1) || (OPENPOSE_VERSION_MAJOR == 1 && OPENPOSE_VERSION_MINOR > 7) || \
82  (OPENPOSE_VERSION_MAJOR == 1 && OPENPOSE_VERSION_MINOR == 7 && OPENPOSE_VERSION_PATCH > 0)
83  scaleAndSizeExtractor = std::make_shared<op::ScaleAndSizeExtractor>(
84  netInputSize,
85  -1, // unclear what this value should be from openpose documentation
86  outputSize,
89 #else
90  scaleAndSizeExtractor = std::make_shared<op::ScaleAndSizeExtractor>(
91  netInputSize, outputSize, settings.scale_number, settings.scale_gap);
92 #endif
93  cvMatToOpInput = std::make_shared<op::CvMatToOpInput>(poseModel);
94  cvMatToOpOutput.reset(new op::CvMatToOpOutput());
95  opOutputToCvMat.reset(new op::OpOutputToCvMat());
96 
97  std::string modelFolder = settings.model_folder;
99  modelFolder,
100  modelFolder,
101  {"/usr/share/OpenPose/", "/usr/local/share/OpenPose/", OPENPOSE_MODELS});
102  if (!found)
103  {
104  ARMARX_ERROR << "Could not find model folder at " << modelFolder;
105  return;
106  }
107  if (!modelFolder.empty() && *modelFolder.rbegin() != '/')
108  {
109  modelFolder += "/";
110  }
111  ARMARX_INFO << "Found model path at: " << modelFolder;
113  std::make_shared<op::PoseExtractorCaffe>(poseModel, modelFolder, settings.num_gpu_start);
114  poseExtractorCaffe->initializationOnThread();
115 
116  TIMING_END(setupOpenPose);
117 }
118 
119 // run openpose and return the openpose representation of keypoints
120 op::Array<float>
121 OpenPoseAdapter::getOpenposeKeypoints(const CByteImage* inputImage)
122 {
123  std::lock_guard l(op_running_mutex);
124 
125  // Step 1 - Convert image to cv::Mat
126  IplImage* iplImage = IplImageAdaptor::Adapt(inputImage);
127  cv::Mat opInputImage = cv::cvarrToMat(iplImage);
128  const op::Point<int> imageSize{opInputImage.cols, opInputImage.rows};
129 
130  // Step 2 - Get desired scale sizes
131  std::vector<double> scaleInputToNetInputs;
132  std::vector<op::Point<int>> netInputSizes;
133  double scaleInputToOutput;
134  op::Point<int> outputResolution;
135  std::tie(scaleInputToNetInputs, netInputSizes, scaleInputToOutput, outputResolution) =
136  scaleAndSizeExtractor->extract(imageSize);
137 
138  // Step 3 - Format input image to OpenPose input and output formats
139  auto inputImage_conv = ConvertToOPCustomType(opInputImage);
140  const auto netInputArray =
141  cvMatToOpInput->createArray(inputImage_conv, scaleInputToNetInputs, netInputSizes);
142 
143  // Step 4 - Estimate poseKeypoints
144  poseExtractorCaffe->forwardPass(netInputArray, imageSize, scaleInputToNetInputs);
145  const op::Array<float> poseKeypoints = poseExtractorCaffe->getPoseKeypoints();
146 
147  return poseKeypoints;
148 }
149 
150 // convert the openpose representation of keypoints into our ice version
151 HumanPose2DMap
152 OpenPoseAdapter::convert2DKeypointsToIce(const op::Array<float>& op_keypoints,
153  const CByteImage* rgbImage) const
154 {
155  ARMARX_CHECK_NOT_NULL(rgbImage);
156 
157  HumanPose2DMap ice_entities;
158  const std::map<unsigned int, std::string> poseBodyPartMapping =
159  op::getPoseBodyPartMapping(poseModel);
160 
161  if (op_keypoints.getSize().empty())
162  {
163  return ice_entities;
164  }
165 
166  int entities = op_keypoints.getSize().at(0);
167  int points = op_keypoints.getSize().at(1);
168 
169  ARMARX_CHECK_EXPRESSION(poseBodyPartMapping.size() > 0);
170  ARMARX_CHECK_EXPRESSION(points == static_cast<int>(poseBodyPartMapping.size()) - 1);
171 
172  for (int i = 0; i < entities; i++)
173  {
174  std::string entity_id = std::to_string(i);
175 
176  ice_entities[entity_id] = HumanPose2D();
177  Keypoint2DMap ice_keypoint_map;
178  for (int id = 0; id < points; id++)
179  {
180  Keypoint2D keypoint;
181  float x = op_keypoints.at({i, id, 0});
182  float y = op_keypoints.at({i, id, 1});
183  float c = op_keypoints.at({i, id, 2});
184  if (x == 0.0f && y == 0.0f && c == 0.0f)
185  {
186  continue; // Invalid keypoint from openpose
187  }
188  keypoint.id = id;
189  keypoint.label = poseBodyPartMapping.at(id);
190  keypoint.x = x;
191  keypoint.y = y;
192  keypoint.confidence = c;
193  keypoint.dominantColor =
194  getDominantColorOfPatch(rgbImage, Vec2d{x, y}, rgbImage->width / 50);
195  ice_keypoint_map[keypoint.label] = keypoint;
196  }
197 
198  if (ice_keypoint_map.size() >= settings.minimum_number_of_valid_keypoints_per_entitiy)
199  {
200  ice_entities[entity_id].keypointMap = ice_keypoint_map;
201  }
202  }
203 
204  return ice_entities;
205 }
206 
207 armarx::DrawColor24Bit
209  const Vec2d& point,
210  int windowSize) const
211 {
212  if (point.x < 0 || point.y < 0 || point.x >= image.width || point.y >= image.height)
213  return DrawColor24Bit{0, 0, 0}; // Black
214  int divisor = 256 / 3; // split into 3*3*3 bins
215  typedef std::tuple<Ice::Byte, Ice::Byte, Ice::Byte> RGBTuple;
216  std::map<RGBTuple, int> histogram;
217  int halfWindowSize = static_cast<int>(windowSize * 0.5);
218  int left = std::max<int>(0, static_cast<int>(point.x) - halfWindowSize);
219  int top = std::max<int>(0, static_cast<int>(point.y) - halfWindowSize);
220  int right = std::min<int>(image.width, static_cast<int>(point.x) + halfWindowSize);
221  int bottom = std::min<int>(image.height, static_cast<int>(point.y) + halfWindowSize);
222 
223  for (int x = left; x < right; x++)
224  {
225  for (int y = top; y < bottom; y++)
226  {
227  int pixelPos = (y * image.width + x) * 3;
228  auto tuple = std::make_tuple<Ice::Byte, Ice::Byte, Ice::Byte>(
229  static_cast<Ice::Byte>(image.pixels[pixelPos] / divisor),
230  static_cast<Ice::Byte>(image.pixels[pixelPos + 1] / divisor),
231  static_cast<Ice::Byte>(image.pixels[pixelPos + 2] / divisor));
232  if (histogram.count(tuple))
233  {
234  histogram.at(tuple)++;
235  }
236  else
237  {
238  histogram[tuple] = 1;
239  }
240  }
241  }
242 
243  float maxHistogramValue = 0;
244  RGBTuple dominantColor;
245  for (auto& pair : histogram)
246  {
247  if (pair.second > maxHistogramValue)
248  {
249  dominantColor = pair.first;
250  maxHistogramValue = pair.second;
251  }
252  }
253  auto rgb = DrawColor24Bit{static_cast<Ice::Byte>(std::get<0>(dominantColor) * divisor),
254  static_cast<Ice::Byte>(std::get<1>(dominantColor) * divisor),
255  static_cast<Ice::Byte>(std::get<2>(dominantColor) * divisor)};
256  return rgb;
257 }
258 
259 void
260 OpenPoseAdapter::render2DResultImage(const CByteImage& inputImage,
261  const op::Array<float>& keypoints,
262  CByteImage& resultImage,
263  float renderThreshold)
264 {
265  //ARMARX_CHECK_EXPRESSION(inputImage.width == resultImage.width);
266  //ARMARX_CHECK_EXPRESSION(inputImage.height == resultImage.height);
267  resultImage.Set(inputImage.width, inputImage.height, inputImage.type);
268  ::ImageProcessor::CopyImage(&inputImage, &resultImage);
269 
270  if (keypoints.getSize().empty())
271  {
272  return;
273  }
274 
275  const std::vector<unsigned int>& posePartPairs = op::getPoseBodyPartPairsRender(poseModel);
276 
277  // Get colors for points and lines from openpose
278  std::vector<float> keypointColors = op::getPoseColors(poseModel);
279 
280  const auto thicknessCircleRatio = 1.f / 75.f;
281  const auto thicknessLineRatioWRTCircle = 0.75f;
282  const auto area = inputImage.width * inputImage.height;
283  const int numberKeypoints = keypoints.getSize(1);
284 
285  for (int person = 0; person < keypoints.getSize(0); person++)
286  {
287  const auto personRectangle = op::getKeypointsRectangle(keypoints, person, 0.1f);
288  if (personRectangle.area() > 0)
289  {
290  // define size-dependent variables
291  const auto ratioAreas = op::fastMin(
292  1.f,
293  op::fastMax(personRectangle.width / static_cast<float>(inputImage.width),
294  personRectangle.height / static_cast<float>(inputImage.height)));
295  const auto thicknessRatio =
296  op::fastMax(positiveIntRound<float>(static_cast<float>(std::sqrt(area)) *
297  thicknessCircleRatio * ratioAreas),
298  2);
299  // Negative thickness in ivt::drawCircle means that a filled circle is to be drawn.
300  const auto thicknessCircle = op::fastMax(1, (ratioAreas > 0.05f ? thicknessRatio : -1));
301  const auto thicknessLine =
302  op::fastMax(1, positiveIntRound(thicknessRatio * thicknessLineRatioWRTCircle));
303  const auto radius = thicknessRatio / 2;
304 
305  // Draw Lines
306  for (unsigned int i = 0; i < posePartPairs.size(); i = i + 2)
307  {
308  const int index1 = (person * numberKeypoints + static_cast<int>(posePartPairs[i])) *
309  keypoints.getSize(2);
310  const int index2 =
311  (person * numberKeypoints + static_cast<int>(posePartPairs[i + 1])) *
312  keypoints.getSize(2);
313 
314  float x1 = keypoints[index1 + 0];
315  float y1 = keypoints[index1 + 1];
316  float x2 = keypoints[index2 + 0];
317  float y2 = keypoints[index2 + 1];
318 
319  if (!(x1 == 0.0f && y1 == 0.0f) && !(x2 == 0.0f && y2 == 0.0f))
320  {
321  if (keypoints[index1 + 2] > renderThreshold &&
322  keypoints[index2 + 2] > renderThreshold)
323  {
324  unsigned int colorIndex = posePartPairs[i + 1] * 3;
325  ::PrimitivesDrawer::DrawLine(
326  &resultImage,
327  Vec2d({x1, y1}),
328  Vec2d({x2, y2}),
329  static_cast<int>(keypointColors[colorIndex + 2]),
330  static_cast<int>(keypointColors[colorIndex + 1]),
331  static_cast<int>(keypointColors[colorIndex + 0]),
332  thicknessLine);
333  }
334  }
335  }
336 
337  // Draw points
338  for (int i = 0; i < numberKeypoints; i++)
339  {
340  const int index = (person * numberKeypoints + i) * keypoints.getSize(2);
341  float x = keypoints[index + 0];
342  float y = keypoints[index + 1];
343 
344  if (!(x == 0.0f && y == 0.0f) && keypoints[index + 2] > renderThreshold)
345  {
346  unsigned int colorIndex = static_cast<unsigned int>(i * 3);
347 
348  ::PrimitivesDrawer::DrawCircle(&resultImage,
349  x,
350  y,
351  radius,
352  static_cast<int>(keypointColors[colorIndex + 2]),
353  static_cast<int>(keypointColors[colorIndex + 1]),
354  static_cast<int>(keypointColors[colorIndex + 0]),
355  thicknessCircle);
356  }
357  }
358  }
359  }
360 }
armarx::OpenPoseAdapter::OpenPoseSettings::model_pose
std::string model_pose
Definition: OpenPoseAdapter.h:72
TIMING_START
#define TIMING_START(name)
Definition: TimeUtil.h:289
TIMING_END
#define TIMING_END(name)
Definition: TimeUtil.h:306
armarx::OpenPoseAdapter::convert2DKeypointsToIce
HumanPose2DMap convert2DKeypointsToIce(const op::Array< float > &op_keypoints, const CByteImage *rgbImage) const
Definition: OpenPoseAdapter.cpp:152
LocalException.h
visionx
ArmarX headers.
Definition: OpenPoseStressTest.h:38
index
uint8_t index
Definition: EtherCATFrame.h:59
armarx::OpenPoseAdapter::poseExtractorCaffe
std::shared_ptr< op::PoseExtractorCaffe > poseExtractorCaffe
Definition: OpenPoseAdapter.h:140
armarx::OpenPoseAdapter::OpenPoseSettings::scale_number
int scale_number
Definition: OpenPoseAdapter.h:75
armarx::OpenPoseAdapter::render2DResultImage
void render2DResultImage(const CByteImage &inputImage, const op::Array< float > &keypoints, CByteImage &resultImage, float renderThreshold)
Definition: OpenPoseAdapter.cpp:260
armarx::OpenPoseAdapter::OpenPoseSettings::model_folder
std::string model_folder
Definition: OpenPoseAdapter.h:73
ARMARX_CHECK_NOT_NULL
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Definition: ExpressionException.h:206
armarx::OpenPoseAdapter::opOutputToCvMat
std::shared_ptr< op::OpOutputToCvMat > opOutputToCvMat
Definition: OpenPoseAdapter.h:141
c
constexpr T c
Definition: UnscentedKalmanFilterTest.cpp:46
armarx::OpenPoseAdapter::scaleAndSizeExtractor
std::shared_ptr< op::ScaleAndSizeExtractor > scaleAndSizeExtractor
Definition: OpenPoseAdapter.h:137
armarx::OpenPoseAdapter::op_running_mutex
std::mutex op_running_mutex
Definition: OpenPoseAdapter.h:136
armarx::OpenPoseAdapter::ConvertToOPCustomType
static std::string ConvertToOPCustomType(const std::string &x)
Definition: OpenPoseAdapter.h:91
armarx::OpenPoseAdapter::getDominantColorOfPatch
armarx::DrawColor24Bit getDominantColorOfPatch(const CByteImage &image, const Vec2d &point, int windowSize) const
Definition: OpenPoseAdapter.cpp:208
armarx::ctrlutil::a
double a(double t, double a0, double j)
Definition: CtrlUtil.h:45
armarx::OpenPoseAdapter::OpenPoseSettings::scale_gap
double scale_gap
Definition: OpenPoseAdapter.h:74
armarx::OpenPoseAdapter::getOpenposeKeypoints
op::Array< float > getOpenposeKeypoints(const CByteImage *imageBuffer)
Definition: OpenPoseAdapter.cpp:121
armarx::OpenPoseAdapter::cvMatToOpInput
std::shared_ptr< op::CvMatToOpInput > cvMatToOpInput
Definition: OpenPoseAdapter.h:138
armarx::OpenPoseAdapter::OpenPoseSettings
Definition: OpenPoseAdapter.h:68
armarx::OpenPoseAdapter::cvMatToOpOutput
std::shared_ptr< op::CvMatToOpOutput > cvMatToOpOutput
Definition: OpenPoseAdapter.h:139
OpenPoseAdapter.h
armarx::OpenPoseAdapter::OpenPoseSettings::output_resolution
std::string output_resolution
Definition: OpenPoseAdapter.h:71
ARMARX_ERROR
#define ARMARX_ERROR
Definition: Logging.h:196
armarx::OpenPoseAdapter::OpenPoseSettings::minimum_number_of_valid_keypoints_per_entitiy
unsigned int minimum_number_of_valid_keypoints_per_entitiy
Definition: OpenPoseAdapter.h:77
armarx::OpenPoseAdapter::OpenPoseSettings::net_resolution
std::string net_resolution
Definition: OpenPoseAdapter.h:70
armarx::to_string
const std::string & to_string(const std::string &s)
Definition: StringHelpers.h:41
GfxTL::Vec2d
VectorXD< 2, double > Vec2d
Definition: VectorXD.h:736
ExpressionException.h
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:704
armarx::OpenPoseAdapter::poseModel
op::PoseModel poseModel
Definition: OpenPoseAdapter.h:142
ARMARX_CHECK_EXPRESSION
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
Definition: ExpressionException.h:73
armarx::OpenPoseAdapter::OpenPoseSettings::num_gpu_start
int num_gpu_start
Definition: OpenPoseAdapter.h:76
TimeUtil.h
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
ARMARX_INFO_S
#define ARMARX_INFO_S
Definition: Logging.h:202
armarx::ArmarXDataPath::getAbsolutePath
static bool getAbsolutePath(const std::string &relativeFilename, std::string &storeAbsoluteFilename, const std::vector< std::string > &additionalSearchPaths={}, bool verbose=true)
Definition: ArmarXDataPath.cpp:109
T
float T
Definition: UnscentedKalmanFilterTest.cpp:38
armarx::OpenPoseAdapter::settings
OpenPoseSettings settings
Definition: OpenPoseAdapter.h:133
ArmarXDataPath.h
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:27