2

一次人脸识别ViewFaceCore使用的经验分享,看我把门店淘汰下来的POS机改成了人脸考勤...

 7 months ago
source link: https://www.cnblogs.com/datacool/p/18004303/ViewFaceCore2024
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

124467-20240203062906551-553804231.png
124467-20240203062324832-968446995.png

门店被淘汰的POS机

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

124467-20240203063358873-464472119.jpg

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

124467-20240203064014044-802287987.png

 下载地址:

GitHub - ViewFaceCore/ViewFaceCore: C# 超简单的离线人脸识别库。( 基于 SeetaFace6 )

开始干活,动手改造。

  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(null, null);
            //默认禁用拍照按钮
            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(0, 0, Width - 32, 188), 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(10, 10));
                     }
                 }
                 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.取消消息弹窗来和用户交互。使用能自动关闭的消息弹窗

124467-20240203095851397-1765483741.png

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

 如果你仔细阅读代码还会发现集成了TTS。反正做得有点像无人值守的一些商业机器。好了,收工了。今天只上半天班。 

124467-20240203101217515-160839449.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK