SegmentableTemplateRecognition.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::Component
17* @author Kai Welke <welke at kit dot edu>
18* @copyright 2013 Humanoids Group, HIS, KIT
19* @license http://www.gnu.org/licenses/gpl-2.0.txt
20* GNU General Public License
21*/
22
24
25#include <fstream>
26
27// Core
29
30// RobotState
33
34// IVT
35#include <Helpers/helpers.h>
36#include <Image/ByteImage.h>
37#include <Image/ImageProcessor.h>
38#include <Image/PrimitivesDrawer.h>
39#include <Math/FloatMatrix.h>
40
41// MemoryX
43
46
47// This file must be included after all Ice-related headers when Ice 3.5.x is used (e.g. with Ubuntu 14.04 or Debian 7.0)!
48// GLContext.h indirectly includes X.h which does "#define None", colliding with IceUtil::None.
49#include <boost/scope_exit.hpp>
50
51#include <SimoxUtility/algorithm/string/string_tools.h>
52
53#include <gui/GLContext.h>
54
55#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
56
57using namespace armarx;
58using namespace visionx;
59using namespace memoryx::EntityWrappers;
60
61namespace
62{
63
64 struct HSVSegmentationParams
65 {
66 int hueValue;
67 int hueTolerance;
68 int saturationMin;
69 int saturationMax;
70 int valueMin;
71 int valueMax;
72
73 float minRatio = 0.0f;
74 };
75
76 HSVSegmentationParams
77 getSegmentationParamsFromColor(CColorParameterSet const& colorParameters, ObjectColor color)
78 {
79 HSVSegmentationParams result = {};
80 int const* colorArray = colorParameters.GetColorParameters(color);
81 if (colorArray)
82 {
83 result.hueValue = colorArray[0];
84 result.hueTolerance = colorArray[1];
85 result.saturationMin = colorArray[2];
86 result.saturationMax = colorArray[3];
87 result.valueMin = colorArray[4];
88 result.valueMax = colorArray[5];
89 }
90 else
91 {
92 ARMARX_WARNING << "Unknown color: " << color;
93 }
94 return result;
95 }
96
97 struct BlobDetector
98 {
99 BlobDetector(int width, int height) :
100 segmented(width, height, CByteImage::eGrayScale),
101 temp(width, height, CByteImage::eGrayScale),
102 hsv(width, height, CByteImage::eRGB24)
103 {
104 }
105
106 BlobDetector(BlobDetector const&) = delete;
107 BlobDetector& operator=(BlobDetector const&) = delete;
108
109 Object2DList
110 findColoredRegions(CByteImage* inputImage,
111 int minPixelsPerRegion,
112 std::vector<HSVSegmentationParams>& segmentationParams,
113 int dilationCount = 2)
114 {
115 ::ImageProcessor::CalculateHSVImage(inputImage, &hsv);
116
117 ::ImageProcessor::Zero(&segmented);
118 for (HSVSegmentationParams const& segParam : segmentationParams)
119 {
120 ::ImageProcessor::FilterHSV(&hsv,
121 &temp,
122 segParam.hueValue,
123 segParam.hueTolerance,
124 segParam.saturationMin,
125 segParam.saturationMax,
126 segParam.valueMin,
127 segParam.valueMax);
128 ::ImageProcessor::Or(&segmented, &temp, &segmented);
129 }
130
131 for (int i = 0; i < dilationCount / 2; ++i)
132 {
133 ::ImageProcessor::Erode(&segmented, &temp);
134 ::ImageProcessor::Dilate(&temp, &segmented);
135 }
136 for (int i = 0; i < dilationCount / 2; ++i)
137 {
138 ::ImageProcessor::Dilate(&segmented, &temp);
139 ::ImageProcessor::Erode(&temp, &segmented);
140 }
141 if (dilationCount == 0)
142 {
143 ::ImageProcessor::Dilate(&segmented, &temp);
144 ::ImageProcessor::Dilate(&temp, &segmented);
145 ::ImageProcessor::Erode(&segmented, &temp);
146 ::ImageProcessor::CopyImage(&temp, &segmented);
147 }
148
149 RegionList regions;
150 ::ImageProcessor::FindRegions(&segmented, regions, minPixelsPerRegion);
151
152 Object2DList objects;
153 objects.reserve(regions.size());
154 int objectId = 0;
155 for (MyRegion const& region : regions)
156 {
157 Object2DEntry object;
158 //object.color = color; // FIXME: We have changed to HSV seg parameters
159 object.region = region;
160
161 for (HSVSegmentationParams& seg : segmentationParams)
162 {
163 if (seg.minRatio > 0.0f)
164 {
165 int count = 0;
166
167 int min_hue = (int)seg.hueValue - (int)seg.hueTolerance;
168 int max_hue = (int)seg.hueValue + (int)seg.hueTolerance;
169
170 if (min_hue < 0)
171 {
172 min_hue += 180;
173 }
174
175 if (max_hue >= 180)
176 {
177 max_hue -= 180;
178 }
179
180 for (int y = region.min_y; y < region.max_y; ++y)
181 {
182 for (int x = region.min_x; x < region.max_x; ++x)
183 {
184 int pixelIndex = y * hsv.width + x;
185 int h = hsv.pixels[pixelIndex * 3 + 0];
186 int s = hsv.pixels[pixelIndex * 3 + 1];
187 int v = hsv.pixels[pixelIndex * 3 + 2];
188 if (seg.saturationMin <= s && s <= seg.saturationMax &&
189 seg.valueMin <= v && v <= seg.valueMax)
190 {
191 if (max_hue >= min_hue)
192 {
193 if (min_hue <= h && h <= max_hue)
194 {
195 ++count;
196 }
197 }
198 else
199 {
200 if (h <= max_hue || h >= min_hue)
201 {
202 ++count;
203 }
204 }
205 }
206 }
207 }
208
209 float ratio = float(count) / region.nPixels;
210 if (ratio < seg.minRatio)
211 {
212 ARMARX_VERBOSE << "Discarding region because ratio too small: "
213 << ratio;
214 continue;
215 }
216 }
217 }
218
219 object.type = eCompactObject;
220 object.sName = "CompactObject";
221 object.id = objectId++;
222
223 objects.push_back(object);
224 }
225 return objects;
226 }
227
228 CByteImage segmented;
229 CByteImage temp;
230 CByteImage hsv;
231 };
232
233 void
234 CropImageToTemplate(CByteImage* inputImage, MyRegion const& region, CByteImage* outputImage)
235 {
236 int width = region.max_x - region.min_x + 1;
237 int height = region.max_y - region.min_y + 1;
238 int k = outputImage->width;
239
240
241 if (width >= height)
242 {
243 int new_height = int((k * height) / float(width) + 0.5f);
244 CByteImage temp_image(k, new_height, CByteImage::eGrayScale);
245
246 ::ImageProcessor::Resize(inputImage, &temp_image, &region);
247
248 const int nPixels = k * new_height;
249 unsigned char* output = outputImage->pixels;
250
251 memcpy(output, temp_image.pixels, nPixels);
252
253 const int nTotalPixels = k * k;
254
255 for (int i = nPixels; i < nTotalPixels; i++)
256 {
257 output[i] = 0;
258 }
259 }
260 else
261 {
262 int new_width = int((k * width) / float(height) + 0.5f);
263 CByteImage temp_image(new_width, k, CByteImage::eGrayScale);
264
265 ::ImageProcessor::Resize(inputImage, &temp_image, &region);
266
267 const unsigned char* input = temp_image.pixels;
268 unsigned char* output = outputImage->pixels;
269
270 for (int i = 0, offset = 0, offset2 = 0; i < k; i++)
271 {
272 int j;
273
274 for (j = 0; j < new_width; j++, offset++, offset2++)
275 {
276 output[offset] = input[offset2];
277 }
278
279 for (j = new_width; j < k; j++, offset++)
280 {
281 output[offset] = 0;
282 }
283 }
284 }
285 }
286
287} // namespace
288
292
296
297void
299{
300 offeringTopic(getProperty<std::string>("DebugObserverName").getValue());
301
302 paramMinPixelsPerRegion = getProperty<int>("MinPixelsPerRegion").getValue();
303}
304
305void
309
310std::vector<std::string>
311getFileList(const std::string& path)
312{
313 std::vector<std::string> result;
314 if (!path.empty())
315 {
316 namespace fs = std::filesystem;
317
318 fs::path apk_path(path);
319 fs::recursive_directory_iterator end;
320
321 for (fs::recursive_directory_iterator i(apk_path); i != end; ++i)
322 {
323 const fs::path cp = (*i);
324 result.push_back(cp.string());
325 }
326 }
327 return result;
328}
329
330bool
332{
333 ARMARX_VERBOSE << "Initializing SegmentableTemplateRecognition";
334
335 Eigen::Vector3f minPoint = getProperty<Eigen::Vector3f>("MinPoint").getValue();
336 Eigen::Vector3f maxPoint = getProperty<Eigen::Vector3f>("MaxPoint").getValue();
337
338 Math3d::SetVec(validResultBoundingBoxMin, minPoint(0), minPoint(1), minPoint(2));
339 Math3d::SetVec(validResultBoundingBoxMax, maxPoint(0), maxPoint(1), maxPoint(2));
340
341 maxEpipolarDistance = getProperty<float>("MaxEpipolarDistance").getValue();
342 std::string colorParemeterFilename = getProperty<std::string>("ColorParameterFile").getValue();
343
344 paramTemplateMatchThreshold = getProperty<float>("TemplateMatchThreshold");
345 paramSizeRatioThreshold = getProperty<float>("SizeRatioThreshold");
346 paramCorrelationThreshold = getProperty<float>("CorrelationThreshold");
347 paramSingleInstance = getProperty<bool>("SingleInstance");
348
349 if (!ArmarXDataPath::getAbsolutePath(colorParemeterFilename, colorParemeterFilename))
350 {
351 ARMARX_ERROR << "Could not find color parameter file in ArmarXDataPath: "
352 << colorParemeterFilename;
353 }
354
355 templatePath = getProperty<std::string>("TemplatePath").getValue();
356 if (!ArmarXDataPath::getAbsolutePath(templatePath, templatePath))
357 {
358 ARMARX_ERROR << "Could not find template path file in ArmarXDataPath: " << templatePath;
359 }
360
361 ARMARX_IMPORTANT << "Looking in template path: " << templatePath;
362 auto templateFiles = getFileList(templatePath);
363 for (std::string const& templateFile : templateFiles)
364 {
365 if (!simox::alg::ends_with(templateFile, ".seg"))
366 {
367 continue;
368 }
369
371
372 ARMARX_INFO << "Loading template: " << templateFile;
373
374 FILE* file = fopen(templateFile.c_str(), "rb");
375 BOOST_SCOPE_EXIT((file))
376 {
377 fclose(file);
378 }
379 BOOST_SCOPE_EXIT_END;
380
381 fseek(file, 0L, SEEK_END);
382 long fileSize = ftell(file);
383 fseek(file, 0L, SEEK_SET);
384
385 void* memory = operator new(fileSize);
386 entry.data.reset(new (memory) SegmentableTemplateHeader);
387 if (fileSize < (long)sizeof(SegmentableTemplateHeader))
388 {
389 ARMARX_WARNING << "File is too short for template header: " << templateFile
390 << " (expected size: " << sizeof(SegmentableTemplateHeader)
391 << ", but got size: " << fileSize << ")";
392 continue;
393 }
394
395 long read = fread(memory, 1, fileSize, file);
396 if (read != fileSize)
397 {
398 ARMARX_WARNING << "Could not read template file completely (expected size: " << fileSize
399 << ", but read: " << read << ")";
400 continue;
401 }
402
403 std::string objectName = entry.data->name;
404 {
405 // Load model
406 std::string modelFilename = templatePath + "/" + objectName + ".mat";
407 entry.model.reset(new CFloatMatrix());
408 if (!entry.model->LoadFromFile(modelFilename.c_str()))
409 {
410 ARMARX_WARNING << "Could not load model file: " << modelFilename;
411 continue;
412 }
413 }
414 std::string colorString;
415 {
416 // Load color
417 std::string colorFilename = templatePath + "/" + objectName + ".color";
418 std::ifstream input(colorFilename.c_str());
419 if (!(input >> colorString))
420 {
421 ARMARX_WARNING << "Could not load color file: " << colorFilename;
422 continue;
423 }
424 ObjectColor color = CColorParameterSet::Translate(colorString.c_str());
425 if (color == eNone)
426 {
427 ARMARX_WARNING << "Unknown color string: " << colorString;
428 continue;
429 }
430 entry.color = color;
431 }
432
433 ARMARX_INFO << "Inserting template into database: " << objectName
434 << ", template count: " << entry.data->templateCount
435 << ", color: " << colorString << " (" << entry.color << ")";
436 database.emplace(objectName, std::move(entry));
437 }
438
439 if (!colorParameters.LoadFromFile(colorParemeterFilename.c_str()))
440 {
441 throw armarx::LocalException("Could not read color parameter file.");
442 }
443
444 // create context
445 ImageFormatInfo imageFormat = getImageFormat();
446 contextGL.reset(new CGLContext());
447 contextGL->CreateContext(imageFormat.dimension.width, imageFormat.dimension.height);
448 contextGL->MakeCurrent();
449
450 m_pOpenGLVisualizer.reset(new COpenGLVisualizer());
451 m_pOpenGLVisualizer->InitByCalibration(getStereoCalibration()->GetRightCalibration());
452
453 m_pObjectFinderStereo.reset(new CObjectFinderStereo());
454 m_pObjectFinderStereo->SetColorParameterSet(&colorParameters);
455 m_pObjectFinderStereo->Init(getStereoCalibration());
456
457 ARMARX_INFO << "SegmentableTemplateRecognition is initialized";
458
459 return true;
460}
461
462bool
464 const memoryx::GridFileManagerPtr& fileManager)
465{
466 std::string entityName = objectClassEntity->getName();
467 ARMARX_VERBOSE << "Adding object class " << objectClassEntity->getName() << armarx::flush;
468
469 auto iter = database.find(entityName);
470 return iter != database.end();
471}
472
473memoryx::ObjectLocalizationResultList
475 const std::vector<std::string>& objectClassNames,
476 CByteImage** cameraImages,
477 armarx::MetaInfoSizeBasePtr imageMetaInfo,
478 CByteImage** resultImages)
479{
480
481 if (objectClassNames.size() < 1)
482 {
483 ARMARX_WARNING << "objectClassNames.size() = " << objectClassNames.size()
484 << ", something is wrong here! ";
485
486 memoryx::ObjectLocalizationResultList resultList;
487 return resultList;
488 }
489
490 ARMARX_VERBOSE << "Localizing " << objectClassNames;
491
492 contextGL->MakeCurrent();
493
494 Object3DList objectList;
495
496 for (std::string const& className : objectClassNames)
497 {
498 Object3DList classObjectList;
499
500 int dilationCount = 2;
501
502 auto classEntryIter = database.find(className);
503 if (classEntryIter == database.end())
504 {
505 ARMARX_WARNING << "Could not find database entry for: " << className;
506 continue;
507 }
508 SegmentableTemplateEntry const& classEntry = classEntryIter->second;
509 ObjectColor color = classEntry.color;
510
511 std::vector<HSVSegmentationParams> hsvSegmentationParams;
512 // FIXME: Hack to allow two colored objects
513 if (color == eYellow3)
514 {
515 hsvSegmentationParams.push_back(getSegmentationParamsFromColor(colorParameters, eRed));
516 hsvSegmentationParams.push_back(
517 getSegmentationParamsFromColor(colorParameters, eYellow));
518 hsvSegmentationParams.back().minRatio = 0.2f;
519 }
520 else
521 {
522 HSVSegmentationParams hsvSegmentationParam =
523 getSegmentationParamsFromColor(colorParameters, color);
524 hsvSegmentationParams.push_back(hsvSegmentationParam);
525 }
526
527 {
528 CByteImage* leftInput = cameraImages[0];
529 CByteImage* rightInput = cameraImages[1];
530 int width = leftInput->width;
531 int height = leftInput->height;
532 ARMARX_CHECK_EXPRESSION(rightInput->width == width && rightInput->height == height);
533
534 CByteImage grayImageBig(width, height, CByteImage::eGrayScale);
535 ::ImageProcessor::ConvertImage(leftInput, &grayImageBig, true);
536
537 BlobDetector leftBlobDetector(width, height);
538 BlobDetector rightBlobDetector(width, height);
539
540 Object2DList leftObjects = leftBlobDetector.findColoredRegions(
541 leftInput, paramMinPixelsPerRegion, hsvSegmentationParams, dilationCount);
542 Object2DList rightObjects = rightBlobDetector.findColoredRegions(
543 rightInput, paramMinPixelsPerRegion, hsvSegmentationParams, dilationCount);
544
545 for (Object2DEntry const& entry : leftObjects)
546 {
547 ::PrimitivesDrawer::DrawRegion(resultImages[0], entry.region, 255, 0, 0, 2);
548 }
549
550 // Calculate blobs with 3D pose from stereo calibration
551 Object3DList blobList;
552
553 float maxEpipolarDistance = this->maxEpipolarDistance;
554 CStereoCalibration* stereoCalibration = getStereoCalibration();
555 bool inputImagesAreRectified = false; // FIXME: Should this be true?
556 bool useDistortionParameters = !getImagesAreUndistorted();
557 float minZDistance = 500;
558 float maxZDistance = 3000;
559 {
560 for (Object2DEntry& leftObject : leftObjects)
561 {
562 Object2DEntry const* bestMatch = nullptr;
563 float bestDiff = maxEpipolarDistance;
564 for (Object2DEntry& rightObject : rightObjects)
565 {
566 float pixelRatio =
567 leftObject.region.nPixels < rightObject.region.nPixels
568 ? (float)leftObject.region.nPixels / rightObject.region.nPixels
569 : (float)rightObject.region.nPixels / leftObject.region.nPixels;
570 float aspectRatioRatio =
571 leftObject.region.ratio < rightObject.region.ratio
572 ? (float)leftObject.region.ratio / rightObject.region.ratio
573 : (float)rightObject.region.ratio / leftObject.region.ratio;
574
575 Vec2d leftCentroid = leftObject.region.centroid;
576 Vec2d rightCentroid = rightObject.region.centroid;
577
578 float yDiff;
579 if (inputImagesAreRectified)
580 {
581 yDiff = fabsf(leftCentroid.y - rightCentroid.y);
582 }
583 else
584 {
585 yDiff = stereoCalibration->CalculateEpipolarLineInLeftImageDistance(
586 leftCentroid, rightCentroid);
587 yDiff = fabs(yDiff);
588 }
589
590 Vec3d position;
591 stereoCalibration->Calculate3DPoint(leftCentroid,
592 rightCentroid,
593 position,
594 inputImagesAreRectified,
595 useDistortionParameters);
596
597
598 if (pixelRatio > 0.5f && aspectRatioRatio > 0.5f &&
599 yDiff <= maxEpipolarDistance && position.z >= minZDistance &&
600 position.z <= maxZDistance && yDiff <= bestDiff)
601 {
602 bestDiff = yDiff;
603 bestMatch = &rightObject;
604 }
605 }
606
607 // We now have the best matching right region in bestMatch
608 if (bestMatch)
609 {
610 Object2DEntry const& rightObject = *bestMatch;
611 Object3DEntry entry;
612
613 entry.region_left = leftObject.region;
614 entry.region_right = rightObject.region;
615 entry.region_id_left = leftObject.id;
616 entry.region_id_right = rightObject.id;
617
618 stereoCalibration->Calculate3DPoint(entry.region_left.centroid,
619 entry.region_right.centroid,
620 entry.pose.translation,
621 inputImagesAreRectified,
622 useDistortionParameters);
623
624 entry.world_point = entry.pose.translation;
625
626 blobList.push_back(entry);
627 }
628 }
629 }
630
631 // Visualize results
632 CByteImage* segmented = &leftBlobDetector.segmented;
633 CByteImage* resultImage = resultImages[0];
634 {
635 const unsigned char* input = segmented->pixels;
636 unsigned char* output = resultImage->pixels;
637 int nPixels = segmented->width * segmented->height;
638
639 // FIXME: Make the highlight color configurable (depending on query color?)
640 int r, g, b;
641 hsv2rgb(0, 255, 100, r, g, b);
642
643 for (int i = 0; i < nPixels; i++)
644 {
645 if (input[i])
646 {
647 const int offset = 3 * i;
648 output[offset + 0] = r;
649 output[offset + 1] = g;
650 output[offset + 2] = b;
651 }
652 }
653 }
654
655
656 // Cut out segmented part of the image
657 // segmentedObject contains the grayscale image of the blob (every other pixel is black)
658 CByteImage segmentedObject(width, height, CByteImage::eGrayScale);
659 ::ImageProcessor::ConvertImage(leftInput, &segmentedObject);
660 ::ImageProcessor::And(segmented, &segmentedObject, &segmentedObject);
661
662 // Try to match templates into the blobs
663 ARMARX_VERBOSE << "Blob count: " << blobList.size();
664 for (Object3DEntry& blob : blobList)
665 {
666 const MyRegion& region = blob.region_left;
667
668 int length = SegmentableBitmapWidth;
669 CByteImage objectGray(length, length, CByteImage::eGrayScale);
670
671 //DEBUG_SAVE_IMAGE(*pGrayImage, "00");
672
673 // Crop the region of the blob
674 CropImageToTemplate(&segmentedObject, region, &objectGray);
675
676 //DEBUG_SAVE_IMAGE(objectGray, "01");
677
678 ::ImageProcessor::ThresholdBinarize(&objectGray, &objectGray, 1);
679 //DEBUG_SAVE_IMAGE(objectGray, "03_binarize");
680
681 // objectGray is now a black/white image (64x64) of the blob
682 CByteImage* input = &objectGray;
683 SegmentableTemplateHeader const& templateHeader = *classEntry.data;
684
685 int bestOverlap = 0;
686 int total = 0;
687 SegmentableTemplate const* bestTemplate = nullptr;
688 for (std::uint32_t i = 0; i < templateHeader.templateCount; ++i)
689 {
690 SegmentableTemplate const& template_ = templateHeader.templates[i];
691
692 // Calculate the overlap between input and template_.bitmap
693 int overlap = 0;
694 int stride = input->width;
695 total = 0;
696 for (int y = 0; y < (int)SegmentableBitmapWidth; ++y)
697 {
698 std::uint32_t const* templateRow = template_.bitmap + y * 2;
699 unsigned char const* inputRow = input->pixels + y * stride;
700 for (int x = 0; x < (int)(SegmentableBitmapWidth / 32); ++x)
701 {
702 std::uint32_t compressed = templateRow[x];
703 // Decompress
704 for (int i = 0; i < 32; ++i)
705 {
706 bool inputPixel = inputRow[x * 32 + i] > 0;
707 bool templatePixel = (compressed & (1 << i)) > 0;
708 if ((inputPixel && templatePixel) ||
709 (!inputPixel && !templatePixel))
710 {
711 ++overlap;
712 }
713 ++total;
714 }
715 }
716 }
717
718 if (overlap > bestOverlap)
719 {
720 bestOverlap = overlap;
721 bestTemplate = &template_;
722 }
723 }
724
725 float overlapRatio =
726 1.0f * bestOverlap / (SegmentableBitmapWidth * SegmentableBitmapWidth);
727
728 if (!bestTemplate || overlapRatio < paramTemplateMatchThreshold)
729 {
731 << "Could not find a matching template (overlap ratio: " << overlapRatio
732 << ")";
733 }
734 else
735 {
736 // Calculate pose from angles
737 {
738 Transformation3d& poseData = blob.pose;
739 Vec3d convention;
740 convention.x = bestTemplate->rotationX;
741 convention.y = bestTemplate->rotationY;
742 convention.z = bestTemplate->rotationZ;
743
744 // calculate rotation matrix
745 Mat3d temp;
746 Math3d::SetRotationMatX(poseData.rotation, convention.x);
747 Math3d::SetRotationMatZ(temp, convention.z);
748 Math3d::MulMatMat(poseData.rotation, temp, poseData.rotation);
749 Math3d::SetRotationMatY(temp, convention.y);
750 Math3d::MulMatMat(poseData.rotation, temp, poseData.rotation);
751 }
752
753 // Calculate corrected orientation
754 Vec3d position = blob.pose.translation;
755 Mat3d orientation = blob.pose.rotation;
756 Mat3d& resultOrientation = blob.pose.rotation;
757 {
758 Vec3d u0 = {0, 0, 1};
759 Vec3d u = position;
760 Math3d::NormalizeVec(u);
761
762 // corrective rotation matrix
763 Mat3d correctiveRotation;
764 Vec3d axis;
765 Math3d::CrossProduct(u0, u, axis);
766 float angle = Math3d::Angle(u0, u, axis);
767 Math3d::SetRotationMatAxis(correctiveRotation, axis, angle);
768
769 // final rotation matrix
770 Math3d::MulMatMat(correctiveRotation, orientation, resultOrientation);
771 }
772
773 // Calculate corrected position
774 orientation = resultOrientation;
775 float resultCorrelation = 0.0f;
776 float resultSizeRatio = 0.0f;
777 CByteImage* initialMask = &objectGray;
778 int nSize = region.nPixels;
779 int nSimulatedSize = 0;
780 {
781 Vec3d initialPosition = position;
782 // Calculate position in rendered images
783 // Add difference pos_real - pos_sim to pos_real
784 // This results in the simulation producing the expected image
785
786 CStereoCalibration* stereoCalibration = getStereoCalibration();
787 const int width = stereoCalibration->width;
788 const int height = stereoCalibration->height;
789
790 Transformation3d pose;
791 Math3d::SetVec(pose.translation, position);
792 Math3d::SetMat(pose.rotation, orientation);
793
794 CByteImage image_left(width, height, CByteImage::eGrayScale);
795 CByteImage image_right(width, height, CByteImage::eGrayScale);
796 CByteImage* ppImages[2] = {&image_left, &image_right};
797
798 // render left image
799 m_pOpenGLVisualizer->Clear();
800 m_pOpenGLVisualizer->ActivateShading(false);
801 m_pOpenGLVisualizer->SetProjectionMatrix(
802 stereoCalibration->GetLeftCalibration());
803 //DrawObjectFromFile(m_pOpenGLVisualizer, entry->sOivPath, pose, entry->sName);
804 CFloatMatrix* model = database[className].model.get();
805 m_pOpenGLVisualizer->DrawObject(model, pose);
806 m_pOpenGLVisualizer->GetImage(&image_left);
807 ::ImageProcessor::FlipY(&image_left, &image_left);
808
809 // render right image
810 m_pOpenGLVisualizer->Clear();
811 m_pOpenGLVisualizer->SetProjectionMatrix(
812 stereoCalibration->GetRightCalibration());
813 //DrawObjectFromFile(m_pOpenGLVisualizer, entry->sOivPath, pose, entry->sName);
814 m_pOpenGLVisualizer->DrawObject(model, pose);
815 m_pOpenGLVisualizer->GetImage(&image_right);
816 ::ImageProcessor::FlipY(&image_right, &image_right);
817
818 // DEBUG_SAVE_IMAGE(image_left, "04_simulated");
819 // DEBUG_SAVE_IMAGE(image_right, "05_simulated");
820
821 // calculate stereo
822 m_pObjectFinderStereo->ClearObjectList();
823 m_pObjectFinderStereo->FindObjectsInSegmentedImage(
824 ppImages, 0, eRed, 100, false);
825 m_pObjectFinderStereo->Finalize(0, 10000, false, eRed, 10, false);
826 const Object3DList& objectList = m_pObjectFinderStereo->GetObject3DList();
827
828 if (objectList.size() == 1)
829 {
830 Object3DEntry const& simEntry = objectList[0];
831
832 Vec3d positionCorrection;
833 Math3d::SubtractVecVec(
834 position, simEntry.pose.translation, positionCorrection);
835 Math3d::AddVecVec(initialPosition, positionCorrection, position);
836
837 // calculate quality measure
838 // get simulated size
839 nSimulatedSize = objectList.at(0).region_left.nPixels;
840
841 // calculate size normalized image
842 const int k = SegmentableBitmapWidth;
843 CByteImage simulatedObject(k, k, CByteImage::eGrayScale);
844 CropImageToTemplate(
845 &image_left, simEntry.region_left, &simulatedObject);
846
847 int sum = 0;
848 for (std::size_t j = 0; j < SegmentableBitmapSize; j++)
849 {
850 sum += abs(simulatedObject.pixels[j] - initialMask->pixels[j]);
851 }
852
853 // calculate final measures
854 float divisor = 255.0f * SegmentableBitmapSize;
855 resultCorrelation = 1.0f - sum / divisor;
856 resultSizeRatio = nSimulatedSize < nSize
857 ? float(nSimulatedSize) / nSize
858 : float(nSize) / nSimulatedSize;
859 }
860 else
861 {
862 ARMARX_VERBOSE << "Could not find object '" << className
863 << "' in simulated image";
864 }
865 }
866
867 if (resultSizeRatio < paramSizeRatioThreshold)
868 {
869 ARMARX_VERBOSE << "Detected blob size is too different in real image "
870 "compared to simulated image ("
871 << "real size: " << nSize
872 << ", simulated size: " << nSimulatedSize
873 << ", ratio: " << resultSizeRatio << ")";
874 }
875 else if (resultCorrelation < paramCorrelationThreshold)
876 {
878 << "Detected blob does not correlate with the simulated blob ("
879 << "ratio: " << resultCorrelation << ")";
880 }
881 else
882 {
883 blob.sName = className;
884 blob.class_id = 0;
885 blob.quality = resultSizeRatio; //fResultRatio;
886 blob.quality2 = resultCorrelation; //fResultCorrelation;
887 blob.localizationValid = true;
888
889 ARMARX_VERBOSE << "Found a match (overlap ratio: " << overlapRatio
890 << ", size ratio: " << resultSizeRatio
891 << ", correlation: " << resultCorrelation
892 << ", blob size: " << nSize << ")";
893
894 classObjectList.push_back(blob);
895 }
896 }
897 }
898 if (classObjectList.size() > 1 && paramSingleInstance)
899 {
900 std::sort(classObjectList.begin(),
901 classObjectList.end(),
902 [](Object3DEntry const& left, Object3DEntry const& right)
903 { return left.quality > right.quality; });
904 Object3DEntry& bestMatch = classObjectList.front();
905 objectList.push_back(bestMatch);
906 }
907 else
908 {
909 objectList.insert(objectList.end(), classObjectList.begin(), classObjectList.end());
910 }
911 }
912 }
913
914 ARMARX_VERBOSE << "Found " << objectList.size() << " object(s)";
915
916 // help for problem analysis
917 if (objectList.size() == 0)
918 {
919 ARMARX_INFO << "Looked unsuccessfully for:";
920 std::string color;
921
922 for (std::string const& className : objectClassNames)
923 {
924 CColorParameterSet::Translate(database[className].color, color);
925 ARMARX_INFO << className << " color: " << color << flush;
926 }
927 }
928 else
929 {
930 visualizeResults(objectList, resultImages);
931 }
932
933 const auto agentName = getProperty<std::string>("AgentName").getValue();
934
935 memoryx::ObjectLocalizationResultList resultList;
936
937 for (Object3DList::iterator iter = objectList.begin(); iter != objectList.end(); iter++)
938 {
939 float x = iter->pose.translation.x;
940 float y = iter->pose.translation.y;
941 float z = iter->pose.translation.z;
942
943 if (seq.count(iter->sName))
944 {
945 seq[iter->sName]++;
946 }
947 else
948 {
949 seq[iter->sName] = 0;
950 }
951
952 StringVariantBaseMap mapValues;
953 mapValues["x"] = new Variant(x);
954 mapValues["y"] = new Variant(y);
955 mapValues["z"] = new Variant(z);
956 mapValues["name"] = new Variant(iter->sName);
957 mapValues["sequence"] = new Variant(seq[iter->sName]);
958 mapValues["timestamp"] = new Variant(imageMetaInfo->timeProvided / 1000.0 / 1000.0);
959 debugObserver->setDebugChannel("ObjectRecognition", mapValues);
960
961 // only accept realistic positions
962 if (x > validResultBoundingBoxMin.x && y > validResultBoundingBoxMin.y &&
963 z > validResultBoundingBoxMin.z && x < validResultBoundingBoxMax.x &&
964 y < validResultBoundingBoxMax.y && z < validResultBoundingBoxMax.z)
965 {
966 // assemble result
967 memoryx::ObjectLocalizationResult result;
968
969 // position and orientation
970 Eigen::Vector3f position(
971 iter->pose.translation.x, iter->pose.translation.y, iter->pose.translation.z);
972 Eigen::Matrix3f orientation;
973 orientation << iter->pose.rotation.r1, iter->pose.rotation.r2, iter->pose.rotation.r3,
974 iter->pose.rotation.r4, iter->pose.rotation.r5, iter->pose.rotation.r6,
975 iter->pose.rotation.r7, iter->pose.rotation.r8, iter->pose.rotation.r9;
976
977 ARMARX_VERBOSE << "Reporting in frame: " << referenceFrameName;
978 result.position = new armarx::FramedPosition(position, referenceFrameName, agentName);
979 result.orientation =
980 new armarx::FramedOrientation(orientation, referenceFrameName, agentName);
981
982 // calculate noise
983 result.positionNoise = calculateLocalizationUncertainty(iter->region_left.centroid,
984 iter->region_right.centroid);
985
986 // calculate recognition certainty
987 result.recognitionCertainty =
988 0.5f + 0.5f * calculateRecognitionCertainty(iter->sName, *iter);
989 result.objectClassName = iter->sName;
990 result.timeStamp = new TimestampVariant(imageMetaInfo->timeProvided);
991
992 resultList.push_back(result);
993 }
994 else
995 {
996 ARMARX_VERBOSE << "Refused unrealistic localization at position: " << x << " " << y
997 << " " << z;
998 }
999 }
1000
1001 ARMARX_VERBOSE << "Finished localizing " << objectClassNames.at(0);
1002
1003 return resultList;
1004}
1005
1006float
1007SegmentableTemplateRecognition::calculateRecognitionCertainty(const std::string& objectClassName,
1008 const Object3DEntry& entry)
1009{
1010 float foundProb = entry.quality * entry.quality2;
1011 float notFoundProb = (1 - entry.quality) * (1 - entry.quality2);
1012
1013 if (foundProb <= 0)
1014 {
1015 return 0.0f;
1016 }
1017
1018 return foundProb / (foundProb + notFoundProb);
1019}
1020
1021void
1022SegmentableTemplateRecognition::visualizeResults(const Object3DList& objectList,
1023 CByteImage** resultImages)
1024{
1025 m_pOpenGLVisualizer->ActivateShading(true);
1026 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1027
1028
1029 m_pOpenGLVisualizer->SetProjectionMatrix(getStereoCalibration()->GetRightCalibration());
1030
1031 m_pOpenGLVisualizer->Clear();
1032
1033
1034 for (int i = 0; i < (int)objectList.size(); i++)
1035 {
1036 const Object3DEntry& entry = objectList.at(i);
1037 CFloatMatrix* model = database[entry.sName].model.get();
1038 m_pOpenGLVisualizer->DrawObject(model, entry.pose);
1039 }
1040
1041 const int nImageIndex = 1;
1042
1043 if (resultImages && resultImages[nImageIndex])
1044 {
1045 CByteImage tempImage(resultImages[nImageIndex]);
1046 m_pOpenGLVisualizer->GetImage(&tempImage);
1047 ::ImageProcessor::FlipY(&tempImage, &tempImage);
1048 const int nBytes = 3 * tempImage.width * tempImage.height;
1049 const unsigned char* pixels = tempImage.pixels;
1050 unsigned char* output = resultImages[nImageIndex]->pixels;
1051
1052 for (int i = 0; i < nBytes; i += 3)
1053 {
1054 if (pixels[i])
1055 {
1056 const unsigned char g = pixels[i];
1057 output[i] = g;
1058 output[i + 1] = g;
1059 output[i + 2] = g;
1060 }
1061 }
1062 }
1063}
#define float
Definition 16_Level.h:22
std::vector< std::string > getFileList(const std::string &path)
static bool getAbsolutePath(const std::string &relativeFilename, std::string &storeAbsoluteFilename, const std::vector< std::string > &additionalSearchPaths={}, bool verbose=true)
Property< PropertyType > getProperty(const std::string &name)
The FramedOrientation class.
Definition FramedPose.h:216
The FramedPosition class.
Definition FramedPose.h:158
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
Implements a Variant type for timestamps.
The Variant class is described here: Variants.
Definition Variant.h:224
Brief description of class memory.
Definition memory.h:39
ImageFormatInfo getImageFormat() const
Retrieve format of input images.
bool getImagesAreUndistorted() const
Retrieve whether images are undistorted.
memoryx::MultivariateNormalDistributionPtr calculateLocalizationUncertainty(Vec2d left_point, Vec2d right_point)
Calculate 3D uncertainty from two 2d points in left and right camera.
armarx::MetaInfoSizeBasePtr imageMetaInfo
CStereoCalibration * getStereoCalibration() const
Retrieve stereo calibration corresponding to image provider.
bool initRecognizer() override
Initializes segmentable recognition.
memoryx::ObjectLocalizationResultList localizeObjectClasses(const std::vector< std::string > &objectClassNames, CByteImage **cameraImages, armarx::MetaInfoSizeBasePtr imageMetaInfo, CByteImage **resultImages) override
localizes segmentable object instances
bool addObjectClass(const memoryx::EntityPtr &objectClassEntity, const memoryx::GridFileManagerPtr &fileManager) override
Add object class to segmentable object recognition.
#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_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#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
double s(double t, double s0, double v0, double a0, double j)
Definition CtrlUtil.h:33
double v(double t, double v0, double a0, double j)
Definition CtrlUtil.h:39
This file offers overloads of toIce() and fromIce() functions for STL container types.
void read(auto &eigen, auto *table)
std::map< std::string, VariantBasePtr > StringVariantBaseMap
std::vector< T > abs(const std::vector< T > &v)
const LogSender::manipulator flush
Definition LogSender.h:251
IceInternal::Handle< Entity > EntityPtr
Typedef of EntityPtr as IceInternal::Handle<Entity> for convenience.
Definition Entity.h:45
std::shared_ptr< GridFileManager > GridFileManagerPtr
ArmarX headers.
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109
std::unique_ptr< SegmentableTemplateHeader > data