ArMarkerLocalizerOpenCV.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5 *
6 * ArmarX is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * ArmarX is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @package VisionX::Component
19 * @author David Schiebener (schiebener at kit dot edu)
20 * @date 2015
21 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22 * GNU General Public License
23 */
24
26
27#include <Eigen/Core>
28
30
31#include <opencv2/calib3d.hpp>
32#include <opencv2/core/eigen.hpp>
33#include <opencv2/highgui.hpp>
34
35// IVT
36#include <SimoxUtility/algorithm/string.h>
37
42
44
48
49#include <Calibration/Calibration.h>
50#include <Calibration/StereoCalibration.h>
51#include <Image/IplImageAdaptor.h>
52
53namespace visionx
54{
55
61
64 {
67
68 defs->optional(
69 p.imageProviderName, "img.ImageProviderName", "Name of the image provider to use.");
70 defs->optional(
71 p.referenceFrame, "img.ReferenceFrameName", "Name of the ReferenceFrameName");
72 defs->optional(p.agentName, "img.AgentName", "Name of the agent");
73 defs->optional(p.cameraIndex, "img.cameraIndex", "Camera index. Left = 0, Right = 1, ...");
74
75 std::string pattern = "[ k3 [, k4, k5, k6 [, s1, s2, s3, s4 ] ] ]";
76 // defs->optional(p.extraDistortionCoeffs, "cam.ExtraDistortionCoeffs",
77 defs->defineOptionalProperty<std::vector<std::string>>(
78 "cam.ExtraDistortionCoeffs",
79 {},
80 "Optional extra distortion coefficients (which cannot be retrieved from the image "
81 "provider)."
82 "\nThe expected parameters are: " +
83 pattern +
84 ""
85 "\nThat is, you must specifiy 1 (k3), 4 (k3-k6), or 8 (k3-k6, s1-s4) elements."
86 "\nNote: k1, k2, p1, p2 are retrieved from the image provider.")
87 .map(pattern, {});
88
89 defs->defineOptionalProperty<float>(
90 "ar.MarkerSize", p.markerSize.load(), "The side length of the marker(s)");
91 defs->optional(p.dictionary, "ar.Dictionary", "The ArUco dictionary.")
92 .map("4X4_50", cv::aruco::DICT_4X4_50)
93 .map("4X4_100", cv::aruco::DICT_4X4_100)
94 .map("4X4_250", cv::aruco::DICT_4X4_250)
95 .map("4X4_1000", cv::aruco::DICT_4X4_1000)
96 .map("5X5_50", cv::aruco::DICT_5X5_50)
97 .map("5X5_100", cv::aruco::DICT_5X5_100)
98 .map("5X5_250", cv::aruco::DICT_5X5_250)
99 .map("5X5_1000", cv::aruco::DICT_5X5_1000)
100 .map("6X6_50", cv::aruco::DICT_6X6_50)
101 .map("6X6_100", cv::aruco::DICT_6X6_100)
102 .map("6X6_250", cv::aruco::DICT_6X6_250)
103 .map("6X6_1000", cv::aruco::DICT_6X6_1000)
104 .map("7X7_50", cv::aruco::DICT_7X7_50)
105 .map("7X7_100", cv::aruco::DICT_7X7_100)
106 .map("7X7_250", cv::aruco::DICT_7X7_250)
107 .map("7X7_1000", cv::aruco::DICT_7X7_1000)
108 .map("ARUCO_ORIGINAL", cv::aruco::DICT_ARUCO_ORIGINAL);
109
110 defs->defineOptionalProperty<std::string>("ar.MarkerGeneration",
111 "https://chev.me/arucogen/",
112 "You can generate ArUco markers here.");
113
114 defs->defineOptionalProperty<bool>(
115 "visu.Enabled", p.visuEnabled.load(), "If true, visualize marker poses.");
116
117 return defs;
118 }
119
121 {
122 addPlugin(virtualRobotReaderPlugin);
123 }
124
125 std::string
130
131 std::string
133 {
134 return "ArMarkerLocalizerOpenCV";
135 }
136
137 void
139 {
140 ARMARX_INFO << VAROUT(p.dictionary);
141 *arucoDictionary = cv::aruco::getPredefinedDictionary(p.dictionary);
142 p.markerSize = getProperty<float>("ar.MarkerSize").getValue();
143 ARMARX_INFO << VAROUT(p.markerSize);
144
145 p.visuEnabled = getProperty<bool>("visu.Enabled").getValue();
146
147 {
148 std::string propName = "cam.ExtraDistortionCoeffs";
149 std::vector<std::string> coeffsStr;
150 getProperty(coeffsStr, "cam.ExtraDistortionCoeffs");
151
152 p.extraDistortionCoeffs.clear();
153 for (const auto& coeffStr : coeffsStr)
154 {
155 try
156 {
157 p.extraDistortionCoeffs.push_back(std::stof(coeffStr));
158 }
159 catch (const std::invalid_argument&)
160 {
161 ARMARX_WARNING << "Could not parse '" << coeffStr << "' as float in property "
162 << propName << "."
163 << "\nIgnoring extra parameters.";
164 p.extraDistortionCoeffs.clear();
165 break;
166 }
167 }
168 }
169 }
170
171 void
173 {
174 ARMARX_CHECK_NOT_NULL(virtualRobotReaderPlugin);
175
176 robot = virtualRobotReaderPlugin->get().getRobot(p.agentName);
177 ARMARX_CHECK_NOT_NULL(robot) << p.agentName;
178
179 referenceNode = robot->getRobotNode(p.referenceFrame);
180 ARMARX_CHECK_NOT_NULL(referenceNode) << p.referenceFrame;
181
182 ARMARX_VERBOSE << "Marker size: " << p.markerSize;
183
184 visionx::ImageProviderInfo imageProviderInfo = getImageProvider(p.imageProviderName);
185 StereoCalibrationInterfacePrx calibrationProvider =
186 StereoCalibrationInterfacePrx::checkedCast(imageProviderInfo.proxy);
187
188 std::unique_ptr<CStereoCalibration> stereoCalibration(
189 visionx::tools::convert(calibrationProvider->getStereoCalibration()));
190 CCalibration::CCameraParameters ivtCameraParameters =
191 stereoCalibration->GetLeftCalibration()->GetCameraParameters();
192
193 this->cameraMatrix = visionx::makeCameraMatrix(ivtCameraParameters);
194 ARMARX_VERBOSE << "Camera matrix: \n" << this->cameraMatrix;
195
196 {
197 // "Vector of distortion coefficients (k1,k2,p1,p2 [,k3 [,k4,k5,k6], [s1,s2,s3,s4]]) of 4, 5, 8 or 12 elements"
198 // Source: https://docs.opencv.org/3.2.0/d9/d6a/group__aruco.html#ga7da45d2e8139504f3a532d884b4fb4ac
199
200 std::set<int> allowedSizes = {4, 5, 8, 12};
201 ARMARX_VERBOSE << "Got " << p.extraDistortionCoeffs.size()
202 << " extra distortion coefficients.";
203
204 int num = int(4 + p.extraDistortionCoeffs.size());
205 ARMARX_CHECK_POSITIVE(allowedSizes.count(num))
206 << "Allowed sizes: "
207 << simox::alg::join(
208 simox::alg::multi_to_string(allowedSizes.begin(), allowedSizes.end()), " ")
209 << "\n num = " << num << " = 4 + " << p.extraDistortionCoeffs.size()
210 << " = 4 + #extra coeffs";
211
212 cv::Mat distortionParameters(num, 1, cv::DataType<float>::type);
213
214 if (!calibrationProvider->getImagesAreUndistorted())
215 {
216 // k1, k2, p1, p2
217 for (int i = 0; i < 4; ++i)
218 {
219 distortionParameters.at<float>(i, 0) = ivtCameraParameters.distortion[i];
220 }
221 // extra
222 for (int i = 0; size_t(i) < p.extraDistortionCoeffs.size(); ++i)
223 {
224 distortionParameters.at<float>(4 + i, 0) =
225 p.extraDistortionCoeffs.at(size_t(i));
226 }
227 }
228 else
229 {
230 for (int i = 0; i < distortionParameters.rows; ++i)
231 {
232 distortionParameters.at<float>(i, 0) = 0;
233 }
234 }
235
236 this->distortionCoeffs = distortionParameters;
237 ARMARX_CHECK_POSITIVE(allowedSizes.count(this->distortionCoeffs.rows));
238 }
239 ARMARX_VERBOSE << "Distortion coefficients: \n" << this->distortionCoeffs;
240
241
242 cameraImages = new CByteImage*[2];
243
244 cameraImages[0] = tools::createByteImage(imageProviderInfo);
245 cameraImages[1] = tools::createByteImage(imageProviderInfo);
246
249 }
250
251 void
253 {
254 if (waitForImages(500))
255 {
256 armarx::MetaInfoSizeBasePtr info;
257 getImages(p.imageProviderName, cameraImages, info);
258 m_timestamp_last_image =
260
261 // getImages(cameraImages);
262 }
263 else
264 {
265 ARMARX_VERBOSE << "Timeout or error in wait for images.";
266 return;
267 }
268
269 ArMarkerLocalizationResultList result = localizeAllMarkersInternal();
270 {
271 std::unique_lock lock(resultMutex);
272 lastLocalizationResult = result;
273 // m_timestamp_last_result = m_timestamp_last_image;
274 }
275
276 {
277 armarx::viz::Layer layer = arviz.layer("Marker Poses");
278 if (p.visuEnabled)
279 {
280 const Eigen::Isometry3f global_T_frame{referenceNode->getGlobalPose()};
281
282 for (const auto& r : result)
283 {
284 const Eigen::Isometry3f frame_T_marker{
285 armarx::FramedPosePtr::dynamicCast(r.pose)->toEigen()};
286 const Eigen::Isometry3f global_T_marker = global_T_frame * frame_T_marker;
287
288 layer.add(
289 armarx::viz::Pose(std::to_string(r.id)).pose(global_T_marker.matrix()));
290 }
291 }
292 arviz.commit({layer});
293 }
294 }
295
296 ArMarkerLocalizationResultList
297 ArMarkerLocalizerOpenCV::localizeAllMarkersInternal()
298 {
301 virtualRobotReaderPlugin->get().synchronizeRobot(*robot, armarx::Clock::Now()));
302
303 cameraImages[0]->bytesPerPixel = 3;
304 cameraImages[1]->bytesPerPixel = 3;
305
306 IplImage* imageIpl = IplImageAdaptor::Adapt(cameraImages[p.cameraIndex]);
307 cv::Mat imageOpenCV = cv::cvarrToMat(imageIpl);
308
309 // cv::imshow("Input", imageOpenCV);
310 // cv::waitKey(10);
311
312 // Detect markers in image.
313
314 ARMARX_DEBUG << "Calling marker detection";
315
316 std::vector<int> markerIDs;
317 std::vector<std::vector<cv::Point2f>> markerCorners;
318 cv::aruco::detectMarkers(
319 imageOpenCV, arucoDictionary, markerCorners, markerIDs, arucoParameters);
320
321
322 ARMARX_CHECK_EQUAL(markerIDs.size(), markerCorners.size());
323
324 ARMARX_VERBOSE << "Localized " << markerIDs.size() << " markers: "
325 << simox::alg::join(simox::alg::multi_to_string(markerIDs), " ");
326
327 // We could draw an visu image:
328 cv::Mat outputImage = imageOpenCV.clone();
329 if (not markerIDs.empty())
330 {
331 cv::aruco::drawDetectedMarkers(outputImage, markerCorners, markerIDs);
332 }
333 // cv::aruco::drawDetectedMarkers(imageOpenCV, markerCorners, markerIDs);
334
335
336 // Estimate marker poses.
337 ARMARX_DEBUG << "Estimating marker poses.";
338
339 std::vector<cv::Vec3d> rvecs, tvecs;
340 cv::aruco::estimatePoseSingleMarkers(
341 markerCorners, p.markerSize, cameraMatrix, distortionCoeffs, rvecs, tvecs);
342 ARMARX_CHECK_EQUAL(rvecs.size(), markerCorners.size());
343 ARMARX_CHECK_EQUAL(tvecs.size(), markerCorners.size());
344 ARMARX_VERBOSE << "Estimated " << rvecs.size() << " marker poses.";
345
346 // Assemble results
347
348 visionx::ArMarkerLocalizationResultList resultList;
349
350 for (size_t i = 0; i < markerIDs.size(); ++i)
351 {
352 visionx::ArMarkerLocalizationResult& result = resultList.emplace_back();
353
354 const cv::Vec3d& tvec = tvecs.at(i);
355 Eigen::Vector3f position = Eigen::Vector3d(tvec(0), tvec(1), tvec(2)).cast<float>();
356
357 cv::Mat cvOrientation;
358 try
359 {
360 cv::Rodrigues(rvecs.at(i), cvOrientation);
361 }
362 catch (const std::exception& e)
363 {
364 ARMARX_ERROR << "Caught exception when running cv::aruco::Rodrigues(): \n"
365 << e.what();
366 return {};
367 }
368 ARMARX_CHECK_EQUAL(cvOrientation.rows, 3);
369 ARMARX_CHECK_EQUAL(cvOrientation.cols, 3);
370
371 Eigen::Matrix3d orientation;
372 cv::cv2eigen(cvOrientation, orientation);
373
374 result.pose = new armarx::FramedPose(
375 orientation.cast<float>(), position, p.referenceFrame, p.agentName);
376 result.id = markerIDs.at(i);
377
378 armarx::core::time::toIce(result.timestamp, m_timestamp_last_image);
379
380 cv::drawFrameAxes(outputImage, cameraMatrix, distortionCoeffs, rvecs[i], tvecs[i], 0.1);
381 }
382
383 CByteImage* output_cimage =
384 new CByteImage(outputImage.size[0], outputImage.size[1], CByteImage::ImageType::eRGB24);
385 copyCvMatToIVT(outputImage, output_cimage);
386
387 CByteImage* result_images[2] = {cameraImages[0], output_cimage};
388 provideResultImages(result_images);
389 delete output_cimage;
390 return resultList;
391 }
392
393 visionx::ArMarkerLocalizationResultList
395 {
396 if (waitForImages(500))
397 {
398 getImages(cameraImages);
399 return localizeAllMarkersInternal();
400 }
401 else
402 {
403 ARMARX_WARNING << "No new Images available!";
404 return {};
405 }
406 }
407
408 ArMarkerLocalizationResultList
410 {
411 std::unique_lock lock(resultMutex);
412 return lastLocalizationResult;
413 }
414
415 void
417 {
418 using namespace armarx::RemoteGui::Client;
419
420 GridLayout grid;
421 int row = 0;
422
423 tab.markerSize.setRange(1.0, 1000.0);
424 tab.markerSize.setSteps(1000);
425 tab.markerSize.setDecimals(1);
426 tab.markerSize.setValue(p.markerSize);
427
428 tab.visuEnabled.setValue(p.visuEnabled);
429
430 grid.add(Label("Marker size:"), {row, 0}).add(tab.markerSize, {row, 1});
431 row++;
432 grid.add(Label("Enable visu:"), {row, 0}).add(tab.visuEnabled, {row, 1});
433 row++;
434
435 VBoxLayout root = {grid, VSpacer()};
436 RemoteGui_createTab(getName(), root, &tab);
437 }
438
439 void
441 {
442 if (tab.markerSize.hasValueChanged())
443 {
444 p.markerSize = tab.markerSize.getValue();
445 }
446 if (tab.visuEnabled.hasValueChanged())
447 {
448 p.visuEnabled = tab.visuEnabled.getValue();
449 }
450 }
451
453
454} // namespace visionx
int Label(int n[], int size, int *curLabel, MiscLib::Vector< std::pair< int, size_t > > *labels)
Definition Bitmap.cpp:801
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
#define VAROUT(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
static Duration MicroSeconds(std::int64_t microSeconds)
Constructs a duration in microseconds.
Definition Duration.cpp:24
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
std::string getName() const
Retrieve name of object.
std::string prefix
Prefix of the properties such as namespace, domain, component name, etc.
Represents a point in time.
Definition DateTime.h:25
DerivedT & pose(Eigen::Matrix4f const &pose)
Definition ElementOps.h:176
ArMarkerLocalizerOpenCV uses CTexturedRecognition of IVTRecognition in order to recognize and localiz...
void onConnectImageProcessor() override
Implement this method in the ImageProcessor in order execute parts when the component is fully initia...
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void process() override
Process the vision component.
void onInitImageProcessor() override
Setup the vision component.
visionx::ArMarkerLocalizationResultList LocalizeAllMarkersNow(const Ice::Current &) override
visionx::ArMarkerLocalizationResultList GetLatestLocalizationResult(const Ice::Current &) override
std::string getDefaultName() const override
ImageProcessorPropertyDefinitions(std::string prefix)
bool waitForImages(int milliseconds=1000)
Wait for new images.
ImageProviderInfo getImageProvider(std::string name, ImageType destinationImageType=eRgb, bool waitForProxy=false)
Select an ImageProvider.
int getImages(CByteImage **ppImages)
Poll images from provider.
void provideResultImages(CByteImage **images, armarx::MetaInfoSizeBasePtr info=nullptr)
sends result images for visualization
ImageProviderInterfacePrx proxy
proxy to image provider
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_POSITIVE(number)
This macro evaluates whether number is positive (> 0) and if it turns out to be false it will throw a...
#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_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#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
void toIce(dto::ClockType::ClockTypeEnum &dto, const ClockType &bo)
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
CByteImage * createByteImage(const ImageFormatInfo &imageFormat, const ImageType imageType)
Creates a ByteImage for the destination type specified in the given imageProviderInfo.
CByteImage::ImageType convert(const ImageType visionxImageType)
Converts a VisionX image type into an image type of IVT's ByteImage.
ArmarX headers.
cv::Mat makeCameraMatrix(const visionx::CameraParameters &cameraParams)
void copyCvMatToIVT(cv::Mat const &input, CByteImage *output)
Copies the contents of an OpenCV matrix to an IVT CByteImage.
void RemoteGui_createTab(std::string const &name, RemoteGui::Client::Widget const &rootWidget, RemoteGui::Client::Tab *tab)
GridLayout & add(Widget const &child, Pos pos, Span span=Span{1, 1})
Definition Widgets.cpp:438
void add(ElementT const &element)
Definition Layer.h:31
#define ARMARX_TRACE
Definition trace.h:77