文章目录

  • 虹软人脸识别 - 基于QT的桌面客户端与微信小程序服务器设计
    • 简介
    • 桌面客户端
    • 微信小程序
      • 登记
      • 信息显示
    • 无线嵌入式摄像头
    • 服务器设计
      • 处理桌面客户端
      • 处理微信小程序
      • 公共信息上传与访问接口
      • 无线摄像头处理
    • 结语

虹软人脸识别 - 基于QT的桌面客户端与微信小程序服务器设计

简介

虹软公司为了帮助巨量的企业打破技术壁垒,快速运用机器视觉技术,让AI真正为行业赋能,为社会造福,将其二十多年来经行业检验积累并已广泛应用于各种智能设备上的人工智能引擎开放供开发者免费使用,并且提供了多种平台的多种语言接口。尤其是C/C++接口,使得人脸识别等功能可以轻易集成于基于C++开发的桌面客户端或服务器中。
本文将基于虹软公司的人脸识别SDK 3.0Windows(X64),C/C++接口与QT等库简单介绍桌面客户端,微信小程序,以及无线嵌入式摄像头软件,服务器开发中的核心技术。本项目为一款智能酒店的解决方案,实现住客、访客、工作人员的人脸注册及识别功能。

桌面客户端

桌面客户端主要实现两个功能,实现人脸的注册与识别。需要注意的是,虹软人脸识别(ArcFace)引擎分为视频处理以及照片处理两种模式,对于注册,考虑为了有效提取人脸特征,使用照片处理方式;而对于识别过程,使用视频处理方式,在ArcFace3.0版本中还提供对视频的人脸追踪,避免重复识别,从而提高了效率。因此,本设计同时初始化两种引擎,处理不同需求下的人脸检测功能。
两种模式通过ASFInitEngine的detectMode参数指定,可选项如下:

//检测模式
enum ASF_DetectMode{ASF_DETECT_MODE_VIDEO = 0x00000000,        //Video模式,一般用于多帧连续检测ASF_DETECT_MODE_IMAGE = 0xFFFFFFFF      //Image模式,一般用于静态图的单次检测
};

具体检测与特征提取方法可以参考虹软的Github仓库。

计算机视觉领域常用OpenCV对图像进行处理,图像以cv::Mat存储,而为了将最终结果显示到QT的Widgets中,应使用QImage,这里提供一种转换方式:

static QImage cvMat2QImage(const cv::Mat& mat)
{return QImage(mat.data, mat.cols, mat.rows, QImage::Format_RGB888);
}

实际使用中有时会发现,对于一个已经注册的人脸,当其刚进入屏幕中时,由于摄像头曝光时间等原因,获取到的某些帧可能存在人脸匹配置信度(confidence level,虹软推荐在0.8左右)无法达到要求的情况;同时还存在某些角度的人脸突然识别为未知等异常情况。由于本设计需要记录人员过往信息,因此设计如下时域滤波算法。

if (++index == 5)index = 0;                //这里设置了滤波器的阶数;
switch (curr_person.type)
{case EMPTY:filter[index] = 0; break;      //无人将滤波器填充0;
case UNKNOWN:filter[index] = -1; break;   //有未知人员填充-1;
default:filter[index] = 1;                //有已知人员填充+1,recordPerson.type = curr_person.type; //并且保存当前已知人员;recordPerson.name = curr_person.name;break;
}int sum = std::accumulate(filter.begin(), filter.end(), 0);if (sum >= 3)                             //设置一个置信度,比如这里为3,超过此值,确信有已知人员;
{if (preState == EMPTY){passModel->addAppearance({ recordPerson.type,recordPerson.name,QDateTime::currentDateTime(),{} });//记录出现时间preName = recordPerson.name;}preState = recordPerson.type;
}
else if (sum <= -3)                       //设置一个置信度,比如这里为-3,低于此值,确信有未知人员;
{if (preState == EMPTY){passModel->addAppearance({ UNKNOWN,u8"未知人员",QDateTime::currentDateTime(),{} });//记录出现时间preName = recordPerson.name;}preState = UNKNOWN;
}
else                                      //其他情况,认为无人存在
{if (preState != EMPTY)//这里检测到上一次调用并非无人,说明一定是从某人转换为无人,因此preName是有效的;{passModel->addDisappearance({ {},preName,{},QDateTime::currentDateTime() });//记录离开时间}preState = EMPTY;
}

通过配合各Widgets,可以在界面上实现多样的功能,结合人脸识别引擎,即可完成桌面客户端的全部业务逻辑。模拟实际业务场景,效果如下图:

微信小程序

登记

微信小程序端可以实现用户的登记,并且在解决方案中,当有人经过猫眼时,小程序可以接收到来自服务器的实时推送。
前端使用chooseImage以及uploadFile发送用户选择的照片,在请求头中写入用户昵称与openid,以区分不同用户。服务器响应该请求,调用虹软人脸识别模块,对面部信息框选,保存到服务器本地。前端可通过URL访问该图片,从而更新界面显示。本设计前端使用了微信小程序官方UI库WeUI。

handleChoose: function () {var that = this;wx.chooseImage({count: 1,                        //最多可以选择的图片总数sizeType: ['compressed'],        // 可以指定是原图还是压缩图,默认二者都有sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有success: function (res) {var tempFilePaths = res.tempFilePaths;var timestamp = that.getTimeStamp();wx.showLoading({title: '上传中',})wx.uploadFile({url: app.data.domain + "multipart",filePath: tempFilePaths[0],name: 'uploadFace',formData: {'username': app.data.userNickName,'openid':app.data.openid},header: {"Content-Type": "multipart/form-data"},success: (res) => {var param = JSON.parse(res.data)var gender = "";if (param['state'] == "ok") {if (param['gender'] == "men") gender = "先生";else if (param['gender'] == "women") gender = "女士";else if (param['gender'] == "notsure") gender = "您好";}that.setData({imagePath: app.data.domain + "RegisteredFace/" + app.data.userNickName + ".jpg?t=" + Math.random(),btnChoose: gender + ",欢迎!",})app.data.FacePath = that.data.imagePathapp.data.btnChoose = that.data.btnChoosewx.showToast({title: '上传成功',icon: 'success',mask: true,duration: 1000})},fail: function (res) {setTimeout(function () {wx.hideLoading()}, 0)console.log(res)wx.hideToast();wx.showModal({title: '错误提示',content: '上传图片失败',showCancel: false,success: function (res) { }})}});}});
},

登记的效果图如下:

信息显示

该页面顶部显示可滑动的酒店展示栏目,下面分为多个子模块,目前的设计第一模块为入住日期的显示,第二模块为从猫眼实体设备上的采集的过往记录。为了简单起见,这里并没有使用Web Socket长连接,而是使用定时器定时向服务器发送请求,来获取实时的信息。注意,图中显示的图片均为学习演示所用,并无商业用意。

无线嵌入式摄像头

为统一管理网络通信,无线摄像头与小程序均使用HTTP协议与服务器通信。嵌入式主控使用ESP32-D0WDQ6,连接OV2640与TFT LCD液晶屏。开启Wifi功能,连接到局域网,并开放两个端口分别接收HTTP请求与HTTP推流。获取到相机RAW RGB信息之后,压缩为JPEG编码,加快传输帧率,并使用HTTP chunk编码实时推送视频流。ESP32系列芯片性价比比较高,在640*480的JPEG编码下,实时推流可达到20+fps,速率满足服务器的识别需求。

while (true)
{fb = esp_camera_fb_get();if (!fb){Serial.printf("Camera capture failed");res = ESP_FAIL;}else{if (fb->format != PIXFORMAT_JPEG){bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);esp_camera_fb_return(fb);fb = NULL;if (!jpeg_converted){Serial.printf("JPEG compression failed");res = ESP_FAIL;}}else{_jpg_buf_len = fb->len;_jpg_buf = fb->buf;}}if (res == ESP_OK){size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);}if (res == ESP_OK){res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);}if (res == ESP_OK){res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));}if (fb){esp_camera_fb_return(fb);fb = NULL;_jpg_buf = NULL;}else if (_jpg_buf){free(_jpg_buf);_jpg_buf = NULL;}if (res != ESP_OK){break;}int64_t fr_end = esp_timer_get_time();int64_t frame_time = fr_end - last_frame;last_frame = fr_end;frame_time /= 1000;Serial.printf("MJPG: %uB %ums (%.1ffps)\n", (uint32_t)(_jpg_buf_len),(uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
}

服务器设计

服务器上的功能比较复杂。对于HTTP请求的处理,这里我使用httplib,当然可以使用Qt Http Server,都是十分优秀的开源跨平台的库。

处理桌面客户端

桌面客户端自身带有提取人脸特征向量的能力,因此这里直接将特征向量以及人员信息使用http的Post请求发送至服务器。并将人脸文件保存至本地,方便小程序访问以及服务器端的管理。

if(req.has_file("uploadFeature"))//桌面客户端
{const MultipartFormData &feature = req.get_file_value("uploadFeature");if (req.has_file("username")){strNickName = req.get_file_value("username").content.data();}person_info &this_person = tmpBuffer[strNickName];this_person.userNickName = strNickName;this_person.feature.featureSize = feature.content.length();this_person.method = CLIENT;if (this_person.feature.feature == nullptr)this_person.feature.feature = new MByte[this_person.feature.featureSize];memcpy(this_person.feature.feature, feature.content.data(), this_person.feature.featureSize);const MultipartFormData &file = req.get_file_value("uploadFace");QByteArray image(file.content.data(), file.content.length());QBuffer buffer(&image);buffer.open(QIODevice::ReadOnly);(void)QImageReader(&buffer, "JPG").read().convertToFormat(QImage::Format_RGB888).save("./RegisteredFace/" + this_person.userNickName + ".jpg");
}

处理微信小程序

微信小程序只能直接上传人脸,人脸识别与特征检测由服务器处理。处理并保存之后,使用虹软性别识别接口,将对应性别返回至小程序,提供前端的显示。

else if (req.has_file("uploadFace"))//微信小程序
{const MultipartFormData &file = req.get_file_value("uploadFace");if (req.has_file("username")){strNickName = req.get_file_value("username").content.data();}QByteArray image(file.content.data(), file.content.length());QBuffer buffer(&image);buffer.open(QIODevice::ReadOnly);const QImage img = QImageReader(&buffer, "JPG").read().convertToFormat(QImage::Format_RGB888);person_info &this_person = tmpBuffer[strNickName];this_person.userNickName = strNickName;this_person.method = WECHAT;handleFeatureExtract(this_person, img.scaledToWidth(img.width() - int(img.width()) % 4));if (this_person.type == EMPTY){res.set_content(R"({"state":"empty"})", "text/plain");}else{QString gender[3] = { "notsure","men","women" };res.set_content(tr(R"({"state":"ok","gender":"%1"})").arg(gender[this_person.gender + 1]).toStdString(), "text/plain");}
}

公共信息上传与访问接口

这里的接口可供为客户端与小程序共同访问,进行信息的录入或者访问。小程序即通过该接口请求当前用户下的过往信息,信息以json形式发送至前端,前端进行相应的显示。

//公用信息录入
network.svr.Post("/register", [&](const Request &req, Response &res)
{QString strNickName;if (req.has_param("nickname")){strNickName = req.get_param_value("nickname").c_str();}person_info &this_person = tmpBuffer[strNickName];switch(std::stoi(req.get_param_value("type"))){case 0:this_person.name = req.get_param_value("name").c_str();this_person.ID = req.get_param_value("id").c_str();this_person.type = LODGER;this_person.info = new Lodger{QDateTime::fromString(req.get_param_value("checkin").c_str(),"yyyy-MM-dd HH:mm"),QDateTime::fromString(req.get_param_value("checkout").c_str(),"yyyy-MM-dd HH:mm"),};//and so on...default: ;}infoModel->appendData(this_person);tmpBuffer.remove(strNickName);
});network.svr.Get("/infomation", [&](const Request &req, Response &res)
{QByteArray dataBytes;QDataStream out(&dataBytes, QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_10);out << infoModel->vPeople;res.set_content(std::string(reinterpret_cast<char*>(dataBytes.data()), dataBytes.length()), "text/plain");
});network.svr.Get("/visit", [&](const Request &req, Response &res)
{QString strNickName;if (req.has_param("nickname")){strNickName = req.get_param_value("nickname").c_str();}QJsonObject obj;person_info* person = infoModel->findName(strNickName);for(int i = 0; i!= person->visitors.size();++i){obj.insert(tr("%1").arg(i),QJsonObject{{u8"face",person->visitors[i].strFile},{u8"who", person->visitors[i].name},{u8"time",person->visitors[i].currTime.toString()} });}QJsonDocument Doc(obj);QByteArray ba = Doc.toJson();res.set_content(std::string(ba), "text/plain");
});

无线摄像头处理

对于这部分的处理,我始终没能找到一个最佳的方案,但目前可以通过以下方式来使用。基于上述ESP32以chunk的方式发送视频流,则服务器可通过该特点进行相应的解码。

while (true)
{std::string body;int freame_length = 0;auto res = cliStream.Get("/stream",[&](const char *data, uint64_t data_length){int length = 0;auto strdata = std::string(data, data_length);if (strdata.length() > 49){(void)sscanf(strdata.c_str(), "Content-Type: image/jpeg\r\nContent-Length: %d\r\n\r\n", &length);}if (length != 0){freame_length = length;return true;}if (strdata == std::string("\r\n--123456789000000000000987654321\r\n")){if (body.length() == freame_length && freame_length != 0){QByteArray image(body.data(), body.length());QBuffer buffer(&image);buffer.open(QIODevice::ReadOnly);QImage img = QImageReader(&buffer, "JPG").read().convertToFormat(QImage::Format_RGB888);cv::Mat mat(img.height(), img.width(), CV_8UC3, img.bits());
#ifdef DEBUG                cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR);  cv::imshow("x", mat);cv::waitKey(1);cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB);
#endifdetection.feedImage(img.copy());}body.clear();freame_length = 0;}elsebody.append(data, data_length);return true;});
}

注意这里我使用detection.feedImage来为队列填充一张图像,这个队列必须是线程安全的。为实现SharedQueue,最简单的方法即封装STL现有Queue,并且使用unique_lock等技术为各操作上锁。可以根据服务器的吞吐量设置队列的最大容量,使前后端处理平衡。
同时在另一处理图像的线程中,从该队列中读取,使用虹软的人脸检测以及特征匹配方案,对比当前人脸与已知人脸。将人脸保存为URL,将信息存入容器,以供前端的提取与显示。

while (true)
{static QString preName;const QImage img = queueImage.waitAndPop();//person_info person = handleDetect(img);ColorSpaceConversion(img.width(), img.height(),ASVL_PAF_RGB24_B8G8R8, const_cast<unsigned char*>(img.bits()), offscreen);person_info person;MRESULT res = ASFDetectFacesEx(handle, &offscreen, &detectedFaces);if (detectedFaces.faceNum > 0){const MRECT facerect = detectedFaces.faceRect[0];ASF_FaceFeature currFeature;ASF_SingleFaceInfo SingleDetectedFace = { facerect,detectedFaces.faceOrient[0] };ASFFaceFeatureExtractEx(handle, &offscreen, &SingleDetectedFace, &currFeature);person = infoModel->checkFeature(handle, currFeature);if (preName != person.name)//state changed{if (person.type != EMPTY){if (person.type == UNKNOWN){QString strFileName = tr(u8"unknown_%1").arg(index++);(void)img.copy(QRect(QPoint{ facerect.left, facerect.top },QPoint{ facerect.right, facerect.bottom })).save("./VisitedFace/" + strFileName + ".jpg");this_person->visitors.push_back(visitor_info{ u8"未知人员",strFileName,QDateTime::currentDateTime() });}else{QString strFileName = tr(u8"visitor_%1").arg(index++);(void)img.copy(QRect(QPoint{ facerect.left, facerect.top },QPoint{ facerect.right, facerect.bottom })).save("./VisitedFace/" + strFileName + ".jpg");this_person->visitors.push_back(visitor_info{ person.name,strFileName,QDateTime::currentDateTime() });}}}}if (preName != person.name)//state changed{preName = person.name;emit newInfo(person.name);}
}

运行效果图如下所示,由于时间关系,界面并未做仔细地调整。

结语

至此,本项目的各部分的核心内容均已介绍完毕。实际上由于文章篇幅所限,有大量细节未能介绍。同时本人也处于学习之中,如果有任何细节使用不当,敬请指出。再次感谢虹软提供的免费视觉技术。

虹软人脸识别 - 基于QT的桌面客户端与微信小程序及服务器设计相关推荐

  1. node mysql商城开发_NideShop:基于Node.js+MySQL开发的微信小程序商城开源啦

    NideShop:基于Node.js+MySQL开发的微信小程序商城开源啦 发布时间:2020-04-14 04:23:37 来源:51CTO 阅读:2894 作者:ch10mmt 高仿网易严选的微信 ...

  2. 基于微信小程序的菜谱设计毕业论文

    微信小程序的菜谱设计毕业论文 随着"互联网+"的大潮兴起,平台型应用再受热捧.其中,微信小程序凭借其强大的用户基础及其应用时的便捷而深受欢迎.在此基础上,以小程序为载体的社群电商, ...

  3. 基于微信小程序的在线商城设计(后台PHP)

    目 录 摘 要 I ABSTRACT II 一.前言 1 1.1 研究的背景.目的和意义 1 1.2 国内外文献综述 2 1.3 研究的主要内容 3 二.系统相关技术研究 5 2.1 微信小程序 5 ...

  4. 饿了么红包,饿了么活动介绍,饿了么外卖分销、饿了么外卖跟单、饿了么外卖返利跟单识别用户的方式, 外卖返利微信小程序源码

    饿了么红包,饿了么活动介绍,饿了么外卖分销.饿了么外卖跟单.饿了么外卖返利跟单识别用户的方式, 外卖返利微信小程序源码 美团/饿了么外卖CPS联盟返利公众号小程序裂变核心源码 源代码地址 http:/ ...

  5. 基于java springboot的小说阅读微信小程序含后台管理系统源码

    系统运行环境 开发工具 eclipse(idea),mysql5.7(大于5.5),navicat,小程序开发工具 硬件要求 windows操作系统 cpu:2.4GHz 内存:4G 硬盘:100G ...

  6. 微信小程序商城服务器搭建,基于微信小程序商城毕业设计(小程序客户端+服务端Node.js源码)毕业论文+运行说明...

    微信小程序商城毕业设计(微信小程序客户端毕业设计) 摘  要 购物商城系统是保证以购物商城为基础的网上交易实现的体系.市场交易是由参与交易双方在平等.自由.互利的基础上进行的基于价值的交换.网上交易同 ...

  7. 大前端(移动端/桌面应用Electron/微信小程序/小程序、快应用框架)

    移动端 Web 总体认识 客户端的所有形式:Native App(IOS.Android.Mac.Windows),小程序(微信.百度.支付宝.字节跳动),桌面端网页.移动端网页(浏览器H5.webv ...

  8. 小程序服务器七牛云,基于七牛云 API 开发的微信小程序 SDK

    概述 Qiniu-wxapp-SDK 是七牛云在小程序上的实现,网络功能依赖于微信小程序 API.您可以基于 SDK 方便的在小程序中上传文件至七牛云. Qiniu-wxapp-SDK  为客户端 S ...

  9. 基于Python Django框架后端的微信小程序开发

    刚做完一个股票信息服务类的微信小程序(小程序名字"博股论基",大家有兴趣可以搜一下),也有一些心得,在这里记录一下开发过程,算是个开发笔记,同时也希望能给需要的同学一些帮助. -- ...

  10. 微信小程序识别图片并提取文字_分享一个 OCR 文字识别,高效图片转文字的微信小程序...

    从白描 App 上线那天起,就一直有人问有没有小程序,我们一直的回答就是:不好意思,暂时没有计划.那从今天开始,就应该回答:去微信里搜索「白描取字扫描」小程序来免费使用吧! 为什么不叫「白描」?当然想 ...

最新文章

  1. mysql intersects_mysql gis 空间数据库 根据 经纬度查找附近 (INTERSECTS,within)
  2. python备份cisco交换机_1.自动备份思科交换机配置
  3. C 指针总结
  4. 可持久化线段树——主席树
  5. POJ 2075 Tangled in Cables
  6. Linux内核分析 - 网络[十六]:TCP三次握手
  7. php_curl.dll libssh2.dll 始终无法加载的原因 及解决办法
  8. u盘锁电脑_如何给u盘设置密码 给u盘设置密码方法【步骤详解】
  9. C语言随机读写数据文件(二)
  10. django 项目中使用项目环境制作脚本 通过终端命令运行脚本文件(management/commands)...
  11. 用户故事 | 李兆龙:博观而约取,厚积而薄发
  12. 数据结构 栈的结构特点及基本操作
  13. [USACO16DEC]Team Building团队建设
  14. 六成应用不需要程序员,一大批程序员会失业?
  15. Show一下拿的奖杯
  16. bat 直接运行vue项目命令
  17. 文章发表前的最后一步:仔细审查校对样本
  18. 翻译:《实用的Python编程》01_04_Strings
  19. 设计模式之备忘录模式(Memento Pattern)
  20. mysql MHA的工作原理

热门文章

  1. jdk15的安装与配置
  2. 2022年来啦!丨新年回馈粉丝丨免费抽奖!
  3. 在线作图|2分钟绘制一张相关性桑基图
  4. 爬取哔哩哔哩单个视频
  5. 自然语言处理(NLP)和语音识别(ASR)的区别
  6. win10相机打不开,qq可以正常打开:Windows 相机应用错误代码 0xA00F4288
  7. 看图写英语作文关于计算机,终于懂了看图写英语作文模板
  8. 互联网信息安全与加密技术
  9. java培训出来能干什么_Java培训出来一般都可以干什么工作
  10. matlab已知上三角实现对角矩阵,MATLAB及其应用考试卷B卷2010-2011