Files
libopenshot/src/effects/Tracker.cpp

281 lines
7.4 KiB
C++
Raw Normal View History

2020-07-08 11:49:46 -03:00
/**
* @file
* @brief Source file for Tracker effect class
* @author Jonathan Thomas <jonathan@openshot.org>
* @author Brenno Caldato <brenno.caldato@outlook.com>
2020-07-08 11:49:46 -03:00
*
* @ref License
*/
// Copyright (c) 2008-2019 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later
2020-07-08 11:49:46 -03:00
#include <string>
#include <memory>
#include <iostream>
2020-10-20 01:04:59 -03:00
#include "effects/Tracker.h"
#include "Exceptions.h"
#include "Timeline.h"
#include "trackerdata.pb.h"
2020-07-08 11:49:46 -03:00
#include <google/protobuf/util/time_util.h>
2020-07-08 11:49:46 -03:00
#include <QImage>
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QRectF>
using namespace std;
2020-07-08 11:49:46 -03:00
using namespace openshot;
using google::protobuf::util::TimeUtil;
2020-07-08 11:49:46 -03:00
// Default constructor
Tracker::Tracker()
2020-07-08 11:49:46 -03:00
{
// Initialize effect metadata
2020-07-08 11:49:46 -03:00
init_effect_details();
// 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);
// Assign ID to the placeholder object
if (trackedData)
trackedData->Id(Id() + "-0");
}
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;
info.has_tracked_object = true;
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
std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number)
{
// Sanitycheck
if (!frame) return frame;
auto frame_image = frame->GetImage();
if (!frame_image || frame_image->isNull()) return frame;
if (!trackedData) return frame;
// 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;
QPainter painter(frame_image.get());
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
2021-03-18 13:13:34 -03: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
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);
QPen pen(QColor(
stroke_rgba[0], stroke_rgba[1], stroke_rgba[2],
int(255 * stroke_alpha)
));
pen.setWidth(stroke_width);
painter.setPen(pen);
QBrush brush(QColor(
bg_rgba[0], bg_rgba[1], bg_rgba[2],
int(255 * bg_alpha)
));
painter.setBrush(brush);
painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
}
painter.end();
return frame;
2020-07-08 11:49:46 -03:00
}
// Get the indexes and IDs of all visible objects in the given frame
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);
root["visible_objects_id"] = Json::Value(Json::arrayValue);
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
}
}
2023-04-14 14:35:46 -05:00
return root.toStyledString();
}
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
// Save the effect's properties on root
2020-07-08 11:49:46 -03:00
root["type"] = info.class_name;
root["protobuf_data_path"] = protobuf_data_path;
root["BaseFPS"]["num"] = BaseFPS.num;
root["BaseFPS"]["den"] = BaseFPS.den;
2020-11-12 21:33:53 -03:00
root["TimeScale"] = this->TimeScale;
// Add trackedObjects IDs to JSON
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-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)");
}
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);
if (!root["BaseFPS"].isNull()) {
if (!root["BaseFPS"]["num"].isNull())
BaseFPS.num = root["BaseFPS"]["num"].asInt();
if (!root["BaseFPS"]["den"].isNull())
BaseFPS.den = root["BaseFPS"]["den"].asInt();
2020-11-12 21:30:11 -03:00
}
if (!root["TimeScale"].isNull()) {
TimeScale = root["TimeScale"].asDouble();
}
2020-11-12 21:33:53 -03:00
if (!root["protobuf_data_path"].isNull() && protobuf_data_path.empty()) {
protobuf_data_path = root["protobuf_data_path"].asString();
if (!trackedData->LoadBoxData(protobuf_data_path)) {
std::clog << "Invalid protobuf data path " << protobuf_data_path << '\n';
protobuf_data_path.clear();
}
else {
// prefix "<effectUUID>-<index>" for each entry
for (auto& kv : trackedObjects) {
auto idx = kv.first;
auto ptr = kv.second;
if (ptr) {
std::string prefix = this->Id();
if (!prefix.empty())
prefix += "-";
ptr->Id(prefix + std::to_string(idx));
}
}
}
}
// then any per-object JSON overrides...
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
}
}
}
2023-04-14 14:35:46 -05:00
// Set the tracked object's ids
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 {
2020-07-08 11:49:46 -03:00
// Generate JSON properties list
Json::Value root = BasePropertiesJSON(requested_frame);
// Add trackedObject properties to JSON
Json::Value objects;
for (auto const& trackedObject : trackedObjects){
Json::Value trackedObjectJSON = trackedObject.second->PropertiesJSON(requested_frame);
// add object json
2023-04-14 14:35:46 -05:00
objects[trackedObject.second->Id()] = trackedObjectJSON;
}
root["objects"] = objects;
2020-07-08 11:49:46 -03:00
// Return formatted string
return root.toStyledString();
}