這學期上《數字圖像處理》這門課,然後想做一個人臉識别系統,從部落格和相關書籍中看到OpenCV實作人臉識别系統十分簡單。下面就OpenCV的安裝及實作人臉識别功能進行說明。由于時間限制,本程式各個步驟未封裝成函數,後續更新将把每步更新成函數。本人水準有限,如有錯誤,歡迎改正。
參考書籍《深入了解OpenCV—實用計算機視覺項目解析》
一、環境搭建
我是在Ubuntu 18.04環境下安裝OpenCV庫的,看許多教程都是使用OpenCV+VS開發環境,我這裡使用的是Qt+OpenCV進行開發。每個人電腦不同,是以安裝時遇到的問題也就各種各樣。第一部分僅做參考。
1.安裝OpenCV和Cmake各種包
2.解壓OpenCV和OpenCV_contrib檔案。(其中人臉識别需要用到opencv_contrib中的face包,opencv包内含有人臉檢測的檔案)
3.在opencv中建立build檔案夾,安裝cmake,将opencv_contrib包連結起來。其shell指令為
cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=/media/antoniodc/Linux_File/opencv_contrib/modules ..
如果顯示不存在,将-D中間的空格删除。/usr/local為安裝的目的檔案夾。/media/antoniodc/Linux_File/opencv_contrib/modules …為我contrib包的位置,根據每個人電腦情況來做。勿忘最後空格加…
4.make成功後,配置環境變量。
二、人臉識别具體實作
其中引用的頭檔案為
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/face.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/core/ocl.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/flann.hpp>
#include <opencv2/xobjdetect.hpp>
#include <opencv2/face/predict_collector.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/ml.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
#include <iostream>
#include <sstream>
#include "QDebug"
1.人臉檢測
人臉檢測是在圖像中定位人臉區域的過程,這一步不關心人是誰,隻是關心是不是人臉。
讀入圖像—>灰階圖像轉換—>直方圖均衡化—>分類器進行對象檢測
(1)當配置好OpenCV庫與Qt Creater開發環境後,調用OpenCV的庫函數,調用cv::Mat image = cv::imread(“1.jpg”, cv::IMREAD_COLOR);打開圖檔,其中:IMREAD_COLOR為圖檔的通道描述,将打開的圖檔以RGB三通道的方式讀取
(2)灰階圖像轉換
灰階圖像轉換一方面是為了簡化矩陣,增強運算速度,一方面是OpenCV中簡單的識别算法對于顔色的依賴性不強。
(3)直方圖均衡化
使用直方圖均衡化改善圖像對比圖和亮度
(4)分類器
OpenCV提供了對象檢測器,通過LBP的特征檢測器可以能通過訓練從大的圖像集中找到人臉,這些圖像存在于XML檔案中。一般情況下圖像分類檢測器通常至少使用1000個獨特的人臉圖像和10000個非人臉圖像作為訓練才能産生訓練資料。OpenCV自帶訓練好的額LBP檢測器供使用者使用,隻要加載不同級聯的分類器的XML檔案給對象檢測器就可以檢測正面人臉、輪廓的面孔,眼睛或鼻子。為了便于後面的人臉圖像預處理。在這裡檢測出人臉和人眼。加載OpenCV給定的XML資料集,使用矩形窗标記出人臉和眼睛。其中人臉的資料集在其中haarcascade_frontalface_alt.xml檔案中,由于個人佩戴眼鏡,是以選擇眼睛所在的資料集為haarcascade_eye_tree_eyeglasses.xml,打開資料集會發現大量訓練好的資料。檢測時是灰色圖像檢測,我最終在彩色圖像上顯示效果圖如下所示。
人臉檢測代碼
//step 1 -----read picture
cv::Mat printImg;
cv::Mat image = cv::imread("3.jpg", cv::IMREAD_COLOR); //read picture
if(image.empty())
qDebug()<<"init photo is empty";
resize(image,printImg,Size(600,800));
//cv::imshow("1",printImg);
cv::Mat image_gray;
cv::Mat Myface;
//step 2 -----convert gray image
cv::cvtColor(image, image_gray,cv::COLOR_BGR2GRAY);
resize(image_gray,printImg,Size(600,800));
//cv::imshow("2",printImg);
//step 3 ------equalizeHis
cv::equalizeHist(image_gray, image_gray); //to improve contrast ratio and brightness
resize(image_gray,printImg,Size(600,800));
//cv::imshow("3",printImg);
//step 4-----detection face
cv::CascadeClassifier face_cascade;
if (!face_cascade.load("haarcascade_frontalface_alt.xml")) //需要将xml文檔放在自己指定的路徑下
{
qDebug()<< "Load haarcascade_face.xml failed!";
}
std::vector<cv::Rect> faceRect;
face_cascade.detectMultiScale(image_gray, faceRect, 1.1, 3, 0 | CASCADE_SCALE_IMAGE, Size(30, 30)); //detect face
//if you only want get one face pelase set CASCADE_FIND_BIGGEST_OBJECT |CASCADE_DO_ROUGH_SEARCH;
for (size_t i = 0; i < faceRect.size(); i++)
{
cv::Mat faceROI = image(faceRect[i]);
if(faceROI.cols > 100)
{
resize(faceROI, Myface, Size(500, 500)); //調整圖像大小為500 * 500
//imshow("4", Myface);
}
rectangle(image, faceRect[i], Scalar(0, 0, 255)); //用矩形畫出檢測到的位置
}
//cv::imshow("5",image); //show the image of detect face
2.人臉預處理
臉預處理是調整人臉圖像,使其看起來更加清楚,也更加符合人識别人臉的過程。
人眼檢測對于人臉的預處理非常有用,對于人臉而言,面部表情、光照條件、錄影機的屬性、與相機的距離等都會發生變化,但是人眼是水準的,并且對稱分布在人臉上,兩隻眼睛在人臉的位置和大小是相當标準的。當人臉檢測器将别的對象當成人臉時,可以通過眼部檢測丢棄這種誤判。人臉檢測完成後,截取出人臉所在的區域,為了保持人臉在水準線上,将檢測到眼睛中心位置的x為準線,将圖像進行旋轉和平移操作,以使眼睛能被對齊,保證訓練圖像和測試圖像在同一位置。由于人臉識别的特征都在虹膜、鼻翼、嘴角等面像五官輪廓的大小、位置、距離等屬性。對于截取的人臉圖像在經過删除額頭、下巴、耳朵和北京的操作,對于圖像中含有噪聲的影響,使用雙邊濾波器進行平滑操作減少噪聲。最後使用橢圓掩碼将圖像中剩餘的頭發和人臉的背景去掉。
檢測人眼代碼為
cv::CascadeClassifier eye_cascade;
if (!eye_cascade.load("haarcascade_eye_tree_eyeglasses.xml")) //wear glass
{
qDebug()<< "Load haarcascade_face.xml failed!";
}
std::vector<cv::Rect> eyeRect;
eye_cascade.detectMultiScale(Myface, eyeRect, 1.1, 4, 0 | CASCADE_SCALE_IMAGE, Size(30, 30)); //detect eye //4 depends the correct rate of eye,the num is bigger,the correct rate is higher
int eye_num = 0;
cv::Point Myeye[2]; //eye center
for (size_t i = 0; i < eyeRect.size(); i++)
{
rectangle(Myface, eyeRect[i], Scalar(0, 255, 255)); //用矩形畫出檢測到的位置
Myeye[eye_num] =getCenterPoint(eyeRect[i]);
cv::circle(Myface,Myeye[eye_num],4,cv::Scalar(255,0,255));
eye_num = eye_num+1;
}
resize(Myface,printImg,Size(500,500));
//cv::imshow("6",printImg);
找到正方形中心代碼為
cv::Point getCenterPoint(Rect rect)
{
cv::Point cpt;
cpt.x = rect.x + cvRound(rect.width/2.0);
cpt.y = rect.y + cvRound(rect.height/2.0);
return cpt;
}
人臉預處理其步驟效果如圖
識别出人眼截取掉額頭和下巴資訊,對人臉進行平滑濾波。
處理代碼如下
cv::Point2f eyesCenter;
eyesCenter.x = (Myeye[0].x+Myeye[1].x)*0.5f;
eyesCenter.y = (Myeye[0].y+Myeye[1].y)*0.5f;
double dy = (Myeye[1].y - Myeye[0].y);
double dx = (Myeye[1].x - Myeye[0].x);
double len = sqrt(dx*dx + dy*dy);
double angle=atan2(dy,dx)*180.0/CV_PI;
const double DESIRED_LEFT_EYE_X = 0.16;
const double DESIRED_LEFT_EYE_Y = 0.14;
const double DESIRED_RIGHT_EYE_X = (1.0f - 0.16);
const int DESIRED_FACE_WIDTH = 150;
const int DESIRED_FACE_HEIGHT= 150;
double desiredLen = (DESIRED_RIGHT_EYE_X - 0.16);
double scale = desiredLen * DESIRED_FACE_WIDTH /len;
cv::Mat rot_mat = getRotationMatrix2D(eyesCenter,angle,scale);
double ex = DESIRED_FACE_WIDTH * 0.5f - eyesCenter.x;
double ey = DESIRED_FACE_HEIGHT* DESIRED_LEFT_EYE_Y - eyesCenter.y;
rot_mat.at<double>(0,2) += ex;
rot_mat.at<double>(1,2) += ey;
cv::Mat warped = Mat(DESIRED_FACE_HEIGHT,DESIRED_FACE_WIDTH,CV_8U,Scalar(128));
warpAffine(Myface,warped,rot_mat,warped.size());
cv::Mat Smoothing_Myface;
bilateralFilter(warped,Smoothing_Myface,0,20.0,2.0);
resize(Smoothing_Myface,printImg,Size(500,500));
//cv::imshow("smoothing processing",printImg); //smoothing processing
cv::Mat mask = Mat(warped.size(),CV_8UC1,Scalar(255));
double dw = DESIRED_FACE_WIDTH;
double dh = DESIRED_FACE_HEIGHT;
Point faceCenter = Point(cvRound(dw*0.5),cvRound(dh*0.4));
Size size = Size(cvRound(dw*0.5),cvRound(dh*0.8));
ellipse(mask,faceCenter,size,0,0,360,Scalar(0),cv::FILLED);
Smoothing_Myface.setTo(Scalar(0),mask);
resize(Smoothing_Myface,printImg,Size(500,500));
cv::Mat preprocess_IMG;
resize(Smoothing_Myface,preprocess_IMG,Size(128,128));
cv::cvtColor(preprocess_IMG,preprocess_IMG,cv::COLOR_BGR2GRAY);
resize(mask,printImg,Size(500,500));
//cv::imshow("mask",printImg); //add mask processing
3.訓練資料及實作人臉識别
這裡用了OpenCV中PCA分類器。PCA通過方差資訊實作降維操作。
其流程為:導入樣本圖檔---->增加标簽----->人臉分類器訓練----->儲存資料------>識别器預測
代碼為
//step 1------>input sample
cv::Mat src_1 = imread("./cuiandong/1.JPG",cv::IMREAD_GRAYSCALE); //Me
cv::Mat src_2 = imread("./cuiandong/C1.jpg",cv::IMREAD_GRAYSCALE);
cv::Mat src_3 = imread("./cuiandong/C2.jpg",cv::IMREAD_GRAYSCALE);
cv::Mat src_4 = imread("./cuiandong/C3.jpg",cv::IMREAD_GRAYSCALE);
cv::Mat src_5 = imread("./cuiandong/C4.jpg",cv::IMREAD_GRAYSCALE);
cv::Mat src_6 = imread("./cuiandong/2.jpeg",cv::IMREAD_GRAYSCALE); //Liu Shishi
cv::Mat src_7 = imread("./cuiandong/3.jpeg",cv::IMREAD_GRAYSCALE); //nomal man
cv::Mat src_8 = imread("./cuiandong/4.jpg",cv::IMREAD_GRAYSCALE); //Liu Yifei
cv::Mat src_9 = imread("./cuiandong/5.jpg",cv::IMREAD_GRAYSCALE); //nomal woman
//modify size
cv::resize(src_1,src_1,Size(128,128));
cv::resize(src_2,src_2,Size(128,128));
cv::resize(src_3,src_3,Size(128,128));
cv::resize(src_4,src_4,Size(128,128));
cv::resize(src_5,src_5,Size(128,128));
cv::resize(src_6,src_6,Size(128,128));
cv::resize(src_7,src_7,Size(128,128));
cv::resize(src_8,src_8,Size(128,128));
cv::resize(src_9,src_9,Size(128,128));
images.push_back(src_1);
images.push_back(src_2);
images.push_back(src_3);
images.push_back(src_4);
images.push_back(src_5);
images.push_back(src_6);
images.push_back(src_7);
images.push_back(src_8);
images.push_back(src_9);
//step 2 -----add labels
labels.push_back(1);
labels.push_back(1);
labels.push_back(1);
labels.push_back(1);
labels.push_back(1);
labels.push_back(2);
labels.push_back(3);
labels.push_back(4);
labels.push_back(5);
//step 3 -----faceRecognzer
Ptr<FaceRecognizer> fisherClass = FisherFaceRecognizer::create();
fisherClass->train(images,labels);
//step 4-----save xml
fisherClass->save("fisherClass.xml");
//step 5-----predict
int faceResult = fisherClass->predict(preprocess_IMG);
qDebug()<<"the face result is "<<faceResult;
最後預測結果為
其中打開xml檔案中的訓練資料為