C# OpencvSharp使用FaceDetectorYN模型进行人脸检测

Opencv中使用FaceDetectorYN可以很方便地进行人脸检测,上篇在Emgucv中用FaceDetectorYN做了个Demo。笔者实际使用中,OpencvSharp使用比较多,本想用OpencvSharp去实现,结果发现OpencvSharp不支持FaceDetectorYN这个类,于是想到用OpencvSharp Dnn模块加载FaceDetectorYN模型进行推理检测。上网搜了一下,貌似有用OpencvSharp使用FaceDetectorYN进行人脸检测的文章,但是是付费的,想想还是自己研究一下吧。下面代码基本实现了该效果,但是与Opencv集成的FaceDetectorYN精度和检测效果方面,还是有点差异,不知道是啥原因,希望有大佬能完善或给指点一下。

模型输入输出:

全部代码如下:

using OpenCvSharp;
using OpenCvSharp.Dnn;
using System;
using System.Collections.Generic;
using System.Linq;
public class YunetFaceDetector
{
    private Net _net;
    private float _confThreshold = 0.8f; // 置信度阈值
    private float _nmsThreshold = 0.35f;   // NMS阈值
    // 初始化模型
    public void LoadModel(string modelPath)
    {
        _net = CvDnn.ReadNet(modelPath); // 加载ONNX模型
        _net.SetPreferableBackend(Backend.OPENCV); // 使用OpenCV后端
        _net.SetPreferableTarget(Target.CPU);       // 默认CPU推理(GPU需配置CUDA)
    }
    // 执行人脸检测
    float ratio_height;
    float ratio_width;
    public Mat Detect(Mat image,out float ratio_height,out float ratio_width)
    {
        //图片缩放
        int h = image.Rows;
        int w = image.Cols;
        Mat temp_image = image.Clone();
        int _max = Math.Max(h, w);
        Mat input_img = Mat.Zeros(new Size(_max, _max), MatType.CV_8UC3);
        Rect roi = new Rect(0, 0, w, h);
        temp_image.CopyTo(input_img[roi]);
        ratio_height = input_img.Rows / 640.0f;
        ratio_width = input_img.Cols / 640.0f;
        Cv2.Resize(input_img,input_img,new Size(640,640));

        Mat blob = CvDnn.BlobFromImage(
            input_img
            //1.0,
            //size: new Size(640, 640), // 输入尺寸固定640x640
            //mean: new Scalar(0, 0, 0), // 归一化均值
            //swapRB: true,               // BGR转RGB(OpenCV默认BGR,模型需RGB)
            //crop: false
        );

        // --- 2. 设置输入并执行推理 ---
        _net.SetInput(blob);
        string[] outputNames = { "cls_8", "cls_16", "cls_32", "obj_8", "obj_16", "obj_32", "bbox_8", "bbox_16", "bbox_32", "kps_8", "kps_16", "kps_32" };
        Mat[] outputs = outputNames.Select(_ => new Mat()).ToArray();
        _net.Forward(outputs, outputNames); // 获取所有输出层

        // --- 3. 解析多尺度输出 ---

        Mat results = ParseOutputs(outputs, input_img.Width, input_img.Height);
        results.ConvertTo(results, MatType.CV_32FC1);

        return results;
    }

    // 解析输出张量(核心逻辑)
    unsafe private Mat ParseOutputs(Mat[] output_blobs, int origW, int origH)
    {
        int[] strides = { 8, 16, 32 };
        Mat faces = new Mat();
        for (int i = 0; i < strides.Length; ++i)
        {
            int cols = (int)(origW / strides[i]);
            int rows = (int)(origH / strides[i]);

            // Extract from output_blobs
            Mat cls = output_blobs[i];
            Mat obj = output_blobs[i + strides.Length * 1];
            Mat bbox = output_blobs[i + strides.Length * 2];
            Mat kps = output_blobs[i + strides.Length * 3];

            // Decode from predictions
            float* cls_v = (float*)(cls.Data);
            float* obj_v = (float*)(obj.Data);
            float* bbox_v = (float*)(bbox.Data);
            float* kps_v = (float*)(kps.Data);

            // (tl_x, tl_y, w, h, re_x, re_y, le_x, le_y, nt_x, nt_y, rcm_x, rcm_y, lcm_x, lcm_y, score)
            // 'tl': top left point of the bounding box
            // 're': right eye, 'le': left eye
            // 'nt':  nose tip
            // 'rcm': right corner of mouth, 'lcm': left corner of mouth
            Mat face = new Mat(1, 15, MatType.CV_32FC1);

            for (int r = 0; r < rows; r++)
            {
                for (int c = 0; c < cols; c++)
                {
                    int idx = r * cols + c;

                    // Get score
                    float cls_score = cls_v[idx];
                    float obj_score = obj_v[idx];

                    // Clamp
                    cls_score = Math.Min(cls_score, 1.0f);
                    cls_score = Math.Max(cls_score, 0.0f);
                    obj_score = Math.Min(obj_score, 1.0f);
                    obj_score = Math.Max(obj_score, 0.0f);
                    float score = (float)Math.Sqrt(cls_score * obj_score);
                    
                    face.At<float>(0, 14) = score;

                    // Checking if the score meets the threshold before adding the face
                    if (score < _confThreshold)
                        continue;
                    // Get bounding box
                    float cx = ((c + bbox_v[idx * 4 + 0]) * strides[i]);
                    float cy = ((r + bbox_v[idx * 4 + 1]) * strides[i]);
                    float w = (float)(Math.Exp(bbox_v[idx * 4 + 2]) * strides[i]);
                    float h = (float)(Math.Exp(bbox_v[idx * 4 + 3]) * strides[i]);

                    float x1 = cx - w / 2.0f;
                    float y1 = cy - h / 2.0f;

                    face.At<float>(0, 0) = x1;
                    face.At<float>(0, 1) = y1;
                    face.At<float>(0, 2) = w;
                    face.At<float>(0, 3) = h;

                    // Get landmarks
                    for (int n = 0; n < 5; n++)
                    {
                        face.At<float>(0, 4 + 2 * n) = (kps_v[idx * 10 + 2 * n] + c) * strides[i];
                        face.At<float>(0, 4 + 2 * n + 1) = (kps_v[idx * 10 + 2 * n + 1] + r) * strides[i];
                    }
                    faces.PushBack(face);
                }
            }
        }

        if (faces.Rows > 1)
        {
            // Retrieve boxes and scores
            List<Rect> faceBoxes = new List<Rect>();
            List<float> faceScores = new List<float>();
            for (int rIdx = 0; rIdx < faces.Rows; rIdx++)
            {
                faceBoxes.Add(new Rect((int)(faces.At<float>(rIdx, 0)),
                                           (int)(faces.At<float>(rIdx, 1)),
                                           (int)(faces.At<float>(rIdx, 2)),
                                           (int)(faces.At<float>(rIdx, 3))));
                faceScores.Add(faces.At<float>(rIdx, 14));
            }

            int[] keepIdx;
            CvDnn.NMSBoxes(faceBoxes, faceScores, _confThreshold, _nmsThreshold, out keepIdx);

            // Get NMS results
            Mat nms_faces = new Mat();
            foreach (int idx in keepIdx)
            {
                nms_faces.PushBack(faces.Row(idx));
            }
            return nms_faces;
        }
        else
        {
            return faces;
        }
        
    }
}

使用方法:

// 初始化检测器
YunetFaceDetector detector = new YunetFaceDetector();
detector.LoadModel("face_detection_yunet_2023mar.onnx"); // 替换为模型路径
// 加载图像
Mat image = Cv2.ImRead("Face.png");
// 执行检测
Stopwatch sw =Stopwatch.StartNew();
Mat faces = detector.Detect(image, out float ratio_height, out float ratio_width);
sw.Stop();

Scalar[] landmark_color =
{
    new Scalar(255, 0, 0), // right eye
    new Scalar(0, 0, 255), // left eye
    new Scalar(0, 255, 0), // nose tip
    new Scalar(255, 0, 255), // right mouth corner
    new Scalar(0, 255, 255)  // left mouth corner
};
if (!faces.Empty())
{
    // 每行对应一个人脸,每行15个元素:[x,y,w,h, 关键点x10, 置信度]
    for (int i = 0; i < faces.Rows; i++)
    {
        //float[] data = new float[15];
        float* data= (float*)faces.Row(i).Data;

        // 提取人脸矩形
        Rect faceRect = new Rect(
            (int)(data[0]*ratio_width), (int)(data[1]*ratio_height) , (int)(data[2]*ratio_width), (int)(data[3]*ratio_height));
        float confidence = data[14];

        // 绘制矩形
        Cv2.Rectangle(image, faceRect, new Scalar(0,255,0), 2);

        // 绘制关键点(5个点:右眼、左眼、鼻尖、右嘴角、左嘴角)
        int colorIndex = 0;
        for (int p = 4; p < 14; p += 2)
        {
            OpenCvSharp.Point point = new OpenCvSharp.Point((int)data[p]* ratio_width, (int)data[p + 1]*ratio_height);
            Cv2.Circle(image, point, 3, landmark_color[colorIndex], -1);
            colorIndex++;
        }
    }
}
Cv2.PutText(image,$"{sw.ElapsedMilliseconds}ms",new OpenCvSharp.Point(20,20),HersheyFonts.HersheySimplex,1,Scalar.Blue);
Cv2.NamedWindow("OpencvSharp-Dnn-Output", OpenCvSharp.WindowFlags.FreeRatio);
Cv2.ImShow("OpencvSharp-Dnn-Output", image);

下面是效果图 (图片由豆包AI生成):

        Emgucv自带FaceDetectorYN检测效果                     OpencvSharp Dnn推理检测效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值