測試和使用了虹軟的人臉API在QT5環境下設計了一個簡單的人臉識別軟件,實現了對人臉的跟蹤和人臉識別。攝像頭的控制以及圖像格式的轉換使用了Opencv,圖像顯示使用的是QT5的Qimage控件。下面是詳細介紹java
**1基本流程**ios
(1)加載存儲的參考圖像數據和圖像標籤,這裏簡單的使用圖像的名字做爲標籤app
(2)使用虹軟人臉識別API計算參考圖像的人臉位置數據並存儲ide
(3)使用opencv VideoCapture 類採集攝像頭圖像數據函數
(2)採集的圖像數據送入虹軟人臉識別API 計算人臉位置,並和參考人臉數據計算類似距離,返回最類似的人臉標籤
**2 Visual Studio 下構建Qt工程**測試
(1)工程目錄以下圖所示:
![在這裏插入圖片描述](https://img-blog.csdn.net/20180827151422853?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d4dGNzdHQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
其中QtGuiApplication1.ui是界面文件,Header File文件夾中的amcomdef.h網站
ammem.h arcsoft_fsdk_face_detection.h arcsoft_fsdk_face_recognition.h ui
asvloffscreen.h merror.h 是從虹軟庫中拷貝的頭文件未作任何修改this
FaceDiscern.h 和FaceDiscern.cpp是自定義的一我的臉識別類spa
(2)工程屬性配置
點擊工程屬性->鏈接器->輸入中出了QT5的庫文件,添加opencv_world340d.lib
![在這裏插入圖片描述](https://img-blog.csdn.net/20180827151527187?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d4dGNzdHQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
點擊工程屬性-》VC++目錄添加OpenCV的頭文件和庫文件的路徑,其中包含目錄添加opencv的頭文件路徑,庫目錄添加opencv的dll路徑,以下圖
![在這裏插入圖片描述](https://img-blog.csdn.net/20180827151539104?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d4dGNzdHQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**2工程類文件詳解**
(1)QtGuiApplication1 ui類的源文件以下所示,其中Mat2QImage函數將opencv採集的圖像數據轉化爲QImage支 持 的數據格式, VideoCapture 是Opencv用來操做攝像頭的類,QImage用來顯示採集的圖像數據
#pragma once #include <QtWidgets/QMainWindow> #include "ui_QtGuiApplication1.h" #include "qmessagebox.h" #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include "qtimer.h" #include "FaceDiscern.h" #include "qrect.h" #include "qpainter.h" using namespace cv; using namespace std; class QtGuiApplication1 : public QMainWindow { Q_OBJECT public: QtGuiApplication1(QWidget *parent = Q_NULLPTR); ~QtGuiApplication1(); QImage Mat2QImage(cv::Mat cvImg); //圖像格式轉換 QTimer *timer; Mat frame; //攝像頭直接得到的數據 FaceDiscern *facediscern; //人臉識別類 private: Ui::QtGuiApplication1Class ui; VideoCapture capture; //採集攝像頭的數據 QImage qImg; //展現圖像的控件 //---槽函數 用做事件觸發 public slots : void openVideo(); void stopVideo(); void nextFrame(); };
(2)QtGuiApplication1.cpp
#include "QtGuiApplication1.h" QtGuiApplication1::QtGuiApplication1(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); ui.image->setScaledContents(true); //fit video to label area facediscern = new FaceDiscern("F:\\trainimages");//加載參考圖像數據和標籤 facediscern->Train();//計算參考數據圖像數據的人臉位置等 } QtGuiApplication1::~QtGuiApplication1() { if (capture.isOpened()) capture.release(); delete(timer); } void QtGuiApplication1::openVideo() { if (capture.isOpened()) capture.release(); //decide if capture is already opened; if so,close it capture.open(0); //open the default camera if (capture.isOpened()) { double rate = capture.get(CV_CAP_PROP_FPS); capture >> frame; //得到攝像頭圖像數據 if (!frame.empty()) { QImage image = Mat2QImage(frame); //將攝像頭的圖像數據轉換爲QImage支持的格式 this->ui.image->setPixmap(QPixmap::fromImage(image)); timer = new QTimer(this); //循環得到攝像頭數據 connect(timer, SIGNAL(timeout()), this, SLOT(nextFrame())); timer->start(40); } } } void QtGuiApplication1::stopVideo() { if (capture.isOpened()) { capture.release(); } } //循環得到攝像頭數據 void QtGuiApplication1::nextFrame() { capture >> frame; double rate = capture.get(CV_CAP_PROP_FPS); if (!frame.empty()) { QImage image = Mat2QImage(frame); //經過人臉檢測API得到人臉的位置並在Qimage上顯示人臉框 QRect rect; //RecognizeFace識別人臉的位置並計算人臉所屬的標籤 string result = facediscern->RecognizeFace(&frame, rect); static QTextCodec *codecForCStrings; QString strQ = QString::fromLocal8Bit(result.c_str()); QString s1 = strQ;//這是在qlabel中顯示中文的辦法 this->ui.result->setText(s1); //在控件上顯示人臉所屬的標籤 QPainter painter(&image); // 設置畫筆顏色 painter.setPen(QColor(255, 0, 0)); painter.drawRect(rect);//繪製人臉的框 this->ui.image->setPixmap(QPixmap::fromImage(image)); } } //將opencv 的cv::Mat 格式圖像轉換爲QImage圖像 QImage QtGuiApplication1::Mat2QImage(cv::Mat cvImg) { if (cvImg.channels() == 3) //3 channels color image { cv::cvtColor(cvImg, cvImg, CV_BGR2RGB); //BGR 轉爲 RGB qImg = QImage((const unsigned char*)(cvImg.data), cvImg.cols, cvImg.rows, cvImg.cols*cvImg.channels(), QImage::Format_RGB888); } else if (cvImg.channels() == 1) //grayscale image { qImg = QImage((const unsigned char*)(cvImg.data), cvImg.cols, cvImg.rows, cvImg.cols*cvImg.channels(), QImage::Format_Indexed8); } else { qImg = QImage((const unsigned char*)(cvImg.data), cvImg.cols, cvImg.rows, cvImg.cols*cvImg.channels(), QImage::Format_RGB888); } return qImg; }
(3) FaceDiscern.h
FaceDiscern 是人臉識別的主類 執行了人臉位置檢測和人臉類似度計算等功能
#pragma once #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <Windows.h> #include <iostream> #include <vector> #include <string> #include <io.h> #include <map> #include "arcsoft_fsdk_face_recognition.h" #include "merror.h" #include "arcsoft_fsdk_face_detection.h" #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "qrect.h" //動態載入人臉識別的API庫 libarcsoft_fsdk_face_detection是人臉檢測庫 //libarcsoft_fsdk_face_recognition.lib是人臉識別庫 #pragma comment(lib,"libarcsoft_fsdk_face_detection.lib") #pragma comment(lib,"./libarcsoft_fsdk_face_recognition.lib") using namespace cv; #define WORKBUF_SIZE (40*1024*1024) class FaceDiscern { public: FaceDiscern(std::string _trainpath); ~FaceDiscern(); //將cv::Mat格式的圖像轉換爲Bitmap void ConvertMatToBitmap(cv::Mat *img, uint8_t **imageData, int *pWidth, int *pHeight); void getFiles(std::string path, std::vector<std::string>& files, std::vector<std::string> &ownname); void Train(); bool readBmp24(const char* path, uint8_t **imageData, int *pWidth, int *pHeight); std::string RecognizeFace(cv::Mat *img, QRect &rect); //APPID是從網站上註冊的無償使用id char APPID[45] = "9aEAsHDYzzzWapX9rH9BZHhdBz8CPTfws4WuF5xdmgnf"; char SDKKey[45] = "61MrwdsfKaMT8cm41uKPQBdCm4rKMLSELtJqs12p7WoV"; //SDKKey char DETECTIONKKey[45] = "61MrwdsfKaMT8cm41uKPQBci7TocqKmAASGS7infomre"; std::string trainpath = "F:\\trainimages"; MRESULT nRet ; MHandle hEngine ; MInt32 nScale ; MInt32 nMaxFace ; MByte *pWorkMem; std::vector<std::string> trainfullfiles;//完整路徑名 std::vector<std::string> trainnamefiles; std::string *labels; std::map<std::string, std::string> dicfilenametoname; /* 初始化引擎和變量 */ MRESULT detectionnRet; MHandle hdetectionEngine; MInt32 ndetetionScale; MInt32 ndetectionMaxFace ; MByte *pdetectionWorkMem; int trainCount = 0; LPAFR_FSDK_FACEMODEL *trainfaceModels; AFR_FSDK_FACEMODEL dectfaceModels; };
(4)FaceDiscern.cpp
#include "FaceDiscern.h" FaceDiscern::FaceDiscern(std::string _trainpath) { nRet = MERR_UNKNOWN; hEngine = nullptr; nScale = 16; nMaxFace = 10; pWorkMem = (MByte *)malloc(WORKBUF_SIZE); /* 初始化引擎和變量 */ detectionnRet = MERR_UNKNOWN; hdetectionEngine = nullptr; ndetetionScale = 16; ndetectionMaxFace = 10; pdetectionWorkMem = (MByte *)malloc(WORKBUF_SIZE); dicfilenametoname.insert(std::pair<std::string, std::string>("bingbing.bmp", "冰冰女神")); dicfilenametoname.insert(std::pair<std::string, std::string>("fangfang.bmp", "村裏有個姑娘叫小芳")); dicfilenametoname.insert(std::pair<std::string, std::string>("feifei.bmp", "劉亦菲")); dicfilenametoname.insert(std::pair<std::string, std::string>("huihui.bmp", "冷工")); dicfilenametoname.insert(std::pair<std::string, std::string>("shishi.bmp", "詩詩妹妹")); dicfilenametoname.insert(std::pair<std::string, std::string>("xiaxia.bmp", "天上掉下個林妹妹")); dicfilenametoname.insert(std::pair<std::string, std::string>("xudasong.bmp", "鬆哥")); dicfilenametoname.insert(std::pair<std::string, std::string>("likunpeng.bmp", "李工")); dicfilenametoname.insert(std::pair<std::string, std::string>("gaojianjun.bmp", "高建軍")); dicfilenametoname.insert(std::pair<std::string, std::string>("liuzhen.bmp", "小鮮肉振哥")); dicfilenametoname.insert(std::pair<std::string, std::string>("liting.bmp", "女王婷姐")); dicfilenametoname.insert(std::pair<std::string, std::string>("wangxuetao.bmp", "雪濤")); dicfilenametoname.insert(std::pair<std::string, std::string>("guowei.bmp", "郭大俠")); dicfilenametoname.insert(std::pair<std::string, std::string>("mingxin.bmp", "寶寶鳴新")); this->trainpath = _trainpath; } FaceDiscern::~FaceDiscern() { /* 釋放引擎和內存 */ detectionnRet = AFD_FSDK_UninitialFaceEngine(hdetectionEngine); if (detectionnRet != MOK) { fprintf(stderr, "UninitialFaceEngine failed , errorcode is %d \n", detectionnRet); } free(pdetectionWorkMem); for (int i = 0; i < trainCount; i++) { if (trainfaceModels[i]->pbFeature != NULL) free(trainfaceModels[i]->pbFeature); } nRet = AFR_FSDK_UninitialEngine(hEngine); if (nRet != MOK) { fprintf(stderr, "UninitialFaceEngine failed , errorcode is %d \n", nRet); } } //加載全部的參考圖像和圖像名字做爲參考庫 void FaceDiscern::getFiles(std::string path, std::vector<std::string>& files, std::vector<std::string> &ownname) { /*files存儲文件的路徑及名稱(eg. C:\Users\WUQP\Desktop\test_devided\data1.txt) 4 ownname只存儲文件的名稱(eg. data1.txt)*/ //文件句柄 long long hFile = 0; //文件信息 struct _finddata_t fileinfo; std::string p; if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1) { do { //若是是目錄,迭代之 //若是不是,加入列表 if ((fileinfo.attrib & _A_SUBDIR)) { /* if(strcmp(fileinfo.name,".") != 0 && strcmp(fileinfo.name,"..") != 0) getFiles( p.assign(path).append("\\").append(fileinfo.name), files, ownname ); */ } else { files.push_back(p.assign(path).append("\\").append(fileinfo.name)); ownname.push_back(fileinfo.name); } } while (_findnext(hFile, &fileinfo) == 0); _findclose(hFile); } } //將cv::Mat轉換爲Bitmap void FaceDiscern::ConvertMatToBitmap(cv::Mat *img, uint8_t **imageData, int *pWidth, int *pHeight) { //======創建位圖信息 =========== int width, height, depth, channel; width = img->cols; height = img->rows; depth = img->depth(); channel = img->channels(); *pWidth = width; //圖像寬。高 *pHeight = height; int linebyte = width * channel; *imageData = (uint8_t *)malloc(linebyte * (*pHeight)); for (int i = 0; i<height; i++) { for (int j = 0; j<width; j++) { *((*imageData) + i * width*channel + j * channel) = (*img).at<Vec3b>(i, j)[2];// (uint8_t)(*(img + i * width*channel + j * width + 2)); *((*imageData) + i * width*channel + j * channel + 1) = (*img).at<Vec3b>(i, j)[1]; *((*imageData) + i * width*channel + j * channel + 2) = (*img).at<Vec3b>(i, j)[0]; } // end of line } } //從文件中讀取圖像並轉化爲bitmap bool FaceDiscern::readBmp24(const char* path, uint8_t **imageData, int *pWidth, int *pHeight) { if (path == NULL || imageData == NULL || pWidth == NULL || pHeight == NULL) { return false; } FILE *fp = fopen(path, "rb"); if (fp == NULL) { return false; } fseek(fp, sizeof(BITMAPFILEHEADER), 0); BITMAPINFOHEADER head; fread(&head, sizeof(BITMAPINFOHEADER), 1, fp); *pWidth = head.biWidth; *pHeight = head.biHeight; int biBitCount = head.biBitCount; if (24 == biBitCount) { int lineByte = ((*pWidth) * biBitCount / 8 + 3) / 4 * 4; *imageData = (uint8_t *)malloc(lineByte * (*pHeight)); uint8_t * data = (uint8_t *)malloc(lineByte * (*pHeight)); fseek(fp, 54, SEEK_SET); fread(data, 1, lineByte * (*pHeight), fp); for (int i = 0; i < *pHeight; i++) { for (int j = 0; j < *pWidth; j++) { memcpy((*imageData) + i * (*pWidth) * 3 + j * 3, data + (((*pHeight) - 1) - i) * lineByte + j * 3, 3); } } free(data); } else { fclose(fp); return false; } fclose(fp); return true; } //加載全部的參考數據 void FaceDiscern::Train() { if (pWorkMem == nullptr) { return; } nRet = AFR_FSDK_InitialEngine(APPID, SDKKey, pWorkMem, WORKBUF_SIZE, &hEngine); //初始化引擎 if (nRet != MOK) { return; } getFiles(trainpath, trainfullfiles, trainnamefiles); //生成訓練數據 特徵集合 if (trainfullfiles.size() > 0) { //參考圖像數據的人臉特徵和標籤的存儲 trainfaceModels = new LPAFR_FSDK_FACEMODEL[trainfullfiles.size()]; labels = new std::string[trainfullfiles.size()]; } else { return ; } for (int i = 0; i < trainfullfiles.size(); i++) { std::string filename = trainfullfiles[i]; /* 讀取第一張靜態圖片信息,並保存到ASVLOFFSCREEN結構體 (以ASVL_PAF_RGB24_B8G8R8格式爲例) */ ASVLOFFSCREEN offInput = { 0 }; offInput.u32PixelArrayFormat = ASVL_PAF_RGB24_B8G8R8; offInput.ppu8Plane[0] = nullptr; const char * path = filename.c_str(); readBmp24(path, (uint8_t**)&offInput.ppu8Plane[0], &offInput.i32Width, &offInput.i32Height); if (!offInput.ppu8Plane[0]) { fprintf(stderr, "fail to ReadBmp(%s)\n", path); AFR_FSDK_UninitialEngine(hEngine); free(pWorkMem); continue ; } offInput.pi32Pitch[0] = offInput.i32Width * 3; AFR_FSDK_FACEMODEL *faceModels = new AFR_FSDK_FACEMODEL(); { AFR_FSDK_FACEINPUT faceInput; //第一張人臉信息經過face detection\face tracking得到 faceInput.lOrient = AFR_FSDK_FOC_0;//人臉方向 //人臉框位置 faceInput.rcFace.left = 0; faceInput.rcFace.top = 0; faceInput.rcFace.right = offInput.i32Width - 2;; faceInput.rcFace.bottom = offInput.i32Height - 2;; //提取第一張人臉特徵 AFR_FSDK_FACEMODEL LocalFaceModels = { 0 }; nRet = AFR_FSDK_ExtractFRFeature(hEngine, &offInput, &faceInput, &LocalFaceModels); if (nRet != MOK) { fprintf(stderr, "fail to Extract 1st FR Feature, error code: %d\n", nRet); } /* 拷貝人臉特徵結果 */ faceModels->lFeatureSize = LocalFaceModels.lFeatureSize; faceModels->pbFeature = (MByte*)malloc(faceModels->lFeatureSize); memcpy(faceModels->pbFeature, LocalFaceModels.pbFeature, faceModels->lFeatureSize); } trainfaceModels[i] = faceModels; labels[i] = trainnamefiles[i]; trainCount++; } if (pdetectionWorkMem == nullptr) { return; } //人臉檢測engine detectionnRet = AFD_FSDK_InitialFaceEngine(APPID, DETECTIONKKey, pdetectionWorkMem, WORKBUF_SIZE, &hdetectionEngine, AFD_FSDK_OPF_0_HIGHER_EXT, ndetetionScale, ndetectionMaxFace); if (detectionnRet != MOK) { return; } } //簡單的經過距離類似計算出最類似的參考圖像 std::string FaceDiscern::RecognizeFace(cv::Mat *img, QRect &rect) { /* 讀取靜態圖片信息,並保存到ASVLOFFSCREEN結構體 (以ASVL_PAF_RGB24_B8G8R8格式爲例) */ /* 人臉檢測 */ ASVLOFFSCREEN offInput = { 0 }; offInput.u32PixelArrayFormat = ASVL_PAF_RGB24_B8G8R8; offInput.ppu8Plane[0] = nullptr; ConvertMatToBitmap(img, (uint8_t**)&offInput.ppu8Plane[0], &offInput.i32Width, &offInput.i32Height); if (!offInput.ppu8Plane[0]) { return ""; } offInput.pi32Pitch[0] = offInput.i32Width * 3; LPAFD_FSDK_FACERES FaceRes = nullptr; detectionnRet = AFD_FSDK_StillImageFaceDetection(hdetectionEngine, &offInput, &FaceRes); void *imgptr = offInput.ppu8Plane[0]; ////識別人臉信息 AFR_FSDK_FACEINPUT faceInput; faceInput.lOrient = AFR_FSDK_FOC_0;//人臉方向 //人臉框位置 faceInput.rcFace.left =FaceRes->rcFace[0].left; faceInput.rcFace.top = FaceRes->rcFace[0].top; faceInput.rcFace.right = FaceRes->rcFace[0].right; faceInput.rcFace.bottom = FaceRes->rcFace[0].bottom; rect.setLeft(FaceRes->rcFace[0].left); rect.setTop(FaceRes->rcFace[0].top); rect.setRight(FaceRes->rcFace[0].right); rect.setBottom(FaceRes->rcFace[0].bottom); //提取人臉特徵 nRet = AFR_FSDK_ExtractFRFeature(hEngine, &offInput, &faceInput, &dectfaceModels); free(imgptr); if (nRet != MOK) { return ""; } float maxscore = -1.0; int index = -1; for (int i = 0; i < trainCount; i++) { MFloat fSimilScore = 0.0f; nRet = AFR_FSDK_FacePairMatching(hEngine, &dectfaceModels, trainfaceModels[i], &fSimilScore); if (fSimilScore > maxscore) { maxscore = fSimilScore; index = i; } } if (index != -1) { double num = maxscore * 100.0; std::string str; char ctr[10]; _gcvt(num, 6, ctr); str = ctr; std::string nameresult = labels[index]; if (dicfilenametoname.find(nameresult) != dicfilenametoname.end()) { nameresult = dicfilenametoname[nameresult]; } return nameresult + "," + str; } //釋放 if(dectfaceModels.lFeatureSize>0) free(dectfaceModels.pbFeature); return ""; }
**(3) 界面展現** ![在這裏插入圖片描述](https://img-blog.csdn.net/20180827163354615?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d4dGNzdHQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 最後是SDK下載地址 https://ai.arcsoft.com.cn/ucenter/user/reg?utm_source=csdn1&utm_medium=referral