经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 大数据/云/AI » 人工智能基础 » 查看文章
一次人脸识别ViewFaceCore使用的经验分享,看我把门店淘汰下来的POS机改成了人脸考勤机
来源:cnblogs  作者:数据酷软件  时间:2024/2/5 10:09:57  对本文有异议

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

 

 

 

门店被淘汰的POS机

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

 

 

 

前言

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

 

 下载地址:

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

开始干活,动手改造。

  1. 程序要支持无人值守,程序启动时自动打开摄像头。超过设定的时间无移动鼠标和敲击键盘,程序自动关闭摄像头,进入“休眠”
  2. 识别人脸成功后记录当前时间作为考勤记录
  3. 人脸信息放在服务器端,桌面程序和服务器端同步人脸信息
  4. 关于不排班实现考勤的思考
  5. 取消消息弹窗来和用户交互。使用能自动关闭的消息弹窗

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

  1. #region 获取键盘和鼠标没有操作的时间
  2. [StructLayout(LayoutKind.Sequential)]
  3. struct LASTINPUTINFO
  4. {
  5. [MarshalAs(UnmanagedType.U4)]
  6. public int cbSize;
  7. [MarshalAs(UnmanagedType.U4)]
  8. public uint dwTime;
  9. }
  10. [DllImport("user32.dll")]
  11. private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
  12. /// <summary>
  13. /// 获取键盘和鼠标没有操作的时间
  14. /// </summary>
  15. /// <returns></returns>
  16. private static long GetLastInputTime()
  17. {
  18. LASTINPUTINFO vLastInputInfo = new LASTINPUTINFO();
  19. vLastInputInfo.cbSize = Marshal.SizeOf(vLastInputInfo);
  20. if (!GetLastInputInfo(ref vLastInputInfo))
  21. return 0;
  22. else
  23. return Environment.TickCount - (long)vLastInputInfo.dwTime;//单位ms
  24. }
  25. #endregion

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

  1. /// <summary>
  2. /// 窗体加载时
  3. /// </summary>
  4. /// <param name="sender"></param>
  5. /// <param name="e"></param>
  6. private void Form_Load(object sender, EventArgs e)
  7. {
  8. #region 窗体初始化
  9. WindowState = FormWindowState.Maximized;
  10. // 隐藏摄像头画面控件
  11. VideoPlayer.Visible = false;
  12. //初始化VideoDevices
  13. 检测摄像头ToolStripMenuItem_Click(null, null);
  14. //默认禁用拍照按钮
  15. FormHelper.SetControlStatus(this.ButtonSave, false);
  16. Text = "WPOS人脸识别&考勤";
  17. #endregion
  18. #region TTS
  19. try
  20. {
  21. VoiceUtilHelper = new SpVoiceUtil();
  22. StartVoiceTaskJob();
  23. }
  24. catch (Exception ex)
  25. {
  26. byte[] zipfile = (byte[])Properties.Resources.ResourceManager.GetObject("TTSrepair");
  27. System.IO.File.WriteAllBytes("TTSrepair.zip", zipfile);
  28. Program.UnZip("TTSrepair.zip", "", "", true);
  29. #region 语音引擎修复安装
  30. try
  31. {
  32. MessageBox.Show("初始化语音引擎出错,错误描述:" + ex.Message + Environment.NewLine +
  33. "正在运行语音引擎安装程序,请点下一步执行安装!", Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
  34. string physicalRoot = AppDomain.CurrentDomain.BaseDirectory;
  35. string info1 = Program.Execute("TTSrepair.exe", 3);
  36. }
  37. finally
  38. {
  39. System.IO.File.Delete("TTSrepair.zip");
  40. Application.Restart();
  41. }
  42. #endregion
  43. }
  44. #endregion
  45. #region 自动打开摄像头
  46. Thread thread = new Thread(() =>
  47. {
  48. Thread.Sleep(5000);
  49. sc.Post(SystemInit, this);
  50. });
  51. thread.Start();
  52. #endregion
  53. #region Sync face data
  54. Thread SyncThread = new Thread(() =>
  55. {
  56. while (IsWorkEnd == false)
  57. {
  58. var theEmployeeList = SyncServerEmployeeInfomation().Where(r => r.EmpFacialFeature != null).ToList();
  59. if (theEmployeeList != null && theEmployeeList.Count > 0)
  60. {
  61. foreach (var emp in theEmployeeList)
  62. {
  63. poolExt.Post(emp);
  64. }
  65. }
  66. Thread.Sleep(5000);
  67. }
  68. });
  69. SyncThread.Start();
  70. #endregion
  71. #region 自动关闭摄像头线程
  72. Thread CameraCheckThread = new Thread(() =>
  73. {
  74. while (IsWorkEnd == false)
  75. {
  76. if (IsNeedAutoCheck)
  77. {
  78. 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);
  79. long ts = GetLastInputTime();
  80. if (ts > Auto_close_camera_interval)
  81. {
  82. IsNeedAutoCheck = false;
  83. sc.Post(CheckCameraStatus, this);
  84. }
  85. }
  86. Thread.Sleep(1000);
  87. }
  88. });
  89. CameraCheckThread.Start();
  90. btnSleep.Enabled = true;
  91. btnStopSleep.Enabled = true;
  92. #endregion
  93. }

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

  1. /// <summary>
  2. /// 持续检测一次人脸,直到停止。
  3. /// </summary>
  4. /// <param name="token">取消标记</param>
  5. private async void StartDetector(CancellationToken token)
  6. {
  7. List<double> fpsList = new List<double>();
  8. double fps = 0;
  9. Stopwatch stopwatchFPS = new Stopwatch();
  10. Stopwatch stopwatch = new Stopwatch();
  11. isDetecting = true;
  12. try
  13. {
  14. if (VideoPlayer == null)
  15. {
  16. return;
  17. }
  18. while (VideoPlayer.IsRunning && !token.IsCancellationRequested)
  19. {
  20. try
  21. {
  22. if (CheckBoxFPS.Checked)
  23. {
  24. stopwatch.Restart();
  25. if (!stopwatchFPS.IsRunning)
  26. { stopwatchFPS.Start(); }
  27. }
  28. Bitmap bitmap = VideoPlayer.GetCurrentVideoFrame(); // 获取摄像头画面
  29. if (bitmap == null)
  30. {
  31. await Task.Delay(10, token);
  32. FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
  33. continue;
  34. }
  35. if (!CheckBoxDetect.Checked)
  36. {
  37. await Task.Delay(1000 / 60, token);
  38. FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
  39. continue;
  40. }
  41. List<Models.FaceInfo> faceInfos = new List<Models.FaceInfo>();
  42. using (FaceImage faceImage = bitmap.ToFaceImage())
  43. {
  44. var infos = await faceFactory.Get<FaceTracker>().TrackAsync(faceImage);
  45. for (int i = 0; i < infos.Length; i++)
  46. {
  47. Models.FaceInfo faceInfo = new Models.FaceInfo
  48. {
  49. Pid = infos[i].Pid,
  50. Location = infos[i].Location
  51. };
  52. if (CheckBoxFaceMask.Checked || CheckBoxFaceProperty.Checked)
  53. {
  54. Model.FaceInfo info = infos[i].ToFaceInfo();
  55. if (CheckBoxFaceMask.Checked)
  56. {
  57. var maskStatus = await faceFactory.Get<MaskDetector>().PlotMaskAsync(faceImage, info);
  58. faceInfo.HasMask = maskStatus.Masked;
  59. }
  60. if (CheckBoxFaceProperty.Checked)
  61. {
  62. FaceRecognizer faceRecognizer = null;
  63. if (faceInfo.HasMask)
  64. {
  65. faceRecognizer = faceFactory.GetFaceRecognizerWithMask();
  66. }
  67. else
  68. {
  69. faceRecognizer = faceFactory.Get<FaceRecognizer>();
  70. }
  71. var points = await faceFactory.Get<FaceLandmarker>().MarkAsync(faceImage, info);
  72. float[] extractData = await faceRecognizer.ExtractAsync(faceImage, points);
  73. UserInfo userInfo = CacheManager.Instance.Get(faceRecognizer, extractData);
  74. if (userInfo != null)
  75. {
  76. faceInfo.Name = userInfo.Name;
  77. faceInfo.Age = userInfo.Age;
  78. switch (userInfo.Gender)
  79. {
  80. case GenderEnum.Male:
  81. faceInfo.Gender = Gender.Male;
  82. break;
  83. case GenderEnum.Female:
  84. faceInfo.Gender = Gender.Female;
  85. break;
  86. case GenderEnum.Unknown:
  87. faceInfo.Gender = Gender.Unknown;
  88. break;
  89. }
  90. pool.Post(userInfo);
  91. }
  92. else
  93. {
  94. faceInfo.Age = await faceFactory.Get<AgePredictor>().PredictAgeAsync(faceImage, points);
  95. faceInfo.Gender = await faceFactory.Get<GenderPredictor>().PredictGenderAsync(faceImage, points);
  96. }
  97. }
  98. }
  99. faceInfos.Add(faceInfo);
  100. }
  101. }
  102. using (Graphics g = Graphics.FromImage(bitmap))
  103. {
  104. #region 绘制当前时间
  105. StringFormat format = new StringFormat();
  106. format.Alignment = StringAlignment.Center;
  107. format.LineAlignment = StringAlignment.Center;
  108. g.DrawString($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", new Font("微软雅黑", 32), Brushes.Green, new Rectangle(0, 0, Width - 32, 188), format);
  109. #endregion
  110. // 如果有人脸,在 bitmap 上绘制出人脸的位置信息
  111. if (faceInfos.Any())
  112. {
  113. g.DrawRectangles(new Pen(Color.Red, 4), faceInfos.Select(p => p.Rectangle).ToArray());
  114. if (CheckBoxDetect.Checked)
  115. {
  116. for (int i = 0; i < faceInfos.Count; i++)
  117. {
  118. StringBuilder builder = new StringBuilder();
  119. if (CheckBoxFaceProperty.Checked)
  120. {
  121. if (!string.IsNullOrEmpty(faceInfos[i].Name))
  122. {
  123. builder.Append(faceInfos[i].Name);
  124. }
  125. }
  126. if (builder.Length > 0)
  127. g.DrawString(builder.ToString(), new Font("微软雅黑", 32), Brushes.Green, new PointF(faceInfos[i].Location.X + faceInfos[i].Location.Width + 24, faceInfos[i].Location.Y));
  128. }
  129. }
  130. }
  131. if (CheckBoxFPS.Checked)
  132. {
  133. stopwatch.Stop();
  134. if (numericUpDownFPSTime.Value > 0)
  135. {
  136. fpsList.Add(1000f / stopwatch.ElapsedMilliseconds);
  137. if (stopwatchFPS.ElapsedMilliseconds >= numericUpDownFPSTime.Value)
  138. {
  139. fps = fpsList.Average();
  140. fpsList.Clear();
  141. stopwatchFPS.Reset();
  142. }
  143. }
  144. else
  145. {
  146. fps = 1000f / stopwatch.ElapsedMilliseconds;
  147. }
  148. g.DrawString($"{fps:#.#} FPS", new Font("微软雅黑", 24), Brushes.Green, new Point(10, 10));
  149. }
  150. }
  151. FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
  152. }
  153. catch (TaskCanceledException)
  154. {
  155. break;
  156. }
  157. catch { }
  158. }
  159. }
  160. finally
  161. {
  162. isDetecting = false;
  163. }
  164. }
  165. #endregion

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

 

  1. /// <summary>
  2. /// 同步人员信息
  3. /// </summary>
  4. private List<PlatEmployeeDto> SyncServerEmployeeInfomation()
  5. {
  6. List<PlatEmployeeDto> list = new List<PlatEmployeeDto>();
  7. string url = $"{config.AppSettings.Settings["Platform"].Value}/business/employeemgr/POSSyncEmployeeInfomation";
  8. try
  9. {
  10. string rs = Program.HttpGetRequest(url);
  11. if (!string.IsNullOrEmpty(rs) && JObject.Parse(rs).Value<int>("code").Equals(200))
  12. {
  13. JObject jo = JObject.Parse(rs);
  14. list = JsonConvert.DeserializeObject<List<PlatEmployeeDto>>(jo["data"].ToString());
  15. }
  16. }
  17. catch (Exception ex)
  18. {
  19. if (ex.Message.Contains("无法连接到远程服务器"))
  20. {
  21. Thread.Sleep(100);
  22. ViewFaceCore.Controls.MessageTip.ShowError("无法连接到远程服务器" + Environment.NewLine + "Unable to connect to remote server", 300);
  23. }
  24. }
  25. return list;
  26. }
  1. private void btnSave_Click(object sender, EventArgs e)
  2. {
  3. try
  4. {
  5. SetUIStatus(false);
  6. UserInfo userInfo = BuildUserInfo();
  7. if (userInfo == null)
  8. {
  9. throw new Exception("获取用户基本信息失败!");
  10. }
  11. using (DefaultDbContext db = new DefaultDbContext())
  12. {
  13. db.UserInfo.Add(userInfo);
  14. if (db.SaveChanges() > 0)
  15. {
  16. CacheManager.Instance.Refesh();
  17. this.Close();
  18. _ = Task.Run(() =>
  19. {
  20. //确保关闭后弹窗
  21. Thread.Sleep(100);
  22. try
  23. {
  24. #region Post Data
  25. string url = $"{config.AppSettings.Settings["Platform"].Value}/business/employeemgr/PosNewEmployeeRegister";
  26. PlatEmployeeDto dto = new PlatEmployeeDto();
  27. dto.KeyId = Guid.NewGuid().ToString();
  28. dto.EmpNo = userInfo.EmpNo;
  29. dto.EmpName = userInfo.Name;
  30. dto.EmpSex = (int)userInfo.Gender.ToInt64();
  31. dto.Mobile = userInfo.Phone;
  32. dto.PositionValue = userInfo.JobPosition.ToString();
  33. dto.EmpFacialFeature = _globalUserInfo.Extract;
  34. dto.EmpMainPhoto = _globalUserInfo.Image;
  35. dto.CreateBy = "Client";
  36. dto.CreateTime = DateTime.Now;
  37. dto.IsAdmin = "N";
  38. dto.Status = 0;
  39. dto.FirstPositionLabel = cbxposition.Text;
  40. string jsondata = JsonConvert.SerializeObject(dto);
  41. string st = Program.PostJsonData(url, jsondata);
  42. #endregion
  43. if (!string.IsNullOrEmpty(st) && st.Contains("200"))
  44. {
  45. //MessageBox.Show("保存用户信息成功!同步到服务器成功,可到其他门店考勤。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
  46. DialogResult = DialogResult.OK;
  47. }
  48. }
  49. catch (Exception ex)
  50. {
  51. MessageBox.Show("本地保存用户信息成功!但同步到服务器出错,不能立即到其他门店考勤。" + ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
  52. }
  53. });
  54. }
  55. }
  56. }
  57. catch (Exception ex)
  58. {
  59. MessageBox.Show(ex.Message, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  60. }
  61. finally
  62. {
  63. SetUIStatus(false);
  64. }
  65. }

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

 

  1. /// <summary>
  2. /// 客户端添加attendance考勤明细
  3. /// </summary>
  4. /// <returns></returns>
  5. [HttpPost("AddAttendanceDetails")]
  6. //[ActionPermissionFilter(Permission = "business:erpattendancedetails:add")]
  7. [Log(Title = "attendance考勤明细", BusinessType = BusinessType.INSERT)]
  8. [AllowAnonymous]
  9. public IActionResult AddAttendanceDetails([FromBody] AttendanceDetailsDto parm)
  10. {
  11. var modal = parm.Adapt<AttendanceDetails>().ToCreate(HttpContext);
  12. if (!string.IsNullOrEmpty(parm.FkStore))
  13. {
  14. int storeId = -1;
  15. int.TryParse(parm.FkStore, out storeId);
  16. var store = _MerchantStoreService.GetFirst(s => s.Id == storeId);
  17. if (store == null)
  18. return BadRequest();
  19. modal.FkStore = store.KeyId;
  20. }
  21. else
  22. return BadRequest();
  23. if (!_AttendanceDetailsService.Any(r => r.AuditDate == parm.AuditDate && r.EmpNo == parm.EmpNo))
  24. {
  25. modal.Remark = "上班&clock in";
  26. var response = _AttendanceDetailsService.AddAttendanceDetails(modal);
  27. return SUCCESS(response);
  28. }
  29. else
  30. {
  31. var list = _AttendanceDetailsService.GetList(r => r.AuditDate == parm.AuditDate && r.EmpNo == parm.EmpNo);
  32. var time1 = list.Max(r => r.AttendanceDatetime);
  33. if (time1 != null)
  34. {
  35. var ts = DateTime.Now - DateTime.Parse(time1);
  36. if (ts.TotalMinutes < 61)
  37. {
  38. return Ok();
  39. }
  40. else
  41. {
  42. modal.Remark = "下班&clock out";
  43. var response = _AttendanceDetailsService.AddAttendanceDetails(modal);
  44. return SUCCESS(response);
  45. }
  46. }
  47. else
  48. {
  49. return BadRequest();
  50. }
  51. }
  52. }

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

 

 

 

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

  

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

 

 

原文链接:https://www.cnblogs.com/datacool/p/18004303/ViewFaceCore2024

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号