C# 人脸识别ViewFaceCore使用的经验分享

共 50423字,需浏览 101分钟

 ·

2024-04-10 22:30

前言

POS软件是什么?你好意思吗,还在用老掉牙的Winform。

22a87384627c2501f5c9802819b10322.webp 门店被淘汰的POS机

销售终端——POS(point of sale)是一种多功能终端,把它安装在信用卡的特约商户和受理网点中与计算机联成网络,就能实现电子资金自动转账,它具有支持消费、预授权、余额查询和转账等功能,使用起来安全、快捷、可靠。

8071496d4d9ed85b0052a639e10261a4.webp

万事俱备只欠东风------一个USB摄像头和一个经过改造的人脸识别程序。

bfc05e5ef58ab3eb5f5fd3b1b48b8de7.webp

地址

ViewFaceCore/ViewFaceCore: C# 超简单的离线人脸识别库。

https://github.com/ViewFaceCore/ViewFaceCore

正文

开始干活,动手改造。

1、程序要支持无人值守,程序启动时自动打开摄像头。超过设定的时间无移动鼠标和敲击键盘,程序自动关闭摄像头,进入“休眠”

2、识别人脸成功后记录当前时间作为考勤记录

3、人脸信息放在服务器端,桌面程序和服务器端同步人脸信息

4、关于不排班实现考勤的思考

5、取消消息弹窗来和用户交互。使用能自动关闭的消息弹窗

1、检测超过设定的时间无移动鼠标和敲击键盘,判断是否无人使用。

      #region 获取键盘和鼠标没有操作的时间 
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
    [MarshalAs(UnmanagedType.U4)]
    public int cbSize;
    [MarshalAs(UnmanagedType.U4)]
    public uint dwTime;
}
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
/// <summary>
/// 获取键盘和鼠标没有操作的时间
/// </summary>
/// <returns></returns>
private static long GetLastInputTime()
{
    LASTINPUTINFO vLastInputInfo = new LASTINPUTINFO();
    vLastInputInfo.cbSize = Marshal.SizeOf(vLastInputInfo);
    if (!GetLastInputInfo(ref vLastInputInfo))
        return 0;
    else
        return Environment.TickCount - (long)vLastInputInfo.dwTime;//单位ms  
}
#endregion

2、把人脸识别这个用途改成考勤

      /// <summary>
/// 窗体加载时
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form_Load(object sender, EventArgs e)
{
    #region 窗体初始化
    WindowState = FormWindowState.Maximized;
    // 隐藏摄像头画面控件
    VideoPlayer.Visible = false;
    //初始化VideoDevices
    检测摄像头ToolStripMenuItem_Click(nullnull);
    //默认禁用拍照按钮
    FormHelper.SetControlStatus(this.ButtonSave, false);
    Text = "WPOS人脸识别&考勤";
    #endregion
    #region TTS
    try
    {
        VoiceUtilHelper = new SpVoiceUtil();
        StartVoiceTaskJob();
    }
    catch (Exception ex)
    {
        byte[] zipfile = (byte[])Properties.Resources.ResourceManager.GetObject("TTSrepair");
        System.IO.File.WriteAllBytes("TTSrepair.zip", zipfile);
        Program.UnZip("TTSrepair.zip"""""true);
        #region 语音引擎修复安装
        try
        {
            MessageBox.Show("初始化语音引擎出错,错误描述:" + ex.Message + Environment.NewLine +
                        "正在运行语音引擎安装程序,请点下一步执行安装!", Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            string physicalRoot = AppDomain.CurrentDomain.BaseDirectory;
            string info1 = Program.Execute("TTSrepair.exe"3);
        }
        finally
        {
            System.IO.File.Delete("TTSrepair.zip");
            Application.Restart();
        }
        #endregion
    }
    #endregion
    #region 自动打开摄像头
    Thread thread = new Thread(() =>
    {
        Thread.Sleep(5000);
        sc.Post(SystemInit, this);
    });
    thread.Start();
    #endregion
    #region Sync face data
    Thread SyncThread = new Thread(() =>
    {
        while (IsWorkEnd == false)
        {
            var theEmployeeList = SyncServerEmployeeInfomation().Where(r => r.EmpFacialFeature != null).ToList();
            if (theEmployeeList != null && theEmployeeList.Count > 0)
            {
                foreach (var emp in theEmployeeList)
                {
                    poolExt.Post(emp);
                }
            }
            Thread.Sleep(5000);
        }
    });
    SyncThread.Start();
    #endregion
    #region 自动关闭摄像头线程
    Thread CameraCheckThread = new Thread(() =>
    {
        while (IsWorkEnd == false)
        {
            if (IsNeedAutoCheck)
            {
                long Auto_close_camera_interval = long.Parse(string.IsNullOrEmpty(config.AppSettings.Settings["Auto_close_camera_interval"].Value) ? "60000" : config.AppSettings.Settings["Auto_close_camera_interval"].Value);
                long ts = GetLastInputTime();
                if (ts > Auto_close_camera_interval)
                {
                    IsNeedAutoCheck = false;
                    sc.Post(CheckCameraStatus, this);
                }
            }
            Thread.Sleep(1000);
        }
    });
    CameraCheckThread.Start();
    btnSleep.Enabled = true;
    btnStopSleep.Enabled = true;
    #endregion
}

修改识别人脸后做的事情:

      /// <summary>
/// 持续检测一次人脸,直到停止。
/// </summary>
/// <param name="token">取消标记</param>
private async void StartDetector(CancellationToken token)
{
    List<double> fpsList = new List<double>();
    double fps = 0;
    Stopwatch stopwatchFPS = new Stopwatch();
    Stopwatch stopwatch = new Stopwatch();
    isDetecting = true;
    try
    {
        if (VideoPlayer == null)
        {
            return;
        }
        while (VideoPlayer.IsRunning && !token.IsCancellationRequested)
        {
            try
            {
                if (CheckBoxFPS.Checked)
                {
                    stopwatch.Restart();
                    if (!stopwatchFPS.IsRunning)
                    { stopwatchFPS.Start(); }
                }
                Bitmap bitmap = VideoPlayer.GetCurrentVideoFrame(); // 获取摄像头画面 
                if (bitmap == null)
                {
                    await Task.Delay(10, token);
                    FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
                    continue;
                }
                if (!CheckBoxDetect.Checked)
                {
                    await Task.Delay(1000 / 60, token);
                    FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
                    continue;
                }
                List<Models.FaceInfo> faceInfos = new List<Models.FaceInfo>();
                using (FaceImage faceImage = bitmap.ToFaceImage())
                {
                    var infos = await faceFactory.Get<FaceTracker>().TrackAsync(faceImage);
                    for (int i = 0; i < infos.Length; i++)
                    {
                        Models.FaceInfo faceInfo = new Models.FaceInfo
                        {
                            Pid = infos[i].Pid,
                            Location = infos[i].Location
                        };
                        if (CheckBoxFaceMask.Checked || CheckBoxFaceProperty.Checked)
                        {
                            Model.FaceInfo info = infos[i].ToFaceInfo();
                            if (CheckBoxFaceMask.Checked)
                            {
                                var maskStatus = await faceFactory.Get<MaskDetector>().PlotMaskAsync(faceImage, info);
                                faceInfo.HasMask = maskStatus.Masked;
                            }
                            if (CheckBoxFaceProperty.Checked)
                            {
                                FaceRecognizer faceRecognizer = null;
                                if (faceInfo.HasMask)
                                {
                                    faceRecognizer = faceFactory.GetFaceRecognizerWithMask();
                                }
                                else
                                {
                                    faceRecognizer = faceFactory.Get<FaceRecognizer>();
                                }
                                var points = await faceFactory.Get<FaceLandmarker>().MarkAsync(faceImage, info);
                                float[] extractData = await faceRecognizer.ExtractAsync(faceImage, points);
                                UserInfo userInfo = CacheManager.Instance.Get(faceRecognizer, extractData);
                                if (userInfo != null)
                                {
                                    faceInfo.Name = userInfo.Name;
                                    faceInfo.Age = userInfo.Age;
                                    switch (userInfo.Gender)
                                    {
                                        case GenderEnum.Male:
                                            faceInfo.Gender = Gender.Male;
                                            break;
                                        case GenderEnum.Female:
                                            faceInfo.Gender = Gender.Female;
                                            break;
                                        case GenderEnum.Unknown:
                                            faceInfo.Gender = Gender.Unknown;
                                            break;
                                    }
                                    pool.Post(userInfo);
                                }
                                else
                                {
                                    faceInfo.Age = await faceFactory.Get<AgePredictor>().PredictAgeAsync(faceImage, points);
                                    faceInfo.Gender = await faceFactory.Get<GenderPredictor>().PredictGenderAsync(faceImage, points);
                                }
                            }
                        }
                        faceInfos.Add(faceInfo);
                    }
                }
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    #region 绘制当前时间
                    StringFormat format = new StringFormat();
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;
                    g.DrawString($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"new Font("微软雅黑"32), Brushes.Green, new Rectangle(00, Width - 32188), format);
                    #endregion
                    // 如果有人脸,在 bitmap 上绘制出人脸的位置信息
                    if (faceInfos.Any())
                    {
                        g.DrawRectangles(new Pen(Color.Red, 4), faceInfos.Select(p => p.Rectangle).ToArray());
                        if (CheckBoxDetect.Checked)
                        {
                            for (int i = 0; i < faceInfos.Count; i++)
                            {
                                StringBuilder builder = new StringBuilder();
                                if (CheckBoxFaceProperty.Checked)
                                {
                                    if (!string.IsNullOrEmpty(faceInfos[i].Name))
                                    {
                                        builder.Append(faceInfos[i].Name);
                                    }
                                }
                                if (builder.Length > 0)
                                    g.DrawString(builder.ToString(), new Font("微软雅黑"32), Brushes.Green, new PointF(faceInfos[i].Location.X + faceInfos[i].Location.Width + 24, faceInfos[i].Location.Y));
                            }
                        }
                    }
                    if (CheckBoxFPS.Checked)
                    {
                        stopwatch.Stop();
                        if (numericUpDownFPSTime.Value > 0)
                        {
                            fpsList.Add(1000f / stopwatch.ElapsedMilliseconds);
                            if (stopwatchFPS.ElapsedMilliseconds >= numericUpDownFPSTime.Value)
                            {
                                fps = fpsList.Average();
                                fpsList.Clear();
                                stopwatchFPS.Reset();
                            }
                        }
                        else
                        {
                            fps = 1000f / stopwatch.ElapsedMilliseconds;
                        }
                        g.DrawString($"{fps:#.#} FPS"new Font("微软雅黑"24), Brushes.Green, new Point(1010));
                    }
                }
                FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
            }
            catch (TaskCanceledException)
            {
                break;
            }
            catch { }
        }
    }
    finally
    {
        isDetecting = false;
    }
}
#endregion

3、把人脸信息放在服务器端,桌面程序和服务器端同步人脸信息

      /// <summary>
/// 同步人员信息
/// </summary>
private List<PlatEmployeeDto> SyncServerEmployeeInfomation()
{
    List<PlatEmployeeDto> list = new List<PlatEmployeeDto>();
    string url = $"{config.AppSettings.Settings["Platform"].Value}/business/employeemgr/POSSyncEmployeeInfomation";
    try
    {
        string rs = Program.HttpGetRequest(url);
        if (!string.IsNullOrEmpty(rs) && JObject.Parse(rs).Value<int>("code").Equals(200))
        {
            JObject jo = JObject.Parse(rs);
            list = JsonConvert.DeserializeObject<List<PlatEmployeeDto>>(jo["data"].ToString());
        }
    }
    catch (Exception ex)
    {
        if (ex.Message.Contains("无法连接到远程服务器"))
        {
            Thread.Sleep(100);
            ViewFaceCore.Controls.MessageTip.ShowError("无法连接到远程服务器" + Environment.NewLine + "Unable to connect to remote server"300);
        }
    }
    return list;
}
      private void btnSave_Click(object sender, EventArgs e)
{
    try
    {
        SetUIStatus(false);
        UserInfo userInfo = BuildUserInfo();
        if (userInfo == null)
        {
            throw new Exception("获取用户基本信息失败!");
        }
        using (DefaultDbContext db = new DefaultDbContext())
        {
            db.UserInfo.Add(userInfo);
            if (db.SaveChanges() > 0)
            {
                CacheManager.Instance.Refesh();
                this.Close();
                _ = Task.Run(() =>
                {
                    //确保关闭后弹窗
                    Thread.Sleep(100);
                    try
                    {
                        #region Post Data
                        string url = $"{config.AppSettings.Settings["Platform"].Value}/business/employeemgr/PosNewEmployeeRegister";
                        PlatEmployeeDto dto = new PlatEmployeeDto();
                        dto.KeyId = Guid.NewGuid().ToString();
                        dto.EmpNo = userInfo.EmpNo;
                        dto.EmpName = userInfo.Name;
                        dto.EmpSex = (int)userInfo.Gender.ToInt64();
                        dto.Mobile = userInfo.Phone;
                        dto.PositionValue = userInfo.JobPosition.ToString();
                        dto.EmpFacialFeature = _globalUserInfo.Extract;
                        dto.EmpMainPhoto = _globalUserInfo.Image;
                        dto.CreateBy = "Client";
                        dto.CreateTime = DateTime.Now;
                        dto.IsAdmin = "N";
                        dto.Status = 0;
                        dto.FirstPositionLabel = cbxposition.Text;
                        string jsondata = JsonConvert.SerializeObject(dto);
                        string st = Program.PostJsonData(url, jsondata);
                        #endregion 
                        if (!string.IsNullOrEmpty(st) && st.Contains("200"))
                        {
                            //MessageBox.Show("保存用户信息成功!同步到服务器成功,可到其他门店考勤。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                            DialogResult = DialogResult.OK;
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("本地保存用户信息成功!但同步到服务器出错,不能立即到其他门店考勤。" + ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                });
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    }
    finally
    {
        SetUIStatus(false);
    }
}

4、关于不排班实现考勤的思考

      /// <summary>
/// 客户端添加attendance考勤明细
/// </summary>
/// <returns></returns>
[HttpPost("AddAttendanceDetails")]
//[ActionPermissionFilter(Permission = "business:erpattendancedetails:add")]
[Log(Title = "attendance考勤明细", BusinessType = BusinessType.INSERT)]
[AllowAnonymous]
public IActionResult AddAttendanceDetails([FromBody] AttendanceDetailsDto parm)
{
    var modal = parm.Adapt<AttendanceDetails>().ToCreate(HttpContext);
    if (!string.IsNullOrEmpty(parm.FkStore))
    {
        int storeId = -1;
        int.TryParse(parm.FkStore, out storeId);
        var store = _MerchantStoreService.GetFirst(s => s.Id == storeId);
        if (store == null)
            return BadRequest();
        modal.FkStore = store.KeyId;
    }
    else
        return BadRequest();
    if (!_AttendanceDetailsService.Any(r => r.AuditDate == parm.AuditDate && r.EmpNo == parm.EmpNo))
    {
        modal.Remark = "上班&clock in";
        var response = _AttendanceDetailsService.AddAttendanceDetails(modal);
        return SUCCESS(response);
    }
    else
    {
        var list = _AttendanceDetailsService.GetList(r => r.AuditDate == parm.AuditDate && r.EmpNo == parm.EmpNo);
        var time1 = list.Max(r => r.AttendanceDatetime);
        if (time1 != null)
        {
            var ts = DateTime.Now - DateTime.Parse(time1);
            if (ts.TotalMinutes < 61)
            {
                return Ok();
            }
            else
            {
                modal.Remark = "下班&clock out";
                var response = _AttendanceDetailsService.AddAttendanceDetails(modal);
                return SUCCESS(response);
            }
        } 
        else 
        { 
            return BadRequest(); 
        }                
    }
}

5、取消消息弹窗来和用户交互。使用能自动关闭的消息弹窗

4a14a5ba18429438e2dac6d1ea2bf966.webp

这个需要感谢以前在园子里的\一位\博主的分享他写的控件名字叫"LayeredWindow",对外暴露的类叫“MessageTip”,不好意思已忘记作者。

如果你仔细阅读代码还会发现集成了TTS。反正做得有点像无人值守的一些商业机器。

204599922bec9afa8913692df68c02a2.webp

转自:数据酷软件

链接:cnblogs.com/datacool/p/18004303/ViewFaceCore2024

73b40afbee12000eb737f97f2236fdac.webp

    

关注公众号DotNet开发跳槽    

浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报