39 #include <Image/ImageProcessor.h>
40 #include <Image/IplImageAdaptor.h>
41 #include <Image/PrimitivesDrawer.h>
42 #include <Image/PrimitivesDrawerCV.h>
45 #include <IceUtil/Time.h>
60 IceUtil::Time timestamp_invalid = IceUtil::Time::milliSeconds(-1);
65 yolo::Component::default_name =
"Yolo";
68 yolo::Component::~Component()
75 yolo::Component::onInitImageProcessor()
80 m_image_received =
false;
83 m_image_provider_id = getProperty<std::string>(
"ipc.ImageProviderName");
84 ARMARX_VERBOSE <<
"Using image provider with ID '" << m_image_provider_id <<
"'.";
85 usingImageProvider(m_image_provider_id);
88 m_image_provider_channel = getProperty<unsigned int>(
"ipc.ImageProviderChannel");
92 std::string topic_name = getProperty<std::string>(
"ipc.ObjectDetectionResultTopicName");
93 offeringTopic(topic_name);
98 std::ifstream infile{getProperty<std::string>(
"darknet.namefile")};
100 while (std::getline(infile, line))
102 m_darknet_classes.push_back(line);
105 m_network.class_count(
static_cast<int>(m_darknet_classes.size()));
116 yolo::Component::onConnectImageProcessor()
119 m_image_provider_info = getImageProvider(m_image_provider_id);
120 m_image_provider = getProxy<visionx::ImageProviderInterfacePrx>(m_image_provider_id);
124 const unsigned int num_images =
125 static_cast<unsigned int>(m_image_provider_info.numberImages);
126 m_input_image_buf = new ::CByteImage*[num_images];
127 for (
unsigned int i = 0; i < num_images; ++i)
136 std::string topic_name = getProperty<std::string>(
"ipc.ObjectDetectionResultTopicName");
137 m_object_detection_listener = getTopic<ObjectListener::ProxyType>(topic_name);
140 if (getProperty<bool>(
"yolo.EnableVisualisation"))
142 const int num_images = 1;
145 m_image_provider_info.imageFormat.dimension,
146 m_image_provider_info.imageFormat.type
153 &yolo::Component::run_object_detection_task);
154 m_task_object_detection->start();
156 ARMARX_INFO <<
"Darknet provider connected. Operation begins now.";
161 yolo::Component::onDisconnectImageProcessor()
165 const bool wait_for_join =
true;
166 m_task_object_detection->stop(wait_for_join);
171 const unsigned int num_images =
172 static_cast<unsigned int>(m_image_provider_info.numberImages);
173 for (
unsigned int i = 0; i < num_images; ++i)
175 delete m_input_image_buf[i];
177 delete[] m_input_image_buf;
185 yolo::Component::onExitImageProcessor()
192 yolo::Component::getDefaultName()
const
194 return yolo::Component::default_name;
199 yolo::Component::process()
204 const IceUtil::Time timeout = IceUtil::Time::milliSeconds(1000);
205 if (not waitForImages(m_image_provider_id,
static_cast<int>(timeout.toMilliSeconds())))
207 ARMARX_WARNING <<
"Timeout while waiting for camera images (>" << timeout <<
")";
212 std::lock_guard<std::mutex> lock{m_input_image_mutex};
214 MetaInfoSizeBasePtr info;
215 int num_images = getImages(m_image_provider_id, m_input_image_buf, info);
216 m_timestamp_last_image = IceUtil::Time::microSeconds(info->timeProvided);
221 ::ImageProcessor::CopyImage(m_input_image_buf[m_image_provider_channel],
222 m_input_image.get());
223 m_image_received =
true;
233 yolo::Component::getThresh(
const Ice::Current&)
235 return static_cast<double>(m_network.thresh());
240 yolo::Component::setThresh(
double thresh,
const Ice::Current&)
242 m_network.thresh(
static_cast<float>(thresh));
248 yolo::Component::getHierThresh(
const Ice::Current&)
250 return static_cast<double>(m_network.hier_thresh());
255 yolo::Component::setHierThresh(
double hier_thresh,
const Ice::Current&)
257 m_network.hier_thresh(
static_cast<float>(hier_thresh));
258 ARMARX_VERBOSE <<
"Parameter hier_thresh is now " << hier_thresh <<
".";
263 yolo::Component::getNms(
const Ice::Current&)
265 return static_cast<double>(m_network.nms());
270 yolo::Component::setNms(
double nms,
const Ice::Current&)
272 m_network.nms(
static_cast<float>(nms));
278 yolo::Component::getFpsCap(
const Ice::Current&)
281 if (m_minimum_loop_time == ::timestamp_invalid)
287 return 1000.f / m_minimum_loop_time.toMilliSeconds();
292 yolo::Component::setFpsCap(
float fps_cap,
const Ice::Current&)
297 m_minimum_loop_time = ::timestamp_invalid;
302 m_minimum_loop_time = IceUtil::Time::milliSecondsDouble(1000. /
static_cast<double>(fps_cap));
306 std::vector<std::string>
307 yolo::Component::getClasses(
const Ice::Current&)
309 return m_darknet_classes;
314 yolo::Component::restoreDefaults(
const Ice::Current&)
316 ARMARX_INFO <<
"Restoring default parameter values.";
317 m_network.thresh(
static_cast<float>(getProperty<double>(
"darknet.thresh")));
318 m_network.hier_thresh(
static_cast<float>(getProperty<double>(
"darknet.hier_thresh")));
319 m_network.nms(
static_cast<float>(getProperty<double>(
"darknet.nms")));
320 setFpsCap(getProperty<float>(
"yolo.FPSCap"));
325 yolo::Component::bootstrap_darknet()
331 ARMARX_INFO << std::fixed << std::setprecision(3) <<
"Bootstrapped Darknet in "
332 << time.toSecondsDouble() <<
" seconds.";
336 const std::string cfgfile = getProperty<std::string>(
"darknet.cfgfile");
337 const std::string weightfile = getProperty<std::string>(
"darknet.weightfile");
340 m_network.load(cfgfile, weightfile);
341 m_network.set_batch(1);
346 yolo::Component::run_object_detection_task()
359 while (not m_task_object_detection->isStopped())
370 std::lock_guard<std::mutex> lock{m_input_image_mutex};
371 if (not m_image_received)
376 timestamp_last_image = m_timestamp_last_image;
377 const bool ok = ::ImageProcessor::CopyImage(m_input_image.get(), input_image.get());
383 m_image_received =
false;
386 ARMARX_VERBOSE <<
"Announcing which frame will be fed to Darknet.";
387 m_object_detection_listener->announceDetectedObjects(timestamp_last_image.toMicroSeconds());
391 std::vector<DetectedObject> detected_objects;
393 ::IplImage* input_image_conv =
394 ::IplImageAdaptor::Adapt(input_image.get());
396 detected_objects = convertOutput(m_network.predict(*input_image_conv),
398 ::cvReleaseImageHeader(&input_image_conv);
399 ARMARX_VERBOSE <<
"Found " << detected_objects.size() <<
" objects.";
403 m_object_detection_listener->reportDetectedObjects(
405 timestamp_last_image.toMicroSeconds()
409 if (getProperty<bool>(
"yolo.EnableVisualisation"))
412 yolo::Component::renderOutput(input_image.get(),
414 m_output_image.get());
415 ::CByteImage* output_images[1] = {m_output_image.get()};
417 resultImageProvider->provideResultImages(
419 timestamp_last_image.toMicroSeconds()
430 if (m_minimum_loop_time != ::timestamp_invalid and loop_duration < m_minimum_loop_time)
432 const IceUtil::Time time_delta = m_minimum_loop_time - loop_duration;
433 ARMARX_VERBOSE <<
"FPS cap enabled, suspending thead for " << time_delta <<
".";
434 std::this_thread::sleep_for(std::chrono::milliseconds{time_delta.toMilliSeconds()});
438 ARMARX_INFO <<
"Detection loop properly shut down.";
442 std::vector<DetectedObject>
443 yolo::Component::convertOutput(
444 const std::vector<darknet::detection>& detections,
445 const std::vector<std::string>& classes)
447 std::vector<DetectedObject> detections_conv;
449 for (
const darknet::detection& detection : detections)
451 DetectedObject detection_conv;
452 const int class_count =
static_cast<int>(classes.size());
453 detection_conv.classCount = class_count;
454 detection_conv.boundingBox.x = detection.x();
455 detection_conv.boundingBox.y = detection.y();
456 detection_conv.boundingBox.w = detection.w();
457 detection_conv.boundingBox.h = detection.h();
459 for (
const auto& [class_index, certainty] : detection.candidates())
461 ClassCandidate candidate_conv;
462 candidate_conv.classIndex = class_index;
463 candidate_conv.certainty = certainty;
464 candidate_conv.className = classes.at(
static_cast<unsigned int>(class_index));
466 DrawColor24Bit color;
467 std::tie(color.r, color.g, color.b) =
468 darknet::get_color(candidate_conv.classIndex, class_count);
469 candidate_conv.color = color;
471 detection_conv.candidates.push_back(candidate_conv);
474 detections_conv.push_back(detection_conv);
477 return detections_conv;
482 yolo::Component::renderOutput(
483 const ::CByteImage* input_image,
484 const std::vector<DetectedObject>& detected_objects,
485 ::CByteImage* output_image)
489 ::ImageProcessor::CopyImage(input_image, output_image);
492 for (
const DetectedObject& detected_object : detected_objects)
494 const BoundingBox2D& bounding_box = detected_object.boundingBox;
495 const ClassCandidate& class_candidate = *std::max_element(
496 detected_object.candidates.begin(),
497 detected_object.candidates.end(),
498 [](
const ClassCandidate & c1,
const ClassCandidate & c2) ->
bool
500 return c1.certainty < c2.certainty;
503 const DrawColor24Bit& color = class_candidate.color;
507 const float angle = 0;
508 const ::Rectangle2d rectangle
511 bounding_box.x* input_image->width,
512 bounding_box.y* input_image->height
514 bounding_box.w* input_image->width,
515 bounding_box.h* input_image->height,
518 const int thickness = 1;
519 ::PrimitivesDrawer::DrawRectangle(
522 color.r, color.g, color.b,
529 const std::string object_instance_name = detected_object.objectName ==
"" ?
530 class_candidate.className : detected_object.objectName;
531 const double text_scale = .4;
532 const int text_thickness = 1;
534 std::max((bounding_box.x - bounding_box.w / 2.f) * input_image->width, 0.f);
536 std::max((bounding_box.y - bounding_box.h / 2.f) * input_image->height, 0.f);
537 const double text_pos_left =
static_cast<double>(left + 2);
538 const double text_pos_top =
static_cast<double>(top + (top < 10 ? 11 : 1));
541 ::PrimitivesDrawerCV::PutText(
543 object_instance_name.c_str(),
544 text_pos_left, text_pos_top,
545 text_scale, text_scale,
546 color.r, color.g, color.b,
551 ::PrimitivesDrawerCV::PutText(
553 object_instance_name.c_str(),
554 text_pos_left, text_pos_top,
555 text_scale, text_scale,
561 ::PrimitivesDrawerCV::PutText(
563 object_instance_name.c_str(),
564 text_pos_left, text_pos_top,
565 text_scale, text_scale,
575 yolo::Component::createPropertyDefinitions()
581 "ipc.ObjectDetectionResultTopicName",
582 "YoloDetectionResult",
583 "Topic name for the object detection result"
585 defs->defineRequiredProperty<std::string>(
586 "ipc.ImageProviderName",
587 "Image provider as data source for Darknet"
589 defs->defineOptionalProperty<
unsigned int>(
590 "ipc.ImageProviderChannel",
592 "Channel of the image provider with RGB images to consider"
596 defs->defineOptionalProperty<
bool>(
597 "yolo.EnableVisualisation",
599 "If set to true, a visualisation of the output will be provided under the topic defined in "
600 "`ipc.ObjectDetectionResultTopicName`"
602 defs->defineOptionalProperty<
float>(
605 "Cap the main loop to a fixed FPS rate to save resources. Values <= 0 disable the cap, so "
606 "that only the hardware limits the FPS"
610 defs->defineOptionalProperty<
double>(
611 "darknet.thresh", .5,
612 "Default thresh. May be overridden at runtime"
614 defs->defineOptionalProperty<
double>(
615 "darknet.hier_thresh",
617 "Default hier_thresh. May be overridden at runtime"
619 defs->defineOptionalProperty<
double>(
622 "Default nms. May be overridden at runtime"
624 defs->defineRequiredProperty<std::string>(
626 "Darknet configuration file"
628 defs->defineRequiredProperty<std::string>(
629 "darknet.weightfile",
630 "Darknet weight file"
632 defs->defineRequiredProperty<std::string>(
634 "File where the classes are listed (usually a '*.names' file in Darknet's data-directory)"