Merge pull request #29804 from MahathirMohammadShuvo:fix/facerecognizersf-match-const-input

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)
This commit is contained in:
Mahathir Mohammad Shuvo
2026-08-28 13:53:22 +06:00
committed by GitHub
parent 6dc8e40903
commit 13c571a801
2 changed files with 25 additions and 3 deletions

View File

@@ -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];

View File

@@ -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<FaceRecognizerSF> 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