-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add tests for FaceDetectorYN #1828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| using OpenCvSharp.Dnn; | ||
| using Xunit; | ||
|
|
||
| namespace OpenCvSharp.Tests.ObjDetect; | ||
|
|
||
| // ReSharper disable InconsistentNaming | ||
| public class FaceDetectorYNTest : TestBase | ||
| { | ||
| // YuNet face detection model from OpenCV Zoo | ||
| // https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet | ||
| private const string ModelUrl = | ||
| "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"; | ||
|
|
||
| private const string ModelPath = "_data/model/face_detection_yunet_2023mar.onnx"; | ||
|
|
||
| static FaceDetectorYNTest() | ||
| { | ||
| if (!File.Exists(ModelPath)) | ||
| { | ||
| var contents = FileDownloader.DownloadData(new Uri(ModelUrl)); | ||
| File.WriteAllBytes(ModelPath, contents); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Create() | ||
| { | ||
| using var detector = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(320, 320)); | ||
|
|
||
| Assert.NotNull(detector); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CreateWithParameters() | ||
| { | ||
| using var detector = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(640, 480), | ||
| scoreThreshold: 0.8f, | ||
| nmsThreshold: 0.4f, | ||
| topK: 3000, | ||
| backendId: Backend.DEFAULT, | ||
| targetId: Target.CPU); | ||
|
|
||
| Assert.NotNull(detector); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Dispose_Twice() | ||
|
Check warning on line 53 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs
|
||
| { | ||
| var detector = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(320, 320)); | ||
|
|
||
| detector.Dispose(); | ||
| detector.Dispose(); // Should not throw | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Detect_Lenna() | ||
|
Check warning on line 65 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs
|
||
| { | ||
| using var image = LoadImage("lenna.png"); | ||
| Assert.False(image.Empty()); | ||
|
|
||
| using var detector = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(image.Cols, image.Rows), | ||
| scoreThreshold: 0.9f, | ||
| nmsThreshold: 0.3f, | ||
| topK: 5000); | ||
|
|
||
| using var faces = new Mat(); | ||
| int result = detector.Detect(image, faces); | ||
|
|
||
| // Lenna image should contain at least one face | ||
| Assert.Equal(1, result); | ||
| Assert.False(faces.Empty()); | ||
|
|
||
| // Each row in faces is a detected face: | ||
| // [x, y, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt, x_rcm, y_rcm, x_lcm, y_lcm, score] | ||
| // 14 landmarks + 1 score = 15 columns | ||
| Assert.True(faces.Rows > 0, "Expected at least one detected face"); | ||
| Assert.Equal(15, faces.Cols); | ||
|
|
||
| // Verify the confidence score (last column) is reasonable | ||
| var score = faces.At<float>(0, 14); | ||
| Assert.True(score > 0.5f, $"Face confidence score {score} is unexpectedly low"); | ||
|
|
||
| ShowImagesWhenDebugMode(image); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Detect_NoFace() | ||
|
Check warning on line 99 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs
|
||
| { | ||
| // Create a blank image with no face | ||
| using var image = new Mat(320, 320, MatType.CV_8UC3, Scalar.All(128)); | ||
|
|
||
| using var detector = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(320, 320), | ||
| scoreThreshold: 0.9f); | ||
|
|
||
| using var faces = new Mat(); | ||
| detector.Detect(image, faces); | ||
|
|
||
| // Blank image should have no detected faces | ||
| Assert.True(faces.Empty() || faces.Rows == 0, | ||
| $"Expected no faces but got {faces.Rows} detection(s)"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Detect_MultipleCalls() | ||
|
Check warning on line 119 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs
|
||
| { | ||
| using var image = LoadImage("lenna.png"); | ||
| using var detector = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(image.Cols, image.Rows)); | ||
|
|
||
| // Verify that calling Detect multiple times produces consistent results | ||
| using var faces1 = new Mat(); | ||
| using var faces2 = new Mat(); | ||
|
|
||
| int result1 = detector.Detect(image, faces1); | ||
| int result2 = detector.Detect(image, faces2); | ||
|
|
||
| Assert.Equal(result1, result2); | ||
| Assert.Equal(faces1.Rows, faces2.Rows); | ||
| Assert.Equal(faces1.Cols, faces2.Cols); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Detect_DifferentThresholds() | ||
|
Check warning on line 140 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs
|
||
| { | ||
| using var image = LoadImage("lenna.png"); | ||
|
|
||
| // With high threshold | ||
| using var detectorHigh = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(image.Cols, image.Rows), | ||
| scoreThreshold: 0.99f); | ||
|
|
||
| using var facesHigh = new Mat(); | ||
| detectorHigh.Detect(image, facesHigh); | ||
|
|
||
| // With low threshold | ||
| using var detectorLow = new FaceDetectorYN( | ||
| ModelPath, | ||
| config: "", | ||
| inputSize: new Size(image.Cols, image.Rows), | ||
| scoreThreshold: 0.1f); | ||
|
|
||
| using var facesLow = new Mat(); | ||
| detectorLow.Detect(image, facesLow); | ||
|
|
||
| // Lower threshold should yield >= detections than higher threshold | ||
| int highCount = facesHigh.Empty() ? 0 : facesHigh.Rows; | ||
| int lowCount = facesLow.Empty() ? 0 : facesLow.Rows; | ||
| Assert.True(lowCount >= highCount, | ||
| $"Low threshold detections ({lowCount}) should be >= high threshold detections ({highCount})"); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden model provisioning to avoid class-initialization failures.
Line 21 writes to a nested relative path without ensuring the directory exists, and any download/write error in the static constructor will surface as a
TypeInitializationExceptionfor the whole test class.Proposed reliability fix
static FaceDetectorYNTest() { if (!File.Exists(ModelPath)) { - var contents = FileDownloader.DownloadData(new Uri(ModelUrl)); - File.WriteAllBytes(ModelPath, contents); + var dir = Path.GetDirectoryName(ModelPath); + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + + try + { + var contents = FileDownloader.DownloadData(new Uri(ModelUrl)); + File.WriteAllBytes(ModelPath, contents); + } + catch (Exception ex) + { + throw new Xunit.Sdk.XunitException( + $"Failed to provision YuNet model at '{ModelPath}' from '{ModelUrl}': {ex.Message}"); + } } }🤖 Prompt for AI Agents