Skip to content

Commit 3af6e1f

Browse files
committed
Refactor of Tracker effect and TrackedObjectBBox:
- Draw Tracker boxes using QPainter and support corner radius (and faster drawing performance by drawing directly on the frame) - Draw child clips with correct aspect ratio - Adding "Yes/No" options for "Visible" and "Draw Box" Tracked Object Box keyframes - default to invisible background - default to 12 corner radius - default to 50% stroke alpha
1 parent 284904d commit 3af6e1f

File tree

3 files changed

+106
-132
lines changed

3 files changed

+106
-132
lines changed

src/TrackedObjectBBox.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,19 @@ using namespace openshot;
2626

2727
// Default Constructor, delegating
2828
TrackedObjectBBox::TrackedObjectBBox()
29-
: TrackedObjectBBox::TrackedObjectBBox(0, 0, 255, 0) {}
29+
: TrackedObjectBBox::TrackedObjectBBox(0, 0, 255, 255) {}
3030

3131
// Constructor that takes RGBA values for stroke, and sets the bounding-box
3232
// displacement as 0 and the scales as 1 for the first frame
3333
TrackedObjectBBox::TrackedObjectBBox(int Red, int Green, int Blue, int Alfa)
3434
: delta_x(0.0), delta_y(0.0),
3535
scale_x(1.0), scale_y(1.0), rotation(0.0),
36-
background_alpha(1.0), background_corner(0),
37-
stroke_width(2) , stroke_alpha(0.0),
36+
background_alpha(0.0), background_corner(12),
37+
stroke_width(2) , stroke_alpha(0.5),
3838
stroke(Red, Green, Blue, Alfa),
39-
background(0, 0, 255, 0)
39+
background(0, 0, 255, Alfa)
4040
{
4141
this->TimeScale = 1.0;
42-
return;
4342
}
4443

4544
// Add a BBox to the BoxVec map
@@ -442,10 +441,12 @@ Json::Value TrackedObjectBBox::PropertiesJSON(int64_t requested_frame) const
442441
root["scale_y"] = add_property_json("Scale (Height)", scale_y.GetValue(requested_frame), "float", "", &scale_y, 0.0, 1.0, false, requested_frame);
443442
root["rotation"] = add_property_json("Rotation", rotation.GetValue(requested_frame), "float", "", &rotation, 0, 360, false, requested_frame);
444443
root["visible"] = add_property_json("Visible", visible.GetValue(requested_frame), "int", "", &visible, 0, 1, false, requested_frame);
444+
root["visible"]["choices"].append(add_property_choice_json("Yes", true, visible.GetValue(requested_frame)));
445+
root["visible"]["choices"].append(add_property_choice_json("No", false, visible.GetValue(requested_frame)));
445446

446-
root["draw_box"] = add_property_json("Draw Box", draw_box.GetValue(requested_frame), "int", "", &draw_box, -1, 1.0, false, requested_frame);
447-
root["draw_box"]["choices"].append(add_property_choice_json("Off", 0, draw_box.GetValue(requested_frame)));
448-
root["draw_box"]["choices"].append(add_property_choice_json("On", 1, draw_box.GetValue(requested_frame)));
447+
root["draw_box"] = add_property_json("Draw Box", draw_box.GetValue(requested_frame), "int", "", &draw_box, 0, 1, false, requested_frame);
448+
root["draw_box"]["choices"].append(add_property_choice_json("Yes", true, draw_box.GetValue(requested_frame)));
449+
root["draw_box"]["choices"].append(add_property_choice_json("No", false, draw_box.GetValue(requested_frame)));
449450

450451
root["stroke"] = add_property_json("Border", 0.0, "color", "", NULL, 0, 255, false, requested_frame);
451452
root["stroke"]["red"] = add_property_json("Red", stroke.red.GetValue(requested_frame), "float", "", &stroke.red, 0, 255, false, requested_frame);

src/effects/Tracker.cpp

Lines changed: 94 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
#include <string>
1515
#include <memory>
16-
#include <fstream>
1716
#include <iostream>
1817

1918
#include "effects/Tracker.h"
@@ -25,6 +24,8 @@
2524

2625
#include <QImage>
2726
#include <QPainter>
27+
#include <QPen>
28+
#include <QBrush>
2829
#include <QRectF>
2930

3031
using namespace std;
@@ -83,129 +84,100 @@ void Tracker::init_effect_details()
8384

8485
// This method is required for all derived classes of EffectBase, and returns a
8586
// modified openshot::Frame object
86-
std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number)
87-
{
88-
// Get the frame's image
89-
cv::Mat frame_image = frame->GetImageCV();
90-
91-
// Initialize the Qt rectangle that will hold the positions of the bounding-box
92-
QRectF boxRect;
93-
// Initialize the image of the TrackedObject child clip
94-
std::shared_ptr<QImage> childClipImage = nullptr;
95-
96-
// Check if frame isn't NULL
97-
if(!frame_image.empty() &&
98-
trackedData->Contains(frame_number) &&
99-
trackedData->visible.GetValue(frame_number) == 1)
100-
{
101-
// Get the width and height of the image
102-
float fw = frame_image.size().width;
103-
float fh = frame_image.size().height;
104-
105-
// Get the bounding-box of given frame
106-
BBox fd = trackedData->GetBox(frame_number);
107-
108-
// Check if track data exists for the requested frame
109-
if (trackedData->draw_box.GetValue(frame_number) == 1)
110-
{
111-
std::vector<int> stroke_rgba = trackedData->stroke.GetColorRGBA(frame_number);
112-
int stroke_width = trackedData->stroke_width.GetValue(frame_number);
113-
float stroke_alpha = trackedData->stroke_alpha.GetValue(frame_number);
114-
std::vector<int> bg_rgba = trackedData->background.GetColorRGBA(frame_number);
115-
float bg_alpha = trackedData->background_alpha.GetValue(frame_number);
116-
117-
// Create a rotated rectangle object that holds the bounding box
118-
cv::RotatedRect box ( cv::Point2f( (int)(fd.cx*fw), (int)(fd.cy*fh) ),
119-
cv::Size2f( (int)(fd.width*fw), (int)(fd.height*fh) ),
120-
(int) (fd.angle) );
121-
122-
DrawRectangleRGBA(frame_image, box, bg_rgba, bg_alpha, 1, true);
123-
DrawRectangleRGBA(frame_image, box, stroke_rgba, stroke_alpha, stroke_width, false);
124-
}
125-
126-
// Get the image of the Tracked Object' child clip
127-
if (trackedData->ChildClipId() != ""){
128-
// Cast the parent timeline of this effect
129-
Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
130-
if (parentTimeline){
131-
// Get the Tracked Object's child clip
132-
Clip* childClip = parentTimeline->GetClip(trackedData->ChildClipId());
133-
if (childClip){
134-
// Get the image of the child clip for this frame
135-
std::shared_ptr<Frame> childClipFrame = childClip->GetFrame(frame_number);
136-
childClipImage = childClipFrame->GetImage();
137-
138-
// Set the Qt rectangle with the bounding-box properties
139-
boxRect.setRect((int)((fd.cx-fd.width/2)*fw),
140-
(int)((fd.cy - fd.height/2)*fh),
141-
(int)(fd.width*fw),
142-
(int)(fd.height*fh) );
143-
}
144-
}
145-
}
146-
147-
}
148-
149-
// Set image with drawn box to frame
150-
// If the input image is NULL or doesn't have tracking data, it's returned as it came
151-
frame->SetImageCV(frame_image);
152-
153-
// Set the bounding-box image with the Tracked Object's child clip image
154-
if (childClipImage){
155-
// Get the frame image
156-
QImage frameImage = *(frame->GetImage());
157-
158-
// Set a Qt painter to the frame image
159-
QPainter painter(&frameImage);
160-
161-
// Draw the child clip image inside the bounding-box
162-
painter.drawImage(boxRect, *childClipImage);
163-
164-
// Set the frame image as the composed image
165-
frame->AddImage(std::make_shared<QImage>(frameImage));
166-
}
167-
168-
return frame;
87+
std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number) {
88+
// Get the frame's QImage
89+
std::shared_ptr<QImage> frame_image = frame->GetImage();
90+
91+
// Check if frame isn't NULL
92+
if(frame_image && !frame_image->isNull() &&
93+
trackedData->Contains(frame_number) &&
94+
trackedData->visible.GetValue(frame_number) == 1) {
95+
QPainter painter(frame_image.get());
96+
97+
// Get the bounding-box of the given frame
98+
BBox fd = trackedData->GetBox(frame_number);
99+
100+
// Create a QRectF for the bounding box
101+
QRectF boxRect((fd.cx - fd.width / 2) * frame_image->width(),
102+
(fd.cy - fd.height / 2) * frame_image->height(),
103+
fd.width * frame_image->width(),
104+
fd.height * frame_image->height());
105+
106+
// Check if track data exists for the requested frame
107+
if (trackedData->draw_box.GetValue(frame_number) == 1) {
108+
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
109+
110+
// Get trackedObjectBox keyframes
111+
std::vector<int> stroke_rgba = trackedData->stroke.GetColorRGBA(frame_number);
112+
int stroke_width = trackedData->stroke_width.GetValue(frame_number);
113+
float stroke_alpha = trackedData->stroke_alpha.GetValue(frame_number);
114+
std::vector<int> bg_rgba = trackedData->background.GetColorRGBA(frame_number);
115+
float bg_alpha = trackedData->background_alpha.GetValue(frame_number);
116+
float bg_corner = trackedData->background_corner.GetValue(frame_number);
117+
118+
// Set the pen for the border
119+
QPen pen(QColor(stroke_rgba[0], stroke_rgba[1], stroke_rgba[2], 255 * stroke_alpha));
120+
pen.setWidth(stroke_width);
121+
painter.setPen(pen);
122+
123+
// Set the brush for the background
124+
QBrush brush(QColor(bg_rgba[0], bg_rgba[1], bg_rgba[2], 255 * bg_alpha));
125+
painter.setBrush(brush);
126+
127+
// Draw the rounded rectangle
128+
painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
129+
}
130+
131+
// Get the image of the Tracked Object' child clip
132+
if (trackedData->ChildClipId() != ""){
133+
// Cast the parent timeline of this effect
134+
Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
135+
if (parentTimeline){
136+
// Get the Tracked Object's child clip
137+
Clip* childClip = parentTimeline->GetClip(trackedData->ChildClipId());
138+
if (childClip){
139+
// Get the image of the child clip for this frame
140+
std::shared_ptr<Frame> childClipFrame = childClip->GetFrame(frame_number);
141+
std::shared_ptr<QImage> childClipImage = childClipFrame->GetImage();
142+
143+
// Scale the original bounding box to this image
144+
QRectF scaledRect = scaleAndCenterRect(QRectF(childClipImage->rect()), boxRect);
145+
QImage scaledImage = childClipImage->scaled(scaledRect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
146+
147+
// Draw the child clip image inside the bounding-box
148+
painter.drawImage(scaledRect, *childClipImage);
149+
}
150+
}
151+
}
152+
153+
painter.end();
154+
}
155+
156+
// No need to set the image back to the frame, as we directly modified the frame's QImage
157+
return frame;
169158
}
170159

171-
void Tracker::DrawRectangleRGBA(cv::Mat &frame_image, cv::RotatedRect box, std::vector<int> color, float alpha, int thickness, bool is_background){
172-
// Get the bouding box vertices
173-
cv::Point2f vertices2f[4];
174-
box.points(vertices2f);
175-
176-
// TODO: take a rectangle of frame_image by refencence and draw on top of that to improve speed
177-
// select min enclosing rectangle to draw on a small portion of the image
178-
// cv::Rect rect = box.boundingRect();
179-
// cv::Mat image = frame_image(rect)
180-
181-
if(is_background){
182-
cv::Mat overlayFrame;
183-
frame_image.copyTo(overlayFrame);
184-
185-
// draw bounding box background
186-
cv::Point vertices[4];
187-
for(int i = 0; i < 4; ++i){
188-
vertices[i] = vertices2f[i];}
189-
190-
cv::Rect rect = box.boundingRect();
191-
cv::fillConvexPoly(overlayFrame, vertices, 4, cv::Scalar(color[2],color[1],color[0]), cv::LINE_AA);
192-
// add opacity
193-
cv::addWeighted(overlayFrame, 1-alpha, frame_image, alpha, 0, frame_image);
194-
}
195-
else{
196-
cv::Mat overlayFrame;
197-
frame_image.copyTo(overlayFrame);
198-
199-
// Draw bounding box
200-
for (int i = 0; i < 4; i++)
201-
{
202-
cv::line(overlayFrame, vertices2f[i], vertices2f[(i+1)%4], cv::Scalar(color[2],color[1],color[0]),
203-
thickness, cv::LINE_AA);
204-
}
205-
206-
// add opacity
207-
cv::addWeighted(overlayFrame, 1-alpha, frame_image, alpha, 0, frame_image);
208-
}
160+
QRectF Tracker::scaleAndCenterRect(const QRectF& sourceRect, const QRectF& targetRect) {
161+
float sourceAspectRatio = sourceRect.width() / sourceRect.height();
162+
float targetWidth = targetRect.width();
163+
float targetHeight = targetRect.height();
164+
float newWidth, newHeight;
165+
166+
if (sourceAspectRatio > targetRect.width() / targetRect.height()) {
167+
// Source is wider relative to target, so it's constrained by target's width
168+
newWidth = targetWidth;
169+
newHeight = newWidth / sourceAspectRatio;
170+
} else {
171+
// Source is taller relative to target, so it's constrained by target's height
172+
newHeight = targetHeight;
173+
newWidth = newHeight * sourceAspectRatio;
174+
}
175+
176+
// Center the new rectangle within the target rectangle
177+
float newX = targetRect.left() + (targetWidth - newWidth) / 2.0;
178+
float newY = targetRect.top() + (targetHeight - newHeight) / 2.0;
179+
180+
return QRectF(newX, newY, newWidth, newHeight);
209181
}
210182

211183
// Get the indexes and IDs of all visible objects in the given frame

src/effects/Tracker.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ namespace openshot
4444
/// Init effect settings
4545
void init_effect_details();
4646

47+
/// Find a rectangle inside another (centered)
48+
QRectF scaleAndCenterRect(const QRectF& sourceRect, const QRectF& targetRect);
49+
4750
Fraction BaseFPS;
4851
double TimeScale;
4952

@@ -71,8 +74,6 @@ namespace openshot
7174
/// Get the indexes and IDs of all visible objects in the given frame
7275
std::string GetVisibleObjects(int64_t frame_number) const override;
7376

74-
void DrawRectangleRGBA(cv::Mat &frame_image, cv::RotatedRect box, std::vector<int> color, float alpha, int thickness, bool is_background);
75-
7677
// Get and Set JSON methods
7778

7879
/// Generate JSON string of this object

0 commit comments

Comments
 (0)