blob: ff4955b8b40421862ecded8225ced4fef9dec9bc [file] [log] [blame]
agsantos5aa39652020-08-11 18:18:04 -04001/**
Sébastien Blincb783e32021-02-12 11:34:10 -05002 * Copyright (C) 2020-2021 Savoir-faire Linux Inc.
agsantos5aa39652020-08-11 18:18:04 -04003 *
4 * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
agsantosac1940d2020-09-17 10:18:40 -040018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 * USA.
agsantos5aa39652020-08-11 18:18:04 -040020 */
21
22#include "pluginProcessor.h"
agsantos5aa39652020-08-11 18:18:04 -040023
agsantosdd6a62a2021-03-29 17:13:27 -040024#include <opencv2/imgproc.hpp>
agsantos5aa39652020-08-11 18:18:04 -040025extern "C" {
26#include <libavutil/display.h>
27}
agsantosdd6a62a2021-03-29 17:13:27 -040028#include <frameUtils.h>
29#include <pluglog.h>
30#ifdef WIN32
31#include <string_utils.h>
32#endif
agsantos5aa39652020-08-11 18:18:04 -040033const char sep = separator();
34
35const std::string TAG = "FORESEG";
36
agsantosac1940d2020-09-17 10:18:40 -040037namespace jami {
agsantos5aa39652020-08-11 18:18:04 -040038
agsantosdd6a62a2021-03-29 17:13:27 -040039PluginProcessor::PluginProcessor(const std::string& model, bool acc)
agsantos5aa39652020-08-11 18:18:04 -040040{
agsantosdd6a62a2021-03-29 17:13:27 -040041 initModel(model, acc);
agsantos796b5af2020-12-22 19:38:27 -050042}
43
44PluginProcessor::~PluginProcessor()
45{
agsantosdd6a62a2021-03-29 17:13:27 -040046 mainFilter_.clean();
agsantos796b5af2020-12-22 19:38:27 -050047 Plog::log(Plog::LogPriority::INFO, TAG, "~pluginprocessor");
48 if (session_)
49 delete session_;
agsantos5aa39652020-08-11 18:18:04 -040050}
51
52void
agsantosdd6a62a2021-03-29 17:13:27 -040053PluginProcessor::initModel(const std::string& modelPath, bool activateAcc)
agsantos5aa39652020-08-11 18:18:04 -040054{
agsantosac1940d2020-09-17 10:18:40 -040055 try {
agsantos796b5af2020-12-22 19:38:27 -050056 auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
agsantosdd6a62a2021-03-29 17:13:27 -040057 input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info,
58 input_image_.data(),
59 input_image_.size(),
60 input_shape_.data(),
61 input_shape_.size());
62 output_tensor_ = Ort::Value::CreateTensor<float>(allocator_info,
63 results_.data(),
64 results_.size(),
65 output_shape_.data(),
66 output_shape_.size());
67 sessOpt_ = Ort::SessionOptions();
agsantos796b5af2020-12-22 19:38:27 -050068
69#ifdef NVIDIA
agsantosdd6a62a2021-03-29 17:13:27 -040070 if (activateAcc)
agsantos796b5af2020-12-22 19:38:27 -050071 Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_CUDA(sessOpt_, 0));
72#endif
agsantosdd6a62a2021-03-29 17:13:27 -040073#ifdef __ANDROID__
74 if (activateAcc)
agsantos796b5af2020-12-22 19:38:27 -050075 Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_Nnapi(sessOpt_, 0));
76#endif
77
78 sessOpt_.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
79#ifdef WIN32
agsantosdd6a62a2021-03-29 17:13:27 -040080 session_ = new Ort::Session(env_, to_wstring(modelPath).c_str(), sessOpt_);
agsantos796b5af2020-12-22 19:38:27 -050081#else
agsantosdd6a62a2021-03-29 17:13:27 -040082 session_ = new Ort::Session(env_, modelPath.c_str(), sessOpt_);
agsantos796b5af2020-12-22 19:38:27 -050083#endif
84 isAllocated_ = true;
agsantosdd6a62a2021-03-29 17:13:27 -040085 Plog::log(Plog::LogPriority::INFO, TAG, "Model is allocated");
agsantosac1940d2020-09-17 10:18:40 -040086 } catch (std::exception& e) {
87 Plog::log(Plog::LogPriority::ERR, TAG, e.what());
88 }
agsantos796b5af2020-12-22 19:38:27 -050089}
90
agsantos5aa39652020-08-11 18:18:04 -040091void
agsantosdd6a62a2021-03-29 17:13:27 -040092PluginProcessor::feedInput(AVFrame* input)
agsantos5aa39652020-08-11 18:18:04 -040093{
agsantosdd6a62a2021-03-29 17:13:27 -040094 cv::Mat frame = cv::Mat {
95 input->height,
96 input->width,
97 CV_8UC3,
98 input->data[0],
99 static_cast<size_t>(
100 input->linesize[0])}; // not zero input->linesize[0] leads to non continuous data
101 cvFrame_ = frame.clone(); // this is done to have continuous data
102 cv::Mat temp(modelInputDimensions.first,
103 modelInputDimensions.second,
104 CV_32FC3,
105 input_image_.data());
106 cvFrame_.convertTo(temp, CV_32FC3);
agsantos5aa39652020-08-11 18:18:04 -0400107}
108
109void
agsantosac1940d2020-09-17 10:18:40 -0400110PluginProcessor::computePredictions()
agsantos5aa39652020-08-11 18:18:04 -0400111{
agsantosdd6a62a2021-03-29 17:13:27 -0400112 if (count_ == 0) {
agsantosb74f4cb2020-10-01 14:30:43 -0400113 // Run the graph
agsantosdd6a62a2021-03-29 17:13:27 -0400114 session_->Run(Ort::RunOptions {nullptr},
115 modelInputNames,
116 &input_tensor_,
117 1,
118 modelOutputNames,
119 &output_tensor_,
120 1);
121 computedMask_ = std::vector(results_.begin(), results_.end());
agsantosb74f4cb2020-10-01 14:30:43 -0400122 }
agsantos5aa39652020-08-11 18:18:04 -0400123}
124
125void
126PluginProcessor::printMask()
127{
agsantosdd6a62a2021-03-29 17:13:27 -0400128 std::ostringstream oss;
129 for (size_t i = 0; i < computedMask_.size(); i++) {
agsantosac1940d2020-09-17 10:18:40 -0400130 // Log the predictions
agsantosdd6a62a2021-03-29 17:13:27 -0400131 if (computedMask_[i] > 2) {
132 oss << computedMask_[i] << " " << std::endl;
agsantosac1940d2020-09-17 10:18:40 -0400133 }
134 }
agsantosdd6a62a2021-03-29 17:13:27 -0400135 Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
agsantos5aa39652020-08-11 18:18:04 -0400136}
137
138void
agsantosdd6a62a2021-03-29 17:13:27 -0400139PluginProcessor::resetInitValues()
agsantos5aa39652020-08-11 18:18:04 -0400140{
agsantosdd6a62a2021-03-29 17:13:27 -0400141 previousMasks_[0] = previousMasks_[1] = cv::Mat(modelInputDimensions.first,
142 modelInputDimensions.second,
143 CV_32FC1,
144 double(0.));
145 kSize_ = cv::Size(modelInputDimensions.first * kernelSize_,
146 modelInputDimensions.second * kernelSize_);
147 if (kSize_.height % 2 == 0) {
148 kSize_.height -= 1;
agsantosac1940d2020-09-17 10:18:40 -0400149 }
agsantosdd6a62a2021-03-29 17:13:27 -0400150 if (kSize_.width % 2 == 0) {
151 kSize_.width -= 1;
152 }
153 count_ = 0;
154 grabCutMode_ = cv::GC_INIT_WITH_MASK;
155 grabCutIterations_ = 4;
156}
agsantosb74f4cb2020-10-01 14:30:43 -0400157
agsantosdd6a62a2021-03-29 17:13:27 -0400158void
159PluginProcessor::drawMaskOnFrame(AVFrame* frame, AVFrame* frameReduced, int angle)
160{
161 if (computedMask_.empty() || !mainFilter_.initialized_)
162 return;
agsantos796b5af2020-12-22 19:38:27 -0500163
agsantosdd6a62a2021-03-29 17:13:27 -0400164 if (count_ == 0) {
165 int maskSize = static_cast<int>(std::sqrt(computedMask_.size()));
166 cv::Mat maskImg(maskSize, maskSize, CV_32FC1, computedMask_.data());
167
168 cv::resize(maskImg,
169 maskImg,
170 cv::Size(modelInputDimensions.first, modelInputDimensions.second));
agsantos5aa39652020-08-11 18:18:04 -0400171
agsantosb74f4cb2020-10-01 14:30:43 -0400172 double m, M;
173 cv::minMaxLoc(maskImg, &m, &M);
agsantosdd6a62a2021-03-29 17:13:27 -0400174 bool improveMask = !isBlur_;
175 if (M < 2) { // avoid detection if there isn't anyone in frame
agsantosb74f4cb2020-10-01 14:30:43 -0400176 maskImg = 0. * maskImg;
agsantosdd6a62a2021-03-29 17:13:27 -0400177 improveMask = false;
agsantosb74f4cb2020-10-01 14:30:43 -0400178 } else {
179 for (int i = 0; i < maskImg.cols; i++) {
180 for (int j = 0; j < maskImg.rows; j++) {
181 maskImg.at<float>(j, i) = (maskImg.at<float>(j, i) - m) / (M - m);
182
183 if (maskImg.at<float>(j, i) < 0.4)
184 maskImg.at<float>(j, i) = 0.;
185 else if (maskImg.at<float>(j, i) < 0.7) {
agsantosdd6a62a2021-03-29 17:13:27 -0400186 float value = maskImg.at<float>(j, i) * smoothFactors_[0]
187 + previousMasks_[0].at<float>(j, i) * smoothFactors_[1]
188 + previousMasks_[1].at<float>(j, i) * smoothFactors_[2];
agsantosb74f4cb2020-10-01 14:30:43 -0400189 maskImg.at<float>(j, i) = 0.;
190 if (value > 0.7)
191 maskImg.at<float>(j, i) = 1.;
192 } else
193 maskImg.at<float>(j, i) = 1.;
194 }
195 }
196 }
agsantosdd6a62a2021-03-29 17:13:27 -0400197
198 if (improveMask) {
agsantos796b5af2020-12-22 19:38:27 -0500199 cv::Mat dilate;
200 cv::dilate(maskImg,
agsantosdd6a62a2021-03-29 17:13:27 -0400201 dilate,
202 cv::getStructuringElement(cv::MORPH_ELLIPSE, kSize_),
203 cv::Point(-1, -1),
204 2);
agsantos796b5af2020-12-22 19:38:27 -0500205 cv::erode(maskImg,
agsantosdd6a62a2021-03-29 17:13:27 -0400206 maskImg,
207 cv::getStructuringElement(cv::MORPH_ELLIPSE, kSize_),
208 cv::Point(-1, -1),
209 2);
agsantos796b5af2020-12-22 19:38:27 -0500210 for (int i = 0; i < maskImg.cols; i++) {
211 for (int j = 0; j < maskImg.rows; j++) {
212 if (dilate.at<float>(j, i) != maskImg.at<float>(j, i))
agsantosdd6a62a2021-03-29 17:13:27 -0400213 maskImg.at<float>(j, i) = grabcutClass_;
agsantosb74f4cb2020-10-01 14:30:43 -0400214 }
agsantosb74f4cb2020-10-01 14:30:43 -0400215 }
agsantosdd6a62a2021-03-29 17:13:27 -0400216 cv::Mat applyMask = cvFrame_.clone();
agsantos796b5af2020-12-22 19:38:27 -0500217 maskImg.convertTo(maskImg, CV_8UC1);
agsantosdd6a62a2021-03-29 17:13:27 -0400218 applyMask.convertTo(applyMask, CV_8UC1);
agsantos796b5af2020-12-22 19:38:27 -0500219 cv::Rect rect(1, 1, maskImg.rows, maskImg.cols);
agsantosdd6a62a2021-03-29 17:13:27 -0400220 cv::grabCut(applyMask,
agsantos796b5af2020-12-22 19:38:27 -0500221 maskImg,
222 rect,
agsantosdd6a62a2021-03-29 17:13:27 -0400223 bgdModel_,
224 fgdModel_,
225 grabCutIterations_,
226 grabCutMode_);
agsantos796b5af2020-12-22 19:38:27 -0500227
agsantosdd6a62a2021-03-29 17:13:27 -0400228 grabCutMode_ = cv::GC_EVAL;
229 grabCutIterations_ = 1;
agsantos796b5af2020-12-22 19:38:27 -0500230
231 maskImg = maskImg & 1;
agsantosb74f4cb2020-10-01 14:30:43 -0400232 maskImg.convertTo(maskImg, CV_32FC1);
233 maskImg *= 255.;
agsantosdd6a62a2021-03-29 17:13:27 -0400234 blur(maskImg, maskImg, cv::Size(7, 7)); // float mask from 0 to 255.
agsantosb74f4cb2020-10-01 14:30:43 -0400235 maskImg = maskImg / 255.;
236 }
agsantosdd6a62a2021-03-29 17:13:27 -0400237
238 previousMasks_[1] = previousMasks_[0].clone();
239 previousMasks_[0] = maskImg.clone();
agsantosb74f4cb2020-10-01 14:30:43 -0400240 }
241
agsantosdd6a62a2021-03-29 17:13:27 -0400242 cv::Mat roiMaskImg = previousMasks_[0].clone() * 255.;
243 roiMaskImg.convertTo(roiMaskImg, CV_8UC1);
agsantos5aa39652020-08-11 18:18:04 -0400244
agsantosdd6a62a2021-03-29 17:13:27 -0400245 gsFrame maskFrame = {av_frame_alloc(), frameFree};
246 maskFrame->format = AV_PIX_FMT_GRAY8;
247 maskFrame->width = roiMaskImg.cols;
248 maskFrame->height = roiMaskImg.rows;
249 maskFrame->linesize[0] = roiMaskImg.step;
250 if (av_frame_get_buffer(maskFrame.get(), 0) < 0)
251 return;
agsantos5aa39652020-08-11 18:18:04 -0400252
agsantosdd6a62a2021-03-29 17:13:27 -0400253 maskFrame->data[0] = roiMaskImg.data;
254 maskFrame->pts = 1;
255 mainFilter_.feedInput(maskFrame.get(), "mask");
256 maskFrame.reset();
agsantos5aa39652020-08-11 18:18:04 -0400257
agsantosdd6a62a2021-03-29 17:13:27 -0400258 if (isBlur_)
259 mainFilter_.feedInput(frameReduced, "input");
260 mainFilter_.feedInput(frame, "input2");
261 AVFrame* filteredFrame;
262 if ((filteredFrame = mainFilter_.readOutput())) {
263 moveFrom(frame, filteredFrame);
264 frameFree(filteredFrame);
265 }
266 count_++;
267 count_ = count_ % frameCount_;
268}
agsantos5aa39652020-08-11 18:18:04 -0400269
agsantosdd6a62a2021-03-29 17:13:27 -0400270MediaStream
271PluginProcessor::getbgAVFrameInfos()
272{
273 AVFormatContext* ctx = avformat_alloc_context();
274 // Open
275 if (avformat_open_input(&ctx, backgroundPath_.c_str(), NULL, NULL) != 0) {
276 avformat_free_context(ctx);
277 Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't open input stream.");
278 return {};
279 }
280 pFormatCtx_.reset(ctx);
281 // Retrieve stream information
282 if (avformat_find_stream_info(pFormatCtx_.get(), NULL) < 0) {
283 Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't find stream information.");
284 return {};
285 }
agsantos5aa39652020-08-11 18:18:04 -0400286
agsantosdd6a62a2021-03-29 17:13:27 -0400287 // Dump valid information onto standard error
288 av_dump_format(pFormatCtx_.get(), 0, backgroundPath_.c_str(), false);
agsantos5aa39652020-08-11 18:18:04 -0400289
agsantosdd6a62a2021-03-29 17:13:27 -0400290 // Find the video stream
291 for (int i = 0; i < static_cast<int>(pFormatCtx_->nb_streams); i++)
292 if (pFormatCtx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
293 videoStream_ = i;
294 break;
295 }
296
297 if (videoStream_ == -1) {
298 Plog::log(Plog::LogPriority::INFO, TAG, "Didn't find a video stream.");
299 return {};
300 }
301
302 rational<int> fr = pFormatCtx_->streams[videoStream_]->r_frame_rate;
303 return MediaStream("background",
304 pFormatCtx_->streams[videoStream_]->codecpar->format,
305 1 / fr,
306 pFormatCtx_->streams[videoStream_]->codecpar->width,
307 pFormatCtx_->streams[videoStream_]->codecpar->height,
308 0,
309 fr);
agsantos5aa39652020-08-11 18:18:04 -0400310}
311
312void
agsantosdd6a62a2021-03-29 17:13:27 -0400313PluginProcessor::loadBackground()
agsantos5aa39652020-08-11 18:18:04 -0400314{
agsantosdd6a62a2021-03-29 17:13:27 -0400315 if (backgroundPath_.empty())
316 return;
317
318 auto bgStream_ = getbgAVFrameInfos();
319 if (mainFilter_.initialize(mainFilterDescription_, {maskms_, bgStream_, ims2_}) < 0)
320 return;
321
322 int got_frame;
323 AVCodecContext* pCodecCtx;
324 AVCodec* pCodec;
325 AVPacket* packet;
326
327 pCodecCtx = pFormatCtx_->streams[videoStream_]->codec;
328 pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
329 if (pCodec == nullptr) {
330 mainFilter_.clean();
331 pFormatCtx_.reset();
332 Plog::log(Plog::LogPriority::INFO, TAG, "Codec not found.");
333 return;
334 }
335
336 // Open codec
337 if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
338 mainFilter_.clean();
339 pFormatCtx_.reset();
340 Plog::log(Plog::LogPriority::INFO, TAG, "Could not open codec.");
341 return;
342 }
343
344 packet = av_packet_alloc();
345 if (av_read_frame(pFormatCtx_.get(), packet) < 0) {
346 mainFilter_.clean();
347 avcodec_close(pCodecCtx);
348 av_packet_free(&packet);
349 pFormatCtx_.reset();
350 Plog::log(Plog::LogPriority::INFO, TAG, "Could not read packet from context.");
351 return;
352 }
353
354 AVFrame* bgImage = av_frame_alloc();
355 avcodec_decode_video2(pCodecCtx, bgImage, &got_frame, packet);
356 if (got_frame) {
357 mainFilter_.feedInput(bgImage, "background");
358 mainFilter_.feedEOF("background");
359 } else
360 mainFilter_.clean();
361
362 frameFree(bgImage);
363 avcodec_close(pCodecCtx);
364 av_packet_free(&packet);
365 pFormatCtx_.reset();
agsantos5aa39652020-08-11 18:18:04 -0400366}
agsantos9dcf4302020-09-01 18:21:48 -0400367
agsantosdd6a62a2021-03-29 17:13:27 -0400368void
369PluginProcessor::initFilters(const std::pair<int, int>& inputSize, int format, int angle)
agsantos9dcf4302020-09-01 18:21:48 -0400370{
agsantosdd6a62a2021-03-29 17:13:27 -0400371 resetInitValues();
372 mainFilter_.clean();
373
374 std::string rotateSides = "";
375 std::string scaleSize = std::to_string(inputSize.first) + ":"
376 + std::to_string(inputSize.second);
377 Plog::log(Plog::LogPriority::INFO, TAG, scaleSize);
378 if (std::abs(angle) == 90) {
379 rotateSides = ":out_w=ih:out_h=iw";
380 scaleSize = std::to_string(inputSize.second) + ":" + std::to_string(inputSize.first);
381 }
382
383 rational<int> fr(1, 1);
384 ims_ = MediaStream("input",
385 AV_PIX_FMT_RGB24,
386 1 / fr,
387 modelInputDimensions.first,
388 modelInputDimensions.second,
389 0,
390 fr);
391 ims2_ = MediaStream("input2", format, 1 / fr, inputSize.first, inputSize.second, 0, fr);
392
393 maskms_ = MediaStream("mask",
394 AV_PIX_FMT_GRAY8,
395 1 / fr,
396 modelInputDimensions.first,
397 modelInputDimensions.second,
398 0,
399 fr);
400
401 if (isBlur_) {
402 mainFilterDescription_ = "[mask]negate[negated],[input][negated]alphamerge,boxblur="
403 + blurLevel_ + ",scale=" + scaleSize
404 + "[blured],[input2]format=rgb24,rotate=" + rotation[-angle]
405 + rotateSides
406 + "[input2formated],[input2formated][blured]overlay,rotate="
407 + rotation[angle] + rotateSides;
408 Plog::log(Plog::LogPriority::INFO, TAG, mainFilterDescription_);
409 mainFilter_.initialize(mainFilterDescription_, {maskms_, ims_, ims2_});
410 } else {
411 mainFilterDescription_ = "[mask]scale=" + scaleSize
412 + "[fgmask],[fgmask]split=2[bg][fg],[bg]negate[bgn],"
413 + "[background]scale=" + scaleSize + "[backgroundformated],"
414 + "[backgroundformated][bgn]alphamerge[background2],"
415 + "[input2]rotate=" + rotation[-angle] + rotateSides
416 + "[input2formated],[input2formated][fg]alphamerge[foreground],"
417 + "[foreground][background2]overlay," + "rotate=" + rotation[angle]
418 + rotateSides;
419 Plog::log(Plog::LogPriority::INFO, TAG, mainFilterDescription_);
420 loadBackground();
421 }
agsantos9dcf4302020-09-01 18:21:48 -0400422}
agsantos5aa39652020-08-11 18:18:04 -0400423} // namespace jami