41#include <Image/ImageProcessor.h>
42#include <Image/IplImageAdaptor.h>
43#include <Image/PrimitivesDrawer.h>
44#include <Image/PrimitivesDrawerCV.h>
47#include <IceUtil/Time.h>
61 IceUtil::Time timestamp_invalid = IceUtil::Time::milliSeconds(-1);
77 m_image_received =
false;
81 ARMARX_VERBOSE <<
"Using image provider with ID '" << m_image_provider_id <<
"'.";
97 while (std::getline(infile, line))
99 m_darknet_classes.push_back(line);
102 m_network.class_count(
static_cast<int>(m_darknet_classes.size()));
120 const unsigned int num_images =
121 static_cast<unsigned int>(m_image_provider_info.numberImages);
122 m_input_image_buf = new ::CByteImage*[num_images];
123 for (
unsigned int i = 0; i < num_images; ++i)
138 const int num_images = 1;
140 m_image_provider_info.imageFormat.dimension,
141 m_image_provider_info.imageFormat.type);
145 m_task_object_detection =
147 m_task_object_detection->start();
149 ARMARX_INFO <<
"Darknet provider connected. Operation begins now.";
157 const bool wait_for_join =
true;
158 m_task_object_detection->stop(wait_for_join);
163 const unsigned int num_images =
164 static_cast<unsigned int>(m_image_provider_info.numberImages);
165 for (
unsigned int i = 0; i < num_images; ++i)
167 delete m_input_image_buf[i];
169 delete[] m_input_image_buf;
199 const IceUtil::Time timeout = IceUtil::Time::milliSeconds(1000);
200 if (not
waitForImages(m_image_provider_id,
static_cast<int>(timeout.toMilliSeconds())))
202 ARMARX_WARNING <<
"Timeout while waiting for camera images (>" << timeout <<
")";
207 std::lock_guard<std::mutex> lock{m_input_image_mutex};
209 MetaInfoSizeBasePtr info;
210 int num_images =
getImages(m_image_provider_id, m_input_image_buf, info);
211 m_timestamp_last_image = IceUtil::Time::microSeconds(info->timeProvided);
216 ::ImageProcessor::CopyImage(m_input_image_buf[m_image_provider_channel],
217 m_input_image.get());
218 m_image_received =
true;
229 return static_cast<double>(m_network.thresh());
235 m_network.thresh(
static_cast<float>(thresh));
242 return static_cast<double>(m_network.hier_thresh());
248 m_network.hier_thresh(
static_cast<float>(hier_thresh));
249 ARMARX_VERBOSE <<
"Parameter hier_thresh is now " << hier_thresh <<
".";
255 return static_cast<double>(m_network.nms());
261 m_network.nms(
static_cast<float>(nms));
269 if (m_minimum_loop_time == ::timestamp_invalid)
275 return 1000.f / m_minimum_loop_time.toMilliSeconds();
284 m_minimum_loop_time = ::timestamp_invalid;
289 m_minimum_loop_time = IceUtil::Time::milliSecondsDouble(1000. /
static_cast<double>(fps_cap));
292std::vector<std::string>
295 return m_darknet_classes;
301 ARMARX_INFO <<
"Restoring default parameter values.";
309yolo::Component::bootstrap_darknet()
316 <<
"Bootstrapped Darknet in " << time.toSecondsDouble()
321 const std::string cfgfile = getProperty<std::string>(
"darknet.cfgfile");
322 const std::string weightfile = getProperty<std::string>(
"darknet.weightfile");
325 m_network.load(cfgfile, weightfile);
326 m_network.set_batch(1);
330yolo::Component::run_object_detection_task()
335 visionx::ImageProviderInfo image_provider_info = getImageProvider(m_image_provider_id);
343 while (not m_task_object_detection->isStopped())
350 IceUtil::Time timestamp_last_image;
354 std::lock_guard<std::mutex> lock{m_input_image_mutex};
355 if (not m_image_received)
360 timestamp_last_image = m_timestamp_last_image;
361 const bool ok = ::ImageProcessor::CopyImage(m_input_image.get(), input_image.get());
367 m_image_received =
false;
370 ARMARX_VERBOSE <<
"Announcing which frame will be fed to Darknet.";
371 m_object_detection_listener->announceDetectedObjects(timestamp_last_image.toMicroSeconds());
375 std::vector<DetectedObject> detected_objects;
377 ::IplImage* input_image_conv =
378 ::IplImageAdaptor::Adapt(input_image.get());
381 convertOutput(m_network.predict(*input_image_conv), m_darknet_classes);
382 ::cvReleaseImageHeader(&input_image_conv);
383 ARMARX_VERBOSE <<
"Found " << detected_objects.size() <<
" objects.";
387 m_object_detection_listener->reportDetectedObjects(detected_objects,
388 timestamp_last_image.toMicroSeconds());
391 if (getProperty<bool>(
"yolo.EnableVisualisation"))
395 input_image.get(), detected_objects, m_output_image.get());
396 ::CByteImage* output_images[1] = {m_output_image.get()};
398 resultImageProvider->provideResultImages(output_images,
399 timestamp_last_image.toMicroSeconds());
403 const IceUtil::Time loop_duration = sw.
stop();
409 if (m_minimum_loop_time != ::timestamp_invalid and loop_duration < m_minimum_loop_time)
411 const IceUtil::Time time_delta = m_minimum_loop_time - loop_duration;
412 ARMARX_VERBOSE <<
"FPS cap enabled, suspending thead for " << time_delta <<
".";
413 std::this_thread::sleep_for(std::chrono::milliseconds{time_delta.toMilliSeconds()});
417 ARMARX_INFO <<
"Detection loop properly shut down.";
420std::vector<DetectedObject>
422 const std::vector<std::string>& classes)
424 std::vector<DetectedObject> detections_conv;
426 for (
const darknet::detection& detection : detections)
428 DetectedObject detection_conv;
429 const int class_count =
static_cast<int>(classes.size());
430 detection_conv.classCount = class_count;
431 detection_conv.boundingBox.x = detection.x();
432 detection_conv.boundingBox.y = detection.y();
433 detection_conv.boundingBox.w = detection.w();
434 detection_conv.boundingBox.h = detection.h();
436 for (
const auto& [class_index, certainty] : detection.candidates())
438 ClassCandidate candidate_conv;
439 candidate_conv.classIndex = class_index;
440 candidate_conv.certainty = certainty;
441 candidate_conv.className = classes.at(
static_cast<unsigned int>(class_index));
443 DrawColor24Bit color;
444 std::tie(color.r, color.g, color.b) =
445 darknet::get_color(candidate_conv.classIndex, class_count);
446 candidate_conv.color = color;
448 detection_conv.candidates.push_back(candidate_conv);
451 detections_conv.push_back(detection_conv);
454 return detections_conv;
459 const std::vector<DetectedObject>& detected_objects,
460 ::CByteImage* output_image)
464 ::ImageProcessor::CopyImage(input_image, output_image);
467 for (
const DetectedObject& detected_object : detected_objects)
469 const BoundingBox2D& bounding_box = detected_object.boundingBox;
470 const ClassCandidate& class_candidate =
471 *std::max_element(detected_object.candidates.begin(),
472 detected_object.candidates.end(),
473 [](
const ClassCandidate& c1,
const ClassCandidate& c2) ->
bool
474 { return c1.certainty < c2.certainty; });
476 const DrawColor24Bit& color = class_candidate.color;
480 const float angle = 0;
481 const ::Rectangle2d rectangle{::Vec2d{bounding_box.x * input_image->width,
482 bounding_box.y * input_image->height},
483 bounding_box.w * input_image->width,
484 bounding_box.h * input_image->height,
486 const int thickness = 1;
487 ::PrimitivesDrawer::DrawRectangle(
488 output_image, rectangle, color.r, color.g, color.b, thickness);
493 const std::string object_instance_name = detected_object.objectName ==
""
494 ? class_candidate.className
495 : detected_object.objectName;
496 const double text_scale = .4;
497 const int text_thickness = 1;
499 std::max((bounding_box.x - bounding_box.w / 2.f) * input_image->width, 0.f);
501 std::max((bounding_box.y - bounding_box.h / 2.f) * input_image->height, 0.f);
502 const double text_pos_left =
static_cast<double>(left + 2);
503 const double text_pos_top =
static_cast<double>(top + (top < 10 ? 11 : 1));
506 ::PrimitivesDrawerCV::PutText(output_image,
507 object_instance_name.c_str(),
518 ::PrimitivesDrawerCV::PutText(output_image,
519 object_instance_name.c_str(),
530 ::PrimitivesDrawerCV::PutText(output_image,
531 object_instance_name.c_str(),
550 defs->defineOptionalProperty<std::string>(
"ipc.ObjectDetectionResultTopicName",
551 "YoloDetectionResult",
552 "Topic name for the object detection result");
553 defs->defineRequiredProperty<std::string>(
"ipc.ImageProviderName",
554 "Image provider as data source for Darknet");
555 defs->defineOptionalProperty<
unsigned int>(
556 "ipc.ImageProviderChannel", 0,
"Channel of the image provider with RGB images to consider");
559 defs->defineOptionalProperty<
bool>(
560 "yolo.EnableVisualisation",
562 "If set to true, a visualisation of the output will be provided under the topic defined in "
563 "`ipc.ObjectDetectionResultTopicName`");
564 defs->defineOptionalProperty<
float>(
567 "Cap the main loop to a fixed FPS rate to save resources. Values <= 0 disable the cap, so "
568 "that only the hardware limits the FPS");
571 defs->defineOptionalProperty<
double>(
572 "darknet.thresh", .5,
"Default thresh. May be overridden at runtime");
573 defs->defineOptionalProperty<
double>(
574 "darknet.hier_thresh", .5,
"Default hier_thresh. May be overridden at runtime");
575 defs->defineOptionalProperty<
double>(
576 "darknet.nms", .45,
"Default nms. May be overridden at runtime");
577 defs->defineRequiredProperty<std::string>(
"darknet.cfgfile",
"Darknet configuration file");
578 defs->defineRequiredProperty<std::string>(
"darknet.weightfile",
"Darknet weight file");
579 defs->defineRequiredProperty<std::string>(
581 "File where the classes are listed (usually a '*.names' file in Darknet's data-directory)");
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Default component property definition container.
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Property< PropertyType > getProperty(const std::string &name)
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
TopicProxyType getTopic(const std::string &name)
Returns a proxy of the specified topic.
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
void enableResultImages(int numberImages, ImageDimension imageDimension, ImageType imageType, const std::string &name="")
Enables visualization.
void usingImageProvider(std::string name)
Registers a delayed topic subscription and a delayed provider proxy retrieval which all will be avail...
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.
static const std::string default_name
virtual double getNms(const Ice::Current &) override
Returns the currently set nms parameter.
virtual void onConnectImageProcessor() override
virtual void setThresh(double thresh, const Ice::Current &) override
Sets the new thresh parameter.
virtual void onExitImageProcessor() override
static void renderOutput(const ::CByteImage *inputImage, const std::vector< DetectedObject > &detectedObjects, ::CByteImage *outputImage)
Renders the objects detectedObjects, belonging to one of a class defined in classes onto the outputIm...
virtual void setHierThresh(double hier_thresh, const Ice::Current &) override
Sets the new hierThresh parameter.
virtual void setNms(double nms, const Ice::Current &) override
Sets the new nms parameter.
virtual void process() override
virtual float getFpsCap(const Ice::Current &) override
virtual ~Component() override
virtual void setFpsCap(float fps_cap, const Ice::Current &=Ice::Current{}) override
virtual void onInitImageProcessor() override
virtual armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
static std::vector< DetectedObject > convertOutput(const std::vector< darknet::detection > &detections, const std::vector< std::string > &classes)
Converts Darknet types to VisionX types.
static std::string GetDefaultName()
virtual double getThresh(const Ice::Current &) override
Returns the currently set thresh parameter.
virtual void onDisconnectImageProcessor() override
virtual std::vector< std::string > getClasses(const Ice::Current &) override
Returns the list of all known classes.
virtual double getHierThresh(const Ice::Current &) override
Returns the currently set hierThresh parameter.
virtual std::string getDefaultName() const override
Retrieve default name of component.
virtual void restoreDefaults(const Ice::Current &=Ice::Current{}) override
Restores the default parameter values.
#define ARMARX_INFO
The normal logging level.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
std::unique_ptr< CByteImage > CByteImageUPtr
double angle(const Point &a, const Point &b, const Point &c)