OpenShot Library | libopenshot  0.7.0
ObjectDetection.cpp
Go to the documentation of this file.
1 
10 // Copyright (c) 2008-2019 OpenShot Studios, LLC
11 //
12 // SPDX-License-Identifier: LGPL-3.0-or-later
13 
14 #include <fstream>
15 #include <iostream>
16 #include <algorithm>
17 
19 #include "effects/Tracker.h"
20 #include "Exceptions.h"
21 #include "Timeline.h"
22 #include "objdetectdata.pb.h"
23 
24 #include <QImage>
25 #include <QPainter>
26 #include <QBrush>
27 #include <QColor>
28 #include <QRectF>
29 #include <QString>
30 #include <QStringList>
31 using namespace std;
32 using namespace openshot;
33 
34 namespace {
35 bool is_all_objects_key(const std::string& name)
36 {
37  const QString normalized = QString::fromStdString(name).trimmed().toLower();
38  return normalized == "all" || normalized == "*" || normalized == "-1";
39 }
40 
41 std::shared_ptr<TrackedObjectBBox> make_all_objects_properties(
42  const std::shared_ptr<TrackedObjectBase>& source,
43  bool has_mask_data = false)
44 {
45  auto properties = std::make_shared<TrackedObjectBBox>();
46  if (source) {
47  properties->SetJsonValue(source->JsonValue());
48  auto source_bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(source);
49  has_mask_data = has_mask_data || (source_bbox && source_bbox->HasMaskData());
50  }
51  if (has_mask_data) {
52  properties->AddMask(0, ObjectMaskData{1, 1, {0, 1}});
53  }
54  properties->Id("All Objects");
55  return properties;
56 }
57 
58 bool has_tracked_object_mask_data(
59  const std::map<int, std::shared_ptr<TrackedObjectBase>>& tracked_objects)
60 {
61  for (const auto& tracked_object : tracked_objects) {
62  auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(tracked_object.second);
63  if (bbox && bbox->HasMaskData())
64  return true;
65  }
66  return false;
67 }
68 
69 cv::Scalar default_class_color(const std::string& class_name, int index)
70 {
71  const QString normalized = QString::fromStdString(class_name).trimmed().toLower();
72 
73  // RGB values. Keep common object-detection classes on clear, saturated colors
74  // instead of the previous deterministic random palette.
75  if (normalized == "person") return cv::Scalar(83, 160, 237);
76  if (normalized == "car") return cv::Scalar(42, 200, 185);
77  if (normalized == "truck") return cv::Scalar(239, 126, 92);
78  if (normalized == "bus") return cv::Scalar(250, 196, 72);
79  if (normalized == "bicycle") return cv::Scalar(122, 201, 67);
80  if (normalized == "motorbike" || normalized == "motorcycle") return cv::Scalar(180, 126, 235);
81  if (normalized == "dog") return cv::Scalar(237, 92, 140);
82  if (normalized == "cat") return cv::Scalar(101, 214, 128);
83 
84  static const cv::Scalar palette[] = {
85  cv::Scalar(83, 160, 237),
86  cv::Scalar(42, 200, 185),
87  cv::Scalar(239, 126, 92),
88  cv::Scalar(250, 196, 72),
89  cv::Scalar(122, 201, 67),
90  cv::Scalar(180, 126, 235),
91  cv::Scalar(237, 92, 140),
92  cv::Scalar(72, 190, 230),
93  };
94  return palette[index % (sizeof(palette) / sizeof(palette[0]))];
95 }
96 
97 QImage alpha_mask_image_from_rle(const ObjectMaskData& mask)
98 {
99  QImage image(mask.width, mask.height, QImage::Format_ARGB32_Premultiplied);
100  image.fill(Qt::transparent);
101  if (!mask.HasData())
102  return image;
103 
104  QRgb* data = reinterpret_cast<QRgb*>(image.bits());
105  const int total = mask.width * mask.height;
106  int offset = 0;
107  bool value = false;
108  for (uint32_t count : mask.rle) {
109  const int end = std::min(total, offset + static_cast<int>(count));
110  if (value) {
111  std::fill(data + offset, data + end, qRgba(255, 255, 255, 255));
112  }
113  offset = end;
114  value = !value;
115  if (offset >= total)
116  break;
117  }
118  return image;
119 }
120 }
121 
122 
123 // Default constructor
124 ObjectDetection::ObjectDetection()
125  : display_box_text(1.0)
126  , display_boxes(1.0)
127 {
128  // Init effect metadata
129  init_effect_details();
130 
131  // We haven’t loaded any protobuf yet, so there's nothing to pick.
132  selectedObjectIndex = -1;
133 }
134 
135 // Init effect settings
136 void ObjectDetection::init_effect_details()
137 {
139  InitEffectInfo();
140 
142  info.class_name = "ObjectDetection";
143  info.name = "Object Detector";
144  info.description = "Detect objects through the video.";
145  info.has_audio = false;
146  info.has_video = true;
147  info.has_tracked_object = true;
148 }
149 
150 // This method is required for all derived classes of EffectBase, and returns a
151 // modified openshot::Frame object
152 std::shared_ptr<Frame> ObjectDetection::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number) {
153  // Get the frame's QImage
154  std::shared_ptr<QImage> frame_image = frame->GetImage();
155 
156  // Check if frame isn't NULL
157  if(!frame_image || frame_image->isNull()) {
158  return frame;
159  }
160 
161  QPainter painter(frame_image.get());
162  painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
163 
164  if (detectionsData.find(frame_number) != detectionsData.end()) {
165  DetectionData detections = detectionsData[frame_number];
166  for (int i = 0; i < detections.boxes.size(); i++) {
167  if (detections.confidences.at(i) < confidence_threshold ||
168  (!display_classes.empty() &&
169  std::find(display_classes.begin(), display_classes.end(), classNames[detections.classIds.at(i)]) == display_classes.end())) {
170  continue;
171  }
172 
173  int objectId = detections.objectIds.at(i);
174  auto trackedObject_it = trackedObjects.find(objectId);
175 
176  if (trackedObject_it != trackedObjects.end()) {
177  std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(trackedObject_it->second);
178 
179  Clip* parentClip = (Clip*) trackedObject->ParentClip();
180  if (parentClip && trackedObject->Contains(frame_number) && trackedObject->visible.GetValue(frame_number) == 1) {
181  BBox trackedBox = trackedObject->GetBox(frame_number);
182  QRectF boxRect((trackedBox.cx - trackedBox.width / 2) * frame_image->width(),
183  (trackedBox.cy - trackedBox.height / 2) * frame_image->height(),
184  trackedBox.width * frame_image->width(),
185  trackedBox.height * frame_image->height());
186 
187  // Get properties of tracked object (i.e. colors, stroke width, etc...)
188  std::vector<int> stroke_rgba = trackedObject->stroke.GetColorRGBA(frame_number);
189  std::vector<int> bg_rgba = trackedObject->background.GetColorRGBA(frame_number);
190  float stroke_alpha = trackedObject->stroke_alpha.GetValue(frame_number);
191  float bg_alpha = trackedObject->background_alpha.GetValue(frame_number);
192  float bg_corner = trackedObject->background_corner.GetValue(frame_number);
193 
194  // Set the pen for the border
195  QPen pen(QColor(stroke_rgba[0], stroke_rgba[1], stroke_rgba[2], 255 * stroke_alpha));
196  pen.setWidthF(trackedObject->ScaledStrokeWidth(
197  frame_number, frame_image->width(), frame_image->height()));
198  painter.setPen(pen);
199 
200  // Set the brush for the background
201  QBrush brush(QColor(bg_rgba[0], bg_rgba[1], bg_rgba[2], 255 * bg_alpha));
202  painter.setBrush(brush);
203 
204  if (display_boxes.GetValue(frame_number) == 1 && trackedObject->draw_box.GetValue(frame_number) == 1) {
205  // Only draw boxes if both properties are set to YES (draw all boxes, and draw box of the selected box)
206  painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
207  }
208 
209  ObjectMaskData object_mask = trackedObject->GetMask(frame_number, 5);
210  if (object_mask.HasData() && trackedObject->draw_mask.GetValue(frame_number) == 1) {
211  QImage mask = alpha_mask_image_from_rle(object_mask)
212  .scaled(frame_image->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
213  std::vector<int> mask_rgba = trackedObject->mask_color.GetColorRGBA(frame_number);
214  float mask_alpha = trackedObject->mask_alpha.GetValue(frame_number);
215  QColor mask_color(mask_rgba[0], mask_rgba[1], mask_rgba[2], 255 * mask_alpha);
216  QImage overlay(frame_image->size(), QImage::Format_ARGB32_Premultiplied);
217  overlay.fill(Qt::transparent);
218  QPainter overlay_painter(&overlay);
219  overlay_painter.setCompositionMode(QPainter::CompositionMode_Source);
220  overlay_painter.fillRect(overlay.rect(), mask_color);
221  overlay_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
222  overlay_painter.drawImage(0, 0, mask);
223  overlay_painter.end();
224  painter.drawImage(0, 0, overlay);
225  }
226 
227  if(display_box_text.GetValue(frame_number) == 1 && trackedObject->draw_text.GetValue(frame_number) == 1) {
228  // Draw text label above bounding box
229  // Get the confidence and classId for the current detection
230  int classId = detections.classIds.at(i);
231 
232  // Get the label for the class name and its confidence
233  QString label = QString::number(objectId);
234  if (!classNames.empty()) {
235  label = QString::fromStdString(classNames[classId]) + ":" + label;
236  }
237 
238  // Set up the painter, font, and pen
239  QFont font;
240  font.setPixelSize(14);
241  painter.setFont(font);
242 
243  // Calculate the size of the text
244  QFontMetrics fontMetrics(font);
245  QSize labelSize = fontMetrics.size(Qt::TextSingleLine, label);
246 
247  // Define the top left point of the rectangle
248  double left = boxRect.center().x() - (labelSize.width() / 2.0);
249  double top = std::max(static_cast<int>(boxRect.top()), labelSize.height()) - 4.0;
250 
251  // Draw the text
252  painter.drawText(QPointF(left, top), label);
253  }
254  }
255  }
256  }
257  }
258 
259  painter.end();
260 
261  // The frame's QImage has been modified in place, so we just return the original frame
262  return frame;
263 }
264 
265 // Load protobuf data file
266 bool ObjectDetection::LoadObjDetectdData(std::string inputFilePath)
267 {
268  // Parse the file
269  pb_objdetect::ObjDetect objMessage;
270  std::fstream input(inputFilePath, std::ios::in | std::ios::binary);
271  if (!objMessage.ParseFromIstream(&input)) {
272  std::cerr << "Failed to parse protobuf message." << std::endl;
273  return false;
274  }
275 
276  // Clear out any old state
277  classNames.clear();
278  classesColor.clear();
279  detectionsData.clear();
280  trackedObjects.clear();
281 
282  // Seed colors for each class
283  for (int i = 0; i < objMessage.classnames_size(); ++i) {
284  const std::string class_name = objMessage.classnames(i);
285  classNames.push_back(class_name);
286  classesColor.push_back(default_class_color(class_name, i));
287  }
288 
289  // Walk every frame in the protobuf
290  for (size_t fi = 0; fi < objMessage.frame_size(); ++fi) {
291  const auto &pbFrame = objMessage.frame(fi);
292  size_t frameId = pbFrame.id();
293 
294  // Buffers for DetectionData
295  std::vector<int> classIds;
296  std::vector<float> confidences;
297  std::vector<cv::Rect_<float>> boxes;
298  std::vector<int> objectIds;
299  std::vector<ObjectMaskData> masks;
300 
301  // For each bounding box in this frame
302  for (int di = 0; di < pbFrame.bounding_box_size(); ++di) {
303  const auto &b = pbFrame.bounding_box(di);
304  float x = b.x(), y = b.y(), w = b.w(), h = b.h();
305  int classId = b.classid();
306  float confidence= b.confidence();
307  int objectId = b.objectid();
308  ObjectMaskData mask;
309  if (b.has_mask()) {
310  mask.width = b.mask().width();
311  mask.height = b.mask().height();
312  for (int rleIndex = 0; rleIndex < b.mask().rle_size(); ++rleIndex) {
313  mask.rle.push_back(b.mask().rle(rleIndex));
314  }
315  }
316 
317  // Record for DetectionData
318  classIds.push_back(classId);
319  confidences.push_back(confidence);
320  boxes.emplace_back(x, y, w, h);
321  objectIds.push_back(objectId);
322  masks.push_back(mask);
323 
324  // Either append to an existing TrackedObjectBBox…
325  auto it = trackedObjects.find(objectId);
326  if (it != trackedObjects.end()) {
327  it->second->AddBox(frameId, x + w/2, y + h/2, w, h, 0.0);
328  auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(it->second);
329  if (bbox && mask.HasData())
330  bbox->AddMask(frameId, mask);
331  }
332  else {
333  // …or create a brand-new one
334  TrackedObjectBBox tmpObj(
335  (int)classesColor[classId][0],
336  (int)classesColor[classId][1],
337  (int)classesColor[classId][2],
338  /*alpha=*/0
339  );
340  tmpObj.stroke_alpha = Keyframe(1.0);
341  tmpObj.background_alpha = Keyframe(0.15);
342  tmpObj.AddBox(frameId, x + w/2, y + h/2, w, h, 0.0);
343  if (mask.HasData())
344  tmpObj.AddMask(frameId, mask);
345 
346  auto ptr = std::make_shared<TrackedObjectBBox>(tmpObj);
347  ptr->ParentClip(this->ParentClip());
348 
349  // Prefix with effect UUID for a unique string ID
350  std::string prefix = this->Id();
351  if (!prefix.empty())
352  prefix += "-";
353  ptr->Id(prefix + std::to_string(objectId));
354  trackedObjects.emplace(objectId, ptr);
355  }
356  }
357 
358  // Save the DetectionData for this frame
359  detectionsData[frameId] = DetectionData(
360  classIds, confidences, boxes, frameId, objectIds, masks
361  );
362  }
363 
364  google::protobuf::ShutdownProtobufLibrary();
365 
366  // Default to the pseudo-selection that edits every tracked object.
367  if (!trackedObjects.empty()) {
368  selectedObjectIndex = -1;
369  }
370 
371  return true;
372 }
373 
374 // Get the indexes and IDs of all visible objects in the given frame
375 std::string ObjectDetection::GetVisibleObjects(int64_t frame_number) const{
376 
377  // Initialize the JSON objects
378  Json::Value root;
379  root["visible_objects_index"] = Json::Value(Json::arrayValue);
380  root["visible_objects_id"] = Json::Value(Json::arrayValue);
381  root["visible_class_names"] = Json::Value(Json::arrayValue);
382 
383  // Check if track data exists for the requested frame
384  if (detectionsData.find(frame_number) == detectionsData.end()){
385  return root.toStyledString();
386  }
387  DetectionData detections = detectionsData.at(frame_number);
388 
389  if (!trackedObjects.empty()) {
390  root["visible_objects_index"].append(-1);
391  root["visible_objects_id"].append("All Objects");
392  root["visible_class_names"].append("All Objects");
393  }
394 
395  // Iterate through the tracked objects
396  for(int i = 0; i<detections.boxes.size(); i++){
397  // Does not show boxes with confidence below the threshold
398  if(detections.confidences.at(i) < confidence_threshold){
399  continue;
400  }
401 
402  // Get class name of tracked object
403  auto className = classNames[detections.classIds.at(i)];
404 
405  // If display_classes is not empty, check if className is in it
406  if (!display_classes.empty()) {
407  auto it = std::find(display_classes.begin(), display_classes.end(), className);
408  if (it == display_classes.end()) {
409  // If not in display_classes, skip this detection
410  continue;
411  }
412  root["visible_class_names"].append(className);
413  } else {
414  // include all class names
415  root["visible_class_names"].append(className);
416  }
417 
418  int objectId = detections.objectIds.at(i);
419  // Search for the object in the trackedObjects map
420  auto trackedObject = trackedObjects.find(objectId);
421 
422  // Get the tracked object JSON properties for this frame
423  Json::Value trackedObjectJSON = trackedObject->second->PropertiesJSON(frame_number);
424 
425  if (trackedObjectJSON["visible"]["value"].asBool() &&
426  trackedObject->second->ExactlyContains(frame_number)){
427  // Save the object's index and ID if it's visible in this frame
428  root["visible_objects_index"].append(trackedObject->first);
429  root["visible_objects_id"].append(trackedObject->second->Id());
430  }
431  }
432 
433  return root.toStyledString();
434 }
435 
436 std::shared_ptr<QImage> ObjectDetection::TrackedObjectMask(std::shared_ptr<QImage> target_image, int64_t frame_number) const {
437  if (!target_image || target_image->isNull())
438  return {};
439 
440  auto detections_it = detectionsData.find(frame_number);
441  if (detections_it == detectionsData.end())
442  return {};
443 
444  auto mask_image = std::make_shared<QImage>(
445  target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
446  mask_image->fill(QColor(0, 0, 0, 255));
447 
448  QPainter painter(mask_image.get());
449  painter.setRenderHint(QPainter::Antialiasing, true);
450  painter.setPen(Qt::NoPen);
451  painter.setBrush(QBrush(QColor(255, 255, 255, 255)));
452 
453  bool drew_any_box = false;
454  const DetectionData& detections = detections_it->second;
455  for (int i = 0; i < detections.boxes.size(); i++) {
456  if (detections.confidences.at(i) < confidence_threshold)
457  continue;
458 
459  const int class_id = detections.classIds.at(i);
460  if (class_id < 0 || class_id >= classNames.size())
461  continue;
462 
463  const std::string class_name = classNames[class_id];
464  if (!display_classes.empty() &&
465  std::find(display_classes.begin(), display_classes.end(), class_name) == display_classes.end()) {
466  continue;
467  }
468 
469  int object_id = detections.objectIds.at(i);
470  auto tracked_object_it = trackedObjects.find(object_id);
471  if (tracked_object_it == trackedObjects.end() || !tracked_object_it->second)
472  continue;
473 
474  auto tracked_object = std::static_pointer_cast<TrackedObjectBBox>(tracked_object_it->second);
475  if (!tracked_object->ExactlyContains(frame_number) ||
476  tracked_object->visible.GetValue(frame_number) != 1) {
477  continue;
478  }
479 
480  BBox box = tracked_object->GetBox(frame_number);
481  if (box.width <= 0.0f || box.height <= 0.0f || box.cx < 0.0f || box.cy < 0.0f)
482  continue;
483 
484  ObjectMaskData object_mask = tracked_object->GetMask(frame_number, 5);
485  if (object_mask.HasData() && tracked_object->draw_mask.GetValue(frame_number) == 1) {
486  QImage mask = alpha_mask_image_from_rle(object_mask)
487  .scaled(target_image->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
488  painter.drawImage(0, 0, mask);
489  drew_any_box = true;
490  continue;
491  }
492 
493  const double x = (box.cx - box.width / 2.0) * target_image->width();
494  const double y = (box.cy - box.height / 2.0) * target_image->height();
495  const double w = box.width * target_image->width();
496  const double h = box.height * target_image->height();
497  const double corner = tracked_object->background_corner.GetValue(frame_number);
498  painter.drawRoundedRect(QRectF(x, y, w, h), corner, corner);
499  drew_any_box = true;
500  }
501 
502  painter.end();
503  if (!drew_any_box)
504  return {};
505  return mask_image;
506 }
507 
508 // Generate JSON string of this object
509 std::string ObjectDetection::Json() const {
510 
511  // Return formatted string
512  return JsonValue().toStyledString();
513 }
514 
515 // Generate Json::Value for this object
516 Json::Value ObjectDetection::JsonValue() const {
517 
518  // Create root json object
519  Json::Value root = EffectBase::JsonValue(); // get parent properties
520  root["type"] = info.class_name;
521  root["protobuf_data_path"] = protobuf_data_path;
522  root["selected_object_index"] = selectedObjectIndex;
523  root["confidence_threshold"] = confidence_threshold;
524  root["display_box_text"] = display_box_text.JsonValue();
525  root["display_boxes"] = display_boxes.JsonValue();
526 
527  // Add tracked object's IDs to root
528  Json::Value objects;
529  for (auto const& trackedObject : trackedObjects){
530  Json::Value trackedObjectJSON = trackedObject.second->JsonValue();
531  // add object json
532  objects[trackedObject.second->Id()] = trackedObjectJSON;
533  }
534  root["objects"] = objects;
535 
536  // return JsonValue
537  return root;
538 }
539 
540 // Load JSON string into this object
541 void ObjectDetection::SetJson(const std::string value) {
542 
543  // Parse JSON string into JSON objects
544  try
545  {
546  const Json::Value root = openshot::stringToJson(value);
547  // Set all values that match
548  SetJsonValue(root);
549  }
550  catch (const std::exception& e)
551  {
552  // Error parsing JSON (or missing keys)
553  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
554  }
555 }
556 
557 // Load Json::Value into this object
558 void ObjectDetection::SetJsonValue(const Json::Value root)
559 {
560  // Parent properties
562 
563  // If a protobuf path is provided, load & prefix IDs
564  if (!root["protobuf_data_path"].isNull()) {
565  std::string new_path = root["protobuf_data_path"].asString();
566  if (protobuf_data_path != new_path || trackedObjects.empty()) {
567  protobuf_data_path = new_path;
568  allObjectsProperties.reset();
569  if (!LoadObjDetectdData(protobuf_data_path)) {
570  throw InvalidFile("Invalid protobuf data path", "");
571  }
572  }
573  }
574 
575  // Selected index, thresholds, UI flags, filters, etc.
576  if (!root["selected_object_index"].isNull())
577  selectedObjectIndex = root["selected_object_index"].asInt();
578  if (!root["confidence_threshold"].isNull())
579  confidence_threshold = root["confidence_threshold"].asFloat();
580  if (!root["display_box_text"].isNull())
581  display_box_text.SetJsonValue(root["display_box_text"]);
582  if (!root["display_boxes"].isNull())
583  display_boxes.SetJsonValue(root["display_boxes"]);
584 
585  if (!root["class_filter"].isNull()) {
586  class_filter = root["class_filter"].asString();
587  QStringList parts = QString::fromStdString(class_filter).split(',');
588  display_classes.clear();
589  for (auto &p : parts) {
590  auto s = p.trimmed().toLower();
591  if (!s.isEmpty()) {
592  display_classes.push_back(s.toStdString());
593  }
594  }
595  }
596 
597  // Apply any per-object overrides
598  if (!root["objects"].isNull()) {
599  // Iterate over the supplied objects (indexed by id or position)
600  const auto memberNames = root["objects"].getMemberNames();
601  for (const auto& name : memberNames)
602  {
603  if (is_all_objects_key(name)) {
604  if (!allObjectsProperties) {
605  std::shared_ptr<TrackedObjectBase> firstObject;
606  if (!trackedObjects.empty())
607  firstObject = trackedObjects.begin()->second;
608  allObjectsProperties = make_all_objects_properties(
609  firstObject, has_tracked_object_mask_data(trackedObjects));
610  }
611  allObjectsProperties->SetJsonValue(root["objects"][name]);
612  for (auto& trackedObject : trackedObjects) {
613  if (trackedObject.second)
614  trackedObject.second->SetJsonValue(root["objects"][name]);
615  }
616  }
617  }
618 
619  for (const auto& name : memberNames)
620  {
621  if (is_all_objects_key(name))
622  continue;
623  // Determine the numeric index of this object
624  int index = -1;
625  bool numeric_key = std::all_of(name.begin(), name.end(), ::isdigit);
626  if (numeric_key) {
627  index = std::stoi(name);
628  }
629  else
630  {
631  size_t pos = name.find_last_of('-');
632  if (pos != std::string::npos) {
633  try {
634  index = std::stoi(name.substr(pos + 1));
635  } catch (...) {
636  index = -1;
637  }
638  }
639  }
640 
641  auto obj_it = trackedObjects.find(index);
642  if (obj_it != trackedObjects.end() && obj_it->second) {
643  // Update object id if provided as a non-numeric key
644  if (!numeric_key)
645  obj_it->second->Id(name);
646  obj_it->second->SetJsonValue(root["objects"][name]);
647  }
648  }
649  }
650  // Set the tracked object's ids (legacy format)
651  if (!root["objects_id"].isNull()) {
652  for (auto& kv : trackedObjects) {
653  if (!root["objects_id"][kv.first].isNull())
654  kv.second->Id(root["objects_id"][kv.first].asString());
655  }
656  }
657 }
658 
659 // Get all properties for a specific frame
660 std::string ObjectDetection::PropertiesJSON(int64_t requested_frame) const {
661 
662  // Generate JSON properties list
663  Json::Value root = BasePropertiesJSON(requested_frame);
664 
665  Json::Value objects;
666  if(selectedObjectIndex == -1 && !trackedObjects.empty()){
667  auto selectedObject = allObjectsProperties
668  ? allObjectsProperties
669  : make_all_objects_properties(trackedObjects.begin()->second, has_tracked_object_mask_data(trackedObjects));
670  if (selectedObject){
671  Json::Value trackedObjectJSON = selectedObject->PropertiesJSON(requested_frame);
672  trackedObjectJSON["box_id"]["memo"] = "All Objects";
673  trackedObjectJSON.removeMember("x1");
674  trackedObjectJSON.removeMember("y1");
675  trackedObjectJSON.removeMember("x2");
676  trackedObjectJSON.removeMember("y2");
677  objects["all"] = trackedObjectJSON;
678  }
679  }
680  else if(trackedObjects.count(selectedObjectIndex) != 0){
681  auto selectedObject = trackedObjects.at(selectedObjectIndex);
682  if (selectedObject){
683  Json::Value trackedObjectJSON = selectedObject->PropertiesJSON(requested_frame);
684  // add object json
685  objects[selectedObject->Id()] = trackedObjectJSON;
686  }
687  }
688  root["objects"] = objects;
689 
690  root["selected_object_index"] = add_property_json("Selected Object", selectedObjectIndex, "int", "", NULL, -1, 200, false, requested_frame);
691  root["confidence_threshold"] = add_property_json("Confidence Threshold", confidence_threshold, "float", "", NULL, 0, 1, false, requested_frame);
692  root["class_filter"] = add_property_json("Class Filter", 0.0, "string", class_filter, NULL, -1, -1, false, requested_frame);
693 
694  // Return formatted string
695  return root.toStyledString();
696 }
openshot::ClipBase::add_property_json
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
Definition: ClipBase.cpp:96
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::TrackedObjectBBox::stroke_alpha
Keyframe stroke_alpha
Stroke box opacity.
Definition: TrackedObjectBBox.h:156
openshot::ObjectMaskData::height
int height
Definition: TrackedObjectBBox.h:30
openshot::ObjectDetection::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: ObjectDetection.cpp:541
openshot::ObjectMaskData::width
int width
Definition: TrackedObjectBBox.h:29
openshot::ObjectDetection::GetFrame
std::shared_ptr< Frame > GetFrame(std::shared_ptr< Frame > frame, int64_t frame_number) override
This method is required for all derived classes of EffectBase, and returns a modified openshot::Frame...
Definition: ObjectDetection.cpp:152
openshot::TrackedObjectBBox::AddBox
void AddBox(int64_t _frame_num, float _cx, float _cy, float _width, float _height, float _angle) override
Add a BBox to the BoxVec map.
Definition: TrackedObjectBBox.cpp:137
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
openshot::ObjectDetection::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: ObjectDetection.cpp:516
openshot::BBox::height
float height
bounding box height
Definition: TrackedObjectBBox.h:51
DetectionData
Definition: ObjectDetection.h:32
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::EffectBase::ParentClip
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Definition: EffectBase.cpp:676
ObjectDetection.h
Header file for Object Detection effect class.
openshot::Clip
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:102
openshot::BBox::cy
float cy
y-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:49
Timeline.h
Header file for Timeline class.
DetectionData::objectIds
std::vector< int > objectIds
Definition: ObjectDetection.h:53
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::ObjectMaskData::HasData
bool HasData() const
Definition: TrackedObjectBBox.h:33
openshot::EffectBase::trackedObjects
std::map< int, std::shared_ptr< openshot::TrackedObjectBase > > trackedObjects
Map of Tracked Object's by their indices (used by Effects that track objects on clips)
Definition: EffectBase.h:111
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::EffectBase::BasePropertiesJSON
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:257
openshot::ObjectMaskData
Definition: TrackedObjectBBox.h:27
openshot::TrackedObjectBBox
This class contains the properties of a tracked object and functions to manipulate it.
Definition: TrackedObjectBBox.h:139
openshot::Keyframe
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
openshot::TrackedObjectBBox::AddMask
void AddMask(int64_t frame_num, const ObjectMaskData &mask)
Definition: TrackedObjectBBox.cpp:163
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::BBox::width
float width
bounding box width
Definition: TrackedObjectBBox.h:50
openshot::ObjectDetection::Json
std::string Json() const override
Generate JSON string of this object.
Definition: ObjectDetection.cpp:509
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:42
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
DetectionData::classIds
std::vector< int > classIds
Definition: ObjectDetection.h:50
DetectionData::confidences
std::vector< float > confidences
Definition: ObjectDetection.h:51
Tracker.h
Header file for Tracker effect class.
openshot::EffectInfoStruct::has_tracked_object
bool has_tracked_object
Determines if this effect track objects through the clip.
Definition: EffectBase.h:45
openshot::InvalidFile
Exception for files that can not be found or opened.
Definition: Exceptions.h:193
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
openshot::BBox
This struct holds the information of a bounding-box.
Definition: TrackedObjectBBox.h:46
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
openshot::ClipBase::Id
void Id(std::string value)
Definition: ClipBase.h:94
openshot::ObjectDetection::LoadObjDetectdData
bool LoadObjDetectdData(std::string inputFilePath)
Load protobuf data file.
Definition: ObjectDetection.cpp:266
openshot::ObjectDetection::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: ObjectDetection.cpp:660
DetectionData::boxes
std::vector< cv::Rect_< float > > boxes
Definition: ObjectDetection.h:52
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::ObjectDetection::TrackedObjectMask
std::shared_ptr< QImage > TrackedObjectMask(std::shared_ptr< QImage > target_image, int64_t frame_number) const override
Generate a black/white mask from visible detected bounding boxes.
Definition: ObjectDetection.cpp:436
openshot::ObjectDetection::GetVisibleObjects
std::string GetVisibleObjects(int64_t frame_number) const override
Get the indexes and IDs of all visible objects in the given frame.
Definition: ObjectDetection.cpp:375
openshot::BBox::cx
float cx
x-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:48
openshot::ObjectMaskData::rle
std::vector< uint32_t > rle
Definition: TrackedObjectBBox.h:31
openshot::ObjectDetection::selectedObjectIndex
int selectedObjectIndex
Index of the Tracked Object that was selected to modify it's properties.
Definition: ObjectDetection.h:94
Exceptions.h
Header file for all Exception classes.
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
openshot::ReaderBase::ParentClip
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:244
openshot::TrackedObjectBBox::background_alpha
Keyframe background_alpha
Background box opacity.
Definition: TrackedObjectBBox.h:152
openshot::ObjectDetection::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: ObjectDetection.cpp:558