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推理检测效果