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>
59 IceUtil::Time timestamp_invalid = IceUtil::Time::milliSeconds(-1);
62 const std::string yolo::Component::default_name =
"Yolo";
64 yolo::Component::~Component()
70 yolo::Component::onInitImageProcessor()
75 m_image_received =
false;
78 m_image_provider_id = getProperty<std::string>(
"ipc.ImageProviderName");
79 ARMARX_VERBOSE <<
"Using image provider with ID '" << m_image_provider_id <<
"'.";
80 usingImageProvider(m_image_provider_id);
83 m_image_provider_channel = getProperty<unsigned int>(
"ipc.ImageProviderChannel");
87 std::string topic_name = getProperty<std::string>(
"ipc.ObjectDetectionResultTopicName");
88 offeringTopic(topic_name);
93 std::ifstream infile{getProperty<std::string>(
"darknet.namefile")};
95 while (std::getline(infile, line))
97 m_darknet_classes.push_back(line);
100 m_network.class_count(
static_cast<int>(m_darknet_classes.size()));
110 yolo::Component::onConnectImageProcessor()
113 m_image_provider_info = getImageProvider(m_image_provider_id);
114 m_image_provider = getProxy<visionx::ImageProviderInterfacePrx>(m_image_provider_id);
118 const unsigned int num_images =
119 static_cast<unsigned int>(m_image_provider_info.numberImages);
120 m_input_image_buf = new ::CByteImage*[num_images];
121 for (
unsigned int i = 0; i < num_images; ++i)
130 std::string topic_name = getProperty<std::string>(
"ipc.ObjectDetectionResultTopicName");
131 m_object_detection_listener = getTopic<ObjectListener::ProxyType>(topic_name);
134 if (getProperty<bool>(
"yolo.EnableVisualisation"))
136 const int num_images = 1;
137 enableResultImages(num_images,
138 m_image_provider_info.imageFormat.dimension,
139 m_image_provider_info.imageFormat.type);
143 m_task_object_detection =
145 m_task_object_detection->start();
147 ARMARX_INFO <<
"Darknet provider connected. Operation begins now.";
151 yolo::Component::onDisconnectImageProcessor()
155 const bool wait_for_join =
true;
156 m_task_object_detection->stop(wait_for_join);
161 const unsigned int num_images =
162 static_cast<unsigned int>(m_image_provider_info.numberImages);
163 for (
unsigned int i = 0; i < num_images; ++i)
165 delete m_input_image_buf[i];
167 delete[] m_input_image_buf;
174 yolo::Component::onExitImageProcessor()
180 yolo::Component::getDefaultName()
const
182 return yolo::Component::default_name;
186 yolo::Component::process()
191 const IceUtil::Time timeout = IceUtil::Time::milliSeconds(1000);
192 if (not waitForImages(m_image_provider_id,
static_cast<int>(timeout.toMilliSeconds())))
194 ARMARX_WARNING <<
"Timeout while waiting for camera images (>" << timeout <<
")";
199 std::lock_guard<std::mutex> lock{m_input_image_mutex};
201 MetaInfoSizeBasePtr info;
202 int num_images = getImages(m_image_provider_id, m_input_image_buf, info);
203 m_timestamp_last_image = IceUtil::Time::microSeconds(info->timeProvided);
208 ::ImageProcessor::CopyImage(m_input_image_buf[m_image_provider_channel],
209 m_input_image.get());
210 m_image_received =
true;
219 yolo::Component::getThresh(
const Ice::Current&)
221 return static_cast<double>(m_network.thresh());
225 yolo::Component::setThresh(
double thresh,
const Ice::Current&)
227 m_network.thresh(
static_cast<float>(thresh));
232 yolo::Component::getHierThresh(
const Ice::Current&)
234 return static_cast<double>(m_network.hier_thresh());
238 yolo::Component::setHierThresh(
double hier_thresh,
const Ice::Current&)
240 m_network.hier_thresh(
static_cast<float>(hier_thresh));
241 ARMARX_VERBOSE <<
"Parameter hier_thresh is now " << hier_thresh <<
".";
245 yolo::Component::getNms(
const Ice::Current&)
247 return static_cast<double>(m_network.nms());
251 yolo::Component::setNms(
double nms,
const Ice::Current&)
253 m_network.nms(
static_cast<float>(nms));
258 yolo::Component::getFpsCap(
const Ice::Current&)
261 if (m_minimum_loop_time == ::timestamp_invalid)
267 return 1000.f / m_minimum_loop_time.toMilliSeconds();
271 yolo::Component::setFpsCap(
float fps_cap,
const Ice::Current&)
276 m_minimum_loop_time = ::timestamp_invalid;
281 m_minimum_loop_time = IceUtil::Time::milliSecondsDouble(1000. /
static_cast<double>(fps_cap));
284 std::vector<std::string>
285 yolo::Component::getClasses(
const Ice::Current&)
287 return m_darknet_classes;
291 yolo::Component::restoreDefaults(
const Ice::Current&)
293 ARMARX_INFO <<
"Restoring default parameter values.";
294 m_network.thresh(
static_cast<float>(getProperty<double>(
"darknet.thresh")));
295 m_network.hier_thresh(
static_cast<float>(getProperty<double>(
"darknet.hier_thresh")));
296 m_network.nms(
static_cast<float>(getProperty<double>(
"darknet.nms")));
297 setFpsCap(getProperty<float>(
"yolo.FPSCap"));
301 yolo::Component::bootstrap_darknet()
308 <<
"Bootstrapped Darknet in " << time.toSecondsDouble()
313 const std::string cfgfile = getProperty<std::string>(
"darknet.cfgfile");
314 const std::string weightfile = getProperty<std::string>(
"darknet.weightfile");
317 m_network.load(cfgfile, weightfile);
318 m_network.set_batch(1);
322 yolo::Component::run_object_detection_task()
335 while (not m_task_object_detection->isStopped())
346 std::lock_guard<std::mutex> lock{m_input_image_mutex};
347 if (not m_image_received)
352 timestamp_last_image = m_timestamp_last_image;
353 const bool ok = ::ImageProcessor::CopyImage(m_input_image.get(), input_image.get());
359 m_image_received =
false;
362 ARMARX_VERBOSE <<
"Announcing which frame will be fed to Darknet.";
363 m_object_detection_listener->announceDetectedObjects(timestamp_last_image.toMicroSeconds());
367 std::vector<DetectedObject> detected_objects;
369 ::IplImage* input_image_conv =
370 ::IplImageAdaptor::Adapt(input_image.get());
373 convertOutput(m_network.predict(*input_image_conv), m_darknet_classes);
374 ::cvReleaseImageHeader(&input_image_conv);
375 ARMARX_VERBOSE <<
"Found " << detected_objects.size() <<
" objects.";
379 m_object_detection_listener->reportDetectedObjects(detected_objects,
380 timestamp_last_image.toMicroSeconds());
383 if (getProperty<bool>(
"yolo.EnableVisualisation"))
386 yolo::Component::renderOutput(
387 input_image.get(), detected_objects, m_output_image.get());
388 ::CByteImage* output_images[1] = {m_output_image.get()};
390 resultImageProvider->provideResultImages(output_images,
391 timestamp_last_image.toMicroSeconds());
401 if (m_minimum_loop_time != ::timestamp_invalid and loop_duration < m_minimum_loop_time)
403 const IceUtil::Time time_delta = m_minimum_loop_time - loop_duration;
404 ARMARX_VERBOSE <<
"FPS cap enabled, suspending thead for " << time_delta <<
".";
405 std::this_thread::sleep_for(std::chrono::milliseconds{time_delta.toMilliSeconds()});
409 ARMARX_INFO <<
"Detection loop properly shut down.";
412 std::vector<DetectedObject>
413 yolo::Component::convertOutput(
const std::vector<darknet::detection>& detections,
414 const std::vector<std::string>& classes)
416 std::vector<DetectedObject> detections_conv;
418 for (
const darknet::detection& detection : detections)
420 DetectedObject detection_conv;
421 const int class_count =
static_cast<int>(classes.size());
422 detection_conv.classCount = class_count;
423 detection_conv.boundingBox.x = detection.x();
424 detection_conv.boundingBox.y = detection.y();
425 detection_conv.boundingBox.w = detection.w();
426 detection_conv.boundingBox.h = detection.h();
428 for (
const auto& [class_index, certainty] : detection.candidates())
430 ClassCandidate candidate_conv;
431 candidate_conv.classIndex = class_index;
432 candidate_conv.certainty = certainty;
433 candidate_conv.className = classes.at(
static_cast<unsigned int>(class_index));
435 DrawColor24Bit color;
436 std::tie(color.r, color.g, color.b) =
437 darknet::get_color(candidate_conv.classIndex, class_count);
438 candidate_conv.color = color;
440 detection_conv.candidates.push_back(candidate_conv);
443 detections_conv.push_back(detection_conv);
446 return detections_conv;
450 yolo::Component::renderOutput(const ::CByteImage* input_image,
451 const std::vector<DetectedObject>& detected_objects,
452 ::CByteImage* output_image)
456 ::ImageProcessor::CopyImage(input_image, output_image);
459 for (
const DetectedObject& detected_object : detected_objects)
461 const BoundingBox2D& bounding_box = detected_object.boundingBox;
462 const ClassCandidate& class_candidate =
463 *std::max_element(detected_object.candidates.begin(),
464 detected_object.candidates.end(),
465 [](
const ClassCandidate& c1,
const ClassCandidate& c2) ->
bool
466 { return c1.certainty < c2.certainty; });
468 const DrawColor24Bit& color = class_candidate.color;
472 const float angle = 0;
473 const ::Rectangle2d rectangle{
::Vec2d{bounding_box.x * input_image->width,
474 bounding_box.y * input_image->height},
475 bounding_box.w * input_image->width,
476 bounding_box.h * input_image->height,
478 const int thickness = 1;
479 ::PrimitivesDrawer::DrawRectangle(
480 output_image, rectangle, color.r, color.g, color.b, thickness);
485 const std::string object_instance_name = detected_object.objectName ==
""
486 ? class_candidate.className
487 : detected_object.objectName;
488 const double text_scale = .4;
489 const int text_thickness = 1;
491 std::max((bounding_box.x - bounding_box.w / 2.f) * input_image->width, 0.f);
493 std::max((bounding_box.y - bounding_box.h / 2.f) * input_image->height, 0.f);
494 const double text_pos_left =
static_cast<double>(left + 2);
495 const double text_pos_top =
static_cast<double>(top + (top < 10 ? 11 : 1));
498 ::PrimitivesDrawerCV::PutText(output_image,
499 object_instance_name.c_str(),
510 ::PrimitivesDrawerCV::PutText(output_image,
511 object_instance_name.c_str(),
522 ::PrimitivesDrawerCV::PutText(output_image,
523 object_instance_name.c_str(),
537 yolo::Component::createPropertyDefinitions()
543 "YoloDetectionResult",
544 "Topic name for the object detection result");
545 defs->defineRequiredProperty<std::string>(
"ipc.ImageProviderName",
546 "Image provider as data source for Darknet");
547 defs->defineOptionalProperty<
unsigned int>(
548 "ipc.ImageProviderChannel", 0,
"Channel of the image provider with RGB images to consider");
551 defs->defineOptionalProperty<
bool>(
552 "yolo.EnableVisualisation",
554 "If set to true, a visualisation of the output will be provided under the topic defined in "
555 "`ipc.ObjectDetectionResultTopicName`");
556 defs->defineOptionalProperty<
float>(
559 "Cap the main loop to a fixed FPS rate to save resources. Values <= 0 disable the cap, so "
560 "that only the hardware limits the FPS");
563 defs->defineOptionalProperty<
double>(
564 "darknet.thresh", .5,
"Default thresh. May be overridden at runtime");
565 defs->defineOptionalProperty<
double>(
566 "darknet.hier_thresh", .5,
"Default hier_thresh. May be overridden at runtime");
567 defs->defineOptionalProperty<
double>(
568 "darknet.nms", .45,
"Default nms. May be overridden at runtime");
569 defs->defineRequiredProperty<std::string>(
"darknet.cfgfile",
"Darknet configuration file");
570 defs->defineRequiredProperty<std::string>(
"darknet.weightfile",
"Darknet weight file");
571 defs->defineRequiredProperty<std::string>(
573 "File where the classes are listed (usually a '*.names' file in Darknet's data-directory)");