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}