agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 1 | /** |
| 2 | * Copyright (C) 2020 Savoir-faire Linux Inc. |
| 3 | * |
| 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 |
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 19 | */ |
| 20 | |
| 21 | #include "pluginProcessor.h" |
| 22 | // System includes |
| 23 | #include <algorithm> |
| 24 | #include <cstring> |
| 25 | // OpenCV headers |
| 26 | #include <opencv2/imgproc.hpp> |
| 27 | #include <opencv2/imgcodecs.hpp> |
| 28 | #include <opencv2/core.hpp> |
| 29 | // Logger |
| 30 | #include <pluglog.h> |
| 31 | |
| 32 | extern "C" { |
| 33 | #include <libavutil/display.h> |
| 34 | } |
| 35 | |
| 36 | const char sep = separator(); |
| 37 | |
| 38 | const std::string TAG = "FORESEG"; |
| 39 | |
| 40 | PluginParameters* mPluginParameters = getGlobalPluginParameters(); |
| 41 | |
| 42 | namespace jami |
| 43 | { |
| 44 | |
| 45 | PluginProcessor::PluginProcessor(const std::string& dataPath): |
agsantos | 364bbae | 2020-08-17 11:29:56 -0400 | [diff] [blame] | 46 | pluginInference{TFModel{dataPath + sep + "models" + sep + mPluginParameters->model}} |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 47 | { |
| 48 | initModel(); |
agsantos | 9dcf430 | 2020-09-01 18:21:48 -0400 | [diff] [blame^] | 49 | setBackgroundImage(mPluginParameters->image); |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 50 | } |
| 51 | |
| 52 | void |
agsantos | 9dcf430 | 2020-09-01 18:21:48 -0400 | [diff] [blame^] | 53 | PluginProcessor::setBackgroundImage(const std::string& backgroundPath) |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 54 | { |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 55 | cv::Size size = cv::Size{0,0}; |
| 56 | |
| 57 | if (!backgroundImage.empty()) |
| 58 | size = backgroundImage.size(); |
| 59 | |
agsantos | 9dcf430 | 2020-09-01 18:21:48 -0400 | [diff] [blame^] | 60 | cv::Mat newBackgroundImage = cv::imread(backgroundPath); |
| 61 | if (newBackgroundImage.cols == 0) { |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 62 | Plog::log(Plog::LogPriority::ERR, TAG, "Background image not Loaded"); |
| 63 | } |
| 64 | else { |
| 65 | Plog::log(Plog::LogPriority::INFO, TAG, "Background image Loaded"); |
agsantos | 9dcf430 | 2020-09-01 18:21:48 -0400 | [diff] [blame^] | 66 | cv::cvtColor(newBackgroundImage, newBackgroundImage, cv::COLOR_BGR2RGB); |
| 67 | newBackgroundImage.convertTo(newBackgroundImage, CV_32FC3); |
| 68 | if (size.height) { |
| 69 | cv::resize(newBackgroundImage, newBackgroundImage, size); |
| 70 | backgroundRotation = 0; |
| 71 | } |
| 72 | backgroundImage = newBackgroundImage.clone(); |
| 73 | newBackgroundImage.release(); |
| 74 | hasBackground_ = true; |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 75 | } |
| 76 | } |
| 77 | |
| 78 | void |
| 79 | PluginProcessor::initModel() |
| 80 | { |
| 81 | try { |
| 82 | pluginInference.init(); |
| 83 | } |
| 84 | catch (std::exception& e) { |
| 85 | Plog::log(Plog::LogPriority::ERR, TAG, e.what()); |
| 86 | } |
| 87 | std::ostringstream oss; |
| 88 | oss << "Model is allocated " << pluginInference.isAllocated(); |
| 89 | Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); |
| 90 | } |
| 91 | |
| 92 | |
| 93 | #ifdef TFLITE |
| 94 | void |
| 95 | PluginProcessor::feedInput(const cv::Mat& frame) |
| 96 | { |
| 97 | auto pair = pluginInference.getInput(); |
| 98 | uint8_t* inputPointer = pair.first; |
| 99 | |
| 100 | cv::Mat temp(frame.rows, frame.cols, CV_8UC3, inputPointer); |
| 101 | frame.convertTo(temp, CV_8UC3); |
| 102 | |
| 103 | inputPointer = nullptr; |
| 104 | } |
| 105 | #else |
| 106 | void |
| 107 | PluginProcessor::feedInput(const cv::Mat& frame) |
| 108 | { |
| 109 | pluginInference.ReadTensorFromMat(frame); |
| 110 | } |
| 111 | #endif //TFLITE |
| 112 | |
| 113 | int |
| 114 | PluginProcessor::getBackgroundRotation() |
| 115 | { |
| 116 | return backgroundRotation; |
| 117 | } |
| 118 | |
| 119 | void |
| 120 | PluginProcessor::setBackgroundRotation(int angle) |
| 121 | { |
| 122 | if (backgroundRotation != angle && (backgroundRotation - angle) != 0) { |
| 123 | switch (backgroundRotation - angle) { |
| 124 | case 90: |
| 125 | cv::rotate(backgroundImage, backgroundImage, cv::ROTATE_90_CLOCKWISE); |
| 126 | break; |
| 127 | case 180: |
| 128 | cv::rotate(backgroundImage, backgroundImage, cv::ROTATE_180); |
| 129 | break; |
| 130 | case -180: |
| 131 | cv::rotate(backgroundImage, backgroundImage, cv::ROTATE_180); |
| 132 | break; |
| 133 | case -90: |
| 134 | cv::rotate(backgroundImage, backgroundImage, cv::ROTATE_90_COUNTERCLOCKWISE); |
| 135 | break; |
| 136 | } |
| 137 | backgroundRotation = angle; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | void |
| 142 | PluginProcessor::computePredictions() |
| 143 | { |
| 144 | // Run the graph |
| 145 | pluginInference.runGraph(); |
| 146 | auto predictions = pluginInference.masksPredictions(); |
| 147 | |
| 148 | // Save the predictions |
| 149 | computedMask = predictions; |
| 150 | } |
| 151 | |
| 152 | void |
| 153 | PluginProcessor::printMask() |
| 154 | { |
| 155 | for (size_t i = 0; i < computedMask.size(); i++) |
| 156 | { |
| 157 | // Log the predictions |
| 158 | std::ostringstream oss; |
| 159 | oss << "\nclass: "<< computedMask[i] << std::endl; |
| 160 | Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | |
| 165 | void |
| 166 | copyByLine(uchar* frameData, uchar* applyMaskData, const int lineSize, cv::Size size) |
| 167 | { |
| 168 | if (3 * size.width == lineSize) { |
| 169 | std::memcpy(frameData, applyMaskData, size.height * size.width * 3);; |
| 170 | } |
| 171 | else { |
| 172 | int rows = size.height; |
| 173 | int offset = 0; |
| 174 | int maskoffset = 0; |
| 175 | for (int i = 0; i < rows; i++) { |
| 176 | std::memcpy(frameData + offset, applyMaskData + maskoffset, lineSize); |
| 177 | offset += lineSize; |
| 178 | maskoffset += 3 * size.width; |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | void |
| 184 | PluginProcessor::drawMaskOnFrame(cv::Mat& frame, |
| 185 | cv::Mat& frameReduced, |
| 186 | std::vector<float>computedMask, |
| 187 | int lineSize, int angle) |
| 188 | { |
| 189 | if (computedMask.empty()) { |
| 190 | return; |
| 191 | } |
| 192 | if (previousMasks[0].empty()) { |
| 193 | previousMasks[0] = cv::Mat(frameReduced.rows, frameReduced.cols, CV_32FC1, double(0.)); |
| 194 | previousMasks[1] = cv::Mat(frameReduced.rows, frameReduced.cols, CV_32FC1, double(0.)); |
| 195 | } |
| 196 | int maskSize = static_cast<int> (std::sqrt(computedMask.size())); |
| 197 | cv::Mat maskImg(maskSize, maskSize, CV_32FC1, computedMask.data()); |
| 198 | |
| 199 | rotateFrame(-angle, maskImg); |
| 200 | #ifdef TFLITE |
| 201 | for (int i = 0; i < maskImg.cols; i++) { |
| 202 | for (int j = 0; j < maskImg.rows; j++) { |
| 203 | if (maskImg.at<float>(j, i) == 15) |
| 204 | maskImg.at<float>(j, i) = 255.; |
| 205 | else |
| 206 | maskImg.at<float>(j, i) = (float)((int)((0.6 * maskImg.at<float>(j, i) + 0.3 * previousMasks[0].at<float>(j, i) + 0.1 * previousMasks[1].at<float>(j, i))) % 256); |
| 207 | } |
| 208 | } |
| 209 | #else // TFLITE |
| 210 | cv::resize(maskImg, maskImg, cv::Size(frameReduced.cols, frameReduced.rows)); |
| 211 | |
| 212 | double m, M; |
| 213 | cv::minMaxLoc(maskImg, &m, &M); |
| 214 | |
| 215 | if (M < 2) { //avoid detection if there is any one in frame |
| 216 | maskImg = 0. * maskImg; |
| 217 | } |
| 218 | else { |
| 219 | for (int i = 0; i < maskImg.cols; i++) { |
| 220 | for (int j = 0; j < maskImg.rows; j++) { |
| 221 | maskImg.at<float>(j, i) = (maskImg.at<float>(j, i) - m) / (M - m); |
| 222 | |
| 223 | if (maskImg.at<float>(j, i) < 0.4) |
| 224 | maskImg.at<float>(j, i) = 0.; |
| 225 | else if (maskImg.at<float>(j, i) < 0.7) { |
| 226 | float value = maskImg.at<float>(j, i) * 0.6 + previousMasks[0].at<float>(j, i) * 0.3 + previousMasks[1].at<float>(j, i) * 0.1; |
| 227 | maskImg.at<float>(j, i) = 0.; |
| 228 | if (value > 0.7) |
| 229 | maskImg.at<float>(j, i) = 1.; |
| 230 | } |
| 231 | else |
| 232 | maskImg.at<float>(j, i) = 1.; |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | #endif |
| 237 | |
| 238 | previousMasks[1] = previousMasks[0].clone(); |
| 239 | previousMasks[0] = maskImg.clone(); |
| 240 | |
| 241 | kSize = cv::Size(maskImg.cols * 0.05, maskImg.rows * 0.05); |
| 242 | if (kSize.height % 2 == 0) |
| 243 | kSize.height -= 1; |
| 244 | if (kSize.width % 2 == 0) |
| 245 | kSize.width -= 1; |
| 246 | |
| 247 | #ifndef TFLITE |
| 248 | cv::dilate(maskImg, maskImg, cv::getStructuringElement(cv::MORPH_CROSS, kSize)); |
| 249 | maskImg = maskImg * 255.; |
| 250 | #endif |
| 251 | GaussianBlur (maskImg, maskImg, kSize, 0); //mask from 0 to 255. |
| 252 | maskImg = maskImg / 255.; |
| 253 | |
| 254 | cv::Mat applyMask = frameReduced.clone(); |
| 255 | cv::Mat roiMaskImg = maskImg.clone(); |
| 256 | cv::Mat roiMaskImgComplementary = 1. - roiMaskImg; //mask from 1. to 0 |
| 257 | |
| 258 | std::vector<cv::Mat> channels; |
| 259 | std::vector<cv::Mat> channelsComplementary; |
| 260 | |
| 261 | channels.emplace_back(roiMaskImg); |
| 262 | channels.emplace_back(roiMaskImg); |
| 263 | channels.emplace_back(roiMaskImg); |
| 264 | channelsComplementary.emplace_back(roiMaskImgComplementary); |
| 265 | channelsComplementary.emplace_back(roiMaskImgComplementary); |
| 266 | channelsComplementary.emplace_back(roiMaskImgComplementary); |
| 267 | |
| 268 | cv::merge(channels, roiMaskImg); |
| 269 | cv::merge(channelsComplementary, roiMaskImgComplementary); |
| 270 | |
| 271 | int origType = frameReduced.type(); |
| 272 | int roiMaskType = roiMaskImg.type(); |
| 273 | |
| 274 | applyMask.convertTo(applyMask, roiMaskType); |
| 275 | applyMask = applyMask.mul(roiMaskImg); |
| 276 | applyMask += backgroundImage.mul(roiMaskImgComplementary); |
| 277 | applyMask.convertTo(applyMask, origType); |
| 278 | |
| 279 | cv::resize(applyMask, applyMask, cv::Size(frame.cols, frame.rows)); |
| 280 | |
| 281 | copyByLine(frame.data, applyMask.data, lineSize, cv::Size(frame.cols, frame.rows)); |
| 282 | } |
| 283 | |
| 284 | void |
| 285 | PluginProcessor::rotateFrame(int angle, cv::Mat& mat) |
| 286 | { |
| 287 | if (angle != 0) { |
| 288 | switch (angle) { |
| 289 | case -90: |
| 290 | cv::rotate(mat, mat, cv::ROTATE_90_COUNTERCLOCKWISE); |
| 291 | break; |
| 292 | case 180: |
| 293 | cv::rotate(mat, mat, cv::ROTATE_180); |
| 294 | break; |
| 295 | case -180: |
| 296 | cv::rotate(mat, mat, cv::ROTATE_180); |
| 297 | break; |
| 298 | case 90: |
| 299 | cv::rotate(mat, mat, cv::ROTATE_90_CLOCKWISE); |
| 300 | break; |
| 301 | } |
| 302 | } |
| 303 | } |
agsantos | 9dcf430 | 2020-09-01 18:21:48 -0400 | [diff] [blame^] | 304 | |
| 305 | bool |
| 306 | PluginProcessor::hasBackground() const |
| 307 | { |
| 308 | return hasBackground_; |
| 309 | } |
agsantos | 5aa3965 | 2020-08-11 18:18:04 -0400 | [diff] [blame] | 310 | } // namespace jami |