OpenShot Library | libopenshot  0.7.0
Tracker.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 <string>
15 #include <memory>
16 #include <iostream>
17 #include <algorithm>
18 
19 #include "effects/Tracker.h"
20 #include "Exceptions.h"
21 #include "Timeline.h"
22 #include "trackerdata.pb.h"
23 
24 #include <google/protobuf/util/time_util.h>
25 
26 #include <QImage>
27 #include <QPainter>
28 #include <QPen>
29 #include <QBrush>
30 #include <QRectF>
31 
32 using namespace std;
33 using namespace openshot;
34 using google::protobuf::util::TimeUtil;
35 
36 
37 // Default constructor
38 Tracker::Tracker()
39 {
40  // Initialize effect metadata
41  init_effect_details();
42 
43  // Create a placeholder object so we always have index 0 available
44  trackedData = std::make_shared<TrackedObjectBBox>();
45  trackedData->ParentClip(this->ParentClip());
46 
47  // Seed our map with a single entry at index 0
48  trackedObjects.clear();
49  trackedObjects.emplace(0, trackedData);
50 
51  // Assign ID to the placeholder object
52  if (trackedData)
53  trackedData->Id(Id() + "-0");
54 }
55 
56 // Init effect settings
57 void Tracker::init_effect_details()
58 {
60  InitEffectInfo();
61 
63  info.class_name = "Tracker";
64  info.name = "Tracker";
65  info.description = "Track the selected bounding box through the video.";
66  info.has_audio = false;
67  info.has_video = true;
68  info.has_tracked_object = true;
69 
70  this->TimeScale = 1.0;
71 }
72 
73 // This method is required for all derived classes of EffectBase, and returns a
74 // modified openshot::Frame object
75 std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number)
76 {
77  // Sanity‐check
78  if (!frame) return frame;
79  auto frame_image = frame->GetImage();
80  if (!frame_image || frame_image->isNull()) return frame;
81  if (!trackedData) return frame;
82 
83  // 2) Only proceed if we actually have a box and it's visible
84  if (!trackedData->Contains(frame_number) ||
85  trackedData->visible.GetValue(frame_number) != 1)
86  return frame;
87 
88  QPainter painter(frame_image.get());
89  painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
90 
91  // Draw the box
92  BBox fd = trackedData->GetBox(frame_number);
93  QRectF boxRect(
94  (fd.cx - fd.width/2) * frame_image->width(),
95  (fd.cy - fd.height/2) * frame_image->height(),
96  fd.width * frame_image->width(),
97  fd.height * frame_image->height()
98  );
99 
100  if (trackedData->draw_box.GetValue(frame_number) == 1)
101  {
102  auto stroke_rgba = trackedData->stroke.GetColorRGBA(frame_number);
103  int stroke_width = trackedData->stroke_width.GetValue(frame_number);
104  float stroke_alpha = trackedData->stroke_alpha.GetValue(frame_number);
105  auto bg_rgba = trackedData->background.GetColorRGBA(frame_number);
106  float bg_alpha = trackedData->background_alpha.GetValue(frame_number);
107  float bg_corner = trackedData->background_corner.GetValue(frame_number);
108 
109  QPen pen(QColor(
110  stroke_rgba[0], stroke_rgba[1], stroke_rgba[2],
111  int(255 * stroke_alpha)
112  ));
113  pen.setWidthF(trackedData->ScaledStrokeWidth(
114  frame_number, frame_image->width(), frame_image->height()));
115  painter.setPen(pen);
116 
117  QBrush brush(QColor(
118  bg_rgba[0], bg_rgba[1], bg_rgba[2],
119  int(255 * bg_alpha)
120  ));
121  painter.setBrush(brush);
122 
123  painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
124  }
125 
126  painter.end();
127  return frame;
128 }
129 
130 // Get the indexes and IDs of all visible objects in the given frame
131 std::string Tracker::GetVisibleObjects(int64_t frame_number) const
132 {
133  Json::Value root;
134  root["visible_objects_index"] = Json::Value(Json::arrayValue);
135  root["visible_objects_id"] = Json::Value(Json::arrayValue);
136 
137  if (trackedObjects.empty())
138  return root.toStyledString();
139 
140  for (auto const& kv : trackedObjects) {
141  auto ptr = kv.second;
142  if (!ptr) continue;
143 
144  // Directly get the Json::Value for this object's properties
145  Json::Value propsJson = ptr->PropertiesJSON(frame_number);
146 
147  if (propsJson["visible"]["value"].asBool()) {
148  root["visible_objects_index"].append(kv.first);
149  root["visible_objects_id"].append(ptr->Id());
150  }
151  }
152 
153  return root.toStyledString();
154 }
155 
156 // Generate JSON string of this object
157 std::string Tracker::Json() const {
158 
159  // Return formatted string
160  return JsonValue().toStyledString();
161 }
162 
163 // Generate Json::Value for this object
164 Json::Value Tracker::JsonValue() const {
165 
166  // Create root json object
167  Json::Value root = EffectBase::JsonValue(); // get parent properties
168 
169  // Save the effect's properties on root
170  root["type"] = info.class_name;
171  root["protobuf_data_path"] = protobuf_data_path;
172  root["BaseFPS"]["num"] = BaseFPS.num;
173  root["BaseFPS"]["den"] = BaseFPS.den;
174  root["TimeScale"] = this->TimeScale;
175 
176  // Add trackedObjects IDs to JSON
177  Json::Value objects;
178  for (auto const& trackedObject : trackedObjects){
179  Json::Value trackedObjectJSON = trackedObject.second->JsonValue();
180  // add object json
181  objects[trackedObject.second->Id()] = trackedObjectJSON;
182  }
183  root["objects"] = objects;
184 
185  // return JsonValue
186  return root;
187 }
188 
189 // Load JSON string into this object
190 void Tracker::SetJson(const std::string value) {
191 
192  // Parse JSON string into JSON objects
193  try
194  {
195  const Json::Value root = openshot::stringToJson(value);
196  // Set all values that match
197  SetJsonValue(root);
198  }
199  catch (const std::exception& e)
200  {
201  // Error parsing JSON (or missing keys)
202  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
203  }
204  return;
205 }
206 
207 // Load Json::Value into this object
208 void Tracker::SetJsonValue(const Json::Value root) {
209 
210  // Set parent data
211  EffectBase::SetJsonValue(root);
212 
213  if (!root["BaseFPS"].isNull()) {
214  if (!root["BaseFPS"]["num"].isNull())
215  BaseFPS.num = root["BaseFPS"]["num"].asInt();
216  if (!root["BaseFPS"]["den"].isNull())
217  BaseFPS.den = root["BaseFPS"]["den"].asInt();
218  }
219 
220  if (!root["TimeScale"].isNull()) {
221  TimeScale = root["TimeScale"].asDouble();
222  }
223 
224  if (!root["protobuf_data_path"].isNull()) {
225  std::string new_path = root["protobuf_data_path"].asString();
226  if (protobuf_data_path != new_path || trackedData->GetLength() == 0) {
227  protobuf_data_path = new_path;
228  if (!trackedData->LoadBoxData(protobuf_data_path)) {
229  std::clog << "Invalid protobuf data path " << protobuf_data_path << '\n';
230  protobuf_data_path.clear();
231  }
232  else {
233  // prefix "<effectUUID>-<index>" for each entry
234  for (auto& kv : trackedObjects) {
235  auto idx = kv.first;
236  auto ptr = kv.second;
237  if (ptr) {
238  std::string prefix = this->Id();
239  if (!prefix.empty())
240  prefix += "-";
241  ptr->Id(prefix + std::to_string(idx));
242  }
243  }
244  }
245  }
246  }
247 
248  // then any per-object JSON overrides...
249  if (!root["objects"].isNull()) {
250  // Iterate over the supplied objects (indexed by id or position)
251  const auto memberNames = root["objects"].getMemberNames();
252  for (const auto& name : memberNames)
253  {
254  // Determine the numeric index of this object
255  int index = -1;
256  bool numeric_key = std::all_of(name.begin(), name.end(), ::isdigit);
257  if (numeric_key) {
258  index = std::stoi(name);
259  }
260  else
261  {
262  size_t pos = name.find_last_of('-');
263  if (pos != std::string::npos) {
264  try {
265  index = std::stoi(name.substr(pos + 1));
266  } catch (...) {
267  index = -1;
268  }
269  }
270  }
271 
272  auto obj_it = trackedObjects.find(index);
273  if (obj_it != trackedObjects.end() && obj_it->second) {
274  // Update object id if provided as a non-numeric key
275  if (!numeric_key)
276  obj_it->second->Id(name);
277  obj_it->second->SetJsonValue(root["objects"][name]);
278  }
279  }
280  }
281 
282  // Set the tracked object's ids (legacy format)
283  if (!root["objects_id"].isNull()) {
284  for (auto& kv : trackedObjects) {
285  if (!root["objects_id"][kv.first].isNull())
286  kv.second->Id(root["objects_id"][kv.first].asString());
287  }
288  }
289 }
290 
291 // Get all properties for a specific frame
292 std::string Tracker::PropertiesJSON(int64_t requested_frame) const {
293 
294  // Generate JSON properties list
295  Json::Value root = BasePropertiesJSON(requested_frame);
296 
297  // Add trackedObject properties to JSON
298  Json::Value objects;
299  for (auto const& trackedObject : trackedObjects){
300  Json::Value trackedObjectJSON = trackedObject.second->PropertiesJSON(requested_frame);
301  // add object json
302  objects[trackedObject.second->Id()] = trackedObjectJSON;
303  }
304  root["objects"] = objects;
305 
306  // Return formatted string
307  return root.toStyledString();
308 }
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::BBox::height
float height
bounding box height
Definition: TrackedObjectBBox.h:42
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::BBox::cy
float cy
y-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:40
Timeline.h
Header file for Timeline class.
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::BBox::width
float width
bounding box width
Definition: TrackedObjectBBox.h:41
Tracker.h
Header file for Tracker effect class.
openshot::BBox
This struct holds the information of a bounding-box.
Definition: TrackedObjectBBox.h:37
openshot::BBox::cx
float cx
x-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:39
Exceptions.h
Header file for all Exception classes.