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

门店被淘汰的POS机

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

前言

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

下载地址:

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

开始干活,动手改造。

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

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

 #region 获取键盘和鼠标没有操作的时间 [StructLayout(LayoutKind.Sequential)]structLASTINPUTINFO
{
[MarshalAs(UnmanagedType.U4)]
public intcbSize;
[MarshalAs(UnmanagedType.U4)]
public uintdwTime;
}
[DllImport(
"user32.dll")]private static extern bool GetLastInputInfo(refLASTINPUTINFO plii);/// <summary> ///获取键盘和鼠标没有操作的时间/// </summary> /// <returns></returns> private static longGetLastInputTime()
{
LASTINPUTINFO vLastInputInfo
= newLASTINPUTINFO();
vLastInputInfo.cbSize
=Marshal.SizeOf(vLastInputInfo);if (!GetLastInputInfo(refvLastInputInfo))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(objectsender, 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
= newSpVoiceUtil();
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 dataThread SyncThread= new Thread(() =>{while (IsWorkEnd == false)
{
var theEmployeeList = SyncServerEmployeeInfomation().Where(r => r.EmpFacialFeature != null).ToList();if (theEmployeeList != null && theEmployeeList.Count > 0)
{
foreach (var emp intheEmployeeList)
{
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 voidStartDetector(CancellationToken token)
{
List
<double> fpsList = new List<double>();double fps = 0;
Stopwatch stopwatchFPS
= newStopwatch();
Stopwatch stopwatch
= newStopwatch();
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
= newModels.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 = awaitfaceRecognizer.ExtractAsync(faceImage, points);
UserInfo userInfo
=CacheManager.Instance.Get(faceRecognizer, extractData);if (userInfo != null)
{
faceInfo.Name
=userInfo.Name;
faceInfo.Age
=userInfo.Age;switch(userInfo.Gender)
{
caseGenderEnum.Male:
faceInfo.Gender
=Gender.Male;break;caseGenderEnum.Female:
faceInfo.Gender
=Gender.Female;break;caseGenderEnum.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= newStringFormat();
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
= newStringBuilder();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);
}
}
returnlist;
}
        private void btnSave_Click(objectsender, EventArgs e)
{
try{
SetUIStatus(
false);
UserInfo userInfo
=BuildUserInfo();if (userInfo == null)
{
throw new Exception("获取用户基本信息失败!");
}
using (DefaultDbContext db = newDefaultDbContext())
{
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
= newPlatEmployeeDto();
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]
publicIActionResult AddAttendanceDetails([FromBody] AttendanceDetailsDto parm)
{
var modal = parm.Adapt<AttendanceDetails>().ToCreate(HttpContext);if (!string.IsNullOrEmpty(parm.FkStore))
{
int storeId = -1;int.TryParse(parm.FkStore, outstoreId);var store = _MerchantStoreService.GetFirst(s => s.Id ==storeId);if (store == null)returnBadRequest();
modal.FkStore
=store.KeyId;
}
else returnBadRequest();if (!_AttendanceDetailsService.Any(r => r.AuditDate == parm.AuditDate && r.EmpNo ==parm.EmpNo))
{
modal.Remark
= "上班&clock in";var response =_AttendanceDetailsService.AddAttendanceDetails(modal);returnSUCCESS(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)
{
returnOk();
}
else{
modal.Remark
= "下班&clock out";var response =_AttendanceDetailsService.AddAttendanceDetails(modal);returnSUCCESS(response);
}
}
else{returnBadRequest();
}
}
}

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

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

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

标签: none

添加新评论