1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 07:13:02 +04:00

Merge pull request #28821 from abhishek-gola:optimized_layers

Parallelize DNN layers using chunking #28821

At a high level, I replaced tensor-level parallelism with chunk-level parallelism. Previously, parallel_for_ was dispatched over the number of input or output tensors i.e. one thread handled one whole tensor's copy.

The new approach precomputes each tensor's destination offset and per-slice size upfront, then slices the total byte work into fixed 64 KB chunks. The full chunk count is handed to parallel_for_ as a single flat range, and each worker decodes its chunk index back into (tensor, slice, byte_offset) using a prefix-sum table before running a plain memcpy on its piece. A small-size threshold falls back to the sequential path so we don't get threading overhead on small tensors.

Performance numbers after these optimizations:

For Device:  Intel(R) Core(TM) i9-14900KS, x86, 32 Cores, ubuntu 24.04,
| Model | `ENGINE_NEW` | `ENGINE_ORT` |
| :--- | :--- | :--- | 
| **YOLOv8n** |10.9 ms| 12.15 ms|
| **YOLOv5n** | 8.36 ms| 9.23 ms|
| **YOLOX-S** | 23.46 ms| 25.16 ms|

For Device: Macbook M1 Air
| Model | `ENGINE_NEW` | `ENGINE_ORT` |
| :--- | :--- | :--- | 
| **YOLOv8n** |34.45 ms| 42.52 ms|
| **YOLOv5n** | 31.62 ms| 25.52 ms|
| **YOLOX-S** | 88.9 ms| 116.7 ms|

### 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:
Abhishek Gola
2026-04-22 17:23:12 +05:30
committed by GitHub
parent 0102afa86f
commit d95badefa4
7 changed files with 384 additions and 104 deletions
+14
View File
@@ -124,6 +124,11 @@ PERF_TEST_P_(DNNTestNetwork, MobileNetv2_ONNX)
processNet("dnn/onnx/models/mobilenetv2.onnx", "", cv::Size(224, 224));
}
PERF_TEST_P_(DNNTestNetwork, ResNet50_QDQ_ONNX)
{
processNet("dnn/onnx/models/resnet50-v1-12-qdq.onnx", "", cv::Size(224, 224));
}
PERF_TEST_P_(DNNTestNetwork, SqueezeNet_v1_1)
{
processNet("dnn/squeezenet_v1.1.caffemodel", "dnn/squeezenet_v1.1.prototxt", cv::Size(227, 227));
@@ -157,6 +162,15 @@ PERF_TEST_P_(DNNTestNetwork, MobileNet_SSD_v2_TensorFlow)
processNet("dnn/ssd_mobilenet_v2_coco_2018_03_29.pb", "ssd_mobilenet_v2_coco_2018_03_29.pbtxt", cv::Size(300, 300));
}
PERF_TEST_P_(DNNTestNetwork, MobileNet_SSD_v1_ONNX)
{
Mat image(cv::Size(300, 300), CV_8UC3);
randu(image, 0, 255);
int imsize[] = {1, image.rows, image.cols, 3};
Mat input(4, imsize, CV_8U, image.data);
processNet("dnn/onnx/models/ssd_mobilenet_v1_12.onnx", "", input);
}
PERF_TEST_P_(DNNTestNetwork, DenseNet_121)
{
processNet("dnn/DenseNet_121.caffemodel", "dnn/DenseNet_121.prototxt", cv::Size(224, 224));
+47 -15
View File
@@ -40,22 +40,54 @@ static void concat(const std::vector<Mat>& inps, Mat& out, int axis)
totalSize += inps[i].total()*esz;
}
parallel_for_(Range(0, ninputs), [&](const Range& r) {
for (int k = r.start; k < r.end; k++) {
const Mat& inp_k = inps[k];
uchar* outptr = out.data;
const uchar* inptr_k = inp_k.data;
int sz_a;
for (int i = 0; i < k; i++) {
sz_a = inps[i].size[axis];
outptr += sliceSize*sz_a;
}
sz_a = inp_k.size[axis];
size_t sliceSize_k = sliceSize*sz_a;
for (int i = 0; i < nslices; i++)
memcpy(outptr + i*outStep, inptr_k + i*sliceSize_k, sliceSize_k);
// Precompute per-input destination offset and per-slice size.
std::vector<size_t> dstOffset(ninputs);
std::vector<size_t> sliceSize_k_vec(ninputs);
{
size_t acc = 0;
for (int k = 0; k < ninputs; k++) {
int sz_a = inps[k].size[axis];
dstOffset[k] = acc;
sliceSize_k_vec[k] = sliceSize * sz_a;
acc += sliceSize_k_vec[k];
}
}, (totalSize > 1000000 ? ninputs : 1));
}
const size_t CHUNK = 64 * 1024;
// Precompute per-input chunk counts and a prefix sum for fast index decode.
std::vector<int> chunkOff(ninputs + 1, 0);
for (int k = 0; k < ninputs; k++)
chunkOff[k + 1] = chunkOff[k] + (int)((sliceSize_k_vec[k] + CHUNK - 1) / CHUNK);
int chunksPerSlice = chunkOff[ninputs];
int totalChunks = chunksPerSlice * nslices;
if (totalSize > CHUNK && totalChunks > 0) {
parallel_for_(Range(0, totalChunks), [&](const Range& r) {
for (int c = r.start; c < r.end; c++) {
int s = c / chunksPerSlice;
int local = c % chunksPerSlice;
int k = 0;
while (local >= chunkOff[k + 1]) k++;
int chunkInK = local - chunkOff[k];
size_t byteStart = (size_t)chunkInK * CHUNK;
size_t byteEnd = std::min(byteStart + CHUNK, sliceSize_k_vec[k]);
const uchar* inptr_k = inps[k].data;
uchar* outptr = out.data + dstOffset[k];
memcpy(outptr + (size_t)s * outStep + byteStart,
inptr_k + (size_t)s * sliceSize_k_vec[k] + byteStart,
byteEnd - byteStart);
}
});
} else {
for (int k = 0; k < ninputs; k++) {
const uchar* inptr_k = inps[k].data;
uchar* outptr = out.data + dstOffset[k];
size_t sliceSize_k = sliceSize_k_vec[k];
for (int s = 0; s < nslices; s++)
memcpy(outptr + (size_t)s * outStep, inptr_k + (size_t)s * sliceSize_k, sliceSize_k);
}
}
}
class Concat2LayerImpl CV_FINAL : public Concat2Layer
+22 -2
View File
@@ -333,8 +333,28 @@ void reshapeAndCopyFirst(InputArrayOfArrays inputs,
if (inpTotal == 0 && outTotal == 0)
return;
Mat inp_ = inp.reshape(0, shape);
if (inp_.data != outref[0].data)
inp_.copyTo(outref[0]);
if (inp_.data != outref[0].data) {
// Parallel memcpy for large buffers to avoid single-thread bottleneck
// on reshape-style layers that don't get in-place-allocated.
CV_Assert(inp_.isContinuous());
CV_Assert(outref[0].isContinuous());
size_t bytes = inpTotal * inp_.elemSize();
const size_t CHUNK_BYTES = 64 * 1024;
if (bytes > 2 * CHUNK_BYTES) {
const uchar* src = inp_.data;
uchar* dst = outref[0].data;
int nChunks = (int)((bytes + CHUNK_BYTES - 1) / CHUNK_BYTES);
parallel_for_(Range(0, nChunks), [&](const Range& r) {
for (int i = r.start; i < r.end; i++) {
size_t off = (size_t)i * CHUNK_BYTES;
size_t len = std::min(CHUNK_BYTES, bytes - off);
memcpy(dst + off, src + off, len);
}
});
} else {
inp_.copyTo(outref[0]);
}
}
}
else {
UMat inp = inputs.getUMat(0);
@@ -621,6 +621,12 @@ public:
const Mat& a = inputs[0];
const Mat& b = inputs[1];
Mat& out = outputs[0];
if (op == OPERATION::POW && std::is_same<T, RESULT_T>::value && b.total() == 1) {
cv::pow(a, (double)(*(const T*)b.data), out);
return;
}
CV_Assert(helper.shapes.size() == 3 && helper.steps.size() == 3);
binary_forward_impl<T, RESULT_T, Functor>(f, helper.max_ndims, helper.shapes[0], a.ptr<char>(), helper.steps[1],
b.ptr<char>(), helper.steps[2], out.ptr<char>(), helper.steps[0], block_size);
+23 -16
View File
@@ -42,7 +42,6 @@ static void split(const Mat& inp, std::vector<Mat>& outs, int axis)
size_t esz = inp.elemSize();
size_t sliceSize = esz;
size_t inpStep = 0;
size_t totalSize = inp.total()*esz;
int outSize_a = 0;
for (int i = ndims-1; i > axis; i--)
sliceSize *= inpShape[i];
@@ -68,22 +67,30 @@ static void split(const Mat& inp, std::vector<Mat>& outs, int axis)
CV_Assert(outSize_a == inpShape[axis]);
parallel_for_(Range(0, (int)noutputs), [&](const Range& r) {
for (int k = r.start; k < r.end; k++) {
const uchar* inptr = inp.data;
Mat& out_k = outs[k];
uchar* outptr_k = out_k.data;
int sz_a;
for (int i = 0; i < k; i++) {
sz_a = outs[i].size[axis];
inptr += sliceSize*sz_a;
}
sz_a = out_k.size[axis];
size_t sliceSize_k = sliceSize*sz_a;
for (int i = 0; i < nslices; i++)
memcpy(outptr_k + i*sliceSize_k, inptr + i*inpStep, sliceSize_k);
// Precompute per-output source offset and per-slice size.
std::vector<size_t> srcOffset(noutputs);
std::vector<size_t> sliceSize_k_vec(noutputs);
{
size_t acc = 0;
for (size_t k = 0; k < noutputs; k++) {
int sz_a = outs[k].size[axis];
srcOffset[k] = acc;
sliceSize_k_vec[k] = sliceSize * sz_a;
acc += sliceSize_k_vec[k];
}
}, (totalSize > 1000000 ? noutputs : 1));
}
int64_t nTasks = (int64_t)nslices * (int64_t)noutputs;
parallel_for_(Range(0, (int)nTasks), [&](const Range& r) {
for (int64_t idx = r.start; idx < r.end; idx++) {
int k = (int)(idx % (int64_t)noutputs);
int s = (int)(idx / (int64_t)noutputs);
uchar* outptr_k = outs[k].data;
const uchar* inptr = inp.data + srcOffset[k];
size_t sliceSize_k = sliceSize_k_vec[k];
memcpy(outptr_k + (size_t)s*sliceSize_k, inptr + (size_t)s*inpStep, sliceSize_k);
}
});
}
class Split2LayerImpl CV_FINAL : public Split2Layer
+30 -21
View File
@@ -75,30 +75,39 @@ static void transpose(const Mat& inp, const std::vector<int>& perm, Mat& out)
size_t p4 = inpStep_[perm_[2]], p3 = inpStep_[perm_[3]];
size_t p2 = inpStep_[perm_[4]], p1 = inpStep_[perm_[5]], p0 = inpStep_[perm_[6]];
int64_t outerTotal = (int64_t)sz6 * sz5 * sz4 * sz3 * sz2;
#undef CV_IMPLEMENT_TRANSPOSE
#define CV_IMPLEMENT_TRANSPOSE(typ) \
const typ* inptr0 = (const typ*)inp.data; \
typ* outptr = (typ*)out.data; \
for (int i6 = 0; i6 < sz6; i6++) { \
for (int i5 = 0; i5 < sz5; i5++) { \
for (int i4 = 0; i4 < sz4; i4++) { \
for (int i3 = 0; i3 < sz3; i3++) { \
for (int i2 = 0; i2 < sz2; i2++) { \
for (int i1 = 0; i1 < sz1; i1++, outptr += sz0) { \
int i0 = 0; \
const typ* inptr = inptr0 + i6*p6 + i5*p5 + i4*p4 + i3*p3 + i2*p2 + i1*p1; \
for (; i0 <= sz0 - 3; i0 += 3) { \
size_t ip0 = i0*p0; \
typ t0 = inptr[ip0]; \
typ t1 = inptr[ip0+p0]; \
typ t2 = inptr[ip0+p0*2]; \
outptr[i0] = t0; \
outptr[i0+1] = t1; \
outptr[i0+2] = t2; \
parallel_for_(Range(0, (int)outerTotal), [&](const Range& r) { \
const typ* inptr0 = (const typ*)inp.data; \
typ* outptr0 = (typ*)out.data; \
for (int64_t idx = r.start; idx < r.end; idx++) { \
int64_t q = idx; \
int i2 = (int)(q % sz2); q /= sz2; \
int i3 = (int)(q % sz3); q /= sz3; \
int i4 = (int)(q % sz4); q /= sz4; \
int i5 = (int)(q % sz5); q /= sz5; \
int i6 = (int)q; \
const typ* inptrBase = inptr0 + i6*p6 + i5*p5 + i4*p4 + i3*p3 + i2*p2; \
typ* outptr = outptr0 + idx * ((int64_t)sz1 * sz0); \
for (int i1 = 0; i1 < sz1; i1++, outptr += sz0) { \
const typ* inptr = inptrBase + i1*p1; \
int i0 = 0; \
for (; i0 <= sz0 - 3; i0 += 3) { \
size_t ip0 = i0*p0; \
typ t0 = inptr[ip0]; \
typ t1 = inptr[ip0+p0]; \
typ t2 = inptr[ip0+p0*2]; \
outptr[i0] = t0; \
outptr[i0+1] = t1; \
outptr[i0+2] = t2; \
} \
for (; i0 < sz0; i0++) \
outptr[i0] = inptr[i0*p0]; \
} \
} \
for (; i0 < sz0; i0++) \
outptr[i0] = inptr[i0*p0]; \
}}}}}}
});
if (esz == 4) {
CV_IMPLEMENT_TRANSPOSE(int)
+242 -50
View File
@@ -880,23 +880,8 @@ TEST_P(Reproducibility_ResNet50_ONNX, Accuracy)
false, true, CV_32F);
ASSERT_TRUE(!input.empty());
Mat out;
double min_t = 0;
const int niters =
#ifdef _DEBUG
1;
#else
30;
#endif
for (int i = 0; i < niters; i++) {
double t = (double)getTickCount();
net.setInput(input);
out = net.forward();
t = (double)getTickCount() - t;
min_t = i == 0 ? t : std::min(min_t, t);
}
printf("run time = %.2fms\n", min_t*1000./getTickFrequency());
net.setInput(input);
Mat out = net.forward();
std::vector<std::pair<int, float> > ref = {{285, 10.13}, {287, 9.68}, {283, 8.83}, {278, 8.56}, {279, 8.34}};
std::vector<std::pair<int, float> > res;
@@ -944,23 +929,8 @@ TEST_P(Reproducibility_ResNet50_QDQ_ONNX, Accuracy)
false, true, CV_32F);
ASSERT_TRUE(!input.empty());
Mat out;
double min_t = 0;
const int niters =
#ifdef _DEBUG
1;
#else
30;
#endif
for (int i = 0; i < niters; i++) {
double t = (double)getTickCount();
net.setInput(input);
out = net.forward();
t = (double)getTickCount() - t;
min_t = i == 0 ? t : std::min(min_t, t);
}
printf("run time = %.2fms\n", min_t*1000./getTickFrequency());
net.setInput(input);
Mat out = net.forward();
const int K = 5;
std::vector<std::pair<int, float> > res;
@@ -1012,22 +982,8 @@ TEST_P(Reproducibility_MobileNetSSD_ONNX, Accuracy)
std::vector<String> outNames = net.getUnconnectedOutLayersNames();
std::vector<Mat> outs;
double min_t = 0;
const int niters =
#ifdef _DEBUG
1;
#else
30;
#endif
for (int i = 0; i < niters; i++) {
double t = (double)getTickCount();
net.setInput(input8dim4);
net.forward(outs, outNames);
t = (double)getTickCount() - t;
min_t = i == 0 ? t : std::min(min_t, t);
}
printf("run time = %.2fms\n", min_t*1000./getTickFrequency());
net.setInput(input8dim4);
net.forward(outs, outNames);
// Model outputs: detection_boxes [1,N,4], detection_classes [1,N],
// detection_scores [1,N], num_detections [1]
@@ -1091,4 +1047,240 @@ TEST_P(Reproducibility_MobileNetSSD_ONNX, Accuracy)
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_MobileNetSSD_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
namespace {
enum YoloFormat { YOLO_V5, YOLO_V8, YOLO_X };
static void YoloPostprocess(const Mat& out, YoloFormat fmt, int inputSize,
float confTh, float nmsTh,
std::vector<int>& classIds,
std::vector<float>& confidences,
std::vector<Rect2d>& boxes)
{
CV_Assert(out.dims == 3);
bool hasObj = (fmt != YOLO_V8);
int nclasses = 80;
int stride = 4 + (hasObj ? 1 : 0) + nclasses;
const float* data = nullptr;
std::vector<float> buf;
int N;
if (fmt == YOLO_V8) {
int C = out.size[1];
N = out.size[2];
CV_Assert(C == stride);
const float* src = out.ptr<float>();
buf.resize((size_t)N * C);
for (int i = 0; i < C; i++)
for (int j = 0; j < N; j++)
buf[j * C + i] = src[i * N + j];
data = buf.data();
} else {
N = out.size[1];
CV_Assert(out.size[2] == stride);
data = out.ptr<float>();
}
// YOLOX grid decode tables
std::vector<float> gridX, gridY, strideVec;
if (fmt == YOLO_X) {
const int strides[] = {8, 16, 32};
gridX.resize(N); gridY.resize(N); strideVec.resize(N);
int idx = 0;
for (int si = 0; si < 3; si++) {
int gs = inputSize / strides[si];
for (int y = 0; y < gs; y++)
for (int x = 0; x < gs; x++) {
gridX[idx] = (float)x;
gridY[idx] = (float)y;
strideVec[idx] = (float)strides[si];
idx++;
}
}
CV_Assert(idx == N);
}
int classOff = hasObj ? 5 : 4;
double scale = 1.0 / inputSize;
std::vector<Rect> intBoxes;
std::vector<int> allCls;
std::vector<float> allConf;
std::vector<Rect2d> allBoxes;
for (int i = 0; i < N; i++) {
const float* r = data + (size_t)i * stride;
float obj = hasObj ? r[4] : 1.0f;
if (obj < confTh) continue;
int bestCls = 0; float bestScore = 0;
for (int c = 0; c < nclasses; c++) {
float s = r[classOff + c] * obj;
if (s > bestScore) { bestScore = s; bestCls = c; }
}
if (bestScore < confTh) continue;
float cx, cy, w, h;
if (fmt == YOLO_X) {
cx = (r[0] + gridX[i]) * strideVec[i];
cy = (r[1] + gridY[i]) * strideVec[i];
w = std::exp(r[2]) * strideVec[i];
h = std::exp(r[3]) * strideVec[i];
} else {
cx = r[0]; cy = r[1]; w = r[2]; h = r[3];
}
intBoxes.push_back(Rect((int)(cx-w/2), (int)(cy-h/2), (int)w, (int)h));
allConf.push_back(bestScore);
allCls.push_back(bestCls);
allBoxes.push_back(Rect2d((cx-w/2)*scale, (cy-h/2)*scale, w*scale, h*scale));
}
std::vector<int> indices;
cv::dnn::NMSBoxes(intBoxes, allConf, confTh, nmsTh, indices);
for (int idx : indices) {
classIds.push_back(allCls[idx]);
confidences.push_back(allConf[idx]);
boxes.push_back(allBoxes[idx]);
}
}
} // local namespace
typedef testing::TestWithParam<Target> Reproducibility_YOLOv5n_ONNX;
TEST_P(Reproducibility_YOLOv5n_ONNX, Accuracy)
{
Target targetId = GetParam();
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
std::string modelname = _tf("yolov5n.onnx", false);
Net net = readNetFromONNX(modelname);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(targetId);
if (targetId == DNN_TARGET_CPU_FP16)
net.enableWinograd(false);
std::string imgname = _tf("dog416.png");
Mat image = imread(imgname);
ASSERT_TRUE(!image.empty());
Mat input = blobFromImage(image, 1.0/255.0, Size(640, 640), Scalar(), true, false, CV_32F);
net.setInput(input);
Mat out = net.forward();
if (out.type() != CV_32F) out.convertTo(out, CV_32F);
std::vector<int> classIds;
std::vector<float> confidences;
std::vector<Rect2d> testBoxes;
YoloPostprocess(out, YOLO_V5, 640, 0.25f, 0.45f, classIds, confidences, testBoxes);
std::vector<int> refClassIds = {16, 2, 1, 1};
std::vector<float> refScores = {0.711f, 0.581f, 0.344f, 0.275f};
std::vector<Rect2d> refBoxes = {
Rect2d(0.168262, 0.374023, 0.247852, 0.577734), // dog
Rect2d(0.605469, 0.134375, 0.286719, 0.156250), // car
Rect2d(0.186279, 0.248828, 0.148926, 0.129688), // bicycle (small)
Rect2d(0.231836, 0.277930, 0.519141, 0.483203), // bicycle (large)
};
normAssertDetections(refClassIds, refScores, refBoxes,
classIds, confidences, testBoxes,
"", 0.25f, /*scoreDiff=*/0.2, /*iouDiff=*/0.2);
}
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOv5n_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
typedef testing::TestWithParam<Target> Reproducibility_YOLOv8n_ONNX;
TEST_P(Reproducibility_YOLOv8n_ONNX, Accuracy)
{
Target targetId = GetParam();
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
std::string modelname = _tf("yolov8n.onnx", false);
Net net = readNetFromONNX(modelname);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(targetId);
if (targetId == DNN_TARGET_CPU_FP16)
net.enableWinograd(false);
std::string imgname = _tf("dog416.png");
Mat image = imread(imgname);
ASSERT_TRUE(!image.empty());
Mat input = blobFromImage(image, 1.0/255.0, Size(640, 640), Scalar(), true, false, CV_32F);
net.setInput(input);
Mat out = net.forward();
if (out.type() != CV_32F) out.convertTo(out, CV_32F);
std::vector<int> classIds;
std::vector<float> confidences;
std::vector<Rect2d> testBoxes;
YoloPostprocess(out, YOLO_V8, 640, 0.25f, 0.45f, classIds, confidences, testBoxes);
std::vector<int> refClassIds = {16, 1, 7};
std::vector<float> refScores = {0.827f, 0.809f, 0.544f};
std::vector<Rect2d> refBoxes = {
Rect2d(0.171157, 0.386951, 0.231909, 0.551873), // dog
Rect2d(0.160967, 0.234788, 0.577899, 0.495077), // bicycle
Rect2d(0.608337, 0.130141, 0.291832, 0.167390), // truck
};
normAssertDetections(refClassIds, refScores, refBoxes,
classIds, confidences, testBoxes,
"", 0.25f, /*scoreDiff=*/0.1, /*iouDiff=*/0.1);
}
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOv8n_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
typedef testing::TestWithParam<Target> Reproducibility_YOLOXS_ONNX;
TEST_P(Reproducibility_YOLOXS_ONNX, Accuracy)
{
Target targetId = GetParam();
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
std::string modelname = _tf("yolox_s.onnx", false);
Net net = readNetFromONNX(modelname);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(targetId);
if (targetId == DNN_TARGET_CPU_FP16)
net.enableWinograd(false);
std::string imgname = _tf("dog416.png");
Mat image = imread(imgname);
ASSERT_TRUE(!image.empty());
Mat input = blobFromImage(image, 1.0, Size(640, 640), Scalar(), false, false, CV_32F);
net.setInput(input);
Mat out = net.forward();
if (out.type() != CV_32F) out.convertTo(out, CV_32F);
std::vector<int> classIds;
std::vector<float> confidences;
std::vector<Rect2d> testBoxes;
YoloPostprocess(out, YOLO_X, 640, 0.25f, 0.45f, classIds, confidences, testBoxes);
std::vector<int> refClassIds = {1, 16, 7, 1};
std::vector<float> refScores = {0.962f, 0.920f, 0.833f, 0.266f};
std::vector<Rect2d> refBoxes = {
Rect2d(0.160787, 0.225276, 0.577830, 0.503752), // bicycle (large)
Rect2d(0.172622, 0.386773, 0.230225, 0.554768), // dog
Rect2d(0.601869, 0.128871, 0.302539, 0.168476), // truck
Rect2d(0.166281, 0.251719, 0.339791, 0.385267), // bicycle (small)
};
normAssertDetections(refClassIds, refScores, refBoxes,
classIds, confidences, testBoxes,
"", 0.25f, /*scoreDiff=*/0.2, /*iouDiff=*/0.2);
}
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOXS_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
}} // namespace