From df0c9d79500d1bee332d06d3192d789592a125c0 Mon Sep 17 00:00:00 2001 From: Pratham Kumar Date: Tue, 23 Jun 2026 11:55:30 +0530 Subject: [PATCH] Merge pull request #29316 from pratham-mcw:dnn-arm64-neon-blobfromimages dnn: optimize blobFromImages using NEON intrinsics on ARM64 #29316 - This PR optimizes the HWC-to-NCHW conversion in blobFromImages() by adding NEON implementation. - On x64, the MSVC compiler automatically vectorizes the existing scalar implementation, resulting in significantly better performance. - On Windows ARM64, MSVC does not auto-vectorize this stride-3 access pattern, leading to a noticeable performance gap compared to x64. - Added CV_NEON macro guard to ensure SIMD optimization is enabled on ARM64 architecture, leaving behavior on other platforms completely unchanged. **Performance Improvements** - This optimization significantly improves the performance of HWC-to-NCHW conversion on Windows ARM64. image - [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 --- modules/dnn/src/dnn_utils.cpp | 64 ++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/modules/dnn/src/dnn_utils.cpp b/modules/dnn/src/dnn_utils.cpp index 5cc1359b0d..29135fc6df 100644 --- a/modules/dnn/src/dnn_utils.cpp +++ b/modules/dnn/src/dnn_utils.cpp @@ -152,29 +152,69 @@ void blobFromImagesNCHWImpl(const std::vector& images, Mat& blob_, const Im if (param.swapRB) std::swap(p_blob_r, p_blob_b); - for (size_t i = 0; i < h; ++i) + if (nch == 1) { - const Tinp* p_img_row = images[k].ptr(i); - - if (nch == 1) + for (size_t i = 0; i < (size_t)h; ++i) { - for (size_t j = 0; j < w; ++j) + const Tinp* p_img_row = images[k].ptr(i); + for (size_t j = 0; j < (size_t)w; ++j) { p_blob[i * w + j] = p_img_row[j]; } } - else if (nch == 3) + } + else if (nch == 3) + { + if (images[k].isContinuous()) { - for (size_t j = 0; j < w; ++j) + const Tinp* p_src = images[k].ptr(); + size_t i = 0; +// NOTE: Visual Studio compiler is not able to vectorize the following loop on ARM64 CPU. +// GCC and Clang does it efficiently. +// TODO: Drop NEON block when MSVC is able to vectorize it too. +#if CV_NEON + if (sizeof(Tinp) == 4 && sizeof(Tout) == 4) { - p_blob_r[i * w + j] = p_img_row[j * 3 ]; - p_blob_g[i * w + j] = p_img_row[j * 3 + 1]; - p_blob_b[i * w + j] = p_img_row[j * 3 + 2]; + const float* src_f = reinterpret_cast(p_src); + float* dst_r = reinterpret_cast(p_blob_r); + float* dst_g = reinterpret_cast(p_blob_g); + float* dst_b = reinterpret_cast(p_blob_b); + for (; i + 4 <= (size_t)wh; i += 4) + { + float32x4x3_t rgb = vld3q_f32(src_f + i * 3); + vst1q_f32(dst_r + i, rgb.val[0]); + vst1q_f32(dst_g + i, rgb.val[1]); + vst1q_f32(dst_b + i, rgb.val[2]); + } + } +#endif + for (; i < (size_t)wh; ++i) + { + p_blob_r[i] = p_src[i * 3 ]; + p_blob_g[i] = p_src[i * 3 + 1]; + p_blob_b[i] = p_src[i * 3 + 2]; } } - else // if (nch == 4) + else { - for (size_t j = 0; j < w; ++j) + for (size_t i = 0; i < (size_t)h; ++i) + { + const Tinp* p_img_row = images[k].ptr(i); + for (size_t j = 0; j < (size_t)w; ++j) + { + p_blob_r[i * w + j] = p_img_row[j * 3 ]; + p_blob_g[i * w + j] = p_img_row[j * 3 + 1]; + p_blob_b[i * w + j] = p_img_row[j * 3 + 2]; + } + } + } + } + else // if (nch == 4) + { + for (size_t i = 0; i < (size_t)h; ++i) + { + const Tinp* p_img_row = images[k].ptr(i); + for (size_t j = 0; j < (size_t)w; ++j) { p_blob_r[i * w + j] = p_img_row[j * 4 ]; p_blob_g[i * w + j] = p_img_row[j * 4 + 1];