samples/dnn/object_detection.cpp#
Check the corresponding tutorial for more details
1//![includes]
2#include <fstream>
3#include <sstream>
4
5#include <opencv2/dnn.hpp>
6#include <opencv2/imgproc.hpp>
7#include <opencv2/imgcodecs.hpp>
8#include <opencv2/highgui.hpp>
9#include <opencv2/core/utils/logger.hpp>
10
11#include <mutex>
12#include <thread>
13#include <queue>
14
15#include "iostream"
16#include "common.hpp"
17//![includes]
18
19using namespace cv;
20using namespace dnn;
21using namespace std;
22
23const string about =
24 "Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
25 "To run:\n"
26 "\t ./example_dnn_object_detection model_name --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)\n"
27 "Sample command:\n"
28 "\t ./example_dnn_object_detection yolov8 --input=$OPENCV_SAMPLES_DATA_PATH/baboon.jpg\n"
29
30 "Model path can also be specified using --model argument. ";
31
32const string param_keys =
33 "{ help h | | Print help message. }"
34 "{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
35 "{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
36 "{ device | 0 | camera device number. }"
37 "{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera. }"
38 "{ thr | .5 | Confidence threshold. }"
39 "{ nms | .4 | Non-maximum suppression threshold. }"
40 "{ async | 0 | Number of asynchronous forwards at the same time. "
41 "Choose 0 for synchronous mode }"
42 "{ padvalue | 114.0 | padding value. }"
43 "{ paddingmode | 2 | Choose one of padding modes: "
44 "0: resize to required input size without extra processing, "
45 "1: Image will be cropped after resize, "
46 "2: Resize image to the desired size while preserving the aspect ratio of original image }";
47
48const string backend_keys = format(
49 "{ backend | default | Choose one of computation backends: "
50 "default: automatically (by default), "
51 "openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
52 "opencv: OpenCV implementation, "
53 "vkcom: VKCOM, "
54 "cuda: CUDA, "
55 "webnn: WebNN }");
56
57const string target_keys = format(
58 "{ target | cpu | Choose one of target computation devices: "
59 "cpu: CPU target (by default), "
60 "opencl: OpenCL, "
61 "opencl_fp16: OpenCL fp16 (half-float precision), "
62 "vpu: VPU, "
63 "vulkan: Vulkan, "
64 "cuda: CUDA, "
65 "cuda_fp16: CUDA fp16 (half-float preprocess) }");
66
67string keys = param_keys + backend_keys + target_keys;
68
69float confThreshold, nmsThreshold, scale, paddingValue;
70vector<string> labels;
71Scalar meanv;
72bool swapRB;
73int inpWidth, inpHeight;
74size_t asyncNumReq = 0;
75ImagePaddingMode paddingMode;
76string modelName, framework;
77
78static void preprocess(const Mat& frame, Net& net, Size inpSize);
79
80static void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, const string postprocessing);
81
82static void drawPred(vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, Mat& frame, FontFace& sans, int stdSize, int stdWeight, int stdImgSize, int stdThickness);
83
84static void callback(int pos, void* userdata);
85
86static Scalar getColor(int classId);
87
88static void yoloPostProcessing(
89 const vector<Mat>& outs,
90 vector<int>& keep_classIds,
91 vector<float>& keep_confidences,
92 vector<Rect2d>& keep_boxes,
93 float conf_threshold,
94 float iou_threshold,
95 const string& postprocessing);
96
97static void printAliases(string& zooFile){
98 vector<string> aliases = findAliases(zooFile, "object_detection");
99
100 cout<<"Alias choices: [ ";
101 for (auto it: aliases){
102 cout<<"'"<<it<<"' ";
103 }
104 cout<<"]"<<endl;
105}
106
107static Scalar getTextColor(Scalar bgColor) {
108 double luminance = 0.299 * bgColor[2] + 0.587 * bgColor[1] + 0.114 * bgColor[0];
109
110 return luminance > 128 ? Scalar(0, 0, 0) : Scalar(255, 255, 255);
111}
112
113template <typename T>
114class QueueFPS : public std::queue<T>
115{
116public:
117 QueueFPS() : counter(0) {}
118
119 void push(const T& entry)
120 {
121 std::lock_guard<std::mutex> lock(mutex);
122
123 std::queue<T>::push(entry);
124 counter += 1;
125 if (counter == 1)
126 {
127 // Start counting from a second frame (warmup).
128 tm.reset();
129 tm.start();
130 }
131 }
132
133 T get()
134 {
135 std::lock_guard<std::mutex> lock(mutex);
136 T entry = this->front();
137 this->pop();
138 return entry;
139 }
140
141 float getFPS()
142 {
143 tm.stop();
144 double fps = counter / tm.getTimeSec();
145 tm.start();
146 return static_cast<float>(fps);
147 }
148
149 void clear()
150 {
151 std::lock_guard<std::mutex> lock(mutex);
152 while (!this->empty())
153 this->pop();
154 }
155
156 unsigned int counter;
157
158private:
159 TickMeter tm;
160 std::mutex mutex;
161};
162
163int main(int argc, char** argv)
164{
165 utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
166
167 CommandLineParser parser(argc, argv, keys);
168
169 string zooFile = parser.get<String>("zoo");
170 if (!parser.has("@alias") || parser.has("help"))
171 {
172 cout << about << endl;
173 parser.printMessage();
174 printAliases(zooFile);
175 return -1;
176 }
177 zooFile = findFile(zooFile);
178 modelName = parser.get<String>("@alias");
179
180 keys += genPreprocArguments(modelName, zooFile);
181
182 parser = CommandLineParser(argc, argv, keys);
183
184 if (!parser.has("model"))
185 {
186 cout << "Path to model is not provided in command line or model alias is not correct" << endl;
187 printAliases(zooFile);
188 return -1;
189 }
190
191 confThreshold = parser.get<float>("thr");
192 nmsThreshold = parser.get<float>("nms");
193 //![preprocess_params]
194 scale = parser.get<float>("scale");
195 meanv = parser.get<Scalar>("mean");
196 swapRB = parser.get<bool>("rgb");
197 inpWidth = parser.get<int>("width");
198 inpHeight = parser.get<int>("height");
199 int async = parser.get<int>("async");
200 paddingValue = parser.get<float>("padvalue");
201 const string postprocessing = parser.get<String>("postprocessing");
202 paddingMode = static_cast<ImagePaddingMode>(parser.get<int>("paddingmode"));
203 //![preprocess_params]
204 String sha1 = parser.get<String>("sha1");
205 String config_sha1 = parser.get<String>("config_sha1");
206 const string modelPath = findModel(parser.get<String>("model"), sha1);
207 const string configPath = findModel(parser.get<String>("config"), config_sha1);
208 framework = modelPath.substr(modelPath.rfind('.') + 1);
209
210 if (parser.has("labels"))
211 {
212 const string file = findFile(parser.get<String>("labels"));
213 ifstream ifs(file.c_str());
214 if (!ifs.is_open())
215 CV_Error(Error::StsError, "File " + file + " not found");
216 string line;
217 while (getline(ifs, line))
218 {
219 labels.push_back(line);
220 }
221 }
222 //![read_net]
223 EngineType engine = ENGINE_AUTO;
224 if ((parser.get<String>("backend") != "default") || (parser.get<String>("target") != "cpu")){
225 engine = ENGINE_CLASSIC;
226 }
227 Net net = readNet(modelPath, configPath, "", engine);
228 int backend = getBackendID(parser.get<String>("backend"));
229 net.setPreferableBackend(backend);
230 net.setPreferableTarget(getTargetID(parser.get<String>("target")));
231 net.setProfilingMode(DNN_PROFILE_SUMMARY);
232 //![read_net]
233
234 // Create a window
235 static const string kWinName = "Deep learning object detection in OpenCV";
236 namedWindow(kWinName, WINDOW_AUTOSIZE);
237 int initialConf = (int)(confThreshold * 100);
238 createTrackbar("Confidence threshold, %", kWinName, &initialConf, 99, callback, &net);
239
240 // Open a video file or an image file or a camera stream.
241 VideoCapture cap;
242 bool openSuccess = parser.has("input") ? cap.open(findFile(parser.get<String>("input"))) : cap.open(parser.get<int>("device"));
243 if (!openSuccess){
244 cout << "Could not open input file or camera device" << endl;
245 return 0;
246 }
247
248 FontFace sans("sans");
249
250 int stdSize = 15;
251 int stdWeight = 150;
252 int stdImgSize = 512;
253 int stdThickness = 2;
254 vector<int> classIds;
255 vector<float> confidences;
256 vector<Rect> boxes;
257
258 if (async > 0 && backend == DNN_BACKEND_INFERENCE_ENGINE){
259 asyncNumReq = async;
260 }
261
262 if (async != 0) {
263 // Threading is enabled
264 bool process = true;
265
266 // Frames capturing thread
267 QueueFPS<Mat> framesQueue;
268 std::thread framesThread([&]() {
269 Mat frame;
270 while (process) {
271 cap >> frame;
272 if (!frame.empty())
273 framesQueue.push(frame.clone());
274 else
275 break;
276 }
277 });
278
279 // Frames processing thread
280 QueueFPS<Mat> processedFramesQueue;
281 QueueFPS<std::vector<Mat>> predictionsQueue;
282 std::thread processingThread([&]() {
283 std::queue<AsyncArray> futureOutputs;
284 Mat blob;
285 while (process) {
286 // Get the next frame
287 Mat frame;
288 {
289 if (!framesQueue.empty()) {
290 frame = framesQueue.get();
291 if (asyncNumReq) {
292 if (futureOutputs.size() == asyncNumReq)
293 frame = Mat();
294 }
295 }
296 }
297
298 // Process the frame
299 if (!frame.empty()) {
300 preprocess(frame, net, Size(inpWidth, inpHeight));
301 processedFramesQueue.push(frame);
302
303 if (asyncNumReq) {
304 futureOutputs.push(net.forwardAsync());
305 } else {
306 //![forward]
307 vector<Mat> outs;
308 net.forward(outs, net.getUnconnectedOutLayersNames());
309 net.printPerfProfile();
310 predictionsQueue.push(outs);
311 //![forward]
312 }
313 }
314
315 while (!futureOutputs.empty() &&
316 futureOutputs.front().wait_for(std::chrono::seconds(0))) {
317 AsyncArray async_out = futureOutputs.front();
318 futureOutputs.pop();
319 Mat out;
320 async_out.get(out);
321 predictionsQueue.push({out});
322 }
323 }
324 });
325
326 // Postprocessing and rendering loop
327 while (waitKey(100) < 0) {
328 if (predictionsQueue.empty())
329 continue;
330
331 vector<Mat> outs = predictionsQueue.get();
332 Mat frame = processedFramesQueue.get();
333
334 classIds.clear();
335 confidences.clear();
336 boxes.clear();
337 postprocess(frame, outs, net, classIds, confidences, boxes, postprocessing);
338
339 drawPred(classIds, confidences, boxes, frame, sans, stdSize, stdWeight, stdImgSize, stdThickness);
340
341 int imgWidth = max(frame.rows, frame.cols);
342 int size = static_cast<int>((stdSize * imgWidth) / (stdImgSize * 1.5));
343 int weight = static_cast<int>((stdWeight * imgWidth) / (stdImgSize * 1.5));
344
345 if (predictionsQueue.counter > 1) {
346 string label = format("Camera: %.2f FPS", framesQueue.getFPS());
347 rectangle(frame, Point(0, 0), Point(10 * size, 3 * size + size / 4), Scalar::all(255), FILLED);
348 putText(frame, label, Point(0, size), Scalar::all(0), sans, size, weight);
349
350 label = format("Network: %.2f FPS", predictionsQueue.getFPS());
351 putText(frame, label, Point(0, 2 * size), Scalar::all(0), sans, size, weight);
352
353 label = format("Skipped frames: %d", framesQueue.counter - predictionsQueue.counter);
354 putText(frame, label, Point(0, 3 * size), Scalar::all(0), sans, size, weight);
355 }
356 imshow(kWinName, frame);
357 }
358
359 process = false;
360 framesThread.join();
361 processingThread.join();
362 } else {
363 if (asyncNumReq)
364 CV_Error(Error::StsNotImplemented, "Asynchronous forward is supported only with Inference Engine backend.");
365 // Threading is disabled, run synchronously
366 Mat frame, blob;
367 while (waitKey(1) < 0) {
368 cap >> frame;
369 if (frame.empty()) {
370 waitKey();
371 break;
372 }
373 preprocess(frame, net, Size(inpWidth, inpHeight));
374
375 TickMeter tickMeter;
376 vector<Mat> outs;
377 tickMeter.start();
378 net.forward(outs, net.getUnconnectedOutLayersNames());
379 tickMeter.stop();
380 net.printPerfProfile();
381
382 classIds.clear();
383 confidences.clear();
384 boxes.clear();
385
386 postprocess(frame, outs, net, classIds, confidences, boxes, postprocessing);
387
388 drawPred(classIds, confidences, boxes, frame, sans, stdSize, stdWeight, stdImgSize, stdThickness);
389
390 int imgWidth = max(frame.rows, frame.cols);
391 int size = static_cast<int>((stdSize * imgWidth) / (stdImgSize * 1.5));
392 int weight = static_cast<int>((stdWeight * imgWidth) / (stdImgSize * 1.5));
393 string label = format("FPS: %.2f", 1000.0 / tickMeter.getTimeMilli());
394 putText(frame, label, Point(0, size), Scalar(0, 255, 0), sans, size, weight);
395 imshow(kWinName, frame);
396 }
397 }
398 return 0;
399}
400
401void preprocess(const Mat& frame, Net& net, Size inpSize)
402{
403 Size size(inpSize.width <= 0 ? frame.cols : inpSize.width, inpSize.height <= 0 ? frame.rows : inpSize.height);
404
405 // Prepare the blob from the image
406 Mat inp;
407 {
408 //![preprocess_call]
409 Image2BlobParams imgParams(
410 Scalar::all(scale),
411 size,
412 meanv,
413 swapRB,
414 CV_32F,
415 DNN_LAYOUT_NCHW,
416 paddingMode,
417 paddingValue);
418
419 inp = blobFromImageWithParams(frame, imgParams);
420 //![preprocess_call]
421 }
422
423 // Set the blob as the network input
424 net.setInput(inp);
425}
426
427void yoloPostProcessing(
428 const vector<Mat>& outs,
429 vector<int>& keep_classIds,
430 vector<float>& keep_confidences,
431 vector<Rect2d>& keep_boxes,
432 float conf_threshold,
433 float iou_threshold,
434 const string& postprocessing)
435{
436 // Retrieve
437 vector<int> classIds;
438 vector<float> confidences;
439 vector<Rect2d> boxes;
440
441 vector<Mat> outs_copy = outs;
442
443 if (postprocessing == "yolov8")
444 {
445 transposeND(outs_copy[0], {0, 2, 1}, outs_copy[0]);
446 }
447
448 if (postprocessing == "yolonas")
449 {
450 // outs contains 2 elements of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84]
451 Mat concat_out;
452 // squeeze the first dimension
453 outs_copy[0] = outs_copy[0].reshape(1, outs_copy[0].size[1]);
454 outs_copy[1] = outs_copy[1].reshape(1, outs_copy[1].size[1]);
455 hconcat(outs_copy[1], outs_copy[0], concat_out);
456 outs_copy[0] = concat_out;
457 // remove the second element
458 outs_copy.pop_back();
459 // unsqueeze the first dimension
460 outs_copy[0] = outs_copy[0].reshape(0, vector<int>{1, 8400, 84});
461 }
462
463 for (auto preds : outs_copy)
464 {
465 preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85]
466 for (int i = 0; i < preds.rows; ++i)
467 {
468 // filter out non-object
469 float obj_conf = (postprocessing == "yolov8" || postprocessing == "yolonas") ? 1.0f : preds.at<float>(i, 4);
470 if (obj_conf < conf_threshold)
471 continue;
472
473 Mat scores = preds.row(i).colRange((postprocessing == "yolov8" || postprocessing == "yolonas") ? 4 : 5, preds.cols);
474 double conf;
475 Point maxLoc;
476 minMaxLoc(scores, 0, &conf, 0, &maxLoc);
477
478 conf = (postprocessing == "yolov8" || postprocessing == "yolonas") ? conf : conf * obj_conf;
479 if (conf < conf_threshold)
480 continue;
481
482 // get bbox coords
483 float* det = preds.ptr<float>(i);
484 double cx = det[0];
485 double cy = det[1];
486 double w = det[2];
487 double h = det[3];
488
489 // [x1, y1, x2, y2]
490 if (postprocessing == "yolonas") {
491 boxes.push_back(Rect2d(cx, cy, w, h));
492 } else {
493 boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h,
494 cx + 0.5 * w, cy + 0.5 * h));
495 }
496 classIds.push_back(maxLoc.x);
497 confidences.push_back(static_cast<float>(conf));
498 }
499 }
500
501 // NMS
502 vector<int> keep_idx;
503 NMSBoxes(boxes, confidences, conf_threshold, iou_threshold, keep_idx);
504
505 for (auto i : keep_idx)
506 {
507 keep_classIds.push_back(classIds[i]);
508 keep_confidences.push_back(confidences[i]);
509 keep_boxes.push_back(boxes[i]);
510 }
511}
512
513void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, const string postprocessing)
514{
515 static vector<int> outLayers = net.getUnconnectedOutLayers();
516 if (postprocessing == "ssd")
517 {
518 // Network produces output blob with a shape 1x1xNx7 where N is a number of
519 // detections and an every detection is a vector of values
520 // [batchId, classId, confidence, left, top, right, bottom]
521 CV_Assert(outs.size() > 0);
522 for (size_t k = 0; k < outs.size(); k++)
523 {
524 float* data = (float*)outs[k].data;
525 for (size_t i = 0; i < outs[k].total(); i += 7)
526 {
527 float confidence = data[i + 2];
528 if (confidence > confThreshold)
529 {
530 int left = (int)data[i + 3];
531 int top = (int)data[i + 4];
532 int right = (int)data[i + 5];
533 int bottom = (int)data[i + 6];
534 int width = right - left + 1;
535 int height = bottom - top + 1;
536 if (width <= 2 || height <= 2)
537 {
538 left = (int)(data[i + 3] * frame.cols);
539 top = (int)(data[i + 4] * frame.rows);
540 right = (int)(data[i + 5] * frame.cols);
541 bottom = (int)(data[i + 6] * frame.rows);
542 width = right - left + 1;
543 height = bottom - top + 1;
544 }
545 classIds.push_back((int)(data[i + 1]) - 1); // Skip 0th background class id.
546 boxes.push_back(Rect(left, top, width, height));
547 confidences.push_back(confidence);
548 }
549 }
550 }
551 }
552 else if (postprocessing == "yolov4")
553 {
554 // boxes[b,N,1,4]+confs[b,N,classes] (normalized) or boxes[b,N,4]+scores[b,N]+classIdx[b,N] (model-px)
555 bool isBoxConfsFormat = (outs.size() == 2 && outs[0].dims == 4 && outs[0].size[outs[0].dims - 1] == 4);
556 bool isBoxScoresIdxFormat = (outs.size() == 3 && outs[0].dims == 3 && outs[0].size[2] == 4);
557 if (isBoxScoresIdxFormat)
558 {
559 int N = outs[0].size[1];
560 const float* boxesPtr = outs[0].ptr<float>(0);
561 const float* scoresPtr = outs[1].ptr<float>(0);
562 const float* classIdxPtr = outs[2].ptr<float>(0);
563 for (int j = 0; j < N; ++j)
564 {
565 float score = scoresPtr[j];
566 if (score > confThreshold)
567 {
568 float x1 = boxesPtr[j * 4 + 0];
569 float y1 = boxesPtr[j * 4 + 1];
570 float x2 = boxesPtr[j * 4 + 2];
571 float y2 = boxesPtr[j * 4 + 3];
572 boxes.push_back(Rect((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1)));
573 confidences.push_back(score);
574 classIds.push_back((int)classIdxPtr[j]);
575 }
576 }
577 }
578 else if (isBoxConfsFormat)
579 {
580 Mat boxesMat = outs[0];
581 Mat confsMat = outs[1];
582 int numBoxes = (int)(boxesMat.total() / 4);
583 boxesMat = boxesMat.reshape(1, numBoxes);
584 confsMat = confsMat.reshape(1, numBoxes);
585 for (int j = 0; j < numBoxes; ++j)
586 {
587 Point maxLoc;
588 double confidence;
589 minMaxLoc(confsMat.row(j), 0, &confidence, 0, &maxLoc);
590 if (confidence > confThreshold)
591 {
592 const float* box = boxesMat.ptr<float>(j);
593 boxes.push_back(Rect((int)(box[0] * inpWidth), (int)(box[1] * inpHeight),
594 (int)((box[2] - box[0]) * inpWidth), (int)((box[3] - box[1]) * inpHeight)));
595 confidences.push_back((float)confidence);
596 classIds.push_back(maxLoc.x);
597 }
598 }
599 }
600 else
601 {
602 cout << "Unsupported YOLO ONNX output format" << endl;
603 exit(-1);
604 }
605 Image2BlobParams paramNet;
606 paramNet.scalefactor = Scalar::all(scale);
607 paramNet.size = Size(inpWidth, inpHeight);
608 paramNet.mean = meanv;
609 paramNet.swapRB = swapRB;
610 paramNet.paddingmode = paddingMode;
611 paramNet.blobRectsToImageRects(boxes, boxes, frame.size());
612 }
613 else if (postprocessing == "yolov8" || postprocessing == "yolov5")
614 {
615 //![forward_buffers]
616 vector<int> keep_classIds;
617 vector<float> keep_confidences;
618 vector<Rect2d> keep_boxes;
619 //![forward_buffers]
620
621 //![postprocess]
622 yoloPostProcessing(outs, keep_classIds, keep_confidences, keep_boxes, confThreshold, nmsThreshold, postprocessing);
623 //![postprocess]
624
625 for (size_t i = 0; i < keep_classIds.size(); ++i)
626 {
627 classIds.push_back(keep_classIds[i]);
628 confidences.push_back(keep_confidences[i]);
629 Rect2d box = keep_boxes[i];
630 boxes.push_back(Rect(cvFloor(box.x), cvFloor(box.y), cvFloor(box.width-box.x), cvFloor(box.height-box.y)));
631 }
632 if (framework == "onnx"){
633 Image2BlobParams paramNet;
634 paramNet.scalefactor = Scalar::all(scale);
635 paramNet.size = Size(inpWidth, inpHeight);
636 paramNet.mean = meanv;
637 paramNet.swapRB = swapRB;
638 paramNet.paddingmode = paddingMode;
639
640 paramNet.blobRectsToImageRects(boxes, boxes, frame.size());
641 }
642 }
643 else
644 {
645 cout<< ("Unknown postprocessing method: " + postprocessing)<<endl;
646 exit(-1);
647 }
648
649 // NMS is used inside Region layer only on DNN_BACKEND_OPENCV for other backends we need NMS in sample
650 // or NMS is required if the number of outputs > 1
651 if (outLayers.size() > 1)
652 {
653 map<int, vector<size_t> > class2indices;
654 for (size_t i = 0; i < classIds.size(); i++)
655 {
656 if (confidences[i] >= confThreshold)
657 {
658 class2indices[classIds[i]].push_back(i);
659 }
660 }
661 vector<Rect> nmsBoxes;
662 vector<float> nmsConfidences;
663 vector<int> nmsClassIds;
664 for (map<int, vector<size_t> >::iterator it = class2indices.begin(); it != class2indices.end(); ++it)
665 {
666 vector<Rect> localBoxes;
667 vector<float> localConfidences;
668 vector<size_t> classIndices = it->second;
669 for (size_t i = 0; i < classIndices.size(); i++)
670 {
671 localBoxes.push_back(boxes[classIndices[i]]);
672 localConfidences.push_back(confidences[classIndices[i]]);
673 }
674 vector<int> nmsIndices;
675 NMSBoxes(localBoxes, localConfidences, confThreshold, nmsThreshold, nmsIndices);
676 for (size_t i = 0; i < nmsIndices.size(); i++)
677 {
678 size_t idx = nmsIndices[i];
679 nmsBoxes.push_back(localBoxes[idx]);
680 nmsConfidences.push_back(localConfidences[idx]);
681 nmsClassIds.push_back(it->first);
682 }
683 }
684 boxes = nmsBoxes;
685 classIds = nmsClassIds;
686 confidences = nmsConfidences;
687 }
688}
689
690void drawPred(vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, Mat& frame, FontFace& sans, int stdSize, int stdWeight, int stdImgSize, int stdThickness)
691{
692 //![draw_boxes]
693 int imgWidth = max(frame.rows, frame.cols);
694 int size = (stdSize*imgWidth)/stdImgSize;
695 int weight = (stdWeight*imgWidth)/stdImgSize;
696 int thickness = (stdThickness*imgWidth)/stdImgSize;
697
698 for (size_t idx = 0; idx < boxes.size(); ++idx){
699 Scalar boxColor = getColor(classIds[idx]);
700 int left = boxes[idx].x;
701 int top = boxes[idx].y;
702 int right = boxes[idx].x + boxes[idx].width;
703 int bottom = boxes[idx].y + boxes[idx].height;
704 rectangle(frame, Point(left, top), Point(right, bottom), boxColor, thickness);
705
706 string label = format("%.2f", confidences[idx]);
707 if (!labels.empty())
708 {
709 CV_Assert(classIds[idx] < (int)labels.size());
710 label = labels[classIds[idx]] + ": " + label;
711 }
712
713 Rect r = getTextSize(Size(), label, Point(), sans, size, weight);
714 int baseline = r.y + r.height;
715 Size labelSize = Size(r.width, r.height + size/4 - baseline);
716
717 top = max(top-thickness/2, labelSize.height);
718 rectangle(frame, Point(left-thickness/2, top-(labelSize.height)),
719 Point(left + labelSize.width, top), boxColor, FILLED);
720 putText(frame, label, Point(left, top-size/4), getTextColor(boxColor), sans, size, weight);
721 }
722 //![draw_boxes]
723}
724
725void callback(int pos, void*)
726{
727 confThreshold = pos * 0.01f;
728}
729
730Scalar getColor(int classId) {
731 int r = min((classId >> 0 & 1) * 128 + (classId >> 3 & 1) * 64 + (classId >> 6 & 1) * 32 + 80, 255);
732 int g = min((classId >> 1 & 1) * 128 + (classId >> 4 & 1) * 64 + (classId >> 7 & 1) * 32 + 40, 255);
733 int b = min((classId >> 2 & 1) * 128 + (classId >> 5 & 1) * 64 + (classId >> 8 & 1) * 32 + 40, 255);
734 return Scalar(b, g, r);
735}