cnosd.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /*************************************************************************
  2. * Copyright (C) [2019] by Cambricon, Inc. All rights reserved
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  13. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  15. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. * THE SOFTWARE.
  19. *************************************************************************/
  20. #include "cnosd.hpp"
  21. #include <algorithm>
  22. #include <memory>
  23. #include <string>
  24. #include <utility>
  25. #include <vector>
  26. #include "cnfont.hpp"
  27. #define CLIP(x) x < 0 ? 0 : (x > 1 ? 1 : x)
  28. namespace cnstream {
  29. // Keep 2 digits after decimal
  30. static std::string FloatToString(float number) {
  31. char buffer[10];
  32. snprintf(buffer, sizeof(buffer), "%.2f", number);
  33. return std::string(buffer);
  34. }
  35. // http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically
  36. static cv::Scalar HSV2RGB(const float h, const float s, const float v) {
  37. const int h_i = static_cast<int>(h * 6);
  38. const float f = h * 6 - h_i;
  39. const float p = v * (1 - s);
  40. const float q = v * (1 - f * s);
  41. const float t = v * (1 - (1 - f) * s);
  42. float r, g, b;
  43. switch (h_i) {
  44. case 0:
  45. r = v;
  46. g = t;
  47. b = p;
  48. break;
  49. case 1:
  50. r = q;
  51. g = v;
  52. b = p;
  53. break;
  54. case 2:
  55. r = p;
  56. g = v;
  57. b = t;
  58. break;
  59. case 3:
  60. r = p;
  61. g = q;
  62. b = v;
  63. break;
  64. case 4:
  65. r = t;
  66. g = p;
  67. b = v;
  68. break;
  69. case 5:
  70. r = v;
  71. g = p;
  72. b = q;
  73. break;
  74. default:
  75. r = 1;
  76. g = 1;
  77. b = 1;
  78. break;
  79. }
  80. return cv::Scalar(r * 255, g * 255, b * 255);
  81. }
  82. static std::vector<cv::Scalar> GenerateColorsForCategories(const int n) {
  83. std::vector<cv::Scalar> colors;
  84. cv::RNG rng(12345);
  85. const float golden_ratio_conjugate = 0.618033988749895f;
  86. const float s = 0.3f;
  87. const float v = 0.99f;
  88. for (int i = 0; i < n; ++i) {
  89. const float h = std::fmod(rng.uniform(0.0f, 1.0f) + golden_ratio_conjugate, 1.0f);
  90. colors.push_back(HSV2RGB(h, s, v));
  91. }
  92. return colors;
  93. }
  94. CnOsd::CnOsd(const std::vector<std::string>& labels) : labels_(labels) {
  95. colors_ = GenerateColorsForCategories(labels_.size());
  96. }
  97. void CnOsd::DrawLogo(cv::Mat image, std::string logo) const {
  98. cv::Point logo_pos(5, image.rows - 5);
  99. uint32_t scale = 1;
  100. uint32_t thickness = 2;
  101. cv::Scalar color(200, 200, 200);
  102. cv::putText(image, logo, logo_pos, font_, scale, color, thickness);
  103. }
  104. void CnOsd::DrawLabel(cv::Mat image, const CNInferObjsPtr& objs_holder, std::vector<std::string> attr_keys) const {
  105. // check input data
  106. if (image.cols * image.rows == 0) {
  107. LOGE(OSD) << "Osd: the image is empty.";
  108. return;
  109. }
  110. bool drawText = true;
  111. if(objs_holder->objs_.size() > 5) drawText = false;
  112. for (uint32_t i = 0; i < objs_holder->objs_.size(); ++i) {
  113. std::shared_ptr<cnstream::CNInferObject> object = objs_holder->objs_[i];
  114. if (!object) continue;
  115. std::pair<cv::Point, cv::Point> corner = GetBboxCorner(*object.get(), image.cols, image.rows);
  116. cv::Point top_left = corner.first;
  117. cv::Point bottom_right = corner.second;
  118. cv::Point bottom_left(top_left.x, bottom_right.y);
  119. cv::Scalar color(100, 200, 0);
  120. int label_id = GetLabelId(object->id);
  121. if (LabelIsFound(label_id)) {
  122. color = colors_[label_id];
  123. }
  124. // Draw Detection window
  125. LOGD(OSD) << "Draw Bounding Box: "
  126. << "top_left: (" << top_left.x << "," << top_left.y << ") bottom_right:(" << bottom_right.x << ","
  127. << bottom_right.y << ")";
  128. DrawBox(image, top_left, bottom_right, color);
  129. // Draw Text label + score + track id
  130. std::string text;
  131. if (LabelIsFound(label_id)) {
  132. text = labels_[label_id];
  133. } else {
  134. text = "Label not found, id = " + std::to_string(label_id);
  135. }
  136. text += " " + FloatToString(object->score);
  137. if (!object->track_id.empty() && object->track_id != "-1") {
  138. text += " track_id: " + object->track_id;
  139. LOGD(OSD) << "Draw Label, Score and TrackID: " << text;
  140. } else {
  141. LOGD(OSD) << "Draw Label and Score: " << text;
  142. }
  143. if(drawText) DrawText(image, bottom_left, text, color);
  144. // draw secondary inference information
  145. int label_bottom_y = 0;
  146. int text_height = 0;
  147. for (auto& key : attr_keys) {
  148. CNInferAttr infer_attr = object->GetAttribute(key);
  149. if (infer_attr.value < 0 || infer_attr.value > static_cast<int>(secondary_labels_.size()) - 1) {
  150. std::string attr_value = object->GetExtraAttribute(key);
  151. if (attr_value.empty()) continue;
  152. std::string secondary_text = key + " : " + attr_value;
  153. DrawText(image, top_left + cv::Point(0, label_bottom_y), secondary_text, color, 0.5, &text_height);
  154. } else {
  155. std::string secondary_label = secondary_labels_[infer_attr.value];
  156. std::string secondary_score = std::to_string(infer_attr.score);
  157. secondary_score = secondary_score.substr(0, std::min(size_t(4), secondary_score.size()));
  158. std::string secondary_text = key + " : " + secondary_label + " score[" + secondary_score + "]";
  159. DrawText(image, top_left + cv::Point(0, label_bottom_y), secondary_text, color, 0.5, &text_height);
  160. }
  161. label_bottom_y += text_height;
  162. }
  163. }
  164. }
  165. std::pair<cv::Point, cv::Point> CnOsd::GetBboxCorner(const CNInferObject& object, int img_width, int img_height) const {
  166. float x = CLIP(object.bbox.x);
  167. float y = CLIP(object.bbox.y);
  168. float w = CLIP(object.bbox.w);
  169. float h = CLIP(object.bbox.h);
  170. w = (x + w > 1) ? (1 - x) : w;
  171. h = (y + h > 1) ? (1 - y) : h;
  172. cv::Point top_left(x * img_width, y * img_height);
  173. cv::Point bottom_right((x + w) * img_width, (y + h) * img_height);
  174. return std::make_pair(top_left, bottom_right);
  175. }
  176. bool CnOsd::LabelIsFound(const int& label_id) const {
  177. if (labels_.size() <= static_cast<size_t>(label_id)) {
  178. return false;
  179. }
  180. return true;
  181. }
  182. int CnOsd::GetLabelId(const std::string& label_id_str) const {
  183. return label_id_str.empty() ? -1 : std::stoi(label_id_str);
  184. }
  185. void CnOsd::DrawBox(cv::Mat image, const cv::Point& top_left, const cv::Point& bottom_right,
  186. const cv::Scalar& color) const {
  187. float w =bottom_right.x - top_left.x;
  188. float h =bottom_right.y - top_left.y;
  189. int wscale = w * .25f;
  190. int hscale = h * .25f;
  191. cv::Point top_right(bottom_right.x,top_left.y);
  192. cv::Point bottom_left(top_left.x,bottom_right.y);
  193. // --
  194. cv::line(image,top_left,cv::Point(top_left.x + wscale,top_left.y),color, CalcThickness(image.cols, box_thickness_));
  195. // |
  196. cv::line(image,top_left,cv::Point(top_left.x ,top_left.y + hscale),color, CalcThickness(image.cols, box_thickness_));
  197. // --
  198. cv::line(image,cv::Point(top_right.x - wscale,top_right.y),top_right,color, CalcThickness(image.cols, box_thickness_));
  199. // |
  200. cv::line(image,top_right,cv::Point(top_right.x,top_right.y + hscale),color, CalcThickness(image.cols, box_thickness_));
  201. //
  202. // __
  203. cv::line(image,bottom_left,cv::Point(bottom_left.x+wscale,bottom_left.y),color, CalcThickness(image.cols, box_thickness_));
  204. //
  205. //|
  206. cv::line(image,bottom_left,cv::Point(bottom_left.x,bottom_left.y - hscale),color, CalcThickness(image.cols, box_thickness_));
  207. //
  208. // |
  209. cv::line(image,bottom_right,cv::Point(bottom_right.x,bottom_right.y - hscale),color, CalcThickness(image.cols, box_thickness_));
  210. cv::line(image,bottom_right,cv::Point(bottom_right.x - wscale,bottom_right.y),color, CalcThickness(image.cols, box_thickness_));
  211. // cv::rectangle(image, top_left, bottom_right, color, CalcThickness(image.cols, box_thickness_));
  212. }
  213. void CnOsd::DrawText(cv::Mat image, const cv::Point& bottom_left, const std::string& text, const cv::Scalar& color,
  214. float scale, int* text_height) const {
  215. double txt_scale = CalcScale(image.cols, text_scale_) * scale;
  216. int txt_thickness = CalcThickness(image.cols, text_thickness_) * scale;
  217. int box_thickness = CalcThickness(image.cols, box_thickness_) * scale;
  218. int baseline = 0;
  219. int space_before = 0;
  220. cv::Size text_size;
  221. int label_height;
  222. if (cn_font_ == nullptr) {
  223. text_size = cv::getTextSize(text, font_, txt_scale, txt_thickness, &baseline);
  224. space_before = 3 * txt_scale;
  225. text_size.width += space_before * 2;
  226. } else {
  227. uint32_t text_h = 0, text_w = 0;
  228. char* str = const_cast<char*>(text.data());
  229. cn_font_->GetTextSize(str, &text_w, &text_h);
  230. baseline = cn_font_->GetFontPixel() / 4;
  231. space_before = baseline / 2;
  232. text_size.height = text_h;
  233. text_size.width = text_w + space_before * 2;
  234. }
  235. label_height = baseline + txt_thickness + text_size.height;
  236. int offset = (box_thickness == 1 ? 0 : -(box_thickness + 1) / 2);
  237. cv::Point label_top_left = bottom_left + cv::Point(offset, offset);
  238. cv::Point label_bottom_right = label_top_left + cv::Point(text_size.width + offset, label_height);
  239. // move up if the label is beyond the bottom of the image
  240. if (label_bottom_right.y > image.rows) {
  241. label_bottom_right.y -= label_height;
  242. label_top_left.y -= label_height;
  243. }
  244. // move left if the label is beyond the right side of the image
  245. if (label_bottom_right.x > image.cols) {
  246. label_bottom_right.x = image.cols;
  247. label_top_left.x = image.cols - text_size.width;
  248. }
  249. // draw text background
  250. cv::rectangle(image, label_top_left, label_bottom_right, color, -1);
  251. // draw text
  252. cv::Point text_left_bottom =
  253. label_top_left + cv::Point(space_before, label_height - baseline / 2 - txt_thickness / 2);
  254. cv::Scalar text_color = cv::Scalar(255, 255, 255) - color;
  255. if (cn_font_ == nullptr) {
  256. cv::putText(image, text, text_left_bottom, font_, txt_scale, text_color, txt_thickness);
  257. } else {
  258. char* str = const_cast<char*>(text.data());
  259. cn_font_->putText(image, str, text_left_bottom, text_color);
  260. }
  261. if (text_height) *text_height = text_size.height + baseline;
  262. }
  263. int CnOsd::CalcThickness(int image_width, float thickness) const {
  264. int result = thickness * image_width / 300;
  265. if (result <= 0) result = 1;
  266. return result;
  267. }
  268. double CnOsd::CalcScale(int image_width, float scale) const { return scale * image_width / 1000; }
  269. } // namespace cnstream