Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs
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);
}
}
Comment on lines +16 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 TypeInitializationException for 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
Verify each finding against the current code and only fix it if needed.

In `@test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs` around lines 16 - 23,
The static constructor FaceDetectorYNTest() currently writes ModelPath without
ensuring its parent directory exists and may throw during static initialization;
modify the provisioning to first ensure the directory for ModelPath exists (use
Path.GetDirectoryName(ModelPath) + Directory.CreateDirectory) before calling
File.WriteAllBytes, and wrap the download/write (FileDownloader.DownloadData(new
Uri(ModelUrl)) and File.WriteAllBytes) in a try/catch that converts/propagates
errors outside the static constructor (e.g., perform provisioning in a
OneTimeSetUp method or rethrow a clearer exception) to avoid
TypeInitializationException for the whole test class.


[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

View workflow job for this annotation

GitHub Actions / build_test (24.04, libgtk-3-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Dispose_Twice() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 53 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (24.04, libgtk-3-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Dispose_Twice() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 53 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (22.04, libgtk2.0-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Dispose_Twice() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 53 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (22.04, libgtk2.0-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Dispose_Twice() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 53 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Dispose_Twice() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 53 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Dispose_Twice() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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

View workflow job for this annotation

GitHub Actions / build_test (24.04, libgtk-3-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_Lenna() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 65 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (22.04, libgtk2.0-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_Lenna() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 65 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_Lenna() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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

View workflow job for this annotation

GitHub Actions / build_test (24.04, libgtk-3-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_NoFace() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 99 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (22.04, libgtk2.0-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_NoFace() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 99 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_NoFace() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
// 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

View workflow job for this annotation

GitHub Actions / build_test (24.04, libgtk-3-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_MultipleCalls() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 119 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (22.04, libgtk2.0-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_MultipleCalls() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 119 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_MultipleCalls() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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

View workflow job for this annotation

GitHub Actions / build_test (24.04, libgtk-3-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_DifferentThresholds() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 140 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build_test (22.04, libgtk2.0-dev)

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_DifferentThresholds() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)

Check warning on line 140 in test/OpenCvSharp.Tests/objdetect/FaceDetectorYNTest.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name OpenCvSharp.Tests.ObjDetect.FaceDetectorYNTest.Detect_DifferentThresholds() (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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})");
}
}
Loading