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/IplImageAdaptor.h>
32 #include <Image/PrimitivesDrawer.h>
33 #include <Image/ImageProcessor.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 positiveIntRound(const T a)
52  {
53  return int(a + 0.5f);
54  }
55 }
56 
57 
58 OpenPoseAdapter::OpenPoseAdapter(const OpenPoseSettings& settings) :
59  settings(settings)
60 {
61  setupOpenPoseEnvironment();
62 }
63 
64 // Initialize all variables to interact with openpose
65 void 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) || (OPENPOSE_VERSION_MAJOR == 1 && OPENPOSE_VERSION_MINOR == 7 && OPENPOSE_VERSION_PATCH > 0)
82  scaleAndSizeExtractor = std::make_shared<op::ScaleAndSizeExtractor>(netInputSize,
83  -1, // unclear what this value should be from openpose documentation
85  #else
86  scaleAndSizeExtractor = std::make_shared<op::ScaleAndSizeExtractor>(netInputSize,
88  #endif
89  cvMatToOpInput = std::make_shared<op::CvMatToOpInput>(poseModel);
90  cvMatToOpOutput.reset(new op::CvMatToOpOutput());
91  opOutputToCvMat.reset(new op::OpOutputToCvMat());
92 
93  std::string modelFolder = settings.model_folder;
94  auto found = ArmarXDataPath::getAbsolutePath(modelFolder, modelFolder, {"/usr/share/OpenPose/", "/usr/local/share/OpenPose/", OPENPOSE_MODELS});
95  if (!found)
96  {
97  ARMARX_ERROR << "Could not find model folder at " << modelFolder;
98  return;
99  }
100  if (!modelFolder.empty() && *modelFolder.rbegin() != '/')
101  {
102  modelFolder += "/";
103  }
104  ARMARX_INFO << "Found model path at: " << modelFolder;
105  poseExtractorCaffe = std::make_shared<op::PoseExtractorCaffe>(poseModel, modelFolder, settings.num_gpu_start);
106  poseExtractorCaffe->initializationOnThread();
107 
108  TIMING_END(setupOpenPose);
109 }
110 
111 // run openpose and return the openpose representation of keypoints
112 op::Array<float> OpenPoseAdapter::getOpenposeKeypoints(const CByteImage* inputImage)
113 {
114  std::lock_guard l(op_running_mutex);
115 
116  // Step 1 - Convert image to cv::Mat
117  IplImage* iplImage = IplImageAdaptor::Adapt(inputImage);
118  cv::Mat opInputImage = cv::cvarrToMat(iplImage);
119  const op::Point<int> imageSize {opInputImage.cols, opInputImage.rows};
120 
121  // Step 2 - Get desired scale sizes
122  std::vector<double> scaleInputToNetInputs;
123  std::vector<op::Point<int>> netInputSizes;
124  double scaleInputToOutput;
125  op::Point<int> outputResolution;
126  std::tie(scaleInputToNetInputs, netInputSizes, scaleInputToOutput, outputResolution) = scaleAndSizeExtractor->extract(imageSize);
127 
128  // Step 3 - Format input image to OpenPose input and output formats
129  auto inputImage_conv = ConvertToOPCustomType(opInputImage);
130  const auto netInputArray = cvMatToOpInput->createArray(inputImage_conv, scaleInputToNetInputs, netInputSizes);
131 
132  // Step 4 - Estimate poseKeypoints
133  poseExtractorCaffe->forwardPass(netInputArray, imageSize, scaleInputToNetInputs);
134  const op::Array<float> poseKeypoints = poseExtractorCaffe->getPoseKeypoints();
135 
136  return poseKeypoints;
137 }
138 
139 // convert the openpose representation of keypoints into our ice version
140 HumanPose2DMap OpenPoseAdapter::convert2DKeypointsToIce(const op::Array<float>& op_keypoints, const CByteImage* rgbImage) const
141 {
142  ARMARX_CHECK_NOT_NULL(rgbImage);
143 
144  HumanPose2DMap ice_entities;
145  const std::map<unsigned int, std::string> poseBodyPartMapping = op::getPoseBodyPartMapping(poseModel);
146 
147  if (op_keypoints.getSize().empty())
148  {
149  return ice_entities;
150  }
151 
152  int entities = op_keypoints.getSize().at(0);
153  int points = op_keypoints.getSize().at(1);
154 
155  ARMARX_CHECK_EXPRESSION(poseBodyPartMapping.size() > 0);
156  ARMARX_CHECK_EXPRESSION(points == static_cast<int>(poseBodyPartMapping.size()) - 1);
157 
158  for (int i = 0; i < entities; i++)
159  {
160  std::string entity_id = std::to_string(i);
161 
162  ice_entities[entity_id] = HumanPose2D();
163  Keypoint2DMap ice_keypoint_map;
164  for (int id = 0; id < points; id++)
165  {
166  Keypoint2D keypoint;
167  float x = op_keypoints.at({i, id, 0});
168  float y = op_keypoints.at({i, id, 1});
169  float c = op_keypoints.at({i, id, 2});
170  if (x == 0.0f && y == 0.0f && c == 0.0f)
171  {
172  continue; // Invalid keypoint from openpose
173  }
174  keypoint.id = id;
175  keypoint.label = poseBodyPartMapping.at(id);
176  keypoint.x = x;
177  keypoint.y = y;
178  keypoint.confidence = c;
179  keypoint.dominantColor = getDominantColorOfPatch(rgbImage, Vec2d {x, y}, rgbImage->width / 50);
180  ice_keypoint_map[keypoint.label] = keypoint;
181  }
182 
183  if (ice_keypoint_map.size() >= settings.minimum_number_of_valid_keypoints_per_entitiy)
184  {
185  ice_entities[entity_id].keypointMap = ice_keypoint_map;
186  }
187  }
188 
189  return ice_entities;
190 }
191 
192 armarx::DrawColor24Bit OpenPoseAdapter::getDominantColorOfPatch(const CByteImage& image, const Vec2d& point, int windowSize) const
193 {
194  if (point.x < 0 || point.y < 0 || point.x >= image.width || point.y >= image.height)
195  return DrawColor24Bit {0, 0, 0}; // Black
196  int divisor = 256 / 3; // split into 3*3*3 bins
197  typedef std::tuple<Ice::Byte, Ice::Byte, Ice::Byte> RGBTuple;
198  std::map<RGBTuple, int> histogram;
199  int halfWindowSize = static_cast<int>(windowSize * 0.5);
200  int left = std::max<int>(0, static_cast<int>(point.x) - halfWindowSize);
201  int top = std::max<int>(0, static_cast<int>(point.y) - halfWindowSize);
202  int right = std::min<int>(image.width, static_cast<int>(point.x) + halfWindowSize);
203  int bottom = std::min<int>(image.height, static_cast<int>(point.y) + halfWindowSize);
204 
205  for (int x = left; x < right; x++)
206  {
207  for (int y = top; y < bottom; y++)
208  {
209  int pixelPos = (y * image.width + x) * 3;
210  auto tuple = std::make_tuple<Ice::Byte, Ice::Byte, Ice::Byte>(static_cast<Ice::Byte>(image.pixels[pixelPos] / divisor),
211  static_cast<Ice::Byte>(image.pixels[pixelPos + 1] / divisor),
212  static_cast<Ice::Byte>(image.pixels[pixelPos + 2] / divisor));
213  if (histogram.count(tuple))
214  {
215  histogram.at(tuple)++;
216  }
217  else
218  {
219  histogram[tuple] = 1;
220  }
221  }
222  }
223 
224  float maxHistogramValue = 0;
225  RGBTuple dominantColor;
226  for (auto& pair : histogram)
227  {
228  if (pair.second > maxHistogramValue)
229  {
230  dominantColor = pair.first;
231  maxHistogramValue = pair.second;
232  }
233  }
234  auto rgb = DrawColor24Bit {static_cast<Ice::Byte>(std::get<0>(dominantColor) * divisor),
235  static_cast<Ice::Byte>(std::get<1>(dominantColor) * divisor),
236  static_cast<Ice::Byte>(std::get<2>(dominantColor) * divisor)};
237  return rgb;
238 }
239 
240 void OpenPoseAdapter::render2DResultImage(const CByteImage& inputImage, const op::Array<float>& keypoints, CByteImage& resultImage, float renderThreshold)
241 {
242  //ARMARX_CHECK_EXPRESSION(inputImage.width == resultImage.width);
243  //ARMARX_CHECK_EXPRESSION(inputImage.height == resultImage.height);
244  resultImage.Set(inputImage.width, inputImage.height, inputImage.type);
245  ::ImageProcessor::CopyImage(&inputImage, &resultImage);
246 
247  if (keypoints.getSize().empty())
248  {
249  return;
250  }
251 
252  const std::vector<unsigned int>& posePartPairs = op::getPoseBodyPartPairsRender(poseModel);
253 
254  // Get colors for points and lines from openpose
255  std::vector<float> keypointColors = op::getPoseColors(poseModel);
256 
257  const auto thicknessCircleRatio = 1.f / 75.f;
258  const auto thicknessLineRatioWRTCircle = 0.75f;
259  const auto area = inputImage.width * inputImage.height;
260  const int numberKeypoints = keypoints.getSize(1);
261 
262  for (int person = 0 ; person < keypoints.getSize(0) ; person++)
263  {
264  const auto personRectangle = op::getKeypointsRectangle(keypoints, person, 0.1f);
265  if (personRectangle.area() > 0)
266  {
267  // define size-dependent variables
268  const auto ratioAreas = op::fastMin(1.f, op::fastMax(personRectangle.width / static_cast<float>(inputImage.width),
269  personRectangle.height / static_cast<float>(inputImage.height)));
270  const auto thicknessRatio = op::fastMax(positiveIntRound<float>(static_cast<float>(std::sqrt(area))
271  * thicknessCircleRatio * ratioAreas), 2);
272  // Negative thickness in ivt::drawCircle means that a filled circle is to be drawn.
273  const auto thicknessCircle = op::fastMax(1, (ratioAreas > 0.05f ? thicknessRatio : -1));
274  const auto thicknessLine = op::fastMax(1, positiveIntRound(thicknessRatio * thicknessLineRatioWRTCircle));
275  const auto radius = thicknessRatio / 2;
276 
277  // Draw Lines
278  for (unsigned int i = 0; i < posePartPairs.size(); i = i + 2)
279  {
280  const int index1 = (person * numberKeypoints + static_cast<int>(posePartPairs[i])) * keypoints.getSize(2);
281  const int index2 = (person * numberKeypoints + static_cast<int>(posePartPairs[i + 1])) * keypoints.getSize(2);
282 
283  float x1 = keypoints[index1 + 0];
284  float y1 = keypoints[index1 + 1];
285  float x2 = keypoints[index2 + 0];
286  float y2 = keypoints[index2 + 1];
287 
288  if (!(x1 == 0.0f && y1 == 0.0f) && !(x2 == 0.0f && y2 == 0.0f))
289  {
290  if (keypoints[index1 + 2] > renderThreshold && keypoints[index2 + 2] > renderThreshold)
291  {
292  unsigned int colorIndex = posePartPairs[i + 1] * 3;
293  ::PrimitivesDrawer::DrawLine(&resultImage,
294  Vec2d({x1, y1}),
295  Vec2d({x2, y2}),
296  static_cast<int>(keypointColors[colorIndex + 2]),
297  static_cast<int>(keypointColors[colorIndex + 1]),
298  static_cast<int>(keypointColors[colorIndex + 0]),
299  thicknessLine);
300  }
301  }
302  }
303 
304  // Draw points
305  for (int i = 0; i < numberKeypoints; i++)
306  {
307  const int index = (person * numberKeypoints + i) * keypoints.getSize(2);
308  float x = keypoints[index + 0];
309  float y = keypoints[index + 1];
310 
311  if (!(x == 0.0f && y == 0.0f) && keypoints[index + 2] > renderThreshold)
312  {
313  unsigned int colorIndex = static_cast<unsigned int>(i * 3);
314 
315  ::PrimitivesDrawer::DrawCircle(&resultImage,
316  x,
317  y,
318  radius,
319  static_cast<int>(keypointColors[colorIndex + 2]),
320  static_cast<int>(keypointColors[colorIndex + 1]),
321  static_cast<int>(keypointColors[colorIndex + 0]),
322  thicknessCircle);
323  }
324  }
325  }
326  }
327 }
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:662
armarx::OpenPoseAdapter::OpenPoseSettings::model_pose
std::string model_pose
Definition: OpenPoseAdapter.h:70
TIMING_START
#define TIMING_START(name)
Definition: TimeUtil.h:280
TIMING_END
#define TIMING_END(name)
Definition: TimeUtil.h:296
armarx::OpenPoseAdapter::convert2DKeypointsToIce
HumanPose2DMap convert2DKeypointsToIce(const op::Array< float > &op_keypoints, const CByteImage *rgbImage) const
Definition: OpenPoseAdapter.cpp:140
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:129
armarx::OpenPoseAdapter::OpenPoseSettings::scale_number
int scale_number
Definition: OpenPoseAdapter.h:73
armarx::OpenPoseAdapter::render2DResultImage
void render2DResultImage(const CByteImage &inputImage, const op::Array< float > &keypoints, CByteImage &resultImage, float renderThreshold)
Definition: OpenPoseAdapter.cpp:240
armarx::OpenPoseAdapter::OpenPoseSettings::model_folder
std::string model_folder
Definition: OpenPoseAdapter.h:71
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
GfxTL::Vec2d
VectorXD< 2, double > Vec2d
Definition: VectorXD.h:694
armarx::OpenPoseAdapter::opOutputToCvMat
std::shared_ptr< op::OpOutputToCvMat > opOutputToCvMat
Definition: OpenPoseAdapter.h:130
c
constexpr T c
Definition: UnscentedKalmanFilterTest.cpp:43
armarx::OpenPoseAdapter::scaleAndSizeExtractor
std::shared_ptr< op::ScaleAndSizeExtractor > scaleAndSizeExtractor
Definition: OpenPoseAdapter.h:126
armarx::OpenPoseAdapter::op_running_mutex
std::mutex op_running_mutex
Definition: OpenPoseAdapter.h:125
armarx::OpenPoseAdapter::ConvertToOPCustomType
static std::string ConvertToOPCustomType(const std::string &x)
Definition: OpenPoseAdapter.h:87
armarx::OpenPoseAdapter::getDominantColorOfPatch
armarx::DrawColor24Bit getDominantColorOfPatch(const CByteImage &image, const Vec2d &point, int windowSize) const
Definition: OpenPoseAdapter.cpp:192
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:72
armarx::OpenPoseAdapter::getOpenposeKeypoints
op::Array< float > getOpenposeKeypoints(const CByteImage *imageBuffer)
Definition: OpenPoseAdapter.cpp:112
armarx::OpenPoseAdapter::cvMatToOpInput
std::shared_ptr< op::CvMatToOpInput > cvMatToOpInput
Definition: OpenPoseAdapter.h:127
armarx::OpenPoseAdapter::OpenPoseSettings
Definition: OpenPoseAdapter.h:66
armarx::OpenPoseAdapter::cvMatToOpOutput
std::shared_ptr< op::CvMatToOpOutput > cvMatToOpOutput
Definition: OpenPoseAdapter.h:128
OpenPoseAdapter.h
armarx::OpenPoseAdapter::OpenPoseSettings::output_resolution
std::string output_resolution
Definition: OpenPoseAdapter.h:69
ARMARX_ERROR
#define ARMARX_ERROR
Definition: Logging.h:189
armarx::OpenPoseAdapter::OpenPoseSettings::minimum_number_of_valid_keypoints_per_entitiy
unsigned int minimum_number_of_valid_keypoints_per_entitiy
Definition: OpenPoseAdapter.h:75
armarx::OpenPoseAdapter::OpenPoseSettings::net_resolution
std::string net_resolution
Definition: OpenPoseAdapter.h:68
armarx::to_string
const std::string & to_string(const std::string &s)
Definition: StringHelpers.h:40
ExpressionException.h
armarx::OpenPoseAdapter::poseModel
op::PoseModel poseModel
Definition: OpenPoseAdapter.h:131
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:74
TimeUtil.h
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:174
ARMARX_INFO_S
#define ARMARX_INFO_S
Definition: Logging.h:195
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:111
T
float T
Definition: UnscentedKalmanFilterTest.cpp:35
armarx::OpenPoseAdapter::settings
OpenPoseSettings settings
Definition: OpenPoseAdapter.h:122
ArmarXDataPath.h
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28