From 13c571a801ad5c67a752e5cd58a8a7e7725f99d2 Mon Sep 17 00:00:00 2001 From: Mahathir Mohammad Shuvo <48558774+MahathirMohammadShuvo@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:53:22 +0600 Subject: [PATCH] Merge pull request #29804 from MahathirMohammadShuvo:fix/facerecognizersf-match-const-input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit objdetect: do not modify the input features in FaceRecognizerSF::match - #29804 `FaceRecognizerSF::match()` normalizes its two `InputArray` features in place, writing through to the caller's buffers, and returns a wrong score when the two overlap. ### Fix Normalize both features into their own destinations. This yields bit-exact the same values as the in-place form, checked across a range of shapes and depths including a non-continuous ROI, so scores for non-overlapping inputs do not move. ### Pull Request Readiness Checklist - [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 - [ ] There is a reference to the original bug report and related work (no issue reports this; #7298 is the related RFC) - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable (accuracy test added in-repo; it reuses the existing model, so there is no opencv_extra patch) - [ ] The feature is well documented and sample code can be built with the project CMake (n/a — bug fix, no API, sample or documentation change) --- modules/objdetect/src/face_recognize.cpp | 7 ++++--- modules/objdetect/test/test_face.cpp | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/modules/objdetect/src/face_recognize.cpp b/modules/objdetect/src/face_recognize.cpp index b024c787eb..639aa4b471 100644 --- a/modules/objdetect/src/face_recognize.cpp +++ b/modules/objdetect/src/face_recognize.cpp @@ -61,9 +61,10 @@ public: } double match(InputArray _face_feature1, InputArray _face_feature2, int dis_type) const override { - Mat face_feature1 = _face_feature1.getMat(), face_feature2 = _face_feature2.getMat(); - normalize(face_feature1, face_feature1); - normalize(face_feature2, face_feature2); + // match() is const and takes InputArray: never write through to the caller's buffers. + Mat face_feature1, face_feature2; + normalize(_face_feature1, face_feature1); + normalize(_face_feature2, face_feature2); if(dis_type == DisType::FR_COSINE){ return sum(face_feature1.mul(face_feature2))[0]; diff --git a/modules/objdetect/test/test_face.cpp b/modules/objdetect/test/test_face.cpp index e55401c061..34b80bd334 100644 --- a/modules/objdetect/test/test_face.cpp +++ b/modules/objdetect/test/test_face.cpp @@ -213,4 +213,25 @@ TEST(Objdetect_face_recognition, regression) } } +TEST(Objdetect_face_recognition, match_does_not_modify_input) +{ + std::string recog_model = findDataFile("dnn/onnx/models/face_recognizer_fast.onnx", false); + Ptr faceRecognizer = FaceRecognizerSF::create(recog_model, ""); + + // match() is declared const and takes both features as InputArray, so it must leave + // the caller's data untouched. + Mat feature1(1, 128, CV_32F), feature2(1, 128, CV_32F); + randu(feature1, Scalar::all(-10), Scalar::all(10)); + randu(feature2, Scalar::all(-10), Scalar::all(10)); + Mat expected1 = feature1.clone(), expected2 = feature2.clone(); + + faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_COSINE); + EXPECT_EQ(0, cvtest::norm(expected1, feature1, NORM_INF)) << "FR_COSINE changed input 1"; + EXPECT_EQ(0, cvtest::norm(expected2, feature2, NORM_INF)) << "FR_COSINE changed input 2"; + + faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_NORM_L2); + EXPECT_EQ(0, cvtest::norm(expected1, feature1, NORM_INF)) << "FR_NORM_L2 changed input 1"; + EXPECT_EQ(0, cvtest::norm(expected2, feature2, NORM_INF)) << "FR_NORM_L2 changed input 2"; +} + }} // namespace