From 56ae1e603b1c93b2367b0ec0dbf1771e82240c62 Mon Sep 17 00:00:00 2001 From: Prasadayus <22bcs191@iiitdmj.ac.in> Date: Fri, 5 Jun 2026 20:12:08 +0530 Subject: [PATCH] replace Qualcomm yolov3.onnx with darknet-converted yolov3 --- modules/dnn/perf/perf_net.cpp | 2 +- modules/dnn/test/test_darknet_importer.cpp | 89 ++++------------------ modules/dnn/test/test_model.cpp | 70 +++++++++++------ samples/dnn/models.yml | 8 +- samples/dnn/object_detection.cpp | 30 -------- samples/dnn/object_detection.py | 53 ++++--------- 6 files changed, 84 insertions(+), 168 deletions(-) diff --git a/modules/dnn/perf/perf_net.cpp b/modules/dnn/perf/perf_net.cpp index c80866d451..3fbcf5b9a1 100644 --- a/modules/dnn/perf/perf_net.cpp +++ b/modules/dnn/perf/perf_net.cpp @@ -218,7 +218,7 @@ PERF_TEST_P_(DNNTestNetwork, YOLOv3) #endif Mat sample = imread(findDataFile("dnn/dog416.png")); - cv::resize(sample, sample, Size(640, 640)); + cv::resize(sample, sample, Size(416, 416)); Mat inp = blobFromImage(sample, 1.0 / 255.0, Size(), Scalar(), true); processNet("dnn/yolov3.onnx", "", inp); } diff --git a/modules/dnn/test/test_darknet_importer.cpp b/modules/dnn/test/test_darknet_importer.cpp index 7970392103..43c206064a 100644 --- a/modules/dnn/test/test_darknet_importer.cpp +++ b/modules/dnn/test/test_darknet_importer.cpp @@ -117,46 +117,12 @@ public: std::vector outs; net.forward(outs, net.getUnconnectedOutLayersNames()); - // Detect output format: pytorch-YOLOv4 exports "boxes" [batch, N, 1, 4] + "confs" [batch, N, classes] - bool isBoxConfsFormat = (outs.size() == 2 && outs[0].dims == 4 && outs[0].size[outs[0].dims - 1] == 4); - // Detect 3-output format: boxes [batch, N, 4] + scores [batch, N] + class_idx [batch, N] - bool isBoxScoresIdxFormat = (outs.size() == 3 && outs[0].dims == 3 && outs[0].size[2] == 4); - for (int b = 0; b < batch_size; ++b) { std::vector classIds; std::vector confidences; std::vector boxes; - if (isBoxScoresIdxFormat) { - // yolov3-style format: boxes [batch, N, 4] + scores [batch, N] + class_idx [batch, N] - // boxes are [x1, y1, x2, y2] in pixel coords (relative to model input size) - int N = outs[0].size[1]; - float* boxesPtr = outs[0].ptr(b); - float* scoresPtr = outs[1].ptr(b); - float* classIdxPtr = outs[2].ptr(b); - - float modelW = (float)inp.size[3]; - float modelH = (float)inp.size[2]; - - for (int j = 0; j < N; ++j) - { - float score = scoresPtr[j]; - if (score > confThreshold) - { - float x1 = boxesPtr[j * 4 + 0] / modelW; - float y1 = boxesPtr[j * 4 + 1] / modelH; - float x2 = boxesPtr[j * 4 + 2] / modelW; - float y2 = boxesPtr[j * 4 + 3] / modelH; - boxes.push_back(Rect2d(x1, y1, x2 - x1, y2 - y1)); - confidences.push_back(score); - classIds.push_back((int)classIdxPtr[j]); - } - } - } - else if (isBoxConfsFormat) - { - // boxes [batch, N, 1, 4] (x1,y1,x2,y2), confs [batch, N, num_classes] Mat boxesMat = outs[0]; Mat confsMat = outs[1]; if (batch_size > 1) @@ -195,40 +161,6 @@ public: } } } - else - { - for (int i = 0; i < (int)outs.size(); ++i) - { - Mat out; - if (batch_size > 1){ - Range ranges[3] = {Range(b, b+1), Range::all(), Range::all()}; - out = outs[i](ranges).reshape(1, outs[i].size[1]); - }else{ - out = outs[i]; - } - for (int j = 0; j < out.rows; ++j) - { - float objConf = out.at(j, 4); - Mat scores = out.row(j).colRange(5, out.cols); - double maxClsScore; - Point maxLoc; - minMaxLoc(scores, 0, &maxClsScore, 0, &maxLoc); - double confidence = objConf * maxClsScore; - - if (confidence > confThreshold) { - float* detection = out.ptr(j); - double centerX = detection[0]; - double centerY = detection[1]; - double width = detection[2]; - double height = detection[3]; - boxes.push_back(Rect2d(centerX - 0.5 * width, centerY - 0.5 * height, - width, height)); - confidences.push_back(confidence); - classIds.push_back(maxLoc.x); - } - } - } - } // here we need NMS of boxes std::vector indices; @@ -384,11 +316,17 @@ TEST_P(Test_YOLO_nets, YOLOv3) // batchId, classId, confidence, left, top, right, bottom const int N0 = 3; - const int N1 = 0; + const int N1 = 5; static const float ref_[/* (N0 + N1) * 7 */] = { -0, 7, 0.606292f, 0.612037f, 0.149921f, 0.910763f, 0.300503f, -0, 16, 0.55195f, 0.17069f, 0.356024f, 0.471459f, 0.877178f, -0, 1, 0.433444f, 0.199235f, 0.301175f, 0.753253f, 0.744156f, +0, 16, 0.998835f, 0.160018f, 0.389962f, 0.417889f, 0.943715f, +0, 1, 0.987915f, 0.150904f, 0.221934f, 0.742265f, 0.746256f, +0, 7, 0.952998f, 0.614625f, 0.150259f, 0.901366f, 0.289251f, + +1, 2, 0.997410f, 0.647584f, 0.459938f, 0.821038f, 0.663948f, +1, 2, 0.989632f, 0.450719f, 0.463353f, 0.496306f, 0.522258f, +1, 0, 0.980047f, 0.195857f, 0.378452f, 0.258626f, 0.629259f, +1, 9, 0.785156f, 0.665503f, 0.373544f, 0.688893f, 0.439243f, +1, 9, 0.733130f, 0.376029f, 0.315696f, 0.401777f, 0.395165f, }; Mat ref(N0 + N1, 7, CV_32FC1, (void*)ref_); @@ -407,7 +345,12 @@ TEST_P(Test_YOLO_nets, YOLOv3) { SCOPED_TRACE("batch size 1"); - testYOLOModel(model_file, ref.rowRange(0, N0), scoreDiff, iouDiff, 0.24, 0.4, false, 0, Size(640, 640)); + testYOLOModel(model_file, ref.rowRange(0, N0), scoreDiff, iouDiff, 0.5, 0.4, false, 0, Size(416, 416)); + } + + { + SCOPED_TRACE("batch size 2"); + testYOLOModel(model_file, ref, scoreDiff, iouDiff, 0.5, 0.4, false, 0, Size(416, 416)); } } diff --git a/modules/dnn/test/test_model.cpp b/modules/dnn/test/test_model.cpp index e67547f16c..83372899de 100644 --- a/modules/dnn/test/test_model.cpp +++ b/modules/dnn/test/test_model.cpp @@ -332,26 +332,19 @@ TEST_P(Test_Model, YOLOv3) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); - - // The in-graph YOLO decode (Split) is not evaluable by the OpenVINO backend. if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); - std::string model_file = _tf("yolov3.onnx", false); - std::string img_path = _tf("dog416.png"); // Matches standard YOLO inference classes + checkBackend(); - // Extracted from original flat ref_ array: batchId, classId, confidence, left, top, right, bottom - std::vector refClassIds = {7, 16, 1}; - std::vector refConfidences = {0.606292f, 0.55195f, 0.433444f}; - - // Rect2d requires (x, y, width, height) format + std::vector refClassIds = {16, 1, 7}; + std::vector refConfidences = {0.998835f, 0.987915f, 0.952998f}; std::vector refBoxes = { - Rect2d(0.612037f, 0.149921f, 0.910763f - 0.612037f, 0.300503f - 0.149921f), // Class 7 - Rect2d(0.170690f, 0.356024f, 0.471459f - 0.170690f, 0.877178f - 0.356024f), // Class 16 - Rect2d(0.199235f, 0.301175f, 0.753253f - 0.199235f, 0.744156f - 0.301175f) // Class 1 + Rect2d(0.160018f, 0.389962f, 0.257871f, 0.553753f), + Rect2d(0.150904f, 0.221934f, 0.591361f, 0.524322f), + Rect2d(0.614625f, 0.150259f, 0.286741f, 0.138992f), }; - // Setting precision tolerances double scoreDiff = 8e-5, iouDiff = 3e-4; if (target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_MYRIAD || target == DNN_TARGET_CPU_FP16) { @@ -364,16 +357,49 @@ TEST_P(Test_Model, YOLOv3) iouDiff = 0.03; } - // Model parameters - double confThreshold = 0.24; - double nmsThreshold = 0.4; - Size size{640, 640}; - double scale = 1.0 / 255.0; // Standard image scaling for YOLO models - Scalar mean = Scalar(); - bool swapRB = true; // YOLO expects RGB + const float confThreshold = 0.5f, nmsThreshold = 0.4f; + const Size inputSize(416, 416); - testDetectModel(model_file, "", img_path, refClassIds, refConfidences, refBoxes, - scoreDiff, iouDiff, confThreshold, nmsThreshold, size, mean, scale, swapRB); + Mat img = imread(_tf("dog416.png")); + cv::resize(img, img, inputSize); + Mat blob = blobFromImage(img, 1.0 / 255.0, inputSize, Scalar(), true, false); + + Net net = readNet(_tf("yolov3.onnx", false)); + net.setPreferableBackend(backend); + net.setPreferableTarget(target); + net.setInput(blob); + std::vector outs; + net.forward(outs, net.getUnconnectedOutLayersNames()); + + int numBoxes = (int)(outs[0].total() / 4); + Mat boxesMat = outs[0].reshape(1, numBoxes); + Mat confsMat = outs[1].reshape(1, numBoxes); + + std::vector boxes; + std::vector confidences; + std::vector classIds; + for (int j = 0; j < numBoxes; ++j) + { + Mat scores = confsMat.row(j); + double confidence; Point maxLoc; + minMaxLoc(scores, 0, &confidence, 0, &maxLoc); + if (confidence >= confThreshold) + { + float* b = boxesMat.ptr(j); + boxes.emplace_back(b[0], b[1], b[2] - b[0], b[3] - b[1]); + confidences.push_back((float)confidence); + classIds.push_back(maxLoc.x); + } + } + + std::vector keep; + NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, keep); + + std::vector nmsBoxes; std::vector nmsConfs; std::vector nmsCls; + for (int k : keep) { nmsBoxes.push_back(boxes[k]); nmsConfs.push_back(confidences[k]); nmsCls.push_back(classIds[k]); } + + normAssertDetections(refClassIds, refConfidences, refBoxes, + nmsCls, nmsConfs, nmsBoxes, "", confThreshold, scoreDiff, iouDiff); } TEST_P(Test_Model, Keypoints_pose) diff --git a/samples/dnn/models.yml b/samples/dnn/models.yml index 76100a41b5..9f420dc634 100644 --- a/samples/dnn/models.yml +++ b/samples/dnn/models.yml @@ -123,13 +123,13 @@ yolov4-tiny: yolov3: load_info: - url: "https://huggingface.co/qualcomm/Yolo-v3/resolve/226ada6de9dcb32eebad7f74bf526714e2af6136/Yolo-v3.onnx" - sha1: "c37641ddf05cfe133efd4b66832f269d95f523cf" + url: "https://huggingface.co/opencv/opencv_contribution/resolve/main/yolov3/yolov3.onnx" + sha1: "2b433d879f318efa55de62a630556162665c6d8e" model: "yolov3.onnx" mean: [0, 0, 0] scale: 0.00392 - width: 640 - height: 640 + width: 416 + height: 416 rgb: true labels: "object_detection_classes_yolo.txt" postprocessing: "yolov4" diff --git a/samples/dnn/object_detection.cpp b/samples/dnn/object_detection.cpp index 9770ccc2e5..8f187c970c 100644 --- a/samples/dnn/object_detection.cpp +++ b/samples/dnn/object_detection.cpp @@ -551,31 +551,6 @@ void postprocess(Mat& frame, const vector& outs, Net& net, vector& cla } else if (postprocessing == "yolov4") { - // boxes[b,N,1,4]+confs[b,N,classes] (normalized) or boxes[b,N,4]+scores[b,N]+classIdx[b,N] (model-px) - bool isBoxConfsFormat = (outs.size() == 2 && outs[0].dims == 4 && outs[0].size[outs[0].dims - 1] == 4); - bool isBoxScoresIdxFormat = (outs.size() == 3 && outs[0].dims == 3 && outs[0].size[2] == 4); - if (isBoxScoresIdxFormat) - { - int N = outs[0].size[1]; - const float* boxesPtr = outs[0].ptr(0); - const float* scoresPtr = outs[1].ptr(0); - const float* classIdxPtr = outs[2].ptr(0); - for (int j = 0; j < N; ++j) - { - float score = scoresPtr[j]; - if (score > confThreshold) - { - float x1 = boxesPtr[j * 4 + 0]; - float y1 = boxesPtr[j * 4 + 1]; - float x2 = boxesPtr[j * 4 + 2]; - float y2 = boxesPtr[j * 4 + 3]; - boxes.push_back(Rect((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1))); - confidences.push_back(score); - classIds.push_back((int)classIdxPtr[j]); - } - } - } - else if (isBoxConfsFormat) { Mat boxesMat = outs[0]; Mat confsMat = outs[1]; @@ -597,11 +572,6 @@ void postprocess(Mat& frame, const vector& outs, Net& net, vector& cla } } } - else - { - cout << "Unsupported YOLO ONNX output format" << endl; - exit(-1); - } Image2BlobParams paramNet; paramNet.scalefactor = Scalar::all(scale); paramNet.size = Size(inpWidth, inpHeight); diff --git a/samples/dnn/object_detection.py b/samples/dnn/object_detection.py index a2509ea303..db59fbaa6e 100644 --- a/samples/dnn/object_detection.py +++ b/samples/dnn/object_detection.py @@ -159,43 +159,20 @@ def postprocess(frame, outs): boxes.append([left, top, width, height]) elif args.postprocessing == 'yolov4': - # boxes[b,N,1,4]+confs[b,N,classes] (normalized) or boxes[b,N,4]+scores[b,N]+classIdx[b,N] (model-px) - if len(outs) == 3 and outs[0].ndim == 3 and outs[0].shape[2] == 4: - boxesArr = outs[0][0] - scoresArr = outs[1][0] - classIdxArr = outs[2][0] - for j in range(boxesArr.shape[0]): - score = float(scoresArr[j]) - if score > confThreshold: - x1 = boxesArr[j][0] / args.width - y1 = boxesArr[j][1] / args.height - x2 = boxesArr[j][2] / args.width - y2 = boxesArr[j][3] / args.height - left = int(x1 * frameWidth) - top = int(y1 * frameHeight) - width = int((x2 - x1) * frameWidth) - height = int((y2 - y1) * frameHeight) - classIds.append(int(classIdxArr[j])) - confidences.append(score) - boxes.append([left, top, width, height]) - elif len(outs) == 2 and outs[0].ndim == 4 and outs[0].shape[-1] == 4: - boxesArr = outs[0].reshape(-1, 4) - confsArr = outs[1].reshape(boxesArr.shape[0], -1) - for j in range(boxesArr.shape[0]): - classId = np.argmax(confsArr[j]) - confidence = float(confsArr[j][classId]) - if confidence > confThreshold: - box = boxesArr[j] - left = int(box[0] * frameWidth) - top = int(box[1] * frameHeight) - width = int((box[2] - box[0]) * frameWidth) - height = int((box[3] - box[1]) * frameHeight) - classIds.append(classId) - confidences.append(confidence) - boxes.append([left, top, width, height]) - else: - print('Unsupported YOLO ONNX output format') - exit() + boxesArr = outs[0].reshape(-1, 4) + confsArr = outs[1].reshape(boxesArr.shape[0], -1) + for j in range(boxesArr.shape[0]): + classId = np.argmax(confsArr[j]) + confidence = float(confsArr[j][classId]) + if confidence > confThreshold: + box = boxesArr[j] + left = int(box[0] * frameWidth) + top = int(box[1] * frameHeight) + width = int((box[2] - box[0]) * frameWidth) + height = int((box[3] - box[1]) * frameHeight) + classIds.append(classId) + confidences.append(confidence) + boxes.append([left, top, width, height]) elif args.postprocessing == 'yolov8' or args.postprocessing == 'yolov5': # Network produces output blob with a shape NxC where N is a number of @@ -236,7 +213,7 @@ def postprocess(frame, outs): # NMS is used inside Region layer only on DNN_BACKEND_OPENCV for another backends we need NMS in sample # or NMS is required if number of outputs > 1 - if len(outNames) > 1 or (args.postprocessing == 'yolov8' or args.postprocessing == 'yolov5') and args.backend != cv.dnn.DNN_BACKEND_OPENCV: + if len(outNames) > 1 or args.postprocessing == 'yolov4' or (args.postprocessing == 'yolov8' or args.postprocessing == 'yolov5') and args.backend != cv.dnn.DNN_BACKEND_OPENCV: indices = [] classIds = np.array(classIds) boxes = np.array(boxes)