2020-07-08 11:49:46 -03:00
|
|
|
|
/**
|
|
|
|
|
|
* @file
|
|
|
|
|
|
* @brief Source file for Tracker effect class
|
|
|
|
|
|
* @author Jonathan Thomas <jonathan@openshot.org>
|
2021-04-20 23:07:23 -03:00
|
|
|
|
* @author Brenno Caldato <brenno.caldato@outlook.com>
|
2020-07-08 11:49:46 -03:00
|
|
|
|
*
|
|
|
|
|
|
* @ref License
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2021-10-16 01:26:26 -04:00
|
|
|
|
// Copyright (c) 2008-2019 OpenShot Studios, LLC
|
|
|
|
|
|
//
|
|
|
|
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
2020-07-08 11:49:46 -03:00
|
|
|
|
|
2021-05-04 07:33:47 -04:00
|
|
|
|
#include <string>
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
2020-10-20 01:04:59 -03:00
|
|
|
|
#include "effects/Tracker.h"
|
2021-01-28 20:15:25 -05:00
|
|
|
|
#include "Exceptions.h"
|
2020-12-22 21:32:36 -03:00
|
|
|
|
#include "Timeline.h"
|
2021-09-11 18:22:28 -04:00
|
|
|
|
#include "trackerdata.pb.h"
|
2020-07-08 11:49:46 -03:00
|
|
|
|
|
2021-01-13 20:22:17 -03:00
|
|
|
|
#include <google/protobuf/util/time_util.h>
|
2020-07-08 11:49:46 -03:00
|
|
|
|
|
2021-02-04 15:45:33 -03:00
|
|
|
|
#include <QImage>
|
|
|
|
|
|
#include <QPainter>
|
2024-02-22 16:22:48 -06:00
|
|
|
|
#include <QPen>
|
|
|
|
|
|
#include <QBrush>
|
2021-02-04 15:45:33 -03:00
|
|
|
|
#include <QRectF>
|
|
|
|
|
|
|
2021-01-13 12:08:33 -05:00
|
|
|
|
using namespace std;
|
2020-07-08 11:49:46 -03:00
|
|
|
|
using namespace openshot;
|
2021-01-13 20:22:17 -03:00
|
|
|
|
using google::protobuf::util::TimeUtil;
|
2020-07-08 11:49:46 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Default constructor
|
2020-12-12 20:23:34 -03:00
|
|
|
|
Tracker::Tracker()
|
2020-07-08 11:49:46 -03:00
|
|
|
|
{
|
2025-06-17 18:27:52 -05:00
|
|
|
|
// Initialize effect metadata
|
2020-07-08 11:49:46 -03:00
|
|
|
|
init_effect_details();
|
|
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
// Create a placeholder object so we always have index 0 available
|
|
|
|
|
|
trackedData = std::make_shared<TrackedObjectBBox>();
|
|
|
|
|
|
trackedData->ParentClip(this->ParentClip());
|
|
|
|
|
|
|
|
|
|
|
|
// Seed our map with a single entry at index 0
|
|
|
|
|
|
trackedObjects.clear();
|
|
|
|
|
|
trackedObjects.emplace(0, trackedData);
|
2025-08-11 14:52:24 -05:00
|
|
|
|
|
|
|
|
|
|
// Assign ID to the placeholder object
|
|
|
|
|
|
if (trackedData)
|
|
|
|
|
|
trackedData->Id(Id() + "-0");
|
2025-06-17 18:27:52 -05:00
|
|
|
|
}
|
2020-11-12 21:35:47 -03:00
|
|
|
|
|
2020-07-08 11:49:46 -03:00
|
|
|
|
// Init effect settings
|
|
|
|
|
|
void Tracker::init_effect_details()
|
|
|
|
|
|
{
|
|
|
|
|
|
/// Initialize the values of the EffectInfo struct.
|
|
|
|
|
|
InitEffectInfo();
|
|
|
|
|
|
|
|
|
|
|
|
/// Set the effect info
|
|
|
|
|
|
info.class_name = "Tracker";
|
|
|
|
|
|
info.name = "Tracker";
|
|
|
|
|
|
info.description = "Track the selected bounding box through the video.";
|
|
|
|
|
|
info.has_audio = false;
|
|
|
|
|
|
info.has_video = true;
|
2021-01-22 19:00:44 -03:00
|
|
|
|
info.has_tracked_object = true;
|
2020-11-12 21:25:27 -03:00
|
|
|
|
|
2020-11-12 21:33:53 -03:00
|
|
|
|
this->TimeScale = 1.0;
|
2020-07-08 11:49:46 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This method is required for all derived classes of EffectBase, and returns a
|
|
|
|
|
|
// modified openshot::Frame object
|
2025-06-17 18:27:52 -05:00
|
|
|
|
std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Sanity‐check
|
|
|
|
|
|
if (!frame) return frame;
|
|
|
|
|
|
auto frame_image = frame->GetImage();
|
|
|
|
|
|
if (!frame_image || frame_image->isNull()) return frame;
|
|
|
|
|
|
if (!trackedData) return frame;
|
2020-07-09 20:26:01 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
// 2) Only proceed if we actually have a box and it's visible
|
|
|
|
|
|
if (!trackedData->Contains(frame_number) ||
|
|
|
|
|
|
trackedData->visible.GetValue(frame_number) != 1)
|
|
|
|
|
|
return frame;
|
2021-02-04 15:45:33 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
QPainter painter(frame_image.get());
|
|
|
|
|
|
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
2021-03-18 13:13:34 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
// Draw the box
|
|
|
|
|
|
BBox fd = trackedData->GetBox(frame_number);
|
|
|
|
|
|
QRectF boxRect(
|
|
|
|
|
|
(fd.cx - fd.width/2) * frame_image->width(),
|
|
|
|
|
|
(fd.cy - fd.height/2) * frame_image->height(),
|
|
|
|
|
|
fd.width * frame_image->width(),
|
|
|
|
|
|
fd.height * frame_image->height()
|
|
|
|
|
|
);
|
2021-03-18 13:13:34 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (trackedData->draw_box.GetValue(frame_number) == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto stroke_rgba = trackedData->stroke.GetColorRGBA(frame_number);
|
|
|
|
|
|
int stroke_width = trackedData->stroke_width.GetValue(frame_number);
|
|
|
|
|
|
float stroke_alpha = trackedData->stroke_alpha.GetValue(frame_number);
|
|
|
|
|
|
auto bg_rgba = trackedData->background.GetColorRGBA(frame_number);
|
|
|
|
|
|
float bg_alpha = trackedData->background_alpha.GetValue(frame_number);
|
|
|
|
|
|
float bg_corner = trackedData->background_corner.GetValue(frame_number);
|
2021-03-27 22:50:01 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
QPen pen(QColor(
|
|
|
|
|
|
stroke_rgba[0], stroke_rgba[1], stroke_rgba[2],
|
|
|
|
|
|
int(255 * stroke_alpha)
|
|
|
|
|
|
));
|
|
|
|
|
|
pen.setWidth(stroke_width);
|
|
|
|
|
|
painter.setPen(pen);
|
2021-03-27 22:50:01 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
QBrush brush(QColor(
|
|
|
|
|
|
bg_rgba[0], bg_rgba[1], bg_rgba[2],
|
|
|
|
|
|
int(255 * bg_alpha)
|
|
|
|
|
|
));
|
|
|
|
|
|
painter.setBrush(brush);
|
2021-09-11 18:22:28 -04:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
|
|
|
|
|
|
}
|
2021-02-04 15:45:33 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
painter.end();
|
|
|
|
|
|
return frame;
|
2020-07-08 11:49:46 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-27 17:18:23 -03:00
|
|
|
|
// Get the indexes and IDs of all visible objects in the given frame
|
2025-06-17 18:27:52 -05:00
|
|
|
|
std::string Tracker::GetVisibleObjects(int64_t frame_number) const
|
|
|
|
|
|
{
|
2023-04-14 14:35:46 -05:00
|
|
|
|
Json::Value root;
|
|
|
|
|
|
root["visible_objects_index"] = Json::Value(Json::arrayValue);
|
2025-06-17 18:27:52 -05:00
|
|
|
|
root["visible_objects_id"] = Json::Value(Json::arrayValue);
|
2021-01-27 17:18:23 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (trackedObjects.empty())
|
|
|
|
|
|
return root.toStyledString();
|
|
|
|
|
|
|
|
|
|
|
|
for (auto const& kv : trackedObjects) {
|
|
|
|
|
|
auto ptr = kv.second;
|
|
|
|
|
|
if (!ptr) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// Directly get the Json::Value for this object's properties
|
|
|
|
|
|
Json::Value propsJson = ptr->PropertiesJSON(frame_number);
|
|
|
|
|
|
|
|
|
|
|
|
if (propsJson["visible"]["value"].asBool()) {
|
|
|
|
|
|
root["visible_objects_index"].append(kv.first);
|
|
|
|
|
|
root["visible_objects_id"].append(ptr->Id());
|
2023-04-14 14:35:46 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-01-27 17:18:23 -03:00
|
|
|
|
|
2023-04-14 14:35:46 -05:00
|
|
|
|
return root.toStyledString();
|
2021-01-27 17:18:23 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 11:49:46 -03:00
|
|
|
|
// Generate JSON string of this object
|
|
|
|
|
|
std::string Tracker::Json() const {
|
|
|
|
|
|
|
|
|
|
|
|
// Return formatted string
|
|
|
|
|
|
return JsonValue().toStyledString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Generate Json::Value for this object
|
|
|
|
|
|
Json::Value Tracker::JsonValue() const {
|
|
|
|
|
|
|
|
|
|
|
|
// Create root json object
|
|
|
|
|
|
Json::Value root = EffectBase::JsonValue(); // get parent properties
|
2020-12-22 21:32:36 -03:00
|
|
|
|
|
|
|
|
|
|
// Save the effect's properties on root
|
2020-07-08 11:49:46 -03:00
|
|
|
|
root["type"] = info.class_name;
|
2020-07-16 21:10:02 -03:00
|
|
|
|
root["protobuf_data_path"] = protobuf_data_path;
|
2022-06-17 15:30:08 -04:00
|
|
|
|
root["BaseFPS"]["num"] = BaseFPS.num;
|
2020-11-12 21:25:27 -03:00
|
|
|
|
root["BaseFPS"]["den"] = BaseFPS.den;
|
2020-11-12 21:33:53 -03:00
|
|
|
|
root["TimeScale"] = this->TimeScale;
|
2020-12-22 21:32:36 -03:00
|
|
|
|
|
2021-01-27 17:18:23 -03:00
|
|
|
|
// Add trackedObjects IDs to JSON
|
2021-05-11 12:12:35 -03:00
|
|
|
|
Json::Value objects;
|
2023-04-14 14:35:46 -05:00
|
|
|
|
for (auto const& trackedObject : trackedObjects){
|
|
|
|
|
|
Json::Value trackedObjectJSON = trackedObject.second->JsonValue();
|
|
|
|
|
|
// add object json
|
|
|
|
|
|
objects[trackedObject.second->Id()] = trackedObjectJSON;
|
|
|
|
|
|
}
|
|
|
|
|
|
root["objects"] = objects;
|
2020-12-22 21:32:36 -03:00
|
|
|
|
|
2020-07-08 11:49:46 -03:00
|
|
|
|
// return JsonValue
|
|
|
|
|
|
return root;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load JSON string into this object
|
|
|
|
|
|
void Tracker::SetJson(const std::string value) {
|
|
|
|
|
|
|
|
|
|
|
|
// Parse JSON string into JSON objects
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
const Json::Value root = openshot::stringToJson(value);
|
|
|
|
|
|
// Set all values that match
|
|
|
|
|
|
SetJsonValue(root);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (const std::exception& e)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Error parsing JSON (or missing keys)
|
|
|
|
|
|
throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
|
|
|
|
|
|
}
|
2020-12-22 21:32:36 -03:00
|
|
|
|
return;
|
2020-07-08 11:49:46 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load Json::Value into this object
|
|
|
|
|
|
void Tracker::SetJsonValue(const Json::Value root) {
|
|
|
|
|
|
|
|
|
|
|
|
// Set parent data
|
|
|
|
|
|
EffectBase::SetJsonValue(root);
|
2020-12-22 21:32:36 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (!root["BaseFPS"].isNull()) {
|
2021-05-04 07:58:43 -04:00
|
|
|
|
if (!root["BaseFPS"]["num"].isNull())
|
2025-06-17 18:27:52 -05:00
|
|
|
|
BaseFPS.num = root["BaseFPS"]["num"].asInt();
|
2021-05-04 07:58:43 -04:00
|
|
|
|
if (!root["BaseFPS"]["den"].isNull())
|
2025-06-17 18:27:52 -05:00
|
|
|
|
BaseFPS.den = root["BaseFPS"]["den"].asInt();
|
2020-11-12 21:30:11 -03:00
|
|
|
|
}
|
2021-09-11 18:22:28 -04:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (!root["TimeScale"].isNull()) {
|
|
|
|
|
|
TimeScale = root["TimeScale"].asDouble();
|
|
|
|
|
|
}
|
2020-11-12 21:33:53 -03:00
|
|
|
|
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (!root["protobuf_data_path"].isNull() && protobuf_data_path.empty()) {
|
2021-02-04 15:57:30 -03:00
|
|
|
|
protobuf_data_path = root["protobuf_data_path"].asString();
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (!trackedData->LoadBoxData(protobuf_data_path)) {
|
2021-05-04 07:58:43 -04:00
|
|
|
|
std::clog << "Invalid protobuf data path " << protobuf_data_path << '\n';
|
2025-06-17 18:27:52 -05:00
|
|
|
|
protobuf_data_path.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
2025-08-11 14:52:24 -05:00
|
|
|
|
// prefix "<effectUUID>-<index>" for each entry
|
2025-06-17 18:27:52 -05:00
|
|
|
|
for (auto& kv : trackedObjects) {
|
|
|
|
|
|
auto idx = kv.first;
|
|
|
|
|
|
auto ptr = kv.second;
|
|
|
|
|
|
if (ptr) {
|
2025-08-11 14:52:24 -05:00
|
|
|
|
std::string prefix = this->Id();
|
|
|
|
|
|
if (!prefix.empty())
|
|
|
|
|
|
prefix += "-";
|
|
|
|
|
|
ptr->Id(prefix + std::to_string(idx));
|
2025-06-17 18:27:52 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-07-16 21:10:02 -03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-12-22 21:32:36 -03:00
|
|
|
|
|
2025-08-11 14:52:24 -05:00
|
|
|
|
// then any per-object JSON overrides...
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (!root["objects"].isNull()) {
|
|
|
|
|
|
for (auto& kv : trackedObjects) {
|
|
|
|
|
|
std::string key = std::to_string(kv.first);
|
|
|
|
|
|
if (!root["objects"][key].isNull()) {
|
|
|
|
|
|
kv.second->SetJsonValue(root["objects"][key]);
|
2023-04-14 14:35:46 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-01-22 19:00:44 -03:00
|
|
|
|
}
|
2021-09-11 18:22:28 -04:00
|
|
|
|
|
2023-04-14 14:35:46 -05:00
|
|
|
|
// Set the tracked object's ids
|
2025-06-17 18:27:52 -05:00
|
|
|
|
if (!root["objects_id"].isNull()) {
|
|
|
|
|
|
for (auto& kv : trackedObjects) {
|
|
|
|
|
|
Json::Value tmp;
|
|
|
|
|
|
tmp["box_id"] = root["objects_id"][kv.first].asString();
|
|
|
|
|
|
kv.second->SetJsonValue(tmp);
|
2023-04-14 14:35:46 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-07-08 11:49:46 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get all properties for a specific frame
|
|
|
|
|
|
std::string Tracker::PropertiesJSON(int64_t requested_frame) const {
|
2021-09-11 18:22:28 -04:00
|
|
|
|
|
2020-07-08 11:49:46 -03:00
|
|
|
|
// Generate JSON properties list
|
2023-05-25 15:22:01 -05:00
|
|
|
|
Json::Value root = BasePropertiesJSON(requested_frame);
|
2021-01-27 17:18:23 -03:00
|
|
|
|
|
2021-02-04 15:57:30 -03:00
|
|
|
|
// Add trackedObject properties to JSON
|
2021-05-11 12:12:35 -03:00
|
|
|
|
Json::Value objects;
|
|
|
|
|
|
for (auto const& trackedObject : trackedObjects){
|
|
|
|
|
|
Json::Value trackedObjectJSON = trackedObject.second->PropertiesJSON(requested_frame);
|
2021-09-11 18:22:28 -04:00
|
|
|
|
// add object json
|
2023-04-14 14:35:46 -05:00
|
|
|
|
objects[trackedObject.second->Id()] = trackedObjectJSON;
|
|
|
|
|
|
}
|
2021-05-11 12:12:35 -03:00
|
|
|
|
root["objects"] = objects;
|
2020-12-12 20:23:34 -03:00
|
|
|
|
|
2020-07-08 11:49:46 -03:00
|
|
|
|
// Return formatted string
|
|
|
|
|
|
return root.toStyledString();
|
|
|
|
|
|
}
|