基于QT的人脸识别考勤打卡系统(移植到ARM板)
阅读原文时间:2021年04月24日阅读:8

       前两篇已经实现了基于QT的人脸识别考勤打卡系统 ,当时我把这个系统分为用户考勤打卡和管理员管理员系统,并且都在window上运行,但是如果要运用到实际当中,还是存在很多问题,虽然我们自己做的系统几乎是很难运用在实际当中,但是我们做的系统要努力贴近实际。

        基于QT的人脸识别考勤管理系统【一】 https://blog.csdn.net/qq_42449351/article/details/99716413

        基于QT的人脸识别考勤管理系统【二】 https://blog.csdn.net/qq_42449351/article/details/99753675

        前面实现的系统,最大的问题就是用户考勤打卡这一块,当用户打卡时,必须打开电脑运行用户考勤打卡系统,很明显这样是不可取的,所以我想着将用户考勤系统移植到Arm gec6818板上,这样打卡只需要在门前安装一个arn板就可以。

一、移植主要的问题

        1、在Arm板上无法用opencv打开摄像头 (只是个人运行会这样)。所以我采用的是专门为 linux 设备设计的一套视频框架V4L2。

        2、需要将pro文件的opencv路径改成linux下opencv的路径。

        3、Arm板的处理速度肯定没有电脑快,所以要减少运算量。

二、移植前期准备

1、交叉编译QT程序

 如果在linux下没有通过交叉编译让QT程序生成可执行文件,那么一切都是空谈。下面我就跟大家分享如何交叉编译QT程序。 首先准备好使用到的工具     

1、ubuntu16.04 --64位

2、arm-linux-gnueabi-5.4.0.tar.xz --交叉编译工具

3、Qt-Embedded-5.7.0.tar.xz ---arm版本Qt

一、安装交叉编译的工具的步骤:

     1、将arm-linux-gnueabi-5.4.0.tar.xz 拷贝到linux系统 /usr/local/arm目录下(如果没有这个目录就创建)解压

     2、解压命令为:sudo tar -xf arm-linux-gnueabi-5.4.0.tar.xz ,解压成功后会得到arm-linux-gnueabi-5.4.0.tar.xz  , usr

3、为了方便操作把usr下面的路径移到当前目录    执行:sudo mv usr/local/arm/5.4.0 ./

4、设置环境变量,打开 ~/.bashrc  在最后面添加 export PATH=/usr/local/arm/5.4.0/usr/bin:$PATH

二、安装arm版本Qt

     1、 把Qt-Embedded-5.7.0.tar.xz拷贝到/usr/local目录下(必须是这个目录)解压,这个过程解压完成即可

三、交叉编译Qt程序

1、 进入到qt程序工程目录下

2、 执行/usr/local/Qt-Embedded-5.7.0/bin/qmake --生makefile文件

3、 make ---编译  把生成的可执行文件下载到arm开发板运行

如果ARM板能运行该可执行文件,那么恭喜你成功交叉编译QT程序

2、将运行时需要使用的文件拷贝到Arm板

     1、opencv官方训练好的人脸检测分类器(haarcascade_frontalface_alt2.xml)。

     2、自己训练成功的人脸识别文件(MyFaceFisherModel.xml)。

     3、 数据库文件(stuface.db)

三、源代码改写

配置文件  (将window的opencv的路径改成linux的路径)

INCLUDEPATH +=/opt/opencv/include
INCLUDEPATH +=/opt/opencv/include/opencv
INCLUDEPATH +=/opt/opencv/include/opencv2
LIBS += -L/opt/opencv/lib/ -lopencv_core -lopencv_highgui -lopencv_imgcodecs \
-lopencv_calib3d     -lopencv_highgui    -lopencv_shape\
-lopencv_core        -lopencv_imgcodecs  -lopencv_stitching\
-lopencv_dnn         -lopencv_imgproc    -lopencv_superres\
-lopencv_face        -lopencv_ml         -lopencv_videoio\
-lopencv_features2d  -lopencv_objdetect  -lopencv_video\
-lopencv_flann       -lopencv_photo      -lopencv_videostab\

1、V4L2打开摄像头和采集图片的程序

将V4L2的程序设计为一个线程类,通过信号将Mat型的图片传给主线程,V4L2采集图片的步骤有:1、打开设备 2、配置设备(采集的频率,图像宽高, 图像格式(jpeg, yuv--422,420))3、在内核空间申请缓冲区队列(2-4个)4、把申请好的缓冲区对象一一映射到用户空间  5、开始采集

1、打开设备(摄像头)

//1.打开设备
this->vfd = ::open(deviceName.c_str(), O_RDWR);
if(this->vfd < 0)
{
    perror("open fail");
    VideoException vexp("open fail");//创建异常对象
    //抛异常
    throw vexp;
}

2、配置设备

//2.配置采集属性
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //
vfmt.fmt.pix.width = WIDTH;
vfmt.fmt.pix.height = HEIGHT;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//(设置视频输出格式,但是要摄像头支持4:2:2)
//通过ioctl把属性写入设备
int ret  = ioctl(this->vfd, VIDIOC_S_FMT, &vfmt);
if(ret < 0)
{
   VideoException vexp("set fail");//创建异常对象
   throw vexp;
}

3、申请缓冲区队列

//1申请缓冲区队列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.count = this->count;//申请缓冲区队列长度
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.memory = V4L2_MEMORY_MMAP;
int ret = ioctl(this->vfd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
   VideoException vexp("req buffer fail");//创建异常对象
   throw vexp;
}

4、申请好的缓冲区对象一一映射到用户空间

for(int i=0; i<this->count; i++)
{
   struct VideoFrame frame;
   struct v4l2_buffer mapbuffer;
   mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
   mapbuffer.index = i;
   mapbuffer.memory = V4L2_MEMORY_MMAP;
   //从队列中拿到内核空间
   ret  = ioctl(this->vfd, VIDIOC_QUERYBUF, &mapbuffer);
   if(ret < 0)
   {
       perror("query fail");
   }
   //映射
   frame.length = mapbuffer.length;
   frame.start = (char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, this->vfd, mapbuffer.m.offset);

   //空间放回队列中(内核空间)
   ret = ioctl(this->vfd, VIDIOC_QBUF, &mapbuffer);

   //把frame添加到容器framebuffers
   framebuffers.push_back(frame);
}

5、开始采集图片

void V4l2Api::grapImage(char *imageBuffer, int *length)
{

    //select (rfds, wfds, efds, time)

    struct v4l2_buffer readbuf;
    readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    readbuf.memory = V4L2_MEMORY_MMAP;
    //perror("read");
    if(ioctl(this->vfd, VIDIOC_DQBUF, &readbuf)<0)//取一针数据
    {
        perror("read image fail");
    }
    //printf("%ld\n", readbuf.length);
    *length = readbuf.length;
    memcpy(imageBuffer,framebuffers[readbuf.index].start, framebuffers[readbuf.index].length);

    //把用完的队列空间放回队列中重复使用
    if(ioctl(vfd, VIDIOC_QBUF, &readbuf)<0)
    {
        perror("destroy fail");
        exit(1);
    }
}

现在V4L2的采集工作已做完,现在我们在这个类的run函数里一直采集图片,并通过opencv将采集的数据变成RGB型,每采集一次,就通过信号发送给主线程

void V4l2Api::run()
{
    Mat yuvImage(HEIGHT,WIDTH,CV_8UC2);
    uchar *buffer = yuvImage.data;

    int len;
    while(1)
    {
        grapImage((char *)buffer, &len);  //采集图片
        //把yuv转rgb
        Mat rgbImage;
        cvtColor(yuvImage, rgbImage, CV_YUV2RGB_YUYV);
        emit sendImage(rgbImage);


        msleep(50);
}

2、人脸识别的程序

以前的程序,是通过opencv进行打开摄像头,现在只需要改成用V4L2线程类打开即可,创建V4L2线程类对象,当点击按钮时,启动线程,这样就会去执行run函数,再将人脸识别的函数与线程类的发送信号相连接,如果有信号来,就会调用人脸识别函数

1、启动线程,绑定信号

//在构造函数绑定信号
connect(&vapi, &V4l2Api::sendImage, this, &ArmFace::recvMat);


void ArmFace::on_openbt_clicked()
{
   /* if(capture.open(1)<0)  // 以前的写法
    {
        qDebug()<<"open fail";
        return ;
    }*/

    //训练好的文件名称,放置在可执行文件同目录下
    cascade.load("haarcascade_frontalface_alt2.xml");
    model = face::FisherFaceRecognizer::create();
    //1.加载训练好的分类器
    model->read("MyFaceFisherModel.xml");
    ui->openbt->setStyleSheet("border-image:url(:/open.png)");
    vapi.start();     //开启线程

    //mtimer.start(20);

}

2、人脸识别函数

因为要考虑Arm板的处理速度,所以这里将接收到的图片变小,并且在进行人脸检测的时候,改变检测次数,以减少运算量

void ArmFace::recvMat(Mat cap)
{

   //capture>>cap;
   std::vector<Rect> faces(0);
   cv::resize(cap, cap, Size(460, 340));       /*原图片为640*480 变成460*340  */
   cvtColor(cap, gray, CV_RGB2GRAY);
   equalizeHist(gray, gray); 
   //检测人脸
   cascade.detectMultiScale(gray, faces,   /*减少匹配次数*/
      1.1, 3,
      //|CV_HAAR_FIND_BIGGEST_OBJECT,    /*只检测最大的物体*/
      CV_HAAR_DO_ROUGH_SEARCH,         /*初略的检测*/
      //CV_HAAR_SCALE_IMAGE,           /*正常比例检测*/
      Size(200, 200));  
    Mat* pImage_roi = new Mat[faces.size()];    
    Mat face;
    .......
    下面的操作不变
}

至此我们的代码就已经改好了,现在只需要将程序进行交叉编译,再将可执行文件下载到Arm板运行即可,效果如下

总结:这次移植虽然能将用户打卡系统运行,但是还是存在着很问题。还记得当时我是把数据库文件拷贝到开发板里,然后用户打卡系统直接操作Arm板的数据库文件,然而我的管理员系统在window上运行。所以这两个系统的数据是不能相通的。

解决方案:将数据库放到服务器上,在服务器上创建一个用C++写的服务端,用户打卡系统和管理员管理系统作为客户端,服务端的功能:接收客户端发送的命令,并通过接收到的命令去执行相应的操作(操作数据库),再将操作的结果返回给客户端,这样两个系统的数据就相通了。这样做的话,程序要改很多,现在没时间,以后有时间再进行完善。

问题咨询及项目源码请加群:

QQ群

名称:IT项目交流群

群号:245022761

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章