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:
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user