用C#和YOLO做一个「颜值检测」上位机?其实原理很简单
其实颜值检测上位机的核心就是**“YOLO找脸 + 颜值模型打分”**,两者都是现成的预训练模型,无需深入理解深度学习细节,只需用C#将它们组合起来,就能实现看似复杂的功能。这种“组合模型”的思路,也是工业界实现AI应用的常用方式——把复杂任务拆解为多个简单的AI子任务,复用现有模型,快速落地产品。
很多人以为“颜值检测”是高大上的AI任务,其实核心原理是**“两步走”:第一步用YOLO检测人脸**(定位图像中的人脸区域),第二步用颜值评分模型(对裁剪后的人脸区域进行颜值打分/分类)。YOLO负责“找脸”,颜值模型负责“打分”,两者结合就能实现颜值检测上位机。
本文将用C# WinForm + YOLOv8n-face(人脸检测) + 轻量化颜值评分模型(ONNX),从零搭建颜值检测上位机——全程无需复杂的深度学习训练,只需复用预训练模型,复制粘贴核心代码,就能实现“上传图片→检测人脸→颜值打分→结果可视化”的完整功能。
一、颜值检测的核心原理(一句话讲透)
YOLO是目标检测模型,无法直接做颜值检测(因为颜值是“回归/分类任务”,不是“目标定位任务”),但可以和颜值模型组合实现:
图像输入 → YOLO检测人脸(输出人脸框坐标)→ 裁剪人脸区域 → 颜值模型推理(输出颜值分数/等级)→ 可视化结果
- 第一步:YOLO人脸检测:用专门的YOLOv8n-face模型(针对人脸优化的YOLOv8,比通用YOLO检测人脸更精准),定位图像中所有人脸的位置(x1,y1,x2,y2);
- 第二步:颜值评分:将裁剪后的人脸区域输入轻量化的颜值评分模型(如CNN回归模型,输出0~10分的颜值分数,或“低/中/高”颜值等级);
- 可视化:在图像上绘制人脸框,并标注颜值分数/等级。
二、技术选型(轻量化+易上手)
为了让上位机开发更简单,选择轻量化、开箱即用的技术栈:
| 技术模块 | 选型说明 |
|---|---|
| UI框架 | WinForm(.NET Framework 4.7.2)——开发最快,新手友好 |
| 图像处理 | OpenCvSharp4——人脸裁剪、图像预处理、结果可视化一键搞定 |
| 推理引擎 | ONNX Runtime——支持所有预训练模型的ONNX格式,CPU推理足够快 |
| 人脸检测模型 | YOLOv8n-face(ONNX)——专门的人脸检测模型,轻量(≈6MB)、精准 |
| 颜值评分模型 | 轻量化颜值CNN模型(ONNX)——输出0~10分颜值分数,或颜值等级(本文提供现成模型) |
| 图像输入 | 本地图片(支持jpg/png/bmp)——聚焦核心功能,后续可扩展摄像头实时检测 |
三、环境搭建(1分钟搞定)
1. 新建WinForm项目
打开Visual Studio 2022(社区版即可),创建Windows 窗体应用(.NET Framework),命名为BeautyScoreDetector,选择.NET Framework 4.7.2。
2. 安装核心NuGet包
右键项目→管理NuGet程序包→浏览,搜索并安装以下包(直接装最新稳定版):
# 1. ONNX Runtime(CPU版)
Install-Package Microsoft.ML.OnnxRuntime
# 2. OpenCvSharp4(图像处理+运行时)
Install-Package OpenCvSharp4
Install-Package OpenCvSharp4.Extensions
Install-Package OpenCvSharp4.runtime.win
# 3. 辅助包(图像转换)
Install-Package System.Drawing.Common
四、模型准备(1分钟,现成模型直接用)
无需自己训练模型,直接下载预训练的ONNX模型:
1. 下载YOLOv8n-face模型(人脸检测)
- 作用:检测图像中的人脸,输出人脸框坐标;
- 下载地址:YOLOv8n-face ONNX(官方人脸检测模型,≈6MB);
- 备用地址:若无法访问,可搜索“YOLOv8n-face onnx”从开源仓库下载。
2. 下载颜值评分模型(颜值打分)
- 作用:对裁剪后的人脸区域打分(0~10分,分数越高颜值越高);
- 模型说明:这是一个轻量化的CNN回归模型(输入112×112×3的人脸图像,输出0~10的颜值分数),已训练好并导出为ONNX格式;
- 下载:可在文末的示例仓库中获取
beauty_score.onnx模型(或用自己训练的模型替换)。
3. 放置模型文件
将下载的yolov8n-face.onnx和beauty_score.onnx复制到项目的bin/Debug目录下(与可执行文件同目录,避免路径问题)。
五、核心代码实现(2分钟,复制粘贴即可)
1. 常量配置类(Config.cs)
集中管理模型参数,便于修改:
namespace BeautyScoreDetector
{
public static class Config
{
// YOLOv8n-face参数
public const int YoloInputWidth = 640;
public const int YoloInputHeight = 640;
public const float YoloConfThres = 0.5f; // 人脸检测置信度阈值
public const float YoloNmsThres = 0.45f;
// 颜值评分模型参数
public const int BeautyInputWidth = 112;
public const int BeautyInputHeight = 112;
}
}
2. 图像工具类(ImageProcessUtil.cs)
封装图像预处理、人脸裁剪、张量转换逻辑:
using OpenCvSharp;
using Microsoft.ML.OnnxRuntime.Tensors;
using System;
using System.Linq;
namespace BeautyScoreDetector.Utils
{
public static class ImageProcessUtil
{
/// <summary>
/// YOLO人脸检测的图像预处理(LetterBox缩放、归一化、通道转换)
/// </summary>
public static Tensor<float> YoloPreProcess(Mat srcMat, out float[] ratio, out float[] pad)
{
// 1. LetterBox缩放(保持比例,填充黑边)
Mat resizedMat = LetterBox(srcMat, Config.YoloInputWidth, Config.YoloInputHeight, out ratio, out pad);
// 2. BGR→RGB + 归一化(0~255→0~1)
float[] floatData = new float[Config.YoloInputWidth * Config.YoloInputHeight * 3];
for (int c = 0; c < 3; c++)
{
for (int h = 0; h < Config.YoloInputHeight; h++)
{
for (int w = 0; w < Config.YoloInputWidth; w++)
{
floatData[c * Config.YoloInputHeight * Config.YoloInputWidth + h * Config.YoloInputWidth + w] =
resizedMat.At<byte>(h, w, 2 - c) / 255.0f;
}
}
}
// 3. 构建NCHW张量
return new DenseTensor<float>(floatData, new[] { 1, 3, Config.YoloInputHeight, Config.YoloInputWidth });
}
/// <summary>
/// 颜值模型的图像预处理(人脸裁剪后缩放、归一化)
/// </summary>
public static Tensor<float> BeautyPreProcess(Mat faceMat)
{
// 1. 缩放至112×112
Mat resizedMat = faceMat.Resize(new Size(Config.BeautyInputWidth, Config.BeautyInputHeight));
// 2. BGR→RGB + 归一化(0~255→0~1)
float[] floatData = new float[Config.BeautyInputWidth * Config.BeautyInputHeight * 3];
for (int c = 0; c < 3; c++)
{
for (int h = 0; h < Config.BeautyInputHeight; h++)
{
for (int w = 0; w < Config.BeautyInputWidth; w++)
{
floatData[c * Config.BeautyInputHeight * Config.BeautyInputWidth + h * Config.BeautyInputWidth + w] =
resizedMat.At<byte>(h, w, 2 - c) / 255.0f;
}
}
}
// 3. 构建NCHW张量
return new DenseTensor<float>(floatData, new[] { 1, 3, Config.BeautyInputHeight, Config.BeautyInputWidth });
}
/// <summary>
/// LetterBox缩放(保持比例,填充黑边)
/// </summary>
private static Mat LetterBox(Mat srcMat, int targetW, int targetH, out float[] ratio, out float[] pad)
{
int srcW = srcMat.Width;
int srcH = srcMat.Height;
// 计算缩放比例
float r = Math.Min((float)targetH / srcH, (float)targetW / srcW);
int newW = (int)(srcW * r);
int newH = (int)(srcH * r);
// 缩放图像
Mat resizedMat = srcMat.Resize(new Size(newW, newH));
// 计算填充量
int padLeft = (targetW - newW) / 2;
int padTop = (targetH - newH) / 2;
int padRight = targetW - newW - padLeft;
int padBottom = targetH - newH - padTop;
// 填充黑边
Mat letterBoxMat = resizedMat.CopyMakeBorder(padTop, padBottom, padLeft, padRight, BorderTypes.Constant, Scalar.Black);
// 记录比例和填充量
ratio = new[] { r, r };
pad = new[] { (float)padLeft, (float)padTop };
return letterBoxMat;
}
/// <summary>
/// 还原人脸框坐标到原始图像
/// </summary>
public static float[] RescaleBbox(float[] bbox, float[] ratio, float[] pad)
{
float x1 = (bbox[0] - pad[0]) / ratio[0];
float y1 = (bbox[1] - pad[1]) / ratio[1];
float x2 = (bbox[2] - pad[0]) / ratio[0];
float y2 = (bbox[3] - pad[1]) / ratio[1];
return new[] { x1, y1, x2, y2 };
}
/// <summary>
/// 裁剪人脸区域(从原始图像中根据人脸框裁剪)
/// </summary>
public static Mat CropFace(Mat srcMat, float[] bbox)
{
// 限制人脸框在图像范围内
int x1 = (int)Math.Max(0, bbox[0]);
int y1 = (int)Math.Max(0, bbox[1]);
int x2 = (int)Math.Min(srcMat.Width, bbox[2]);
int y2 = (int)Math.Min(srcMat.Height, bbox[3]);
// 裁剪人脸区域
return new Mat(srcMat, new Rect(x1, y1, x2 - x1, y2 - y1));
}
}
}
3. 核心推理类(DetectorEngine.cs)
封装YOLO人脸检测和颜值评分的推理逻辑:
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using BeautyScoreDetector.Utils;
namespace BeautyScoreDetector.Core
{
// 检测结果实体(人脸框+颜值分数)
public class BeautyResult
{
public float[] Bbox { get; set; } // 人脸框坐标(x1,y1,x2,y2)
public float Confidence { get; set; } // 人脸检测置信度
public float BeautyScore { get; set; } // 颜值分数(0~10)
public string BeautyLevel => GetBeautyLevel(BeautyScore); // 颜值等级
// 根据分数判断颜值等级
private string GetBeautyLevel(float score)
{
if (score >= 8) return "高颜值";
else if (score >= 6) return "中等偏上";
else if (score >= 4) return "中等";
else return "中等偏下";
}
}
public class DetectorEngine : IDisposable
{
private readonly InferenceSession _yoloSession; // YOLO人脸检测会话
private readonly InferenceSession _beautySession; // 颜值评分会话
private bool _disposed = false;
public DetectorEngine(string yoloModelPath, string beautyModelPath)
{
// 初始化YOLO会话
var yoloOptions = new SessionOptions();
yoloOptions.InterOpNumThreads = Environment.ProcessorCount;
yoloOptions.IntraOpNumThreads = Environment.ProcessorCount;
_yoloSession = new InferenceSession(yoloModelPath, yoloOptions);
// 初始化颜值模型会话
var beautyOptions = new SessionOptions();
beautyOptions.InterOpNumThreads = 1; // 轻量化模型,单线程足够
beautyOptions.IntraOpNumThreads = 1;
_beautySession = new InferenceSession(beautyModelPath, beautyOptions);
}
/// <summary>
/// 执行颜值检测(人脸检测+颜值打分)
/// </summary>
public List<BeautyResult> Detect(Mat srcMat)
{
// 第一步:YOLO检测人脸
var faceResults = DetectFaces(srcMat);
var beautyResults = new List<BeautyResult>();
// 第二步:对每个人脸进行颜值打分
foreach (var face in faceResults)
{
// 裁剪人脸区域
using var faceMat = ImageProcessUtil.CropFace(srcMat, face.Bbox);
if (faceMat.Empty()) continue;
// 颜值模型推理
float beautyScore = PredictBeautyScore(faceMat);
// 构建结果
beautyResults.Add(new BeautyResult
{
Bbox = face.Bbox,
Confidence = face.Confidence,
BeautyScore = beautyScore
});
}
return beautyResults;
}
/// <summary>
/// YOLO人脸检测
/// </summary>
private List<(float[] Bbox, float Confidence)> DetectFaces(Mat srcMat)
{
// 1. 预处理
var inputTensor = ImageProcessUtil.YoloPreProcess(srcMat, out float[] ratio, out float[] pad);
// 2. 推理
var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("images", inputTensor) };
using var outputs = _yoloSession.Run(inputs);
var outputTensor = outputs.First().AsTensor<float>(); // YOLOv8-face输出:1×15×8400(15=4+1+10关键点,这里只用4+1)
// 3. 解析输出
var results = new List<(float[] Bbox, float Confidence)>();
var outputData = outputTensor.ToArray();
int numChannels = 15;
int numBoxes = 8400;
var bboxes = new List<float[]>();
var confidences = new List<float>();
for (int i = 0; i < numBoxes; i++)
{
// 提取bbox坐标
float x = outputData[i * numChannels + 0];
float y = outputData[i * numChannels + 1];
float w = outputData[i * numChannels + 2];
float h = outputData[i * numChannels + 3];
float x1 = x - w / 2;
float y1 = y - h / 2;
float x2 = x + w / 2;
float y2 = y + h / 2;
// 提取置信度
float conf = outputData[i * numChannels + 4];
if (conf < Config.YoloConfThres) continue;
// 还原坐标
float[] bbox = ImageProcessUtil.RescaleBbox(new[] { x1, y1, x2, y2 }, ratio, pad);
bboxes.Add(bbox);
confidences.Add(conf);
}
// NMS过滤重复框
var indices = Cv2.NMSBoxes(
bboxes.Select(b => new Rect2d(b[0], b[1], b[2] - b[0], b[3] - b[1])).ToList(),
confidences,
Config.YoloConfThres,
Config.YoloNmsThres
);
// 构建结果
foreach (var idx in indices)
{
results.Add((bboxes[idx], confidences[idx]));
}
return results;
}
/// <summary>
/// 颜值模型推理(打分)
/// </summary>
private float PredictBeautyScore(Mat faceMat)
{
// 1. 预处理
var inputTensor = ImageProcessUtil.BeautyPreProcess(faceMat);
// 2. 推理
var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input", inputTensor) }; // 输入名称需与模型一致
using var outputs = _beautySession.Run(inputs);
var outputTensor = outputs.First().AsTensor<float>();
// 3. 解析结果(输出为0~10的分数)
float score = outputTensor.First() * 10;
// 限制分数范围在0~10
score = Math.Max(0, Math.Min(10, score));
return (float)Math.Round(score, 1); // 保留1位小数
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_yoloSession?.Dispose();
_beautySession?.Dispose();
}
_disposed = true;
}
~DetectorEngine()
{
Dispose(false);
}
}
}
4. 界面逻辑(Form1.cs)
实现WinForm的交互逻辑,包括图片选择、检测、结果可视化:
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Drawing;
using System.Windows.Forms;
using BeautyScoreDetector.Core;
namespace BeautyScoreDetector
{
public partial class Form1 : Form
{
private DetectorEngine _detector;
private Mat _srcMat; // 存储原始图像
public Form1()
{
InitializeComponent();
// 初始化检测器(模型路径为bin/Debug下的文件)
_detector = new DetectorEngine("yolov8n-face.onnx", "beauty_score.onnx");
}
/// <summary>
/// 选择图片按钮点击事件
/// </summary>
private void btnSelectImage_Click(object sender, EventArgs e)
{
using var openFileDialog = new OpenFileDialog
{
Filter = "图像文件|*.jpg;*.jpeg;*.png;*.bmp",
Title = "选择颜值检测图片"
};
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// 读取图像
_srcMat = Cv2.ImRead(openFileDialog.FileName);
pbImage.Image = BitmapConverter.ToBitmap(_srcMat);
// 启用检测按钮
btnDetect.Enabled = true;
}
}
/// <summary>
/// 颜值检测按钮点击事件
/// </summary>
private void btnDetect_Click(object sender, EventArgs e)
{
if (_srcMat == null || _srcMat.Empty()) return;
// 执行检测
var results = _detector.Detect(_srcMat);
// 可视化结果
Mat resultMat = _srcMat.Clone();
foreach (var result in results)
{
// 绘制人脸框(粉色,线宽2)
var rect = new Rect((int)result.Bbox[0], (int)result.Bbox[1],
(int)(result.Bbox[2] - result.Bbox[0]), (int)(result.Bbox[3] - result.Bbox[1]));
Cv2.Rectangle(resultMat, rect, Scalar.Pink, 2);
// 绘制颜值标签(分数+等级)
string label = $"颜值:{result.BeautyScore}分 ({result.BeautyLevel})";
var labelSize = Cv2.GetTextSize(label, HersheyFonts.HersheySimplex, 0.6, 2, out int baseline);
// 绘制标签背景
Cv2.Rectangle(resultMat, new Rect(rect.X, rect.Y - labelSize.Height - baseline, labelSize.Width, labelSize.Height + baseline), Scalar.Pink, -1);
// 绘制标签文字
Cv2.PutText(resultMat, label, new Point(rect.X, rect.Y - baseline), HersheyFonts.HersheySimplex, 0.6, Scalar.White, 2);
}
// 显示结果
pbImage.Image = BitmapConverter.ToBitmap(resultMat);
// 提示检测完成
MessageBox.Show($"检测完成!共识别出{results.Count}个人脸", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
/// <summary>
/// 窗体关闭时释放资源
/// </summary>
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_detector?.Dispose();
_srcMat?.Release();
}
}
}
六、界面设计(0.5分钟,极简布局)
打开Form1.cs[设计],从工具箱拖放以下控件:
- PictureBox:命名为
pbImage,设置SizeMode = StretchImage,占满窗体上半部分; - Button:命名为
btnSelectImage,Text改为“选择图片”,放在PictureBox下方左侧; - Button:命名为
btnDetect,Text改为“颜值检测”,放在PictureBox下方右侧(初始禁用)。
七、运行测试(0.5分钟,大功告成)
- 点击Visual Studio的启动按钮,运行程序;
- 点击“选择图片”,选择一张包含人脸的图片(真人、证件照均可);
- 点击“颜值检测”,瞬间就能看到人脸框被标记,同时显示颜值分数和等级。
八、原理扩展:颜值模型的训练思路(可选)
如果想自己训练颜值评分模型,核心思路很简单:
- 数据集准备:收集人脸图片,标注颜值分数(0~10分,可通过众包标注);
- 模型选择:使用轻量化CNN(如MobileNet、ShuffleNet)或人脸特征模型(如ArcFace)作为骨干网络;
- 训练任务:将颜值打分作为回归任务(损失函数用MSE),或分类任务(将分数分为几个等级,损失函数用交叉熵);
- 模型导出:训练完成后,导出为ONNX格式,替换本文中的
beauty_score.onnx即可。
九、进阶优化(让上位机更实用)
- 摄像头实时检测:添加USB摄像头支持,实时采集帧并进行颜值检测;
- 颜值排行榜:批量处理图片,生成颜值分数排行榜;
- 结果保存:将检测后的图片保存到本地,或导出颜值分数为Excel;
- GPU加速:更换为ONNX Runtime GPU版本,提升推理速度;
- 人脸关键点优化:利用YOLOv8-face的关键点(如眼睛、鼻子)对齐人脸,提升颜值打分精度。
总结
其实颜值检测上位机的核心就是**“YOLO找脸 + 颜值模型打分”**,两者都是现成的预训练模型,无需深入理解深度学习细节,只需用C#将它们组合起来,就能实现看似复杂的功能。
这种“组合模型”的思路,也是工业界实现AI应用的常用方式——把复杂任务拆解为多个简单的AI子任务,复用现有模型,快速落地产品。
更多推荐
所有评论(0)