RGBDOpenPoseEstimationComponent.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 armarx::ArmarXObjects::OpenPoseEstimation
17 * @author Stefan Reither ( stef dot reither at web dot de )
18 * @date 2018
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <SimoxUtility/algorithm/string.h>
26
28
29#include <VisionX/interface/components/RGBDImageProvider.h>
32//#include <VisionX/libraries/ArViz/HumanPoseBody25.h>
33
34using namespace armarx;
35
36void
39{
41
42 def->topic(listener3DPrx, "OpenPoseEstimation3D", "OpenPoseEstimation3DTopicName");
43 def->component(robotStateInterface, "RobotStateComponent");
44
45 def->optional(useDistortionParameters,
46 "UseDistortionParameters",
47 "Whether to use distortion parameters when transforming image coordinates into "
48 "world coordinates");
49
50 def->optional(radius, "DepthMedianRadius", "Depth Median Radius");
51 def->optional(
53 "MaxDepth",
54 "Pixels with a distance higher than this value are masked out. Only for depth camera mode.",
56 def->optional(maxDepthDifference,
57 "MaxDepthDifference",
58 "Allowed difference of depth value for one keypoint to median of all keypoints.",
60 def->optional(cameraNodeName, "CameraNodeName", "The robot node name of the camera");
61
62 def->optional(brightnessIncrease, "BrightnessIncrease", "Increase brightness of masked pixels");
63}
64
65void
67{
69
70 // Trying to access robotStateComponent if it is avaiable
72
73 ARMARX_VERBOSE << "Trying to get StereoCalibrationInterface proxy: "
74 << getIceManager()->getCommunicator()->proxyToString(imageProviderInfo.proxy);
75 visionx::StereoCalibrationInterfacePrx calib =
76 visionx::StereoCalibrationInterfacePrx::checkedCast(imageProviderInfo.proxy);
77 if (!calib)
78 {
79 ARMARX_ERROR << "Image provider does not provide a stereo calibration - 3D will not work - "
80 << imageProviderInfo.proxy->ice_ids();
81 }
82 else
83 {
84 ARMARX_VERBOSE << "got StereoCalibrationInterface proxy";
85 CStereoCalibration* stereoCalibration =
86 visionx::tools::convert(calib->getStereoCalibration());
87 ARMARX_VERBOSE << "got StereoCalibration";
88 calibration = stereoCalibration->GetLeftCalibration();
89 ARMARX_VERBOSE << "got mono Calibration";
90 }
91
92
94 {
96 }
97}
98
99void
107
108void
113
114void
119
120void
125
129
130void
132{
133 Base::renderOutputImage(op_array);
134
135 if (!localRobot || !running3D)
136 {
138 << "Could not render output image for 3d pose estimation";
139 return;
140 }
141
142 std::unique_lock depthImage_lock(depthImageBufferMutex);
143
144 maskOutputImageBasedOnDepth();
147
148 const int depthThreshold = maxDepthDifference;
149 openposeResult3D.clear();
150
151 for (const auto& [name, entity] : openposeResult)
152 {
153 if (entity.keypointMap.size() == 0)
154 {
155 continue;
156 }
157
158 std::map<std::string, int>
159 depthStorage; // we do not want to store perspective depth-values in a Keypoint, we transfer them into world-coordiantes (in cameraframe) anyways
160 std::vector<int> depthsCopy;
161
162 // Get camera depth information from image coordinates
163 for (const auto& [kp_name, point] : entity.keypointMap)
164 {
165 int depth = getMedianDepthFromImage(
166 static_cast<int>(point.x), static_cast<int>(point.y), radius);
167 depthStorage[kp_name] = depth;
168 depthsCopy.push_back(depth);
169 }
170
171 // Find outlier in depth values and set them to median of depth values
172 std::sort(depthsCopy.begin(), depthsCopy.end());
173 const int medianDepth = depthsCopy.at(depthsCopy.size() / 2);
174 for (auto& [storage_name, depth] : depthStorage)
175 {
176 if (depth > medianDepth + depthThreshold || depth < medianDepth - depthThreshold)
177 {
178 depth = medianDepth;
179 }
180 }
181
182 // Transform pixel + depth into world coordinates
183 // and update stored 3d entity
184 openposeResult3D[name] = HumanPose3D();
185 for (const auto& [kp_name, point] : entity.keypointMap)
186 {
187 Vec2d imagePoint;
188 imagePoint.x = point.x;
189 imagePoint.y = point.y;
190 Vec3d result;
192 calibration->ImageToWorldCoordinates(imagePoint,
193 result,
194 static_cast<float>(depthStorage.at(kp_name)),
196
197 FramedPosition pos = FramedPosition(Eigen::Vector3f(result.x, result.y, result.z),
199 localRobot->getName());
200 auto globalEigen = pos.toGlobalEigen(localRobot);
201 openposeResult3D[name].keypointMap[kp_name].x = result.x;
202 openposeResult3D[name].keypointMap[kp_name].y = result.y;
203 openposeResult3D[name].keypointMap[kp_name].z = result.z;
204
205 openposeResult3D[name].keypointMap[kp_name].globalX = globalEigen(0);
206 openposeResult3D[name].keypointMap[kp_name].globalY = globalEigen(1);
207 openposeResult3D[name].keypointMap[kp_name].globalZ = globalEigen(2);
208
209 openposeResult3D[name].keypointMap[kp_name].label = point.label;
210 openposeResult3D[name].keypointMap[kp_name].confidence = point.confidence;
211 openposeResult3D[name].keypointMap[kp_name].dominantColor = point.dominantColor;
212 }
213 }
214
215 filterToNearest();
216}
217
218void
220{
222 ARMARX_DEBUG << deactivateSpam(0.5) << "Reporting 3Dkeypoints for " << openposeResult3D.size()
223 << " entities";
225}
226
227int
228RGBDOpenPoseEstimationComponentPluginUser::getMedianDepthFromImage(int x, int y, int radius) const
229{
230 std::vector<int> depths;
231 for (int xoffset = -radius; xoffset < radius; xoffset++)
232 {
233 int xo = x + xoffset;
234 if (xo < 0 || xo > depthImageBuffer->width)
235 {
236 continue;
237 }
238 for (int yoffset = -radius; yoffset < radius; yoffset++)
239 {
240 int yo = y + yoffset;
241 if (yo < 0 || yo > depthImageBuffer->height)
242 {
243 continue;
244 }
245
246 // Check whether (x,y) is in circle:
247 if (xoffset * xoffset + yoffset * yoffset <= radius * radius)
248 {
249 unsigned int pixelPos =
250 static_cast<unsigned int>(3 * (yo * depthImageBuffer->width + xo));
251 int z_value = depthImageBuffer->pixels[pixelPos + 0] +
252 (depthImageBuffer->pixels[pixelPos + 1] << 8) +
253 (depthImageBuffer->pixels[pixelPos + 2] << 16);
254 if (z_value > 0)
255 {
256 depths.push_back(z_value);
257 }
258 }
259 }
260 }
261 std::sort(depths.begin(), depths.end());
262
263 return depths.empty() ? 0 : depths[depths.size() / 2];
264}
265
266void
267RGBDOpenPoseEstimationComponentPluginUser::maskOutputImageBasedOnDepth()
268{
269 if (maxDepth <= 0)
270 {
271 return;
272 }
273
276
278
279 int pixelCount = depthImageBuffer->width * depthImageBuffer->height;
280 int depthThresholdmm = maxDepth;
281 CByteImage depthMaskImage(
282 depthImageBuffer->width, depthImageBuffer->height, CByteImage::eGrayScale);
283 CByteImage brightenMaskImage(
284 openposeResultImage[0]->width, openposeResultImage[0]->height, CByteImage::eGrayScale);
285 ::ImageProcessor::Zero(&depthMaskImage);
286 ::ImageProcessor::Zero(&brightenMaskImage);
287
288 for (int i = 0; i < pixelCount; i += 1)
289 {
290 int z_value = depthImageBuffer->pixels[i * 3 + 0] +
291 (depthImageBuffer->pixels[i * 3 + 1] << 8) +
292 (depthImageBuffer->pixels[i * 3 + 2] << 16);
293 depthMaskImage.pixels[i] = z_value > depthThresholdmm || z_value == 0 ? 0 : 255;
294 }
295
296 ::ImageProcessor::Erode(&depthMaskImage, &depthMaskImage, 5);
297 ::ImageProcessor::Dilate(&depthMaskImage, &depthMaskImage, 20);
298
299 for (int i = 0; i < pixelCount; i += 1)
300 {
301 if (depthMaskImage.pixels[i] == 0)
302 {
303 // set to green
304 openposeResultImage[0]->pixels[i * 3] = 0;
305 openposeResultImage[0]->pixels[i * 3 + 1] = 255;
306 openposeResultImage[0]->pixels[i * 3 + 2] = 0;
307
308 // add brightness to mask
309 brightenMaskImage.pixels[i] = 255;
310 }
311 }
312
313 // brighten if necessary
314 if (brightnessIncrease > 0)
315 {
316 CByteImage smoothedImageMask(&brightenMaskImage);
317 ::ImageProcessor::GaussianSmooth5x5(&brightenMaskImage, &smoothedImageMask);
318
319 for (int i = 0; i < pixelCount; i += 1)
320 {
321 if (depthMaskImage.pixels[i] == 0)
322 {
323 float perc = static_cast<float>(smoothedImageMask.pixels[i]) / 255.f;
324 int effectiveBrightnessIncrease = brightnessIncrease * perc;
325 openposeResultImage[0]->pixels[i * 3] = std::min<int>(
326 openposeResultImage[0]->pixels[i * 3] + effectiveBrightnessIncrease, 255);
327 openposeResultImage[0]->pixels[i * 3 + 1] = std::min<int>(
328 openposeResultImage[0]->pixels[i * 3 + 1] + effectiveBrightnessIncrease, 255);
329 openposeResultImage[0]->pixels[i * 3 + 2] = std::min<int>(
330 openposeResultImage[0]->pixels[i * 3 + 2] + effectiveBrightnessIncrease, 255);
331 }
332 }
333 }
334}
335
336void
342
343void
345{
346 if (running3D)
347 {
348 return;
349 }
350 else
351 {
352 ARMARX_INFO << "Starting OpenposeEstimation -- 3D";
353 running3D = true;
354 }
355}
356
357void
359{
360 start();
361 if (running3D)
362 {
363 ARMARX_INFO << "Stopping OpenposeEstimation -- 3D";
364 running3D = false;
365 }
366}
367
368void
370 const EnableHumanPoseEstimationInput& input,
371 const Ice::Current&)
372{
373 running3D = input.enable3d;
375}
376
377/*void OpenPoseEstimationComponent::filterEntitiesBasedOnWorkspacePolygon()
378{
379 // polygon was: -5000,-5000;5000,-5000;5000,5000;-5000,5000;-5000,-5000
380 // Setup workspace-polygon
381 std::vector<Polygon2D::Point> points;
382 std::vector<std::string> pointStrings = simox::alg::split(pluginUser.workspacePolygonString, ";");
383 for (auto s : pointStrings)
384 {
385 ARMARX_VERBOSE << "WorkspacePolygon: " << s;
386 std::vector<std::string> workSpacePolygonPoint = simox::alg::split(s, ",");
387 ARMARX_CHECK_EXPRESSION(workSpacePolygonPoint.size() == 2);
388 Polygon2D::Point point;
389 point.x = std::strtof(workSpacePolygonPoint.at(0).c_str(), nullptr);
390 point.y = std::strtof(workSpacePolygonPoint.at(1).c_str(), nullptr);
391 points.push_back(point);
392 }
393 pluginUser.workspacePolygon = Polygon2D(points);
394
395 if (!localRobot || !running3D_is_possible || !running3D)
396 {
397 return;
398 }
399
400 Entity3DMap::iterator iter = openposeResult3D.begin();
401 while (iter != openposeResult3D.end())
402 {
403 const std::string& name = iter->first;
404 const Entity3D& entity = iter->second;
405
406 for(const auto& [kp_name, point] : entity.keypointMap)
407 {
408 FramedPositionPtr pos = point->get3D()->toGlobal(localRobot); // Changing frame of copy
409 if (!workspacePolygon.isInside(pos))
410 {
411 ARMARX_VERBOSE << deactivateSpam(1) << "removing entity due out of workspace";
412 iter = openposeResult3D.erase(iter);
413 }
414 else
415 {
416 iter++;
417 }
418 }
419 }
420}*/
421
422void
423RGBDOpenPoseEstimationComponentPluginUser::filterToNearest()
424{
425 if (!reportOnlyNearestPerson || openposeResult3D.size() == 0)
426 {
427 return;
428 }
429
430 std::vector<std::pair<std::string, HumanPose3D>> poses;
431 for (const auto& [key, humanPose] : openposeResult3D)
432 {
433 poses.push_back({key, humanPose});
434 }
435
436 std::sort(poses.begin(),
437 poses.end(),
438 [](std::pair<std::string, HumanPose3D> o1, std::pair<std::string, HumanPose3D> o2)
439 {
440 auto humanPose1 = o1.second;
441 auto humanPose2 = o2.second;
442 float humanPose1AverageDepth = std::numeric_limits<float>::quiet_NaN();
443 float humanPose2AverageDepth = std::numeric_limits<float>::quiet_NaN();
444 if (humanPose1.keypointMap.size() == 0)
445 {
446 float result = 0.0f;
447 int amountDepths = 0;
448 for (const auto& [k, point] : humanPose1.keypointMap)
449 {
450 result += point.z;
451 amountDepths++;
452 }
453 humanPose1AverageDepth = result / static_cast<float>(amountDepths);
454 }
455
456 if (humanPose2.keypointMap.size() == 0)
457 {
458 float result = 0.0f;
459 int amountDepths = 0;
460 for (const auto& [k, point] : humanPose2.keypointMap)
461 {
462 result += point.z;
463 amountDepths++;
464 }
465 humanPose2AverageDepth = result / static_cast<float>(amountDepths);
466 }
467
468 return humanPose1AverageDepth < humanPose2AverageDepth;
469 });
470 poses.resize(1);
471
472 openposeResult3D.clear();
473 for (const auto& p : poses)
474 {
475 openposeResult3D[p.first] = p.second;
476 }
477}
478
479void
481{
482 // also show in ArViz
483 viz::Layer openPoseArVizLayer = arviz.layer(layerName);
484 if (openposeResult3D.empty())
485 {
486 arviz.commit(openPoseArVizLayer);
487
488 return;
489 }
490
491 int human = 1;
492 for (const auto& [name, humanPose] : openposeResult3D)
493 {
494 std::string objectName = "human_" + name;
495 //armarx::viz::HumanPoseBody25::addPoseToLayer(humanPose.keypointMap, openPoseArVizLayer, objectName);
496 human++;
497 }
498
499 arviz.commit(openPoseArVizLayer);
500 openposeResult3D.clear(); // its the last step
502}
if(!yyvaluep)
Definition Grammar.cpp:645
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
constexpr T c
The FramedPosition class.
Definition FramedPose.h:158
Eigen::Vector3f toGlobalEigen(const SharedRobotInterfacePrx &referenceRobot) const
IceManagerPtr getIceManager() const
Returns the IceManager.
void stop(const Ice::Current &=Ice::emptyCurrent) override
void start(const Ice::Current &=Ice::emptyCurrent) override
virtual void postCreatePropertyDefinitions(PropertyDefinitionsPtr &properties)
void enableHumanPoseEstimation(const EnableHumanPoseEstimationInput &input, const Ice::Current &=Ice::emptyCurrent) override
virtual void renderOutputImage(const op::Array< float > &)
void start3DPoseEstimation(const Ice::Current &=Ice::emptyCurrent) override
void stop(const Ice::Current &=Ice::emptyCurrent) override
void stop3DPoseEstimation(const Ice::Current &=Ice::emptyCurrent) override
void postCreatePropertyDefinitions(PropertyDefinitionsPtr &properties) override
void enableHumanPoseEstimation(const EnableHumanPoseEstimationInput &input, const Ice::Current &) override
VirtualRobot::RobotPtr createLocalClone()
Clones the structure of this remote robot to a local instance.
static bool synchronizeLocalCloneToTimestamp(VirtualRobot::RobotPtr robot, RobotStateComponentInterfacePrx robotStatePrx, Ice::Long timestamp)
Synchronizes a local robot to a robot state at timestamp.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#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...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
CByteImage::ImageType convert(const ImageType visionxImageType)
Converts a VisionX image type into an image type of IVT's ByteImage.