环境配置: vs2019 , .Net FrameWork 4.8 Opencvsharp4
在Nuget内下载最新的Opencvsharp4即可,
大概说一下我所理解的人脸识别的原理吧,就是先给训练器一些训练数据,就是我们告诉训练器这些数据分别对应的是哪些人,然后训练器就记住这些图像的特征以及对应的人名,然后在识别时找出识别图像的特征,再与已经训练好的特征进行比对,找出训练集中与之最相近的那个特征,并给出其对应的名字,和相似程度,也就是得分。
在Opencvsharp4内,有相应的模块,在OpenCvSharp.Face 里面,FaceRecognizer 就是我们需要的训练器,注意,添加训练集时,我们给的训练数据是,图像以及对应的ID数字,转换成名字时我们可以在添加训练集时把对应的名字自己保存下来,在给出训练结果时,用给出的ID数字去找出对应的名字即可;还有添加训练集时,同一张人脸的照片放在同一个目录下,也就是说一个名字可以对应多张不同的人脸,但一个人脸只能有一个名字;添加训练集和进行人脸识别时,图像大小要一样;请至少添加两组训练图像。
界面实现:
简单的Winform界面 ,控件都是自带即可
我把添加训练图像也放在界面上了,当然也可以直接在本地训练集里面进行修改,但是要注意图像的大小要保持一致
大概的运行结果
主要代码:
首先 ,定义一个训练器
public static FaceRecognizer faceRecongnizer = FisherFaceRecognizer.Create();
再定义一个自定义的类 即把图像与ID 绑定起来
class yImgs
{
// 图像
public Mat Image { set; get; }
// 编号
public int ImageGroupId { set; get; }
}
用一个List
有了这个List之后,我们再定义一个字典,来绑定编号和人的名字,这里就不考虑重名的。
public static Dictionary namesDatas = new Dictionary();
然后我们就可以添加训练图像了 我目前都是往本地添加图像,设置成想要的格式,然后在训练前,把所有的图像信息从本地读出来
///
/// 添加训练集图片 即把指定图片调整为指定大小 并保存在训练集路径中
/// 同一人的照片都放在一个文件夹内 该文件夹的名字为 名字ID_名字,如:1_Lena
/// 图片名字是按顺序自己生成的 1.jpg 2.jpg....
///
/// 训练图像
/// 图像大小
/// 编号,与名字一一对应
/// 名字
public static void AddTrainImg(Mat src, OpenCvSharp.Size size,int groupId,string name)
{
string path0 = groupId.ToString() + "_" + name;
string path = yVars.path + "\\" + path0 + "\\";
// 判断图像是否可以作为训练图像添加
// 即 图像包含人脸 且只有一张人脸
if (yMethods.TrainImgISOK(src) == false)
{
return;
}
try
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
catch (Exception ex)
{
YXH._01.yMessagebox.ShowDialogCN("路径创建失败:" + ex.Message);
return;
}
DirectoryInfo _path = new DirectoryInfo(path);
int i = 0;
do
{
i++;
} while (File.Exists(path + i.ToString() + ".jpg"));
string picname = path + i.ToString() + ".jpg";
try
{
Cv2.Resize(src, src, size);
src.SaveImage(picname);
yVars.TrainAgain = true;
YXH._01.yMessagebox.ShowDialogCN("训练图像添加成功");
}
catch (Exception ex)
{
YXH._01.yMessagebox.ShowDialogCN("训练图像添加失败:" + ex.Message);
}
}
添加在本地的格式 如下
训练集图像添加在本地后,就读取本地信息,并对训练器进行训练。就是给我们最开始定义的List
// 读取本地信息
private static bool GetInfos()
{
// 把原先的信息清空
yVars.faceDatas.Clear(); // 训练数据 List
yVars.namesDatas.Clear(); // 字典 Dictionary
DirectoryInfo _path = new DirectoryInfo(yVars.path); //训练集存放路径
if (_path.GetDirectories().Length < 2)
{
YXH._01.yMessagebox.ShowDialogCN("本地训练集小于两组,请添加训练集");
return false;
}
if (yFiles.DirectoryHasTwoGroup(yVars.path) == false)
{
YXH._01.yMessagebox.ShowDialogCN("本地训练集小于两组,请添加训练集");
return false;
}
foreach (DirectoryInfo var in _path.GetDirectories())
{
string[] tempstr = var.ToString().Split('_');
int groupID = 0;
int.TryParse(tempstr[0], out groupID);
foreach (FileInfo vv in var.GetFiles())
{
if (!vv.FullName.Contains(".jpg"))
continue;
yVars.faceDatas.Add(
new yImgs
{
Image = new Mat(vv.FullName, ImreadModes.Grayscale),
ImageGroupId = groupID,
});
}
yVars.namesDatas.Add(groupID, tempstr[1]);
}
return true;
}
训练的话就直接调用 训练器的训练方法即可
yVars.faceRecongnizer.Train(yVars.faceDatas.Select(x => x.Image), yVars.faceDatas.Select(x => x.ImageGroupId));
训练好之后就能进行识别了。
大概步骤如下:
1、从识别的图像中获取出所有的人脸图像,并将这些人脸图像调整为跟训练集一样的大小.(识别图像内可以有多张人脸,训练图像内只能有一张人脸)
// 从图片中获取所有的人脸图片 并调整为指定大小
private static List GetFaces(Mat mm, OpenCvSharp.Rect[] rects, OpenCvSharp.Size size)
{
List faces = new List();
foreach (Rect rect in rects)
{
Mat m1 = new Mat(mm, rect);
Cv2.CvtColor(m1, m1, ColorConversionCodes.BGR2GRAY);
Cv2.Resize(m1, m1, size);
// Cv2.EqualizeHist(m1, m1);
faces.Add(m1);
}
return faces;
}
2、得到上面所有的人脸的位置
// 获取图像所有的人脸框
private static OpenCvSharp.Rect[] GetRects(Mat mm)
{
Mat grayImage = new Mat();
Cv2.CvtColor(mm, grayImage, ColorConversionCodes.BGR2GRAY);
Cv2.EqualizeHist(grayImage, grayImage);
string path = System.Windows.Forms.Application.StartupPath + "\\xml\\haarcascades\\" + "haarcascade_frontalface_alt.xml";
CascadeClassifier face = new CascadeClassifier(path);
Rect[] faces = face.DetectMultiScale(mm);
return faces;
}
3、对获取出来的所有人脸照片进行识别,得到其对应的名字
// 获取所有名字
private static List GetNames(List mm)
{
List names = new List();
for (int i = 0; i < mm.Count; i++)
{
int groupId = -2;
//groupId = yVars.FaceDetect.faceRecongnizer.Predict(mm[i]);
double confidence = 0.0;
yVars.faceRecongnizer.Predict(mm[i], out groupId, out confidence);
string desName;
yVars.namesDatas.TryGetValue(groupId, out desName);
// names.Add(desName + " " + confidence.ToString("0.00"));
names.Add(desName);
}
return names;
}
4、最后把所有的名字在对应位置画出来就行
// 画出所有的框和名字
public static Mat ShowFaceRects(Mat mm, Rect[] faces, List names)
{
Random rnd = new Random();
int i = -1;
foreach (Rect face in faces)
{
i++;
Scalar color = new Scalar(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));
Cv2.Rectangle(mm, face, color);
// 无法显示中文 可以考虑用 System.Drawing.Graphics
Cv2.PutText(mm, names[i], face.TopLeft, HersheyFonts.HersheySimplex, 0.8, new Scalar(255, 23, 0));
}
return mm;
}
注意一下写名字的时候,如果用 Cv2.PutText 名字就不能是中文的
这样就实现了简单的人脸识别了。