Skip to content

Commit 98383fd

Browse files
committed
Refactor of ObjectDetection effect:
- Draw bounding 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" option for "Draw Text" - default to fully opaque stroke
1 parent 3af6e1f commit 98383fd

File tree

5 files changed

+123
-224
lines changed

5 files changed

+123
-224
lines changed

src/TrackedObjectBBox.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ 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),
3636
background_alpha(0.0), background_corner(12),
37-
stroke_width(2) , stroke_alpha(0.5),
37+
stroke_width(2) , stroke_alpha(0.70),
3838
stroke(Red, Green, Blue, Alfa),
3939
background(0, 0, 255, Alfa)
4040
{

src/effects/ObjectDetection.cpp

Lines changed: 119 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "Exceptions.h"
2020
#include "Timeline.h"
2121
#include "objdetectdata.pb.h"
22+
#include "Tracker.h"
2223

2324
#include <QImage>
2425
#include <QPainter>
@@ -67,213 +68,120 @@ void ObjectDetection::init_effect_details()
6768

6869
// This method is required for all derived classes of EffectBase, and returns a
6970
// modified openshot::Frame object
70-
std::shared_ptr<Frame> ObjectDetection::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number)
71-
{
72-
// Get the frame's image
73-
cv::Mat cv_image = frame->GetImageCV();
74-
75-
// Check if frame isn't NULL
76-
if(cv_image.empty()){
77-
return frame;
78-
}
79-
80-
// Initialize the Qt rectangle that will hold the positions of the bounding-box
81-
std::vector<QRectF> boxRects;
82-
// Initialize the image of the TrackedObject child clip
83-
std::vector<std::shared_ptr<QImage>> childClipImages;
84-
85-
// Check if track data exists for the requested frame
86-
if (detectionsData.find(frame_number) != detectionsData.end()) {
87-
float fw = cv_image.size().width;
88-
float fh = cv_image.size().height;
89-
90-
DetectionData detections = detectionsData[frame_number];
91-
for(int i = 0; i<detections.boxes.size(); i++){
92-
93-
// Does not show boxes with confidence below the threshold
94-
if(detections.confidences.at(i) < confidence_threshold){
95-
continue;
96-
}
97-
// Just display selected classes
98-
if( display_classes.size() > 0 &&
99-
std::find(display_classes.begin(), display_classes.end(), classNames[detections.classIds.at(i)]) == display_classes.end()){
100-
continue;
101-
}
102-
103-
// Get the object id
104-
int objectId = detections.objectIds.at(i);
105-
106-
// Search for the object in the trackedObjects map
107-
auto trackedObject_it = trackedObjects.find(objectId);
108-
109-
// Cast the object as TrackedObjectBBox
110-
std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(trackedObject_it->second);
111-
112-
// Check if the tracked object has data for this frame
113-
if (trackedObject->Contains(frame_number) &&
114-
trackedObject->visible.GetValue(frame_number) == 1)
115-
{
116-
// Get the bounding-box of given frame
117-
BBox trackedBox = trackedObject->GetBox(frame_number);
118-
bool draw_text = !display_box_text.GetValue(frame_number);
119-
std::vector<int> stroke_rgba = trackedObject->stroke.GetColorRGBA(frame_number);
120-
int stroke_width = trackedObject->stroke_width.GetValue(frame_number);
121-
float stroke_alpha = trackedObject->stroke_alpha.GetValue(frame_number);
122-
std::vector<int> bg_rgba = trackedObject->background.GetColorRGBA(frame_number);
123-
float bg_alpha = trackedObject->background_alpha.GetValue(frame_number);
124-
125-
cv::Rect2d box(
126-
(int)( (trackedBox.cx-trackedBox.width/2)*fw),
127-
(int)( (trackedBox.cy-trackedBox.height/2)*fh),
128-
(int)( trackedBox.width*fw),
129-
(int)( trackedBox.height*fh)
130-
);
131-
132-
// If the Draw Box property is off, then make the box invisible
133-
if (trackedObject->draw_box.GetValue(frame_number) == 0)
134-
{
135-
bg_alpha = 1.0;
136-
stroke_alpha = 1.0;
137-
}
138-
139-
drawPred(detections.classIds.at(i), detections.confidences.at(i),
140-
box, cv_image, detections.objectIds.at(i), bg_rgba, bg_alpha, 1, true, draw_text);
141-
drawPred(detections.classIds.at(i), detections.confidences.at(i),
142-
box, cv_image, detections.objectIds.at(i), stroke_rgba, stroke_alpha, stroke_width, false, draw_text);
143-
144-
145-
// Get the Detected Object's child clip
146-
if (trackedObject->ChildClipId() != ""){
147-
// Cast the parent timeline of this effect
148-
Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
149-
if (parentTimeline){
150-
// Get the Tracked Object's child clip
151-
Clip* childClip = parentTimeline->GetClip(trackedObject->ChildClipId());
152-
153-
if (childClip){
154-
// Get the image of the child clip for this frame
155-
std::shared_ptr<Frame> childClipFrame = childClip->GetFrame(frame_number);
156-
childClipImages.push_back(childClipFrame->GetImage());
157-
158-
// Set the Qt rectangle with the bounding-box properties
159-
QRectF boxRect;
160-
boxRect.setRect((int)((trackedBox.cx-trackedBox.width/2)*fw),
161-
(int)((trackedBox.cy - trackedBox.height/2)*fh),
162-
(int)(trackedBox.width*fw),
163-
(int)(trackedBox.height*fh));
164-
boxRects.push_back(boxRect);
165-
}
166-
}
167-
}
168-
}
169-
}
170-
}
171-
172-
// Update Qt image with new Opencv frame
173-
frame->SetImageCV(cv_image);
174-
175-
// Set the bounding-box image with the Tracked Object's child clip image
176-
if(boxRects.size() > 0){
177-
// Get the frame image
178-
QImage frameImage = *(frame->GetImage());
179-
for(int i; i < boxRects.size();i++){
180-
// Set a Qt painter to the frame image
181-
QPainter painter(&frameImage);
182-
// Draw the child clip image inside the bounding-box
183-
painter.drawImage(boxRects[i], *childClipImages[i]);
184-
}
185-
// Set the frame image as the composed image
186-
frame->AddImage(std::make_shared<QImage>(frameImage));
187-
}
188-
189-
return frame;
190-
}
191-
192-
void ObjectDetection::DrawRectangleRGBA(cv::Mat &frame_image, cv::RotatedRect box, std::vector<int> color, float alpha,
193-
int thickness, bool is_background){
194-
// Get the bouding box vertices
195-
cv::Point2f vertices2f[4];
196-
box.points(vertices2f);
197-
198-
// TODO: take a rectangle of frame_image by refencence and draw on top of that to improve speed
199-
// select min enclosing rectangle to draw on a small portion of the image
200-
// cv::Rect rect = box.boundingRect();
201-
// cv::Mat image = frame_image(rect)
202-
203-
if(is_background){
204-
cv::Mat overlayFrame;
205-
frame_image.copyTo(overlayFrame);
206-
207-
// draw bounding box background
208-
cv::Point vertices[4];
209-
for(int i = 0; i < 4; ++i){
210-
vertices[i] = vertices2f[i];}
211-
212-
cv::Rect rect = box.boundingRect();
213-
cv::fillConvexPoly(overlayFrame, vertices, 4, cv::Scalar(color[2],color[1],color[0]), cv::LINE_AA);
214-
// add opacity
215-
cv::addWeighted(overlayFrame, 1-alpha, frame_image, alpha, 0, frame_image);
216-
}
217-
else{
218-
cv::Mat overlayFrame;
219-
frame_image.copyTo(overlayFrame);
220-
221-
// Draw bounding box
222-
for (int i = 0; i < 4; i++)
223-
{
224-
cv::line(overlayFrame, vertices2f[i], vertices2f[(i+1)%4], cv::Scalar(color[2],color[1],color[0]),
225-
thickness, cv::LINE_AA);
226-
}
227-
228-
// add opacity
229-
cv::addWeighted(overlayFrame, 1-alpha, frame_image, alpha, 0, frame_image);
230-
}
231-
}
232-
233-
void ObjectDetection::drawPred(int classId, float conf, cv::Rect2d box, cv::Mat& frame, int objectNumber, std::vector<int> color,
234-
float alpha, int thickness, bool is_background, bool display_text)
235-
{
236-
237-
if(is_background){
238-
cv::Mat overlayFrame;
239-
frame.copyTo(overlayFrame);
240-
241-
//Draw a rectangle displaying the bounding box
242-
cv::rectangle(overlayFrame, box, cv::Scalar(color[2],color[1],color[0]), cv::FILLED);
243-
244-
// add opacity
245-
cv::addWeighted(overlayFrame, 1-alpha, frame, alpha, 0, frame);
246-
}
247-
else{
248-
cv::Mat overlayFrame;
249-
frame.copyTo(overlayFrame);
250-
251-
//Draw a rectangle displaying the bounding box
252-
cv::rectangle(overlayFrame, box, cv::Scalar(color[2],color[1],color[0]), thickness);
253-
254-
if(display_text){
255-
//Get the label for the class name and its confidence
256-
std::string label = cv::format("%.2f", conf);
257-
if (!classNames.empty())
258-
{
259-
CV_Assert(classId < (int)classNames.size());
260-
label = classNames[classId] + ":" + label;
261-
}
262-
263-
//Display the label at the top of the bounding box
264-
int baseLine;
265-
cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
266-
267-
double left = box.x;
268-
double top = std::max((int)box.y, labelSize.height);
269-
270-
cv::rectangle(overlayFrame, cv::Point(left, top - round(1.025*labelSize.height)), cv::Point(left + round(1.025*labelSize.width), top + baseLine),
271-
cv::Scalar(color[2],color[1],color[0]), cv::FILLED);
272-
putText(overlayFrame, label, cv::Point(left+1, top), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,0,0),1);
273-
}
274-
// add opacity
275-
cv::addWeighted(overlayFrame, 1-alpha, frame, alpha, 0, frame);
276-
}
71+
std::shared_ptr<Frame> ObjectDetection::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number) {
72+
// Get the frame's QImage
73+
std::shared_ptr<QImage> frame_image = frame->GetImage();
74+
75+
// Check if frame isn't NULL
76+
if(!frame_image || frame_image->isNull()) {
77+
return frame;
78+
}
79+
80+
QPainter painter(frame_image.get());
81+
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
82+
83+
if (detectionsData.find(frame_number) != detectionsData.end()) {
84+
float fw = frame_image->width();
85+
float fh = frame_image->height();
86+
87+
DetectionData detections = detectionsData[frame_number];
88+
for (int i = 0; i < detections.boxes.size(); i++) {
89+
if (detections.confidences.at(i) < confidence_threshold ||
90+
(!display_classes.empty() &&
91+
std::find(display_classes.begin(), display_classes.end(), classNames[detections.classIds.at(i)]) == display_classes.end())) {
92+
continue;
93+
}
94+
95+
int objectId = detections.objectIds.at(i);
96+
auto trackedObject_it = trackedObjects.find(objectId);
97+
98+
if (trackedObject_it != trackedObjects.end()) {
99+
std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(trackedObject_it->second);
100+
101+
if (trackedObject->Contains(frame_number) && trackedObject->visible.GetValue(frame_number) == 1) {
102+
BBox trackedBox = trackedObject->GetBox(frame_number);
103+
104+
QRectF boxRect((trackedBox.cx - trackedBox.width / 2) * fw,
105+
(trackedBox.cy - trackedBox.height / 2) * fh,
106+
trackedBox.width * fw,
107+
trackedBox.height * fh);
108+
109+
if (trackedObject->draw_box.GetValue(frame_number) == 1) {
110+
// Draw bounding box
111+
bool display_text = !display_box_text.GetValue(frame_number);
112+
std::vector<int> stroke_rgba = trackedObject->stroke.GetColorRGBA(frame_number);
113+
std::vector<int> bg_rgba = trackedObject->background.GetColorRGBA(frame_number);
114+
int stroke_width = trackedObject->stroke_width.GetValue(frame_number);
115+
float stroke_alpha = trackedObject->stroke_alpha.GetValue(frame_number);
116+
float bg_alpha = trackedObject->background_alpha.GetValue(frame_number);
117+
float bg_corner = trackedObject->background_corner.GetValue(frame_number);
118+
119+
// Set the pen for the border
120+
QPen pen(QColor(stroke_rgba[0], stroke_rgba[1], stroke_rgba[2], 255 * stroke_alpha));
121+
pen.setWidth(stroke_width);
122+
painter.setPen(pen);
123+
124+
// Set the brush for the background
125+
QBrush brush(QColor(bg_rgba[0], bg_rgba[1], bg_rgba[2], 255 * bg_alpha));
126+
painter.setBrush(brush);
127+
128+
// Draw the rounded rectangle
129+
painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
130+
131+
if(display_text) {
132+
// Draw text label above bounding box
133+
// Get the confidence and classId for the current detection
134+
float conf = detections.confidences.at(i);
135+
int classId = detections.classIds.at(i);
136+
137+
// Get the label for the class name and its confidence
138+
QString label = QString::number(conf, 'f', 2); // Format confidence to two decimal places
139+
if (!classNames.empty()) {
140+
label = QString::fromStdString(classNames[classId]) + ":" + label;
141+
}
142+
143+
// Set up the painter, font, and pen
144+
QFont font;
145+
font.setPixelSize(14);
146+
painter.setFont(font);
147+
148+
// Calculate the size of the text
149+
QFontMetrics fontMetrics(font);
150+
QSize labelSize = fontMetrics.size(Qt::TextSingleLine, label);
151+
152+
// Define the top left point of the rectangle
153+
double left = boxRect.center().x() - (labelSize.width() / 2.0);
154+
double top = std::max(static_cast<int>(boxRect.top()), labelSize.height()) - 4.0;
155+
156+
// Draw the text
157+
painter.drawText(QPointF(left, top), label);
158+
}
159+
}
160+
161+
// Get the image of the Tracked Object' child clip
162+
if (trackedObject->ChildClipId() != "") {
163+
Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
164+
if (parentTimeline) {
165+
Clip* childClip = parentTimeline->GetClip(trackedObject->ChildClipId());
166+
if (childClip) {
167+
std::shared_ptr<Frame> childClipFrame = childClip->GetFrame(frame_number);
168+
std::shared_ptr<QImage> childClipImage = childClipFrame->GetImage();
169+
170+
// Scale the original bounding box to this image
171+
QRectF scaledRect = Tracker::scaleAndCenterRect(QRectF(childClipImage->rect()), boxRect);
172+
painter.drawImage(scaledRect, *childClipImage);
173+
}
174+
}
175+
}
176+
}
177+
}
178+
}
179+
}
180+
181+
painter.end();
182+
183+
// The frame's QImage has been modified in place, so we just return the original frame
184+
return frame;
277185
}
278186

279187
// Load protobuf data file
@@ -348,6 +256,7 @@ bool ObjectDetection::LoadObjDetectdData(std::string inputFilePath){
348256
{
349257
// There is no tracked object with that id, so insert a new one
350258
TrackedObjectBBox trackedObj((int)classesColor[classId](0), (int)classesColor[classId](1), (int)classesColor[classId](2), (int)0);
259+
trackedObj.stroke_alpha = Keyframe(1.0);
351260
trackedObj.AddBox(id, x+(w/2), y+(h/2), w, h, 0.0);
352261

353262
std::shared_ptr<TrackedObjectBBox> trackedObjPtr = std::make_shared<TrackedObjectBBox>(trackedObj);
@@ -551,9 +460,9 @@ std::string ObjectDetection::PropertiesJSON(int64_t requested_frame) const {
551460
root["confidence_threshold"] = add_property_json("Confidence Theshold", confidence_threshold, "float", "", NULL, 0, 1, false, requested_frame);
552461
root["class_filter"] = add_property_json("Class Filter", 0.0, "string", class_filter, NULL, -1, -1, false, requested_frame);
553462

554-
root["display_box_text"] = add_property_json("Draw Box Text", display_box_text.GetValue(requested_frame), "int", "", &display_box_text, 0, 1.0, false, requested_frame);
555-
root["display_box_text"]["choices"].append(add_property_choice_json("Off", 1, display_box_text.GetValue(requested_frame)));
556-
root["display_box_text"]["choices"].append(add_property_choice_json("On", 0, display_box_text.GetValue(requested_frame)));
463+
root["display_box_text"] = add_property_json("Draw Box Text", display_box_text.GetValue(requested_frame), "int", "", &display_box_text, 0, 1, false, requested_frame);
464+
root["display_box_text"]["choices"].append(add_property_choice_json("Yes", true, display_box_text.GetValue(requested_frame)));
465+
root["display_box_text"]["choices"].append(add_property_choice_json("No", false, display_box_text.GetValue(requested_frame)));
557466

558467
// Return formatted string
559468
return root.toStyledString();

src/effects/ObjectDetection.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ namespace openshot
6060
std::string protobuf_data_path;
6161
std::map<size_t, DetectionData> detectionsData;
6262
std::vector<std::string> classNames;
63-
6463
std::vector<cv::Scalar> classesColor;
6564

6665
/// Draw class name and confidence score on top of the bounding box
@@ -73,12 +72,6 @@ namespace openshot
7372

7473
/// Init effect settings
7574
void init_effect_details();
76-
/// Draw bounding box with class and score text
77-
void drawPred(int classId, float conf, cv::Rect2d box, cv::Mat& frame, int objectNumber, std::vector<int> color, float alpha,
78-
int thickness, bool is_background, bool draw_text);
79-
/// Draw rotated rectangle with alpha channel
80-
void DrawRectangleRGBA(cv::Mat &frame_image, cv::RotatedRect box, std::vector<int> color, float alpha, int thickness, bool is_background);
81-
8275

8376
public:
8477
/// Index of the Tracked Object that was selected to modify it's properties

src/effects/Tracker.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,6 @@ std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t f
142142

143143
// Scale the original bounding box to this image
144144
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
148145
painter.drawImage(scaledRect, *childClipImage);
149146
}
150147
}

0 commit comments

Comments
 (0)