OpenShot Library | libopenshot 1.1.1
CVObjectMask.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2026 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "CVObjectMask.h"
14
15#include "Exceptions.h"
16#include "Logger.h"
17#include "objdetectdata.pb.h"
18
19#define int64 int64_t
20#define uint64 uint64_t
21#include <opencv2/core/ocl.hpp>
22#if CV_VERSION_MAJOR >= 5
23#include <opencv2/geometry/2d.hpp>
24#else
25#include <opencv2/imgproc.hpp>
26#endif
27#undef uint64
28#undef int64
29
30#include <algorithm>
31#include <cctype>
32#include <cmath>
33#include <deque>
34#include <fstream>
35#include <iostream>
36#include <cstring>
37#include <limits>
38#include <numeric>
39
40#include <google/protobuf/util/time_util.h>
41
42using namespace openshot;
43using google::protobuf::util::TimeUtil;
44
45namespace {
46
47std::string LoadONNXModel(const std::string& modelPath, cv::dnn::Net* net)
48{
49 try {
50 cv::dnn::Net loadedNet = cv::dnn::readNetFromONNX(modelPath);
51 if (net)
52 *net = loadedNet;
53 return "";
54 } catch (const cv::Exception& e) {
55 return std::string("Failed to load ONNX model: ") + e.what();
56 } catch (const std::exception& e) {
57 return std::string("Failed to load ONNX model: ") + e.what();
58 }
59}
60
61std::vector<uint32_t> EncodeBinaryMaskRLE(const cv::Mat& mask)
62{
63 std::vector<uint32_t> rle;
64 if (mask.empty())
65 return rle;
66
67 uint8_t current = 0;
68 uint32_t count = 0;
69 for (int y = 0; y < mask.rows; ++y) {
70 const uint8_t* row = mask.ptr<uint8_t>(y);
71 for (int x = 0; x < mask.cols; ++x) {
72 const uint8_t value = row[x] ? 1 : 0;
73 if (value == current) {
74 ++count;
75 } else {
76 rle.push_back(count);
77 current = value;
78 count = 1;
79 }
80 }
81 }
82 rle.push_back(count);
83 return rle;
84}
85
86struct EfficientSamPreprocessResult {
87 cv::Mat blob;
88 float scaleX = 1.0f;
89 float scaleY = 1.0f;
90};
91
92EfficientSamPreprocessResult MakeEfficientSamBlob(const cv::Mat& bgr, int modelSize)
93{
94 EfficientSamPreprocessResult result;
95 result.scaleX = static_cast<float>(modelSize) / static_cast<float>(bgr.cols);
96 result.scaleY = static_cast<float>(modelSize) / static_cast<float>(bgr.rows);
97
98 cv::Mat resized;
99 cv::resize(bgr, resized, cv::Size(modelSize, modelSize), 0, 0, cv::INTER_LINEAR);
100
101 const int shape[] = {1, 3, modelSize, modelSize};
102 result.blob = cv::Mat(4, shape, CV_32F);
103 float* dst = result.blob.ptr<float>();
104
105 for (int y = 0; y < resized.rows; ++y) {
106 const cv::Vec3b* row = resized.ptr<cv::Vec3b>(y);
107 for (int x = 0; x < resized.cols; ++x) {
108 const float rgb[] = {
109 static_cast<float>(row[x][2]) / 255.0f,
110 static_cast<float>(row[x][1]) / 255.0f,
111 static_cast<float>(row[x][0]) / 255.0f,
112 };
113 for (int c = 0; c < 3; ++c)
114 dst[(c * modelSize + y) * modelSize + x] = rgb[c];
115 }
116 }
117
118 return result;
119}
120
121cv::Rect_<float> NormalizedBoundingBox(const cv::Mat& mask)
122{
123 std::vector<cv::Point> points;
124 cv::findNonZero(mask, points);
125 if (points.empty())
126 return {};
127
128 cv::Rect rect = cv::boundingRect(points);
129 return cv::Rect_<float>(
130 rect.x / static_cast<float>(mask.cols),
131 rect.y / static_cast<float>(mask.rows),
132 rect.width / static_cast<float>(mask.cols),
133 rect.height / static_cast<float>(mask.rows));
134}
135
136cv::Mat EfficientSamMaskToFrameMask(const cv::Mat& modelMask, const cv::Size& frameSize, float maskThreshold)
137{
138 cv::Mat fullSize;
139 cv::resize(modelMask, fullSize, frameSize, 0, 0, cv::INTER_LINEAR);
140
141 cv::Mat binary;
142 cv::threshold(fullSize, binary, maskThreshold, 255.0, cv::THRESH_BINARY);
143 if (cv::countNonZero(binary) == 0) {
144 double maxValue = 0.0;
145 cv::minMaxLoc(fullSize, nullptr, &maxValue);
146 if (maxValue > 0.0) {
147 cv::threshold(fullSize, binary, maxValue * 0.5, 255.0, cv::THRESH_BINARY);
148 }
149 }
150 binary.convertTo(binary, CV_8U);
151 return binary;
152}
153
154cv::Mat MakeEfficientSamPromptBlob(
155 const CVObjectMaskPromptSet& prompts,
156 const EfficientSamPreprocessResult& prep,
157 int promptSlots,
158 std::vector<cv::Point>& backgroundPoints,
159 std::vector<cv::Rect>& backgroundRects)
160{
161 const int coordsShape[] = {1, 1, promptSlots, 2};
162 cv::Mat pointCoords(4, coordsShape, CV_32F, cv::Scalar(0.0f));
163
164 float* coords = pointCoords.ptr<float>();
165 int promptIndex = 0;
166 for (const auto& rect : prompts.positiveRects) {
167 if (promptIndex + 1 >= promptSlots)
168 break;
169 coords[promptIndex * 2] = rect.x * prep.scaleX;
170 coords[promptIndex * 2 + 1] = rect.y * prep.scaleY;
171 ++promptIndex;
172 coords[promptIndex * 2] = (rect.x + rect.width) * prep.scaleX;
173 coords[promptIndex * 2 + 1] = (rect.y + rect.height) * prep.scaleY;
174 ++promptIndex;
175 }
176 for (const auto& point : prompts.positivePoints) {
177 if (promptIndex >= promptSlots)
178 break;
179 coords[promptIndex * 2] = point.x * prep.scaleX;
180 coords[promptIndex * 2 + 1] = point.y * prep.scaleY;
181 ++promptIndex;
182 }
183 for (const auto& point : prompts.negativePoints) {
184 backgroundPoints.emplace_back(
185 static_cast<int>(std::lround(point.x * prep.scaleX)),
186 static_cast<int>(std::lround(point.y * prep.scaleY)));
187 }
188 for (const auto& rect : prompts.negativeRects) {
189 const int x1 = static_cast<int>(std::floor(rect.x * prep.scaleX));
190 const int y1 = static_cast<int>(std::floor(rect.y * prep.scaleY));
191 const int x2 = static_cast<int>(std::ceil((rect.x + rect.width) * prep.scaleX));
192 const int y2 = static_cast<int>(std::ceil((rect.y + rect.height) * prep.scaleY));
193 const int modelWidth = prep.blob.size[3];
194 const int modelHeight = prep.blob.size[2];
195 const int left = std::max(0, std::min(modelWidth - 1, x1));
196 const int top = std::max(0, std::min(modelHeight - 1, y1));
197 const int right = std::max(left + 1, std::min(modelWidth, x2));
198 const int bottom = std::max(top + 1, std::min(modelHeight, y2));
199 backgroundRects.emplace_back(left, top, right - left, bottom - top);
200 }
201
202 return pointCoords;
203}
204
205cv::Mat MakeEfficientSamLabelBlob(const CVObjectMaskPromptSet& prompts, int promptSlots)
206{
207 const int labelsShape[] = {1, 1, promptSlots, 1};
208 cv::Mat pointLabels(4, labelsShape, CV_32F, cv::Scalar(-1.0f));
209
210 float* labels = pointLabels.ptr<float>();
211 int promptIndex = 0;
212 for (size_t i = 0; i < prompts.positiveRects.size() && promptIndex + 1 < promptSlots; ++i) {
213 labels[promptIndex++] = 2.0f;
214 labels[promptIndex++] = 3.0f;
215 }
216 for (size_t i = 0; i < prompts.positivePoints.size() && promptIndex < promptSlots; ++i, ++promptIndex)
217 labels[promptIndex] = 1.0f;
218
219 return pointLabels;
220}
221
222cv::Mat SelectEfficientSamMask(const cv::Mat& outputMasks, const cv::Mat& iouPredictions,
223 const std::vector<cv::Point>& backgroundPoints,
224 const std::vector<cv::Rect>& backgroundRects,
225 float maskThreshold)
226{
227 if (outputMasks.dims != 5 || iouPredictions.empty())
228 return cv::Mat();
229
230 const int candidateCount = outputMasks.size[2];
231 const int maskHeight = outputMasks.size[3];
232 const int maskWidth = outputMasks.size[4];
233 const float* ious = iouPredictions.ptr<float>();
234
235 const float* masks = outputMasks.ptr<float>();
236 const size_t candidatePixels = static_cast<size_t>(maskHeight) * static_cast<size_t>(maskWidth);
237 cv::Mat bestMask;
238 float bestScore = -std::numeric_limits<float>::infinity();
239 for (int candidate = 0; candidate < candidateCount; ++candidate) {
240 cv::Mat mask(maskHeight, maskWidth, CV_32F,
241 const_cast<float*>(masks + static_cast<size_t>(candidate) * candidatePixels));
242
243 int backgroundHits = 0;
244 for (const cv::Point& point : backgroundPoints) {
245 const int x = std::max(0, std::min(maskWidth - 1, point.x));
246 const int y = std::max(0, std::min(maskHeight - 1, point.y));
247 if (mask.at<float>(y, x) >= maskThreshold)
248 ++backgroundHits;
249 }
250
251 float rectOverlapPenalty = 0.0f;
252 for (const cv::Rect& rect : backgroundRects) {
253 const cv::Rect clipped = rect & cv::Rect(0, 0, maskWidth, maskHeight);
254 const int area = clipped.area();
255 if (area <= 0)
256 continue;
257 int overlap = 0;
258 for (int y = clipped.y; y < clipped.y + clipped.height; ++y) {
259 const float* row = mask.ptr<float>(y);
260 for (int x = clipped.x; x < clipped.x + clipped.width; ++x) {
261 if (row[x] >= maskThreshold)
262 ++overlap;
263 }
264 }
265 rectOverlapPenalty += static_cast<float>(overlap) / static_cast<float>(area);
266 }
267
268 const float pointPenalty = backgroundPoints.empty()
269 ? 0.0f
270 : static_cast<float>(backgroundHits) / static_cast<float>(backgroundPoints.size());
271 if (!backgroundRects.empty())
272 rectOverlapPenalty /= static_cast<float>(backgroundRects.size());
273
274 const float score = ious[candidate] - (0.35f * pointPenalty) - (0.75f * rectOverlapPenalty);
275 if (bestMask.empty() || score > bestScore) {
276 bestScore = score;
277 bestMask = mask.clone();
278 }
279 }
280 return bestMask;
281}
282
283CVObjectMaskFrameData FrameDataFromMask(const cv::Mat& mask, size_t frameId, float score)
284{
285 CVObjectMaskFrameData frameData;
286 frameData.frameId = frameId;
287 frameData.objectId = 1;
288 if (mask.empty())
289 return frameData;
290
291 frameData.score = score;
292 frameData.width = mask.cols;
293 frameData.height = mask.rows;
294 frameData.rle = EncodeBinaryMaskRLE(mask);
295 frameData.box = NormalizedBoundingBox(mask);
296 return frameData;
297}
298
299cv::Point2f JsonPoint(const Json::Value& value)
300{
301 if (!value.isObject() || value["x"].isNull() || value["y"].isNull())
302 return cv::Point2f(-1.0f, -1.0f);
303 return cv::Point2f(value["x"].asFloat(), value["y"].asFloat());
304}
305
306bool IsValidPoint(const cv::Point2f& point)
307{
308 return point.x >= 0.0f && point.y >= 0.0f;
309}
310
311void AppendJsonPoints(const Json::Value& values, std::vector<cv::Point2f>& points)
312{
313 if (!values.isArray())
314 return;
315 for (const auto& value : values) {
316 cv::Point2f point = JsonPoint(value);
317 if (IsValidPoint(point))
318 points.push_back(point);
319 }
320}
321
322size_t JsonFrameNumber(const std::string& frameName)
323{
324 try {
325 return static_cast<size_t>(std::max(0, std::stoi(frameName)));
326 } catch (...) {
327 return 0;
328 }
329}
330
331bool RectFromJson(const Json::Value& rect, cv::Rect_<float>& output)
332{
333 if (!rect.isObject() || rect["x1"].isNull() || rect["y1"].isNull() ||
334 rect["x2"].isNull() || rect["y2"].isNull()) {
335 return false;
336 }
337
338 const float x1 = std::min(rect["x1"].asFloat(), rect["x2"].asFloat());
339 const float y1 = std::min(rect["y1"].asFloat(), rect["y2"].asFloat());
340 const float x2 = std::max(rect["x1"].asFloat(), rect["x2"].asFloat());
341 const float y2 = std::max(rect["y1"].asFloat(), rect["y2"].asFloat());
342 cv::Point2f topLeft(x1, y1);
343 cv::Point2f bottomRight(x2, y2);
344 if (!IsValidPoint(topLeft) || !IsValidPoint(bottomRight) || x2 <= x1 || y2 <= y1)
345 return false;
346
347 output = cv::Rect_<float>(x1, y1, x2 - x1, y2 - y1);
348 return true;
349}
350
351void AppendJsonRects(const Json::Value& values, std::vector<cv::Rect_<float>>& rects)
352{
353 if (!values.isArray())
354 return;
355 for (const auto& rect : values) {
356 cv::Rect_<float> parsed;
357 if (RectFromJson(rect, parsed))
358 rects.push_back(parsed);
359 }
360}
361
362CVObjectMaskPromptSet PromptSetFromJson(const Json::Value& framePayload)
363{
364 CVObjectMaskPromptSet prompts;
365 AppendJsonPoints(framePayload["positive_points"], prompts.positivePoints);
366 AppendJsonPoints(framePayload["negative_points"], prompts.negativePoints);
367 AppendJsonRects(framePayload["positive_rects"], prompts.positiveRects);
368 AppendJsonRects(framePayload["negative_rects"], prompts.negativeRects);
369 return prompts;
370}
371
372cv::Mat MakeBlob(const std::vector<int>& shape, float value = 0.0f)
373{
374 cv::Mat output(static_cast<int>(shape.size()), shape.data(), CV_32F);
375 output.setTo(value);
376 return output;
377}
378
379std::string SetNetDevice(cv::dnn::Net& net, const std::string& processingDevice)
380{
381 if (processingDevice == "CPU") {
382 net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
383 net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
384 return "CPU";
385 }
386
387 if (processingDevice == "GPU" || processingDevice == "GPU_AUTO" || processingDevice == "GPU_CUDA") {
388 try {
389 const std::vector<cv::dnn::Target> targets = cv::dnn::getAvailableTargets(cv::dnn::DNN_BACKEND_CUDA);
390 if (std::find(targets.begin(), targets.end(), cv::dnn::DNN_TARGET_CUDA) != targets.end()) {
391 net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
392 net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
393 return "CUDA";
394 }
395 } catch (const cv::Exception&) {
396 }
397 }
398
399 if (processingDevice == "GPU_OPENCL") {
400 try {
401 const std::vector<cv::dnn::Target> targets = cv::dnn::getAvailableTargets(cv::dnn::DNN_BACKEND_OPENCV);
402 if (std::find(targets.begin(), targets.end(), cv::dnn::DNN_TARGET_OPENCL) != targets.end()) {
403 cv::ocl::setUseOpenCL(true);
404 net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
405 net.setPreferableTarget(cv::dnn::DNN_TARGET_OPENCL);
406 return "OpenCL";
407 }
408 } catch (const cv::Exception&) {
409 }
410 }
411
412 net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
413 net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
414 return "CPU";
415}
416
417class CutiePropagator {
418private:
419 static constexpr int memorySlots = 6;
420 int modelWidth = 640;
421 int modelHeight = 368;
422 int stride16Width = modelWidth / 16;
423 int stride16Height = modelHeight / 16;
424
425 struct MemoryFrame {
426 cv::Mat key;
427 cv::Mat shrinkage;
428 cv::Mat value;
429 cv::Mat valid;
430 };
431
432 struct LetterboxTransform {
433 cv::Size originalSize;
434 cv::Rect contentRect;
435 };
436
437 cv::dnn::Net encodeKey;
438 cv::dnn::Net encodeValue;
439 cv::dnn::Net memoryReadout;
440 cv::dnn::Net decode;
441 cv::Mat sensory;
442 cv::Mat lastMask;
443 cv::Mat objectMemory;
444 MemoryFrame permanentMemory;
445 bool hasPermanentMemory = false;
446 std::deque<MemoryFrame> workingMemoryFrames;
447 int frameIndex = 0;
448 int lastMemoryFrame = -1000000;
449 int memEvery = 5;
450 int maxMemoryFrames = memorySlots;
451
452 static bool ParseModelSize(const std::string& modelPath, int& width, int& height)
453 {
454 size_t xPos = modelPath.find('x');
455 while (xPos != std::string::npos) {
456 size_t widthStart = xPos;
457 while (widthStart > 0 && std::isdigit(static_cast<unsigned char>(modelPath[widthStart - 1])))
458 --widthStart;
459
460 size_t heightEnd = xPos + 1;
461 while (heightEnd < modelPath.size() && std::isdigit(static_cast<unsigned char>(modelPath[heightEnd])))
462 ++heightEnd;
463
464 if (widthStart != xPos && heightEnd != xPos + 1) {
465 width = std::stoi(modelPath.substr(widthStart, xPos - widthStart));
466 height = std::stoi(modelPath.substr(xPos + 1, heightEnd - xPos - 1));
467 if (width > 0 && height > 0 && width % 16 == 0 && height % 16 == 0)
468 return true;
469 }
470 xPos = modelPath.find('x', xPos + 1);
471 }
472 return false;
473 }
474
475 void ConfigureModelSize(const std::string& modelPath)
476 {
477 int width = modelWidth;
478 int height = modelHeight;
479 if (!ParseModelSize(modelPath, width, height))
480 return;
481 modelWidth = width;
482 modelHeight = height;
483 stride16Width = modelWidth / 16;
484 stride16Height = modelHeight / 16;
485 }
486
487 LetterboxTransform ComputeLetterbox(const cv::Size& sourceSize) const
488 {
489 LetterboxTransform transform;
490 transform.originalSize = sourceSize;
491 if (sourceSize.width <= 0 || sourceSize.height <= 0) {
492 transform.contentRect = cv::Rect(0, 0, modelWidth, modelHeight);
493 return transform;
494 }
495
496 const float scaleX = static_cast<float>(modelWidth) / static_cast<float>(sourceSize.width);
497 const float scaleY = static_cast<float>(modelHeight) / static_cast<float>(sourceSize.height);
498 const float scale = std::min(scaleX, scaleY);
499
500 const int resizedWidth = std::max(1, std::min(
501 modelWidth, static_cast<int>(std::lround(sourceSize.width * scale))));
502 const int resizedHeight = std::max(1, std::min(
503 modelHeight, static_cast<int>(std::lround(sourceSize.height * scale))));
504 const int offsetX = (modelWidth - resizedWidth) / 2;
505 const int offsetY = (modelHeight - resizedHeight) / 2;
506 transform.contentRect = cv::Rect(offsetX, offsetY, resizedWidth, resizedHeight);
507 return transform;
508 }
509
510 cv::Mat MakeImageBlob(const cv::Mat& bgr, const LetterboxTransform& transform) const
511 {
512 cv::Mat resized;
513 cv::resize(bgr, resized, transform.contentRect.size(), 0, 0, cv::INTER_LINEAR);
514 cv::Mat canvas(modelHeight, modelWidth, bgr.type(), cv::Scalar::all(0));
515 resized.copyTo(canvas(transform.contentRect));
516
517 const int shape[] = {1, 3, modelHeight, modelWidth};
518 cv::Mat blob(4, shape, CV_32F);
519 float* dst = blob.ptr<float>();
520 for (int y = 0; y < canvas.rows; ++y) {
521 const cv::Vec3b* row = canvas.ptr<cv::Vec3b>(y);
522 for (int x = 0; x < canvas.cols; ++x) {
523 dst[(0 * modelHeight + y) * modelWidth + x] = static_cast<float>(row[x][2]) / 255.0f;
524 dst[(1 * modelHeight + y) * modelWidth + x] = static_cast<float>(row[x][1]) / 255.0f;
525 dst[(2 * modelHeight + y) * modelWidth + x] = static_cast<float>(row[x][0]) / 255.0f;
526 }
527 }
528 return blob;
529 }
530
531 cv::Mat MakeMaskBlob(const cv::Mat& mask, const LetterboxTransform& transform) const
532 {
533 cv::Mat resized;
534 cv::resize(mask, resized, transform.contentRect.size(), 0, 0, cv::INTER_NEAREST);
535 cv::Mat canvas(modelHeight, modelWidth, CV_8U, cv::Scalar(0));
536 resized.copyTo(canvas(transform.contentRect));
537
538 const int shape[] = {1, 1, modelHeight, modelWidth};
539 cv::Mat blob(4, shape, CV_32F, cv::Scalar(0.0f));
540 float* dst = blob.ptr<float>();
541 for (int y = 0; y < canvas.rows; ++y) {
542 const uint8_t* row = canvas.ptr<uint8_t>(y);
543 for (int x = 0; x < canvas.cols; ++x)
544 dst[y * modelWidth + x] = row[x] ? 1.0f : 0.0f;
545 }
546 return blob;
547 }
548
549 cv::Mat ForegroundFromProb(const cv::Mat& prob) const
550 {
551 const int shape[] = {1, 1, modelHeight, modelWidth};
552 cv::Mat foreground(4, shape, CV_32F);
553 const float* src = prob.ptr<float>();
554 float* dst = foreground.ptr<float>();
555 const int plane = modelWidth * modelHeight;
556 std::memcpy(dst, src + plane, sizeof(float) * plane);
557 return foreground;
558 }
559
560 cv::Mat BinaryMaskFromForeground(const cv::Mat& foreground, const LetterboxTransform& transform) const
561 {
562 cv::Mat modelMask(modelHeight, modelWidth, CV_8U, cv::Scalar(0));
563 const float* src = foreground.ptr<float>();
564 for (int y = 0; y < modelMask.rows; ++y) {
565 uint8_t* row = modelMask.ptr<uint8_t>(y);
566 for (int x = 0; x < modelMask.cols; ++x)
567 row[x] = src[y * modelWidth + x] >= 0.5f ? 255 : 0;
568 }
569
570 cv::Mat cropped = modelMask(transform.contentRect);
571 cv::Mat restored;
572 cv::resize(cropped, restored, transform.originalSize, 0, 0, cv::INTER_NEAREST);
573 return restored;
574 }
575
576 cv::Mat ValidMaskFromLetterbox(const LetterboxTransform& transform) const
577 {
578 cv::Mat valid(stride16Height, stride16Width, CV_32F, cv::Scalar(0.0f));
579 for (int y = 0; y < stride16Height; ++y) {
580 float* row = valid.ptr<float>(y);
581 const int centerY = y * 16 + 8;
582 for (int x = 0; x < stride16Width; ++x) {
583 const int centerX = x * 16 + 8;
584 if (transform.contentRect.contains(cv::Point(centerX, centerY)))
585 row[x] = 1.0f;
586 }
587 }
588
589 const int shape[] = {1, 1, stride16Height, stride16Width};
590 cv::Mat blob(4, shape, CV_32F);
591 std::memcpy(blob.ptr<float>(), valid.ptr<float>(), sizeof(float) * valid.total());
592 return blob;
593 }
594
595 void CopyKeySlot(const cv::Mat& src, cv::Mat& dst, int slot, int channels) const
596 {
597 const float* in = src.ptr<float>();
598 float* out = dst.ptr<float>();
599 const int plane = stride16Width * stride16Height;
600 for (int c = 0; c < channels; ++c) {
601 std::memcpy(out + (c * memorySlots + slot) * plane,
602 in + c * plane,
603 sizeof(float) * plane);
604 }
605 }
606
607 void CopyValueSlot(const cv::Mat& src, cv::Mat& dst, int slot) const
608 {
609 const float* in = src.ptr<float>();
610 float* out = dst.ptr<float>();
611 const int plane = stride16Width * stride16Height;
612 for (int c = 0; c < 256; ++c) {
613 std::memcpy(out + (c * memorySlots + slot) * plane,
614 in + c * plane,
615 sizeof(float) * plane);
616 }
617 }
618
619 cv::Mat MemoryKeyBlob() const
620 {
621 cv::Mat output = MakeBlob({1, 64, memorySlots, stride16Height, stride16Width});
622 int slot = 0;
623 if (hasPermanentMemory)
624 CopyKeySlot(permanentMemory.key, output, slot++, 64);
625 for (int index = 0;
626 index < static_cast<int>(workingMemoryFrames.size()) && slot < memorySlots;
627 ++index, ++slot)
628 CopyKeySlot(workingMemoryFrames[index].key, output, slot, 64);
629 return output;
630 }
631
632 cv::Mat MemoryShrinkageBlob() const
633 {
634 cv::Mat output = MakeBlob({1, 1, memorySlots, stride16Height, stride16Width});
635 int slot = 0;
636 if (hasPermanentMemory)
637 CopyKeySlot(permanentMemory.shrinkage, output, slot++, 1);
638 for (int index = 0;
639 index < static_cast<int>(workingMemoryFrames.size()) && slot < memorySlots;
640 ++index, ++slot)
641 CopyKeySlot(workingMemoryFrames[index].shrinkage, output, slot, 1);
642 return output;
643 }
644
645 cv::Mat MemoryValueBlob() const
646 {
647 cv::Mat output = MakeBlob({1, 1, 256, memorySlots, stride16Height, stride16Width});
648 int slot = 0;
649 if (hasPermanentMemory)
650 CopyValueSlot(permanentMemory.value, output, slot++);
651 for (int index = 0;
652 index < static_cast<int>(workingMemoryFrames.size()) && slot < memorySlots;
653 ++index, ++slot)
654 CopyValueSlot(workingMemoryFrames[index].value, output, slot);
655 return output;
656 }
657
658 cv::Mat MemoryValidBlob() const
659 {
660 cv::Mat output = MakeBlob({1, 1, memorySlots, stride16Height, stride16Width});
661 float* data = output.ptr<float>();
662 const int plane = stride16Width * stride16Height;
663 auto copyValidSlot = [&](const cv::Mat& valid, int slot) {
664 std::memcpy(data + slot * plane, valid.ptr<float>(), sizeof(float) * plane);
665 };
666
667 int slot = 0;
668 if (hasPermanentMemory)
669 copyValidSlot(permanentMemory.valid, slot++);
670 for (int index = 0;
671 index < static_cast<int>(workingMemoryFrames.size()) && slot < memorySlots;
672 ++index, ++slot)
673 copyValidSlot(workingMemoryFrames[index].valid, slot);
674 return output;
675 }
676
677 void AddMemory(const cv::Mat& key, const cv::Mat& shrinkage, const cv::Mat& value,
678 const cv::Mat& valid, bool asPermanent)
679 {
680 MemoryFrame frame;
681 frame.key = key.clone();
682 frame.shrinkage = shrinkage.clone();
683 frame.value = value.clone();
684 frame.valid = valid.clone();
685
686 if (asPermanent || !hasPermanentMemory) {
687 permanentMemory = frame;
688 hasPermanentMemory = true;
689 return;
690 }
691
692 workingMemoryFrames.push_back(frame);
693 const int workingCapacity = std::max(0, maxMemoryFrames - 1);
694 while (static_cast<int>(workingMemoryFrames.size()) > workingCapacity)
695 workingMemoryFrames.pop_front();
696 }
697
698 void AddObjectMemory(const cv::Mat& value)
699 {
700 if (objectMemory.empty()) {
701 objectMemory = MakeBlob({1, 1, 1, 16, 257});
702 std::memcpy(objectMemory.ptr<float>(), value.ptr<float>(), sizeof(float) * value.total());
703 return;
704 }
705
706 float* dst = objectMemory.ptr<float>();
707 const float* src = value.ptr<float>();
708 for (size_t i = 0; i < value.total(); ++i)
709 dst[i] += src[i];
710 }
711
712public:
713 void Load(const std::string& encodeKeyPath, const std::string& encodeValuePath,
714 const std::string& memoryReadoutPath, const std::string& decodePath)
715 {
716 ConfigureModelSize(encodeKeyPath);
717 encodeKey = cv::dnn::readNetFromONNX(encodeKeyPath);
718 encodeValue = cv::dnn::readNetFromONNX(encodeValuePath);
719 memoryReadout = cv::dnn::readNetFromONNX(memoryReadoutPath);
720 decode = cv::dnn::readNetFromONNX(decodePath);
721 sensory = MakeBlob({1, 1, 256, stride16Height, stride16Width});
722 }
723
724 std::string SetDevice(const std::string& processingDevice)
725 {
726 std::string selected = SetNetDevice(encodeKey, processingDevice);
727 const std::string valueDevice = SetNetDevice(encodeValue, processingDevice);
728 const std::string readoutDevice = SetNetDevice(memoryReadout, processingDevice);
729 const std::string decodeDevice = SetNetDevice(decode, processingDevice);
730 if (selected != valueDevice || selected != readoutDevice || selected != decodeDevice)
731 return "Mixed";
732 return selected;
733 }
734
735 void Reset()
736 {
737 sensory = MakeBlob({1, 1, 256, stride16Height, stride16Width});
738 lastMask.release();
739 objectMemory.release();
740 permanentMemory = MemoryFrame();
741 hasPermanentMemory = false;
742 workingMemoryFrames.clear();
743 frameIndex = 0;
744 lastMemoryFrame = -1000000;
745 }
746
747 bool HasMemory() const
748 {
749 return hasPermanentMemory || !workingMemoryFrames.empty();
750 }
751
752 cv::Mat Step(const cv::Mat& frame, const cv::Mat& seedMask = cv::Mat())
753 {
754 const LetterboxTransform transform = ComputeLetterbox(frame.size());
755 const cv::Mat validMask = ValidMaskFromLetterbox(transform);
756 cv::Mat image = MakeImageBlob(frame, transform);
757
758 encodeKey.setInput(image, "image");
759 std::vector<cv::Mat> keyOutputs;
760 encodeKey.forward(keyOutputs, std::vector<cv::String>{"f16", "f8", "f4", "pix_feat", "key", "shrinkage", "selection"});
761 cv::Mat f8 = keyOutputs[1];
762 cv::Mat f4 = keyOutputs[2];
763 cv::Mat pixFeat = keyOutputs[3];
764 cv::Mat key = keyOutputs[4];
765 cv::Mat shrinkage = keyOutputs[5];
766 cv::Mat selection = keyOutputs[6];
767
768 cv::Mat foreground;
769 if (!seedMask.empty()) {
770 foreground = MakeMaskBlob(seedMask, transform);
771 } else if (HasMemory()) {
772 memoryReadout.setInput(key, "query_key");
773 memoryReadout.setInput(selection, "query_selection");
774 memoryReadout.setInput(MemoryKeyBlob(), "memory_key");
775 memoryReadout.setInput(MemoryShrinkageBlob(), "memory_shrinkage");
776 memoryReadout.setInput(MemoryValueBlob(), "memory_value");
777 memoryReadout.setInput(MemoryValidBlob(), "memory_valid");
778 memoryReadout.setInput(objectMemory, "object_memory");
779 memoryReadout.setInput(pixFeat, "pix_feat");
780 memoryReadout.setInput(sensory, "sensory");
781 memoryReadout.setInput(lastMask, "last_mask");
782 std::vector<cv::Mat> readoutOutputs;
783 memoryReadout.forward(readoutOutputs, std::vector<cv::String>{"memory_readout"});
784
785 decode.setInput(f8, "f8");
786 decode.setInput(f4, "f4");
787 decode.setInput(readoutOutputs[0], "memory_readout");
788 decode.setInput(sensory, "sensory");
789 std::vector<cv::Mat> decodeOutputs;
790 decode.forward(decodeOutputs, std::vector<cv::String>{"new_sensory", "logits", "prob"});
791 sensory = decodeOutputs[0].clone();
792 foreground = ForegroundFromProb(decodeOutputs[2]);
793 } else {
794 ++frameIndex;
795 return cv::Mat();
796 }
797
798 const bool isMemoryFrame = !seedMask.empty() || frameIndex - lastMemoryFrame >= memEvery;
799 if (isMemoryFrame) {
800 encodeValue.setInput(image, "image");
801 encodeValue.setInput(pixFeat, "pix_feat");
802 encodeValue.setInput(sensory, "sensory");
803 encodeValue.setInput(foreground, "mask");
804 std::vector<cv::Mat> valueOutputs;
805 encodeValue.forward(valueOutputs, std::vector<cv::String>{"mask_value", "new_sensory", "object_memory"});
806 sensory = valueOutputs[1].clone();
807 AddObjectMemory(valueOutputs[2]);
808 AddMemory(key, shrinkage, valueOutputs[0], validMask, !seedMask.empty());
809 lastMemoryFrame = frameIndex;
810 }
811
812 lastMask = foreground.clone();
813 cv::Mat outputMask = BinaryMaskFromForeground(foreground, transform);
814 ++frameIndex;
815 return outputMask;
816 }
817};
818
819}
820
821CVObjectMask::CVObjectMask(std::string processInfoJson, ProcessingController& controller)
822 : processingController(&controller)
823{
824 SetJson(processInfoJson);
825}
826
827std::string CVObjectMask::ValidateONNXModel(std::string modelPath)
828{
829 return LoadONNXModel(modelPath, nullptr);
830}
831
832std::shared_ptr<Frame> CVObjectMask::PreviewSeedMask(std::shared_ptr<Frame> frame)
833{
834 if (!frame || efficientSamModelPath.empty() || promptKeyframes.empty())
835 return std::shared_ptr<Frame>();
836
837 std::string loadError = LoadONNXModel(efficientSamModelPath, &efficientSam);
838 if (!loadError.empty())
839 return std::shared_ptr<Frame>();
840 SetProcessingDevice();
841
842 CVObjectMaskPromptSet prompts = promptKeyframes.begin()->second;
843 cv::Mat frameImage = frame->GetImageCV();
844 cv::Mat seedMask = CreateEfficientSAMSeedMask(frameImage, prompts);
845 if (seedMask.empty())
846 return std::shared_ptr<Frame>();
847
848 auto maskImage = std::make_shared<QImage>(
849 seedMask.cols, seedMask.rows, QImage::Format_RGBA8888_Premultiplied);
850 maskImage->fill(Qt::transparent);
851 for (int y = 0; y < seedMask.rows; ++y) {
852 const uint8_t* src = seedMask.ptr<uint8_t>(y);
853 QRgb* dst = reinterpret_cast<QRgb*>(maskImage->scanLine(y));
854 for (int x = 0; x < seedMask.cols; ++x)
855 dst[x] = src[x] ? qRgba(255, 255, 255, 255) : qRgba(0, 0, 0, 0);
856 }
857
858 auto result = std::make_shared<Frame>(frame->number, seedMask.cols, seedMask.rows, "#000000");
859 result->AddImage(maskImage);
860 return result;
861}
862
863void CVObjectMask::SetProcessingDevice()
864{
865 const std::string requestedDevice = processingDevice;
866 if (processingDevice == "CPU") {
867 efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
868 efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
869 Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested CPU, selected CPU");
870 return;
871 }
872
873 if (processingDevice == "GPU" || processingDevice == "GPU_AUTO" || processingDevice == "GPU_CUDA") {
874 try {
875 const std::vector<cv::dnn::Target> targets = cv::dnn::getAvailableTargets(cv::dnn::DNN_BACKEND_CUDA);
876 if (std::find(targets.begin(), targets.end(), cv::dnn::DNN_TARGET_CUDA) != targets.end()) {
877 efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
878 efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
879 Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected CUDA");
880 return;
881 }
882 } catch (const cv::Exception&) {
883 }
884 }
885
886 if (processingDevice == "GPU_OPENCL") {
887 try {
888 const std::vector<cv::dnn::Target> targets = cv::dnn::getAvailableTargets(cv::dnn::DNN_BACKEND_OPENCV);
889 if (std::find(targets.begin(), targets.end(), cv::dnn::DNN_TARGET_OPENCL) != targets.end()) {
890 cv::ocl::setUseOpenCL(true);
891 efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
892 efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_OPENCL);
893 Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected OpenCL");
894 return;
895 }
896 } catch (const cv::Exception&) {
897 }
898 }
899
900 processingDevice = "CPU";
901 efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
902 efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
903 Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected CPU");
904}
905
906void CVObjectMask::maskClip(openshot::Clip& video, size_t _start, size_t _end, bool process_interval)
907{
908 start = _start;
909 end = _end;
910
911 video.Open();
912 processingController->SetError(false, "");
913
914 if (efficientSamModelPath.empty()) {
915 processingController->SetError(true, "Missing path to EfficientSAM ONNX model file");
916 error = true;
917 return;
918 }
919 if (protobufDataPath.empty()) {
920 processingController->SetError(true, "Missing path to object mask protobuf data file");
921 error = true;
922 return;
923 }
924 if (promptKeyframes.empty()) {
925 processingController->SetError(true, "Missing positive prompt point for Object Mask preprocessing");
926 error = true;
927 return;
928 }
929
930 std::string loadError = LoadONNXModel(efficientSamModelPath, &efficientSam);
931 if (!loadError.empty()) {
932 processingController->SetError(true, loadError);
933 error = true;
934 return;
935 }
936 SetProcessingDevice();
937
938 CutiePropagator cutie;
939 if (cutieEncodeKeyModelPath.empty() && !cutieModelDir.empty())
940 cutieEncodeKeyModelPath = cutieModelDir + "/cutie-encode-key-640x368.onnx";
941 if (cutieEncodeValueModelPath.empty() && !cutieModelDir.empty())
942 cutieEncodeValueModelPath = cutieModelDir + "/cutie-encode-value-640x368.onnx";
943 if (cutieMemoryReadoutModelPath.empty() && !cutieModelDir.empty())
944 cutieMemoryReadoutModelPath = cutieModelDir + "/cutie-memory-readout-floatmask-valid-640x368-m6-topk30-opencv.onnx";
945 if (cutieDecodeModelPath.empty() && !cutieModelDir.empty())
946 cutieDecodeModelPath = cutieModelDir + "/cutie-decode-640x368.onnx";
947 if (cutieEncodeKeyModelPath.empty() || cutieEncodeValueModelPath.empty() ||
948 cutieMemoryReadoutModelPath.empty() || cutieDecodeModelPath.empty()) {
949 processingController->SetError(true, "Missing path to Cutie ONNX model files");
950 error = true;
951 return;
952 }
953 try {
954 cutie.Load(cutieEncodeKeyModelPath, cutieEncodeValueModelPath, cutieMemoryReadoutModelPath, cutieDecodeModelPath);
955 const std::string cutieDevice = cutie.SetDevice(processingDevice);
956 Logger::Instance()->Log("Object Mask Cutie DNN device: requested " + processingDevice + ", selected " + cutieDevice);
957 } catch (const cv::Exception& e) {
958 processingController->SetError(true, std::string("Failed to load Cutie ONNX models: ") + e.what());
959 error = true;
960 return;
961 } catch (const std::exception& e) {
962 processingController->SetError(true, std::string("Failed to load Cutie ONNX models: ") + e.what());
963 error = true;
964 return;
965 }
966
967 if (!process_interval || end <= 1 || end - start == 0) {
968 start = static_cast<size_t>(video.Start() * video.Reader()->info.fps.ToFloat());
969 end = static_cast<size_t>(video.End() * video.Reader()->info.fps.ToFloat());
970 }
971 if (end < start)
972 end = start;
973
974 CVObjectMaskPromptSet activePrompts;
975 auto promptBeforeStart = promptKeyframes.upper_bound(start);
976 if (promptBeforeStart != promptKeyframes.begin()) {
977 --promptBeforeStart;
978 activePrompts = promptBeforeStart->second;
979 }
980 auto firstPromptAtOrAfterStart = promptKeyframes.lower_bound(start);
981
982 for (size_t frameNumber = start; frameNumber <= end; ++frameNumber) {
983 if (processingController->ShouldStop())
984 return;
985
986 std::shared_ptr<openshot::Frame> frame = video.GetFrame(frameNumber);
987 if (!frame)
988 continue;
989
990 auto promptIt = promptKeyframes.find(frameNumber);
991 bool isPromptKeyframe = promptIt != promptKeyframes.end();
992 if (promptIt != promptKeyframes.end()) {
993 activePrompts = promptIt->second;
994 cutie.Reset();
995 } else if (!activePrompts.HasPositivePrompt()) {
996 if (firstPromptAtOrAfterStart != promptKeyframes.end() && frameNumber >= firstPromptAtOrAfterStart->first) {
997 activePrompts = firstPromptAtOrAfterStart->second;
998 isPromptKeyframe = true;
999 cutie.Reset();
1000 } else {
1001 CVObjectMaskFrameData emptyFrame;
1002 emptyFrame.frameId = frameNumber;
1003 masksData[frameNumber] = emptyFrame;
1004 continue;
1005 }
1006 }
1007
1008 const cv::Mat frameImage = frame->GetImageCV();
1009 cv::Mat seedMask;
1010 if (isPromptKeyframe || !cutie.HasMemory()) {
1011 seedMask = CreateEfficientSAMSeedMask(frameImage, activePrompts);
1012 if (seedMask.empty()) {
1013 CVObjectMaskFrameData emptyFrame;
1014 emptyFrame.frameId = frameNumber;
1015 masksData[frameNumber] = emptyFrame;
1016 continue;
1017 }
1018 if (!isPromptKeyframe)
1019 cutie.Reset();
1020 }
1021
1022 cv::Mat propagatedMask;
1023 try {
1024 propagatedMask = cutie.Step(frameImage, seedMask);
1025 } catch (const cv::Exception& e) {
1026 processingController->SetError(true, std::string("Failed to propagate Object Mask with Cutie: ") + e.what());
1027 error = true;
1028 return;
1029 }
1030
1031 cv::Mat outputMask;
1032 if (!seedMask.empty()) {
1033 outputMask = seedMask;
1034 } else if (!propagatedMask.empty()) {
1035 cv::resize(propagatedMask, outputMask, frameImage.size(), 0, 0, cv::INTER_NEAREST);
1036 }
1037 masksData[frameNumber] = FrameDataFromMask(outputMask, frameNumber, 1.0f);
1038
1039 const size_t range = std::max<size_t>(1, end - start);
1040 processingController->SetProgress(uint(100 * (frameNumber - start) / range));
1041 }
1042}
1043
1044cv::Mat CVObjectMask::CreateEfficientSAMSeedMask(const cv::Mat& frame, const CVObjectMaskPromptSet& prompts)
1045{
1046 EfficientSamPreprocessResult prep = MakeEfficientSamBlob(frame, modelSize);
1047
1048 auto runPromptSet = [&](const CVObjectMaskPromptSet& promptSet) -> cv::Mat {
1049 std::vector<cv::Point> backgroundPoints;
1050 std::vector<cv::Rect> backgroundRects;
1051 cv::Mat pointCoords = MakeEfficientSamPromptBlob(promptSet, prep, promptSlots, backgroundPoints, backgroundRects);
1052 cv::Mat pointLabels = MakeEfficientSamLabelBlob(promptSet, promptSlots);
1053
1054 efficientSam.setInput(prep.blob, "batched_images");
1055 efficientSam.setInput(pointCoords, "batched_point_coords");
1056 efficientSam.setInput(pointLabels, "batched_point_labels");
1057
1058 std::vector<cv::Mat> outputs;
1059 efficientSam.forward(outputs, std::vector<cv::String>{"output_masks", "iou_predictions"});
1060 if (outputs.size() != 2)
1061 return cv::Mat();
1062
1063 cv::Mat modelMask = SelectEfficientSamMask(outputs[0], outputs[1], backgroundPoints, backgroundRects, maskThreshold);
1064 if (modelMask.empty())
1065 return cv::Mat();
1066 return EfficientSamMaskToFrameMask(modelMask, frame.size(), maskThreshold);
1067 };
1068
1069 if (prompts.positiveRects.size() <= 1)
1070 return runPromptSet(prompts);
1071
1072 cv::Mat combinedMask(frame.rows, frame.cols, CV_8U, cv::Scalar(0));
1073 bool hasMask = false;
1074 for (const auto& rect : prompts.positiveRects) {
1075 CVObjectMaskPromptSet rectPrompt;
1076 rectPrompt.positiveRects.push_back(rect);
1077 rectPrompt.negativePoints = prompts.negativePoints;
1078 rectPrompt.negativeRects = prompts.negativeRects;
1079 cv::Mat rectMask = runPromptSet(rectPrompt);
1080 if (rectMask.empty())
1081 continue;
1082 cv::bitwise_or(combinedMask, rectMask, combinedMask);
1083 hasMask = true;
1084 }
1085
1086 if (!prompts.positivePoints.empty()) {
1087 CVObjectMaskPromptSet pointPrompt;
1088 pointPrompt.positivePoints = prompts.positivePoints;
1089 pointPrompt.negativePoints = prompts.negativePoints;
1090 pointPrompt.negativeRects = prompts.negativeRects;
1091 cv::Mat pointMask = runPromptSet(pointPrompt);
1092 if (!pointMask.empty()) {
1093 cv::bitwise_or(combinedMask, pointMask, combinedMask);
1094 hasMask = true;
1095 }
1096 }
1097
1098 return hasMask ? combinedMask : cv::Mat();
1099}
1100
1102{
1103 if (protobufDataPath.empty()) {
1104 std::cerr << "Missing path to object mask protobuf data file." << std::endl;
1105 return false;
1106 }
1107 if (error)
1108 return false;
1109
1110 pb_objdetect::ObjDetect objMessage;
1111 objMessage.add_classnames()->assign("object mask");
1112
1113 for (const auto& frameData : masksData)
1114 AddFrameDataToProto(objMessage.add_frame(), frameData.second);
1115
1116 *objMessage.mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL));
1117
1118 std::fstream output(protobufDataPath, std::ios::out | std::ios::trunc | std::ios::binary);
1119 if (!objMessage.SerializeToOstream(&output)) {
1120 std::cerr << "Failed to write object mask protobuf message." << std::endl;
1121 return false;
1122 }
1123
1124 return true;
1125}
1126
1127void CVObjectMask::AddFrameDataToProto(pb_objdetect::Frame* pbFrameData, const CVObjectMaskFrameData& frameData)
1128{
1129 pbFrameData->set_id(frameData.frameId);
1130 if (!frameData.HasMask())
1131 return;
1132
1133 pb_objdetect::Frame_Box* box = pbFrameData->add_bounding_box();
1134 box->set_x(frameData.box.x);
1135 box->set_y(frameData.box.y);
1136 box->set_w(frameData.box.width);
1137 box->set_h(frameData.box.height);
1138 box->set_classid(0);
1139 box->set_confidence(frameData.score);
1140 box->set_objectid(frameData.objectId);
1141
1142 pb_objdetect::Frame_Box_Mask* mask = box->mutable_mask();
1143 mask->set_width(frameData.width);
1144 mask->set_height(frameData.height);
1145 for (uint32_t count : frameData.rle)
1146 mask->add_rle(count);
1147}
1148
1149void CVObjectMask::SetJson(const std::string value)
1150{
1151 try {
1153 } catch (const std::exception&) {
1154 std::cout << "JSON is invalid (missing keys or invalid data types)" << std::endl;
1155 }
1156}
1157
1158void CVObjectMask::SetJsonValue(const Json::Value root)
1159{
1160 if (!root["protobuf_data_path"].isNull())
1161 protobufDataPath = root["protobuf_data_path"].asString();
1162 if (!root["efficient_sam_model"].isNull())
1163 efficientSamModelPath = root["efficient_sam_model"].asString();
1164 if (!root["efficient_sam_model_path"].isNull())
1165 efficientSamModelPath = root["efficient_sam_model_path"].asString();
1166 if (!root["sam_model"].isNull())
1167 efficientSamModelPath = root["sam_model"].asString();
1168 if (!root["sam_model_path"].isNull())
1169 efficientSamModelPath = root["sam_model_path"].asString();
1170 if (!root["encoder_model"].isNull())
1171 efficientSamModelPath = root["encoder_model"].asString();
1172 if (!root["encoder_model_path"].isNull())
1173 efficientSamModelPath = root["encoder_model_path"].asString();
1174 if (!root["cutie_model_dir"].isNull())
1175 cutieModelDir = root["cutie_model_dir"].asString();
1176 if (!root["cutie_encode_key_model"].isNull())
1177 cutieEncodeKeyModelPath = root["cutie_encode_key_model"].asString();
1178 if (!root["cutie_encode_key_model_path"].isNull())
1179 cutieEncodeKeyModelPath = root["cutie_encode_key_model_path"].asString();
1180 if (!root["cutie_encode_value_model"].isNull())
1181 cutieEncodeValueModelPath = root["cutie_encode_value_model"].asString();
1182 if (!root["cutie_encode_value_model_path"].isNull())
1183 cutieEncodeValueModelPath = root["cutie_encode_value_model_path"].asString();
1184 if (!root["cutie_memory_readout_model"].isNull())
1185 cutieMemoryReadoutModelPath = root["cutie_memory_readout_model"].asString();
1186 if (!root["cutie_memory_readout_model_path"].isNull())
1187 cutieMemoryReadoutModelPath = root["cutie_memory_readout_model_path"].asString();
1188 if (!root["cutie_decode_model"].isNull())
1189 cutieDecodeModelPath = root["cutie_decode_model"].asString();
1190 if (!root["cutie_decode_model_path"].isNull())
1191 cutieDecodeModelPath = root["cutie_decode_model_path"].asString();
1192 if (!root["processing-device"].isNull())
1193 processingDevice = root["processing-device"].asString();
1194 if (!root["processing_device"].isNull())
1195 processingDevice = root["processing_device"].asString();
1196 if (!root["prompt_slots"].isNull())
1197 promptSlots = std::max(1, std::min(6, root["prompt_slots"].asInt()));
1198 if (!root["mask_threshold"].isNull())
1199 maskThreshold = root["mask_threshold"].asFloat();
1200 if (!root["model_size"].isNull())
1201 modelSize = root["model_size"].asInt();
1202 promptKeyframes.clear();
1203 if (!root["object_mask_selection"].isNull()) {
1204 const Json::Value& selection = root["object_mask_selection"];
1205 const Json::Value& frames = selection["frames"];
1206 if (frames.isObject()) {
1207 for (const auto& frameName : frames.getMemberNames()) {
1208 const size_t frameNumber = JsonFrameNumber(frameName);
1209 if (frameNumber == 0)
1210 continue;
1211 CVObjectMaskPromptSet prompts = PromptSetFromJson(frames[frameName]);
1212 if (prompts.HasPositivePrompt())
1213 promptKeyframes[frameNumber] = prompts;
1214 }
1215 }
1216 }
1217
1218 CVObjectMaskPromptSet legacyPrompts;
1219 if (!root["positive_points"].isNull())
1220 AppendJsonPoints(root["positive_points"], legacyPrompts.positivePoints);
1221 if (!root["negative_points"].isNull())
1222 AppendJsonPoints(root["negative_points"], legacyPrompts.negativePoints);
1223
1224 if (!root["positive_x"].isNull() && !root["positive_y"].isNull()) {
1225 cv::Point2f point(root["positive_x"].asFloat(), root["positive_y"].asFloat());
1226 if (IsValidPoint(point) && legacyPrompts.positivePoints.empty())
1227 legacyPrompts.positivePoints.push_back(point);
1228 }
1229 if (!root["negative_x"].isNull() && !root["negative_y"].isNull()) {
1230 cv::Point2f point(root["negative_x"].asFloat(), root["negative_y"].asFloat());
1231 if (IsValidPoint(point) && legacyPrompts.negativePoints.empty())
1232 legacyPrompts.negativePoints.push_back(point);
1233 }
1234 if (!root["rect_x1"].isNull() && !root["rect_y1"].isNull() &&
1235 !root["rect_x2"].isNull() && !root["rect_y2"].isNull()) {
1236 Json::Value rect;
1237 rect["x1"] = root["rect_x1"];
1238 rect["y1"] = root["rect_y1"];
1239 rect["x2"] = root["rect_x2"];
1240 rect["y2"] = root["rect_y2"];
1241 cv::Rect_<float> parsed;
1242 if (RectFromJson(rect, parsed))
1243 legacyPrompts.positiveRects.push_back(parsed);
1244 }
1245 if (legacyPrompts.HasPositivePrompt() && promptKeyframes.empty())
1246 promptKeyframes[1] = legacyPrompts;
1247}
Header file for CVObjectMask class.
Header file for all Exception classes.
void SetError(bool err, std::string message)
std::map< size_t, CVObjectMaskFrameData > masksData
Definition: CVObjectMask.h:87
void maskClip(openshot::Clip &video, size_t start=0, size_t end=0, bool process_interval=false)
std::shared_ptr< Frame > PreviewSeedMask(std::shared_ptr< Frame > frame)
void SetJson(const std::string value)
static std::string ValidateONNXModel(std::string modelPath)
void SetJsonValue(const Json::Value root)
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition: ClipBase.h:91
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
void Open() override
Open the internal reader.
Definition: Clip.cpp:393
float End() const override
Get end position (in seconds) of clip (trim end of video), which can be affected by the time curve.
Definition: Clip.cpp:429
std::shared_ptr< openshot::Frame > GetFrame(int64_t clip_frame_number) override
Get an openshot::Frame object for a specific frame number of this clip. The image size and number of ...
Definition: Clip.cpp:464
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
Definition: Clip.cpp:346
void Log(std::string message, Level level=LevelDebug)
Definition: Logger.cpp:146
static Logger * Instance()
Definition: Logger.cpp:55
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
std::vector< uint32_t > rle
Definition: CVObjectMask.h:40
std::vector< cv::Rect_< float > > positiveRects
Definition: CVObjectMask.h:48
std::vector< cv::Point2f > positivePoints
Definition: CVObjectMask.h:46
std::vector< cv::Rect_< float > > negativeRects
Definition: CVObjectMask.h:49
std::vector< cv::Point2f > negativePoints
Definition: CVObjectMask.h:47