C#离线人脸识别ArcSoft

人脸识别&比对发展到今天,已经是一个非常成熟的技术了,而且应用在生活的方方面面,比如手机、车站、天网等。

虹软人脸识别服务是免费的。最重要的是它还支持离线识别,并且提供Android、iOS、C++、C#版SDK,现在已经升级到全新的3.0版本,支持活体识别。包含人脸检测,人脸对比,人脸检索功能。.

过程如下:

1、 传入一张单人脸照片;

2、调用检测人脸函数ASFDetectFaces,成功返回人脸信息的指针;

3、使用 Marshal.ReadByte(intPtr,offset) 函数读出字节数,发现前16个字节是人脸框范围。

/// <summary>
/// 初始化引擎
/// </summary>
private void InitEngines()
{
//读取配置文件
AppSettingsReader reader = new AppSettingsReader();
string appId = (string)reader.GetValue("APP_ID", typeof(string));
string sdkKey64 = (string)reader.GetValue("SDKKEY64", typeof(string));
string sdkKey32 = (string)reader.GetValue("SDKKEY32", typeof(string));
rgbCameraIndex = (int)reader.GetValue("RGB_CAMERA_INDEX", typeof(int));
irCameraIndex = (int)reader.GetValue("IR_CAMERA_INDEX", typeof(int));
//判断CPU位数
var is64CPU = Environment.Is64BitProcess;
if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(is64CPU?sdkKey64: sdkKey32))
{
//禁用相关功能按钮
ControlsEnable(false, chooseMultiImgBtn, matchBtn, btnClearFaceList, chooseImgBtn);
MessageBox.Show(string.Format("请在App.config配置文件中先配置APP_ID和SDKKEY{0}!", is64CPU ? "64" : "32"));
return;
}

//在线激活引擎    如出现错误,1.请先确认从官网下载的sdk库已放到对应的bin中,2.当前选择的CPU为x86或者x64
int retCode = 0;
try
{
retCode = ASFFunctions.ASFActivation(appId, is64CPU ? sdkKey64 : sdkKey32);
}
catch (Exception ex)
{
//禁用相关功能按钮
ControlsEnable(false, chooseMultiImgBtn, matchBtn, btnClearFaceList, chooseImgBtn);
if (ex.Message.Contains("无法加载 DLL"))
{
MessageBox.Show("请将sdk相关DLL放入bin对应的x86或x64下的文件夹中!");
}
else
{
MessageBox.Show("激活引擎失败!");
}
return;
}
Console.WriteLine("Activate Result:" + retCode);

//初始化引擎
uint detectMode = DetectionMode.ASF_DETECT_MODE_IMAGE;
//Video模式下检测脸部的角度优先值
int videoDetectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_HIGHER_EXT;
//Image模式下检测脸部的角度优先值
int imageDetectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_ONLY;
//人脸在图片中所占比例,如果需要调整检测人脸尺寸请修改此值,有效数值为2-32
int detectFaceScaleVal = 16;
//最大需要检测的人脸个数
int detectFaceMaxNum = 5;
//引擎初始化时需要初始化的检测功能组合
int combinedMask = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION | FaceEngineMask.ASF_AGE | FaceEngineMask.ASF_GENDER | FaceEngineMask.ASF_FACE3DANGLE;
//初始化引擎,正常值为0,其他返回值请参考http://ai.arcsoft.com.cn/bbs/forum.php?mod=viewthread&tid=19&_dsign=dbad527e
retCode = ASFFunctions.ASFInitEngine(detectMode, imageDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref pImageEngine);
Console.WriteLine("InitEngine Result:" + retCode);
AppendText((retCode == 0) ? "引擎初始化成功!\n" : string.Format("引擎初始化失败!错误码为:{0}\n", retCode));
if (retCode != 0)
{
//禁用相关功能按钮
ControlsEnable(false, chooseMultiImgBtn, matchBtn, btnClearFaceList, chooseImgBtn);
}

//初始化视频模式下人脸检测引擎
uint detectModeVideo = DetectionMode.ASF_DETECT_MODE_VIDEO;
int combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION;
retCode = ASFFunctions.ASFInitEngine(detectModeVideo, videoDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pVideoEngine);
//RGB视频专用FR引擎
detectFaceMaxNum = 1;
combinedMask = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION | FaceEngineMask.ASF_LIVENESS;
retCode = ASFFunctions.ASFInitEngine(detectMode, imageDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref pVideoRGBImageEngine);

//IR视频专用FR引擎
combinedMask = FaceEngineMask.ASF_FACE_DETECT  | FaceEngineMask.ASF_FACERECOGNITION | FaceEngineMask.ASF_IR_LIVENESS;
retCode = ASFFunctions.ASFInitEngine(detectMode, imageDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref pVideoIRImageEngine);

Console.WriteLine("InitVideoEngine Result:" + retCode);


initVideo();
}

/// <summary>
/// 摄像头初始化
/// </summary>
private void initVideo()
{
filterInfoCollection = new FilterInfoCollection(FilterCategory.VideoInputDevice);
//如果没有可用摄像头,“启用摄像头”按钮禁用,否则使可用
if (filterInfoCollection.Count == 0)
{
btnStartVideo.Enabled = false;
}
else
{
btnStartVideo.Enabled = true;
}
}

#endregion

#region 注册人脸按钮事件

private object locker = new object();
/// <summary>
/// 人脸库图片选择按钮事件
/// </summary>
private void ChooseMultiImg(object sender, EventArgs e)
{
lock (locker)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Title = "选择图片";
openFileDialog.Filter = "图片文件|*.bmp;*.jpg;*.jpeg;*.png";
openFileDialog.Multiselect = true;
openFileDialog.FileName = string.Empty;
imageList.Refresh();
if (openFileDialog.ShowDialog() == DialogResult.OK)
{

List<string> imagePathListTemp = new List<string>();
var numStart = imagePathList.Count;
int isGoodImage = 0;

//保存图片路径并显示
string[] fileNames = openFileDialog.FileNames;
for (int i = 0; i < fileNames.Length; i++)
{
//图片格式判断
if (checkImage(fileNames[i])) { 
imagePathListTemp.Add(fileNames[i]);
}
}

//人脸检测以及提取人脸特征
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
{
//禁止点击按钮
Invoke(new Action(delegate
{
chooseMultiImgBtn.Enabled = false;
matchBtn.Enabled = false;
btnClearFaceList.Enabled = false;
chooseImgBtn.Enabled = false;
btnStartVideo.Enabled = false;
}));

//人脸检测和剪裁
for (int i = 0; i < imagePathListTemp.Count; i++)
{
Image image = ImageUtil.readFromFile(imagePathListTemp[i]);
if(image == null)
{
continue;
}
if (image.Width > 1536 || image.Height > 1536)
{
image = ImageUtil.ScaleImage(image, 1536, 1536);
}
if (image == null)
{
continue;
}
if (image.Width % 4 != 0)
{
image = ImageUtil.ScaleImage(image, image.Width - (image.Width % 4), image.Height);
}
//人脸检测
ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pImageEngine, image);
//判断检测结果
if (multiFaceInfo.faceNum > 0)
{
imagePathList.Add(imagePathListTemp[i]);
MRECT rect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects);
image = ImageUtil.CutImage(image, rect.left, rect.top, rect.right, rect.bottom);
}
else
{
if(image != null)
{
image.Dispose();
}
continue;
}
//显示人脸
this.Invoke(new Action(delegate
{
if (image == null)
{
image = ImageUtil.readFromFile(imagePathListTemp[i]);

if (image.Width > 1536 || image.Height > 1536)
{
image = ImageUtil.ScaleImage(image, 1536, 1536);
}
}
imageLists.Images.Add(imagePathListTemp[i], image);
imageList.Items.Add((numStart + isGoodImage) + "号", imagePathListTemp[i]);
imageList.Refresh();
isGoodImage += 1;
if (image != null)
{
image.Dispose();
}
}));
}

//提取人脸特征
for (int i = numStart; i < imagePathList.Count; i++)
{
ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo();
Image image = ImageUtil.readFromFile(imagePathList[i]);
if (image == null)
{
continue;
}
IntPtr feature = FaceUtil.ExtractFeature(pImageEngine, image, out singleFaceInfo);
this.Invoke(new Action(delegate
{
if (singleFaceInfo.faceRect.left == 0 && singleFaceInfo.faceRect.right == 0)
{
AppendText(string.Format("{0}号未检测到人脸\r\n", i));
}
else
{
AppendText(string.Format("已提取{0}号人脸特征值,[left:{1},right:{2},top:{3},bottom:{4},orient:{5}]\r\n", i, singleFaceInfo.faceRect.left, singleFaceInfo.faceRect.right, singleFaceInfo.faceRect.top, singleFaceInfo.faceRect.bottom, singleFaceInfo.faceOrient));
imagesFeatureList.Add(feature);
}
}));
if (image != null)
{
image.Dispose();
}
}
//允许点击按钮
Invoke(new Action(delegate
{
chooseMultiImgBtn.Enabled = true;
btnClearFaceList.Enabled = true;
btnStartVideo.Enabled = true;

if (btnStartVideo.Text == "启用摄像头")
{
chooseImgBtn.Enabled = true;
matchBtn.Enabled = true;
}
else
{
chooseImgBtn.Enabled = false;
matchBtn.Enabled = false;
}
}));
}));

}
}
}
#endregion

#region 清空人脸库按钮事件
/// <summary>
/// 清除人脸库事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnClearFaceList_Click(object sender, EventArgs e)
{
//清除数据
imageLists.Images.Clear();
imageList.Items.Clear();
foreach (IntPtr intptr in imagesFeatureList)
{
MemoryUtil.Free(intptr);
}
imagesFeatureList.Clear();
imagePathList.Clear();
}
#endregion

#region 选择识别图按钮事件
/// <summary>
/// “选择识别图片”按钮事件
/// </summary>
private void ChooseImg(object sender, EventArgs e)
{
lblCompareInfo.Text = "";
//判断引擎是否初始化成功
if (pImageEngine == IntPtr.Zero)
{
//禁用相关功能按钮
ControlsEnable(false, chooseMultiImgBtn, matchBtn, btnClearFaceList, chooseImgBtn);
MessageBox.Show("请先初始化引擎!");
return;
}
//选择图片
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Title = "选择图片";
openFileDialog.Filter = "图片文件|*.bmp;*.jpg;*.jpeg;*.png";
openFileDialog.Multiselect = false;
openFileDialog.FileName = string.Empty;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{

image1Path = openFileDialog.FileName;
//检测图片格式
if (!checkImage(image1Path))
{
return;
}
DateTime detectStartTime = DateTime.Now;
AppendText(string.Format("------------------------------开始检测,时间:{0}------------------------------\n", detectStartTime.ToString("yyyy-MM-dd HH:mm:ss:ms")));

//获取文件,拒绝过大的图片
FileInfo fileInfo = new FileInfo(image1Path);
if (fileInfo.Length > maxSize)
{
MessageBox.Show("图像文件最大为2MB,请压缩后再导入!");
AppendText(string.Format("------------------------------检测结束,时间:{0}------------------------------\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
AppendText("\n");
return;
}

Image srcImage = ImageUtil.readFromFile(image1Path);
if (srcImage == null)
{
MessageBox.Show("图像数据获取失败,请稍后重试!");
AppendText(string.Format("------------------------------检测结束,时间:{0}------------------------------\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
AppendText("\n");
return;
}
if (srcImage.Width > 1536 || srcImage.Height > 1536)
{
srcImage = ImageUtil.ScaleImage(srcImage, 1536, 1536);
}
if (srcImage == null)
{
MessageBox.Show("图像数据获取失败,请稍后重试!");
AppendText(string.Format("------------------------------检测结束,时间:{0}------------------------------\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
AppendText("\n");
return;
}
//调整图像宽度,需要宽度为4的倍数
if (srcImage.Width % 4 != 0)
{
srcImage = ImageUtil.ScaleImage(srcImage, srcImage.Width - (srcImage.Width % 4), srcImage.Height);
}
//调整图片数据,非常重要
ImageInfo imageInfo = ImageUtil.ReadBMP(srcImage);
if (imageInfo == null)
{
MessageBox.Show("图像数据获取失败,请稍后重试!");
AppendText(string.Format("------------------------------检测结束,时间:{0}------------------------------\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
AppendText("\n");
return;
}
//人脸检测
ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pImageEngine, imageInfo);
//年龄检测
int retCode_Age = -1;
ASF_AgeInfo ageInfo = FaceUtil.AgeEstimation(pImageEngine, imageInfo, multiFaceInfo, out retCode_Age);
//性别检测
int retCode_Gender = -1;
ASF_GenderInfo genderInfo = FaceUtil.GenderEstimation(pImageEngine, imageInfo, multiFaceInfo, out retCode_Gender);

//3DAngle检测
int retCode_3DAngle = -1;
ASF_Face3DAngle face3DAngleInfo = FaceUtil.Face3DAngleDetection(pImageEngine, imageInfo, multiFaceInfo, out retCode_3DAngle);

MemoryUtil.Free(imageInfo.imgData);

if (multiFaceInfo.faceNum < 1)
{
srcImage = ImageUtil.ScaleImage(srcImage, picImageCompare.Width, picImageCompare.Height);
image1Feature = IntPtr.Zero;
picImageCompare.Image = srcImage;
AppendText(string.Format("{0} - 未检测出人脸!\n\n", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
AppendText(string.Format("------------------------------检测结束,时间:{0}------------------------------\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
AppendText("\n");
return;
}

MRECT temp = new MRECT();
int ageTemp = 0;
int genderTemp = 0;
int rectTemp = 0;

//标记出检测到的人脸
for (int i = 0; i < multiFaceInfo.faceNum; i++)
{
MRECT rect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects + MemoryUtil.SizeOf<MRECT>() * i);
int orient = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceOrients + MemoryUtil.SizeOf<int>() * i);
int age = 0;

if (retCode_Age != 0)
{
AppendText(string.Format("年龄检测失败,返回{0}!\n\n", retCode_Age));
}
else
{
age = MemoryUtil.PtrToStructure<int>(ageInfo.ageArray + MemoryUtil.SizeOf<int>() * i);
}

int gender = -1;
if (retCode_Gender != 0)
{
AppendText(string.Format("性别检测失败,返回{0}!\n\n", retCode_Gender));
}
else
{
gender = MemoryUtil.PtrToStructure<int>(genderInfo.genderArray + MemoryUtil.SizeOf<int>() * i);
}

int face3DStatus = -1;
float roll = 0f;
float pitch = 0f;
float yaw = 0f;
if (retCode_3DAngle != 0)
{
AppendText(string.Format("3DAngle检测失败,返回{0}!\n\n", retCode_3DAngle));
}
else
{
//角度状态 非0表示人脸不可信
face3DStatus = MemoryUtil.PtrToStructure<int>(face3DAngleInfo.status + MemoryUtil.SizeOf<int>() * i);
//roll为侧倾角,pitch为俯仰角,yaw为偏航角
roll = MemoryUtil.PtrToStructure<float>(face3DAngleInfo.roll + MemoryUtil.SizeOf<float>() * i);
pitch = MemoryUtil.PtrToStructure<float>(face3DAngleInfo.pitch + MemoryUtil.SizeOf<float>() * i);
yaw = MemoryUtil.PtrToStructure<float>(face3DAngleInfo.yaw + MemoryUtil.SizeOf<float>() * i);
}

int rectWidth = rect.right - rect.left;
int rectHeight = rect.bottom - rect.top;

//查找最大人脸
if (rectWidth * rectHeight > rectTemp)
{
rectTemp = rectWidth * rectHeight;
temp = rect;
ageTemp = age;
genderTemp = gender;
}
AppendText(string.Format("{0} - 人脸坐标:[left:{1},top:{2},right:{3},bottom:{4},orient:{5},roll:{6},pitch:{7},yaw:{8},status:{11}] Age:{9} Gender:{10}\n", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), rect.left, rect.top, rect.right, rect.bottom, orient, roll, pitch, yaw, age, (gender >= 0 ? gender.ToString() : ""), face3DStatus));
}

AppendText(string.Format("{0} - 人脸数量:{1}\n\n", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), multiFaceInfo.faceNum));

DateTime detectEndTime = DateTime.Now;
AppendText(string.Format("------------------------------检测结束,时间:{0}------------------------------\n", detectEndTime.ToString("yyyy-MM-dd HH:mm:ss:ms")));
AppendText("\n");
ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo();
//提取人脸特征
image1Feature = FaceUtil.ExtractFeature(pImageEngine, srcImage, out singleFaceInfo);

//清空上次的匹配结果
for (int i = 0; i < imagesFeatureList.Count; i++)
{
imageList.Items[i].Text = string.Format("{0}号", i);
}
//获取缩放比例
float scaleRate = ImageUtil.getWidthAndHeight(srcImage.Width, srcImage.Height, picImageCompare.Width, picImageCompare.Height);
//缩放图片
srcImage = ImageUtil.ScaleImage(srcImage, picImageCompare.Width, picImageCompare.Height);
//添加标记
srcImage = ImageUtil.MarkRectAndString(srcImage, (int)(temp.left * scaleRate), (int)(temp.top * scaleRate), (int)(temp.right * scaleRate) - (int)(temp.left * scaleRate), (int)(temp.bottom * scaleRate) - (int)(temp.top * scaleRate), ageTemp, genderTemp, picImageCompare.Width);

//显示标记后的图像
picImageCompare.Image = srcImage;
}
}
#endregion

#region 开始匹配按钮事件
/// <summary>
/// 匹配事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void matchBtn_Click(object sender, EventArgs e)
{
if (imagesFeatureList.Count == 0)
{
MessageBox.Show("请注册人脸!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

if (image1Feature == IntPtr.Zero)
{
if (picImageCompare.Image == null)
{
MessageBox.Show("请选择识别图!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
MessageBox.Show("比对失败,识别图未提取到特征值!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return;
}
//标记已经做了匹配比对,在开启视频的时候要清除比对结果
isCompare = true;
float compareSimilarity = 0f;
int compareNum = 0;
AppendText(string.Format("------------------------------开始比对,时间:{0}------------------------------\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
for (int i = 0; i < imagesFeatureList.Count; i++)
{
IntPtr feature = imagesFeatureList[i];
float similarity = 0f;
int ret = ASFFunctions.ASFFaceFeatureCompare(pImageEngine, image1Feature, feature, ref similarity);
//增加异常值处理
if(similarity.ToString().IndexOf("E") > -1)
{
similarity = 0f;
}
AppendText(string.Format("与{0}号比对结果:{1}\r\n", i, similarity));
imageList.Items[i].Text = string.Format("{0}号({1})", i, similarity);
if (similarity > compareSimilarity)
{
compareSimilarity = similarity;
compareNum = i;
}
}
if (compareSimilarity > 0)
{
lblCompareInfo.Text = " " + compareNum + "号," + compareSimilarity;
}
AppendText(string.Format("------------------------------比对结束,时间:{0}------------------------------\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ms")));
}
#endregion

#region 视频检测相关(<摄像头按钮点击事件、摄像头Paint事件、特征比对、摄像头播放完成事件>)

/// <summary>
/// 摄像头按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStartVideo_Click(object sender, EventArgs e)
{
//在点击开始的时候再坐下初始化检测,防止程序启动时有摄像头,在点击摄像头按钮之前将摄像头拔掉的情况
initVideo();
//必须保证有可用摄像头
if (filterInfoCollection.Count == 0)
{
MessageBox.Show("未检测到摄像头,请确保已安装摄像头或驱动!");
return;
}
if (rgbVideoSource.IsRunning || irVideoSource.IsRunning)
{
btnStartVideo.Text = "启用摄像头";
//关闭摄像头
if (irVideoSource.IsRunning)
{
irVideoSource.SignalToStop();
irVideoSource.Hide();
}
if (rgbVideoSource.IsRunning)
{
rgbVideoSource.SignalToStop();
rgbVideoSource.Hide();
}
//“选择识别图”、“开始匹配”按钮可用,阈值控件禁用
chooseImgBtn.Enabled = true;
matchBtn.Enabled = true;
txtThreshold.Enabled = false;
}
else
{
if (isCompare)
{
//比对结果清除
for (int i = 0; i < imagesFeatureList.Count; i++)
{
imageList.Items[i].Text = string.Format("{0}号", i);
}
lblCompareInfo.Text = "";
isCompare = false;
}
//“选择识别图”、“开始匹配”按钮禁用,阈值控件可用,显示摄像头控件
txtThreshold.Enabled = true;
rgbVideoSource.Show();
irVideoSource.Show();
chooseImgBtn.Enabled = false;
matchBtn.Enabled = false;
btnStartVideo.Text = "关闭摄像头";
//获取filterInfoCollection的总数
int maxCameraCount = filterInfoCollection.Count;
//如果配置了两个不同的摄像头索引
if(rgbCameraIndex != irCameraIndex && maxCameraCount>=2)
{
//RGB摄像头加载
rgbDeviceVideo = new VideoCaptureDevice(filterInfoCollection[rgbCameraIndex < maxCameraCount ? rgbCameraIndex : 0].MonikerString);
rgbDeviceVideo.VideoResolution = rgbDeviceVideo.VideoCapabilities[0];
rgbVideoSource.VideoSource = rgbDeviceVideo;
rgbVideoSource.Start();

//IR摄像头
irDeviceVideo = new VideoCaptureDevice(filterInfoCollection[irCameraIndex< maxCameraCount? irCameraIndex:0].MonikerString);
irDeviceVideo.VideoResolution = irDeviceVideo.VideoCapabilities[0];
irVideoSource.VideoSource = irDeviceVideo;
irVideoSource.Start();
//双摄标志设为true
isDoubleShot = true;
}
else
{
//仅打开RGB摄像头,IR摄像头控件隐藏
rgbDeviceVideo = new VideoCaptureDevice(filterInfoCollection[rgbCameraIndex <= maxCameraCount ? rgbCameraIndex : 0].MonikerString);
rgbDeviceVideo.VideoResolution = rgbDeviceVideo.VideoCapabilities[0];
rgbVideoSource.VideoSource = rgbDeviceVideo;
rgbVideoSource.Start();
irVideoSource.Hide();
}
}
}

private FaceTrackUnit trackRGBUnit = new FaceTrackUnit();
private FaceTrackUnit trackIRUnit = new FaceTrackUnit();
private Font font = new Font(FontFamily.GenericSerif, 10f, FontStyle.Bold);
private SolidBrush yellowBrush = new SolidBrush(Color.Yellow);
private SolidBrush blueBrush = new SolidBrush(Color.Blue);
private bool isRGBLock = false;
private bool isIRLock = false;
private MRECT allRect = new MRECT();
private object rectLock = new object();

/// <summary>
/// RGB摄像头Paint事件,图像显示到窗体上,得到每一帧图像,并进行处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void videoSource_Paint(object sender, PaintEventArgs e)
{
if (rgbVideoSource.IsRunning)
{
//得到当前RGB摄像头下的图片
Bitmap bitmap = rgbVideoSource.GetCurrentVideoFrame();
if (bitmap == null)
{
return;
}
//检测人脸,得到Rect框
ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pVideoEngine, bitmap);
//得到最大人脸
ASF_SingleFaceInfo maxFace = FaceUtil.GetMaxFace(multiFaceInfo);
//得到Rect
MRECT rect = maxFace.faceRect;
//检测RGB摄像头下最大人脸
Graphics g = e.Graphics;
float offsetX = rgbVideoSource.Width * 1f / bitmap.Width;
float offsetY = rgbVideoSource.Height * 1f / bitmap.Height;
float x = rect.left * offsetX;
float width = rect.right * offsetX - x;
float y = rect.top * offsetY;
float height = rect.bottom * offsetY - y;
//根据Rect进行画框
g.DrawRectangle(Pens.Red, x, y, width, height);
if (trackRGBUnit.message != "" && x > 0 && y > 0)
{
//将上一帧检测结果显示到页面上
g.DrawString(trackRGBUnit.message, font, trackRGBUnit.message.Contains("活体") ? blueBrush : yellowBrush, x, y - 15);
}

//保证只检测一帧,防止页面卡顿以及出现其他内存被占用情况
if (isRGBLock == false)
{
isRGBLock = true;
//异步处理提取特征值和比对,不然页面会比较卡
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
{
if (rect.left != 0 && rect.right != 0 && rect.top != 0 && rect.bottom != 0)
{
try
{
lock (rectLock)
{
allRect.left = (int)(rect.left * offsetX);
allRect.top = (int)(rect.top * offsetY);
allRect.right = (int)(rect.right * offsetX);
allRect.bottom = (int)(rect.bottom * offsetY);
}

bool isLiveness = false;

//调整图片数据,非常重要
ImageInfo imageInfo = ImageUtil.ReadBMP(bitmap);
if (imageInfo == null)
{
return;
}
int retCode_Liveness = -1;
//RGB活体检测
ASF_LivenessInfo liveInfo = FaceUtil.LivenessInfo_RGB(pVideoRGBImageEngine, imageInfo, multiFaceInfo, out retCode_Liveness);
//判断检测结果
if (retCode_Liveness == 0 && liveInfo.num > 0)
{
int isLive = MemoryUtil.PtrToStructure<int>(liveInfo.isLive);
isLiveness = (isLive == 1) ? true : false;
}
if (imageInfo != null)
{
MemoryUtil.Free(imageInfo.imgData);
}
if (isLiveness)
{
//提取人脸特征
IntPtr feature = FaceUtil.ExtractFeature(pVideoRGBImageEngine, bitmap, maxFace);
float similarity = 0f;
//得到比对结果
int result = compareFeature(feature, out similarity);
MemoryUtil.Free(feature);
if (result > -1)
{
//将比对结果放到显示消息中,用于最新显示
trackRGBUnit.message = string.Format(" {0}号 {1},{2}", result, similarity, string.Format("RGB{0}", isLiveness ? "活体" : "假体"));
}
else
{
//显示消息
trackRGBUnit.message = string.Format("RGB{0}", isLiveness ? "活体" : "假体");
}
}
else
{
//显示消息
trackRGBUnit.message = string.Format("RGB{0}", isLiveness ? "活体" : "假体");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if(bitmap != null)
{
bitmap.Dispose();
}
isRGBLock = false;
}
}
else
{
lock (rectLock)
{
allRect.left = 0;
allRect.top = 0;
allRect.right = 0;
allRect.bottom = 0;
}
}
isRGBLock = false;
}));
}
}
}

/// <summary>
/// RGB摄像头Paint事件,同步RGB人脸框,对比人脸框后进行IR活体检测
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void irVideoSource_Paint(object sender, PaintEventArgs e)
{
if (isDoubleShot && irVideoSource.IsRunning)
{
//如果双摄,且IR摄像头工作,获取IR摄像头图片
Bitmap irBitmap = irVideoSource.GetCurrentVideoFrame();
if (irBitmap == null)
{
return;
}
//得到Rect
MRECT rect = new MRECT();
lock (rectLock)
{
rect = allRect;
}
float irOffsetX = irVideoSource.Width * 1f / irBitmap.Width;
float irOffsetY = irVideoSource.Height * 1f / irBitmap.Height;
float offsetX = irVideoSource.Width * 1f / rgbVideoSource.Width;
float offsetY = irVideoSource.Height * 1f / rgbVideoSource.Height;
//检测IR摄像头下最大人脸
Graphics g = e.Graphics;

float x = rect.left * offsetX;
float width = rect.right * offsetX - x;
float y = rect.top * offsetY;
float height = rect.bottom * offsetY - y;
//根据Rect进行画框
g.DrawRectangle(Pens.Red, x, y, width, height);
if (trackIRUnit.message != "" && x > 0 && y > 0)
{
//将上一帧检测结果显示到页面上
g.DrawString(trackIRUnit.message, font, trackIRUnit.message.Contains("活体") ? blueBrush : yellowBrush, x, y - 15);
}

//保证只检测一帧,防止页面卡顿以及出现其他内存被占用情况
if (isIRLock == false)
{
isIRLock = true;
//异步处理提取特征值和比对,不然页面会比较卡
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
{
if (rect.left != 0 && rect.right != 0 && rect.top != 0 && rect.bottom != 0)
{
bool isLiveness = false;
try
{
//得到当前摄像头下的图片
if (irBitmap != null)
{
//检测人脸,得到Rect框
ASF_MultiFaceInfo irMultiFaceInfo = FaceUtil.DetectFace(pVideoIRImageEngine, irBitmap);
if (irMultiFaceInfo.faceNum <= 0)
{
return;
}
//得到最大人脸
ASF_SingleFaceInfo irMaxFace = FaceUtil.GetMaxFace(irMultiFaceInfo);
//得到Rect
MRECT irRect = irMaxFace.faceRect;
//判断RGB图片检测的人脸框与IR摄像头检测的人脸框偏移量是否在误差允许范围内
if (isInAllowErrorRange(rect.left * offsetX / irOffsetX, irRect.left) && isInAllowErrorRange(rect.right * offsetX / irOffsetX, irRect.right)
&& isInAllowErrorRange(rect.top * offsetY / irOffsetY, irRect.top) && isInAllowErrorRange(rect.bottom * offsetY / irOffsetY, irRect.bottom))
{
int retCode_Liveness = -1;
//将图片进行灰度转换,然后获取图片数据
ImageInfo irImageInfo = ImageUtil.ReadBMP_IR(irBitmap);
if (irImageInfo == null)
{
return;
}
//IR活体检测
ASF_LivenessInfo liveInfo = FaceUtil.LivenessInfo_IR(pVideoIRImageEngine, irImageInfo, irMultiFaceInfo, out retCode_Liveness);
//判断检测结果
if (retCode_Liveness == 0 && liveInfo.num > 0)
{
int isLive = MemoryUtil.PtrToStructure<int>(liveInfo.isLive);
isLiveness = (isLive == 1) ? true : false;
}
if (irImageInfo != null)
{
MemoryUtil.Free(irImageInfo.imgData);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
trackIRUnit.message = string.Format("IR{0}", isLiveness ? "活体" : "假体");
if (irBitmap != null)
{
irBitmap.Dispose();
}
isIRLock = false;
}
}
else
{
trackIRUnit.message = string.Empty;
}
isIRLock = false;
}));
}
}
}


/// <summary>
/// 得到feature比较结果
/// </summary>
/// <param name="feature"></param>
/// <returns></returns>
private int compareFeature(IntPtr feature, out float similarity)
{
int result = -1;
similarity = 0f;
//如果人脸库不为空,则进行人脸匹配
if (imagesFeatureList != null && imagesFeatureList.Count > 0)
{
for (int i = 0; i < imagesFeatureList.Count; i++)
{
//调用人脸匹配方法,进行匹配
ASFFunctions.ASFFaceFeatureCompare(pVideoRGBImageEngine, feature, imagesFeatureList[i], ref similarity);
if (similarity >= threshold)
{
result = i;
break;
}
}
}
return result;
}        

/// <summary>
/// 摄像头播放完成事件
/// </summary>
/// <param name="sender"></param>
/// <param name="reason"></param>
private void videoSource_PlayingFinished(object sender, AForge.Video.ReasonToFinishPlaying reason)
{
try
{
Control.CheckForIllegalCrossThreadCalls = false;
chooseImgBtn.Enabled = true;
matchBtn.Enabled = true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

#endregion

#region 阈值相关
/// <summary>
/// 阈值文本框键按下事件,检测输入内容是否正确,不正确不能输入
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtThreshold_KeyPress(object sender, KeyPressEventArgs e)
{
//阻止从键盘输入键
e.Handled = true;
//是数值键,回退键,.能输入,其他不能输入
if (char.IsDigit(e.KeyChar) || e.KeyChar == 8 || e.KeyChar == '.')
{
//渠道当前文本框的内容
string thresholdStr = txtThreshold.Text.Trim();
int countStr = 0;
int startIndex = 0;
//如果当前输入的内容是否是“.”
if (e.KeyChar == '.')
{
countStr = 1;
}
//检测当前内容是否含有.的个数
if (thresholdStr.IndexOf('.', startIndex) > -1)
{
countStr += 1;
}
//如果输入的内容已经超过12个字符,
if (e.KeyChar != 8 && (thresholdStr.Length > 12 || countStr > 1))
{
return;
}
e.Handled = false;
}
}

/// <summary>
/// 阈值文本框键抬起事件,检测阈值是否正确,不正确改为0.8f
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtThreshold_KeyUp(object sender, KeyEventArgs e)
{
//如果输入的内容不正确改为默认值
if (!float.TryParse(txtThreshold.Text.Trim(), out threshold))
{
threshold = 0.8f;
}
}
#endregion

#region 窗体关闭
/// <summary>
/// 窗体关闭事件
/// </summary>
private void Form_Closed(object sender, FormClosedEventArgs e)
{
try
{
if (rgbVideoSource.IsRunning)
{
btnStartVideo_Click(sender, e); //关闭摄像头
}

//销毁引擎
int retCode = ASFFunctions.ASFUninitEngine(pImageEngine);
Console.WriteLine("UninitEngine pImageEngine Result:" + retCode);
//销毁引擎
retCode = ASFFunctions.ASFUninitEngine(pVideoEngine);
Console.WriteLine("UninitEngine pVideoEngine Result:" + retCode);

//销毁引擎
retCode = ASFFunctions.ASFUninitEngine(pVideoRGBImageEngine);
Console.WriteLine("UninitEngine pVideoImageEngine Result:" + retCode);

//销毁引擎
retCode = ASFFunctions.ASFUninitEngine(pVideoIRImageEngine);
Console.WriteLine("UninitEngine pVideoIRImageEngine Result:" + retCode);
}
catch (Exception ex)
{
Console.WriteLine("UninitEngine pImageEngine Error:" + ex.Message);
}
}
#endregion

#region 公用方法
/// <summary>
/// 恢复使用/禁用控件列表控件
/// </summary>
/// <param name="isEnable"></param>
/// <param name="controls">控件列表</param>
private void ControlsEnable(bool isEnable,params Control[] controls)
{
if(controls == null || controls.Length <= 0)
{
return;
}
foreach(Control control in controls)
{
control.Enabled = isEnable;
}
}

/// <summary>
/// 校验图片
/// </summary>
/// <param name="imagePath"></param>
/// <returns></returns>
private bool checkImage(string imagePath)
{
if (imagePath == null)
{
AppendText("图片不存在,请确认后再导入\r\n");
return false;
}
try
{
//判断图片是否正常,如将其他文件把后缀改为.jpg,这样就会报错
Image image = ImageUtil.readFromFile(imagePath);
if(image == null)
{
throw new Exception();
}else
{
image.Dispose();
}
}
catch
{
AppendText(string.Format("{0} 图片格式有问题,请确认后再导入\r\n", imagePath));
return false;
}
FileInfo fileCheck = new FileInfo(imagePath);
if (fileCheck.Exists == false)
{
AppendText(string.Format("{0} 不存在\r\n", fileCheck.Name));
return false;
}
else if (fileCheck.Length > maxSize)
{
AppendText(string.Format("{0} 图片大小超过2M,请压缩后再导入\r\n", fileCheck.Name));
return false;
}
else if (fileCheck.Length < 2)
{
AppendText(string.Format("{0} 图像质量太小,请重新选择\r\n", fileCheck.Name));
return false;
}
return true;
}

/// <summary>
/// 追加公用方法
/// </summary>
/// <param name="message"></param>
private void AppendText(string message)
{
logBox.AppendText(message);
}

/// <summary>
/// 判断参数0与参数1是否在误差允许范围内
/// </summary>
/// <param name="arg0">参数0</param>
/// <param name="arg1">参数1</param>
/// <returns></returns>
private bool isInAllowErrorRange(float arg0, float arg1)
{
bool rel = false;
if (arg0 > arg1 - allowAbleErrorRange && arg0 < arg1 + allowAbleErrorRange)
{
rel = true;
}
return rel;
}
#endregion
}