1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 07:43:03 +04:00

Merge pull request #25794 from Abdurrahheem:ash/yolov10-support

Add sample support of YOLOv9 and YOLOv10 in OpenCV #25794

This PR adds sample support of  [`YOLOv9`](https://github.com/WongKinYiu/yolov9) and [`YOLOv10`](https://github.com/THU-MIG/yolov10/tree/main)) in OpenCV. Models for this test are located in this [PR](https://github.com/opencv/opencv_extra/pull/1186). 

**Running YOLOv10 using OpenCV.** 
1. In oder to run `YOLOv10` one needs to cut off postporcessing with dynamic shapes from torch and then convert it to ONNX. If someone is looking for ready solution, there is [this forked branch](https://github.com/Abdurrahheem/yolov10/tree/ash/opencv-export) from official YOLOv10.  Particularty follow this proceduce. 

```bash
git clone git@github.com:Abdurrahheem/yolov10.git
conda create -n yolov10 python=3.9
conda activate yolov10
pip install -r requirements.txt
python export_opencv.py --model=<model-name> --imgsz=<input-img-size>
```
By default `model="yolov10s"` and `imgsz=(480,640)`. This will generate file `yolov10s.onnx`, which can be use for inference in OpenCV

2. For inference part on OpenCV.  one can use `yolo_detector.cpp` [sample](https://github.com/opencv/opencv/blob/4.x/samples/dnn/yolo_detector.cpp). If you have followed above exporting procedure, then you can use following command to run the model. 

``` bash
build opencv from source 
cd build 
./bin/example_dnn_yolo_detector --model=<path-to-yolov10s.onnx-file> --yolo=yolov10 --width=640 --height=480 --input=<path-to-image> --scale=0.003921568627 --padvalue=114
```
If you do not specify `--input` argument, OpenCV will grab first camera that is avaliable on your platform. 
For more deatils on how to run the `yolo_detector.cpp` file see this [guide](https://docs.opencv.org/4.x/da/d9d/tutorial_dnn_yolo.html#autotoc_md443) 


**Running YOLOv9 using OpenCV**

1. Export model following [official guide](https://github.com/WongKinYiu/yolov9)of the YOLOv9 repository. Particularly you can do following for converting.

```bash
git clone https://github.com/WongKinYiu/yolov9.git
cd yolov9
conda create -n yolov9 python=3.9
conda activate yolov9
pip install -r requirements.txt
wget https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-t-converted.pt
python export.py --weights=./yolov9-t-converted.pt --include=onnx --img-size=(480,640) 
```

This will generate <yolov9-t-converted.onnx> file.

2.  Inference on OpenCV.

```bash
build opencv from source 
cd build 
./bin/example_dnn_yolo_detector --model=<path-to-yolov9-t-converted.onnx> --yolo=yolov9 --width=640 --height=480 --scale=0.003921568627 --padvalue=114 --path=<path-to-image>
```

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Abduragim Shtanchaev
2024-07-02 18:26:34 +03:00
committed by GitHub
parent 939cb58cd6
commit a8d1373919
3 changed files with 151 additions and 28 deletions
+86 -10
View File
@@ -19,7 +19,8 @@ void yoloPostProcessing(
std::vector<Rect2d>& keep_boxes,
float conf_threshold,
float iou_threshold,
const std::string& test_name);
const std::string& model_name,
const int nc=80);
template<typename TString>
static std::string _tf(TString filename, bool required = true)
@@ -2670,7 +2671,8 @@ void yoloPostProcessing(
std::vector<Rect2d>& keep_boxes,
float conf_threshold,
float iou_threshold,
const std::string& test_name
const std::string& model_name,
const int nc
){
// Retrieve
@@ -2678,11 +2680,13 @@ void yoloPostProcessing(
std::vector<float> confidences;
std::vector<Rect2d> boxes;
if (test_name == "yolov8"){
if (model_name == "yolov8" || model_name == "yolov10" ||
model_name == "yolov9")
{
cv::transposeND(outs[0], {0, 2, 1}, outs[0]);
}
if (test_name == "yolonas"){
if (model_name == "yolonas"){
// outs contains 2 elemets of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84]
Mat concat_out;
// squeeze the first dimension
@@ -2696,22 +2700,27 @@ void yoloPostProcessing(
outs[0] = outs[0].reshape(0, std::vector<int>{1, 8400, 84});
}
// assert if last dim is 85 or 84
CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, 85 or 84]");
CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == 80 + 4), true, "Invalid output shape: ");
for (auto preds : outs){
preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85]
for (int i = 0; i < preds.rows; ++i)
{
// filter out non object
float obj_conf = (test_name == "yolov8" || test_name == "yolonas") ? 1.0f : preds.at<float>(i, 4) ;
float obj_conf = (model_name == "yolov8" || model_name == "yolonas" ||
model_name == "yolov9" || model_name == "yolov10") ? 1.0f : preds.at<float>(i, 4) ;
if (obj_conf < conf_threshold)
continue;
Mat scores = preds.row(i).colRange((test_name == "yolov8" || test_name == "yolonas") ? 4 : 5, preds.cols);
Mat scores = preds.row(i).colRange((model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? 4 : 5, preds.cols);
double conf;
Point maxLoc;
minMaxLoc(scores, 0, &conf, 0, &maxLoc);
conf = (test_name == "yolov8" || test_name == "yolonas") ? conf : conf * obj_conf;
conf = (model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? conf : conf * obj_conf;
if (conf < conf_threshold)
continue;
@@ -2722,15 +2731,14 @@ void yoloPostProcessing(
double w = det[2];
double h = det[3];
// std::cout << "cx: " << cx << " cy: " << cy << " w: " << w << " h: " << h << " conf: " << conf << " idx: " << maxLoc.x << std::endl;
// [x1, y1, x2, y2]
if (test_name == "yolonas"){
if (model_name == "yolonas" || model_name == "yolov10"){
boxes.push_back(Rect2d(cx, cy, w, h));
} else {
boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h,
cx + 0.5 * w, cy + 0.5 * h));
}
classIds.push_back(maxLoc.x);
classIds.push_back(maxLoc.x);
confidences.push_back(conf);
}
}
@@ -2747,7 +2755,75 @@ void yoloPostProcessing(
}
}
TEST_P(Test_ONNX_nets, YOLOv10)
{
std::string weightPath = _tf("models/yolov10s.onnx", false);
Size targetSize{640, 480};
float conf_threshold = 0.50;
float iou_threshold = 0.50;
std::vector<int> refClassIds{1, 16, 7};
std::vector<float> refScores{0.9510f, 0.9454f, 0.8404f};
std::vector<Rect2d> refBoxes{
Rect2d(105.5014, 112.8838, 472.9274, 350.0603),
Rect2d(109.8231, 185.7994, 258.5916, 452.9302),
Rect2d(388.5018, 62.1034, 576.6399, 143.3986)
};
Image2BlobParams imgParams(
Scalar::all(1 / 255.0),
targetSize,
Scalar::all(0),
true,
CV_32F,
DNN_LAYOUT_NCHW,
DNN_PMODE_LETTERBOX,
Scalar::all(114)
);
testYOLO(
weightPath, refClassIds, refScores, refBoxes,
imgParams, conf_threshold, iou_threshold,
1.0e-4, 1.0e-4, "yolov10");
}
TEST_P(Test_ONNX_nets, YOLOv9)
{
std::string weightPath = _tf("models/yolov9t.onnx", false);
Size targetSize{640, 480};
float conf_threshold = 0.50;
float iou_threshold = 0.50;
std::vector<int> refClassIds{1, 16, 2}; // wrong class mapping for yolov9
std::vector<float> refScores{0.959274f, 0.901125, 0.559396f};
std::vector<Rect2d> refBoxes{
Rect2d(106.255, 107.927, 472.497, 350.309),
Rect2d(108.633, 185.256, 259.287, 450.672),
Rect2d(390.701, 62.1454, 576.928, 141.795)
};
Image2BlobParams imgParams(
Scalar::all(1 / 255.0),
targetSize,
Scalar::all(0),
true,
CV_32F,
DNN_LAYOUT_NCHW,
DNN_PMODE_LETTERBOX,
Scalar::all(114)
);
testYOLO(
weightPath, refClassIds, refScores, refBoxes,
imgParams, conf_threshold, iou_threshold,
1.0e-4, 1.0e-4, "yolov9");
}
TEST_P(Test_ONNX_nets, YOLOX)
{
applyTestTag(CV_TEST_TAG_DEBUG_VERYLONG);