一、faceRecognition接口說明
/*******************************************************************
** 函數(shù)名: faceRecognition
** 函數(shù)描述: 人臉識別
** 參數(shù): [int] recognitionPic: 識別的照片,[int] targetFaceIndex: 目標(biāo)匹配照片索引值
** 返回: 失敗返回-1,成功返回0
** 注意:
********************************************************************/
int faceRecognition(char *recognitionPic, int targetFaceIndex)
{
Mat srcImg, targetImg, compImage;
IplImage* faceImage;
int predict = 0;
char cmd[100] = {0};
/* 加載人臉檢測器 */
CascadeClassifier cascade;
if(!cascade.load(cascadeName))
{
printf("[cwr] load CascadeClassifier fail\n");
return -1;
}
//model = createEigenFaceRecognizer(0, SIMILAR_VALUE);
model = createEigenFaceRecognizer();
if (0 != access(TRAIN_XML, F_OK)) {
trainPicture();
}
model->load(TRAIN_XML);
srcImg = imread(recognitionPic);
faceImage = detectFace(srcImg, cascade);
if (faceImage == NULL) return -1;
Mat cutImage(faceImage, 1);
resize(cutImage, compImage, Size(DEFAULT_CUT_SIZE_W, DEFAULT_CUT_SIZE_H), ZOOM_SCALE, ZOOM_SCALE);//對人臉進(jìn)行縮放,并且設(shè)置成特定分辨率
imwrite(savePicture, compImage);
/* 開始人臉比對 */
#if 0
imshow("compare", compImage);
waitKey(0);
#endif
int predictLabel = -1;
double predictConfidence = 0.0;
model->predict(compImage, predictLabel, predictConfidence);
printf("[cwr] predictLabel = %d\n", predictLabel);
printf("[cwr] predictConfidence = ");
cout<<predictConfidence<<endl;
//predict = model->predict(compImage);
//printf("[cwr] faceRecognition return predict: %d\n", predict);
#if 1
if (predictLabel == targetFaceIndex && predictConfidence <= SIMILAR_VALUE) {
printf("[cwr] Face recognition successful\n");
s_faceRecognitionSuc++;
} else {
printf("[cwr] Face recognition fail\n");
s_faceRecognitionFail++;
}
#endif
printf("[cwr] Face recognition success total cnt = %d, Face recognition fail total cnt = %d, Detect face fail total cnt = %d\n", s_faceRecognitionSuc, s_faceRecognitionFail, s_delectFaceFail);
return 0;
}
1.1 Mat
(1)什么是Mat
矩陣的英文釋義matrix,因此延伸出Mat這個縮寫,Mat類簡單理解為存放圖片數(shù)據(jù)的容器。
在計算機(jī)內(nèi)存中,數(shù)字圖像以矩陣的形式存儲和運算,比如,在MatLab中,圖像讀取之后對應(yīng)一個矩陣,在OpenCV中,同樣也是如此。數(shù)字圖像存儲時,我們存儲的是圖像每個像素點的數(shù)值,對應(yīng)的是一個數(shù)字矩陣。
【照片是以矩陣的方式存儲】
灰度圖像的像素數(shù)據(jù)就是一個矩陣,矩陣的行對應(yīng)圖像的高(單位為像素),矩陣的列對應(yīng)圖像的寬(單位為像素),矩陣的元素對應(yīng)圖像的像素,矩陣元素的值就是像素的灰度值。
灰度圖像就是黑白照片,所以opencv操作圖像都需要先轉(zhuǎn)化為灰度照片來提高速度。
灰度值,我自己的理解應(yīng)該就是黑白之前的差異,亮或者暗,這兩者關(guān)系。
圖像灰度值 灰度值與像素值的關(guān)系 - bingqingsuimeng的專欄 - CSDN博客 https://blog.csdn.net/bingqingsuimeng/article/details/64440699
Mat類的組成,由兩部分?jǐn)?shù)據(jù)組成:矩陣頭(包含矩陣尺寸、存儲方法、存儲地址等)和一個指向存儲所有像素值的矩陣(根據(jù)所選存儲方法的不同,矩陣可以是不同的維數(shù))的指針。
1.2 IplImage
在OpenCV中IplImage是表示一個圖像的結(jié)構(gòu)體,也是從OpenCV1.0到目前最為重要的一個結(jié)構(gòu);
在之前的圖像表示用IplImage,而且之前的OpenCV是用C語言編寫的,提供的接口也是C語言接口。
Mat是后來OpenCV封裝的一個C++類,用來表示一個圖像,和IplImage表示基本一致,但是Mat還添加了一些圖像函數(shù);
IplImage轉(zhuǎn)Mat
IplImage* faceImage;
Mat cutImage(faceImage, 1);
1.3 opencv常用圖像處理函數(shù)
(1)resize函數(shù)說明
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR );
參數(shù)說明:
src:輸入,原圖像,即待改變大小的圖像;
dst:輸出,改變大小之后的圖像,這個圖像和原圖像具有相同的內(nèi)容,只是大小和原圖像不一樣而已;
dsize:輸出圖像的大小。如果這個參數(shù)不為0,那么就代表將原圖像縮放到這個Size(width,height)指定的大??;如果這個參數(shù)為0,那么原圖像縮放之后的大小就要通過下面的公式來計算:
dsize = Size(round(fx*src.cols), round(fy*src.rows)) cols列 rows行
其中,fx和fy就是下面要說的兩個參數(shù),是圖像width方向和height方向的縮放比例。
fx:width方向的縮放比例,如果它是0,那么它就會按照(double)dsize.width/src.cols來計算;
fy:height方向的縮放比例,如果它是0,那么它就會按照(double)dsize.height/src.rows來計算;
interpolation:這個是指定插值的方式,圖像縮放之后,肯定像素要進(jìn)行重新計算的,就靠這個參數(shù)來指定重新計算像素的方式,有以下幾種:
INTER_NEAREST - 最鄰近插值
INTER_LINEAR - 雙線性插值,如果最后一個參數(shù)你不指定,默認(rèn)使用這種方法
INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
INTER_CUBIC - 4x4像素鄰域內(nèi)的雙立方插值
INTER_LANCZOS4 - 8x8像素鄰域內(nèi)的Lanczos插值
【感覺最后一個參數(shù)都不用設(shè)置】
resize(img, dst, Size(300, 200), 0.3, 0.3);//將原圖比例縮放0.3 并且分辨率設(shè)置為300 200
縮放就是按照原圖的分辨率進(jìn)行縮放。如果Size已經(jīng)設(shè)置分辨率了,就沒必要設(shè)置縮放比例了。優(yōu)先會設(shè)置分辨率。
但是如果是打算圖片按比例縮小,最簡單的方法就是調(diào)整比例。resize(img, dst, Size(), 0.3, 0.3) Size()直接內(nèi)部參數(shù)為空就好。
保存指定的分辨率直接resize(img, dst, Size(300, 200), 1, 1) 在Size()內(nèi)部設(shè)置自己想要的參數(shù)即可。
使用注意事項:
- dsize和fx/fy不能同時為0,要么你就指定好dsize的值,讓fx和fy空置直接使用默認(rèn)值,就像
resize(img, imgDst, Size(30,30));
要么你就讓dsize為0,指定好fx和fy的值,比如fx=fy=0.5,那么就相當(dāng)于把原圖兩個方向縮小一倍! - 至于最后的插值方法,正常情況下使用默認(rèn)的雙線性插值就夠用了。
幾種常用方法的效率是:最鄰近插值>雙線性插值>雙立方插值>Lanczos插值;
但是效率和效果成反比,所以根據(jù)自己的情況酌情使用。 - 正常情況下,在使用之前dst圖像的大小和類型都是不知道的,類型從src圖像繼承而來,大小也是從原圖像根據(jù)參數(shù)計算出來。但是如果你事先已經(jīng)指定好dst圖像的大小,那么你可以通過下面這種方式來調(diào)用函數(shù):
resize(src, dst, dst.size(), 0, 0, interpolation);
OpenCV圖像中的x,y;width,height;cols,rows
x:橫坐標(biāo)
y:縱坐標(biāo)
width:寬度
height:高度
cols:代表有多少列,其實就是x
rows:代表有多少行,其實就是y
(2)imwrite
1、filename:需要寫入的文件名,會自己創(chuàng)建(像imwrite("1.jpeg",src);這樣)
2、img:要保存的圖像
3、params:表示為特定格式保存的參數(shù)編碼
注意:你要保存圖片為哪種格式,就帶什么后綴。
(3)imshow
Displays an image in the specified window. 在特定的窗口上顯示圖像。
C++: void imshow(const string& winname, InputArray image)
Parameters:
winname – Name of the window. 窗口名稱。
image – Image to be shown. 要顯示的圖像。
(4)imread
API詳解:
原型:Mat imread(const string& filename, int flags = 1) ;
參數(shù)1:需要載入圖片的路徑名,例如“C:/daima practice/opencv/mat3/mat3/image4.jpg”
參數(shù)2:加載圖像的顏色類型。默認(rèn)為1. 若為0則灰度返回,若為1則原圖返回。
flags = -1:imread按解碼得到的方式讀入圖像
flags = 0:imread按單通道的方式讀入圖像,即灰白圖像
flags = 1:imread按三通道方式讀入圖像,即彩色圖像
(5) waitKey
waitKey(x);
第一個參數(shù): 等待x ms,如果在此期間有按鍵按下,則立即結(jié)束并返回按下按鍵的
ASCII碼,否則返回-1
如果x=0,那么無限等待下去,直到有按鍵按下
另外,在imshow之后如果沒有waitKey語句則不會正常顯示圖像。
1.4 人臉檢測
(1)CascadeClassifier
這個叫級聯(lián)分類器。用于opencv人臉檢測。
cascade.load(cascadeName) 對級聯(lián)分類器進(jìn)行初始化。
只需要初始化CascadeClassifier級聯(lián)分類器,通過加載xml文件(人臉檢測器),級聯(lián)分類器可以根據(jù)xml來支持不同的識別效果。
有識別眼睛、身體等。
常用的一些成員說明,具體可以查看源碼/opencv-2.4.10/modules/objdetect/doc/cascade_classification.rst
定義以及函數(shù)解釋都在源碼中注明了
class CV_EXPORTS_W CascadeClassifier
{
public:
CV_WRAP CascadeClassifier();
//從文件中加載級聯(lián)分類器
CV_WRAP CascadeClassifier(const String& filename);
~CascadeClassifier();
//檢測級聯(lián)分類器是否被加載
CV_WRAP bool empty() const;
//從文件中加載級聯(lián)分類器
CV_WRAP bool load( const String& filename );
//從FileStorage節(jié)點讀取分類器
CV_WRAP bool read( const FileNode& node );
/** 檢測輸入圖像中不同大小的對象。檢測到的對象以矩形列表的形式返回。
參數(shù):
image: 包含檢測對象的圖像的CV_8U類型矩陣
objects: 矩形的向量,其中每個矩形包含被檢測的對象,矩形可以部分位于原始圖像之外
scaleFactor: 指定在每個圖像縮放時的縮放比例
minNeighbors:指定每個候選矩形需要保留多少個相鄰矩形
flags:含義與函數(shù)cvHaarDetectObjects中的舊級聯(lián)相同。它不用于新的級聯(lián)
minSize:對象最小大小,小于該值的對象被忽略。
maxSize:最大可能的對象大小,大于這個值的對象被忽略
該函數(shù)與TBB庫并行
*/
CV_WRAP void detectMultiScale( InputArray image,
CV_OUT std::vector<Rect>& objects,
double scaleFactor = 1.1,
int minNeighbors = 3, int flags = 0,
Size minSize = Size(),
Size maxSize = Size() );
/**
detectMultiScale重載函數(shù)
參數(shù):
image:包含檢測對象的圖像的CV_8U類型矩陣
objects: 矩形的向量,其中每個矩形包含被檢測的對象,矩形可以部分位于原始圖像之外
numDetections: 對應(yīng)對象的檢測編號向量。一個物體被探測到的次數(shù)是相鄰的被積極分類的矩形的數(shù)量,這些矩形被連接在一起形成物體
scaleFactor: 指定在每個圖像縮放時的縮放比例
minNeighbors:指定每個候選矩形需要保留多少個相鄰矩形
flags:含義與函數(shù)cvHaarDetectObjects中的舊級聯(lián)相同。它不用于新的級聯(lián)
minSize:對象最小大小,小于該值的對象被忽略。
maxSize:最大可能的對象大小,大于這個值的對象被忽略
*/
CV_WRAP_AS(detectMultiScale2) void detectMultiScale( InputArray image,
CV_OUT std::vector<Rect>& objects,
CV_OUT std::vector<int>& numDetections,
double scaleFactor=1.1,
int minNeighbors=3, int flags=0,
Size minSize=Size(),
Size maxSize=Size() );
/**
detectMultiScale重載函數(shù),此函數(shù)允許您檢索分類的最終階段決策確定性
為此,需要將' outputRejectLevels '設(shè)置為true,并提供' rejectLevels '和' levelWeights '參數(shù)。
對于每一個結(jié)果檢測,‘levelWeights’將在最后階段包含分類的確定性。
這個值可以用來區(qū)分強(qiáng)分類和弱分類。
具體使用示例代碼
Mat img;
vector<double> weights;
vector<int> levels;
vector<Rect> detections;
CascadeClassifier model("/path/to/your/model.xml");
model.detectMultiScale(img, detections, levels, weights, 1.1, 3, 0, Size(), Size(), true);
cerr << "Detection " << detections[0] << " with weight " << weights[0] << endl;
*/
CV_WRAP_AS(detectMultiScale3) void detectMultiScale( InputArray image,
CV_OUT std::vector<Rect>& objects,
CV_OUT std::vector<int>& rejectLevels,
CV_OUT std::vector<double>& levelWeights,
double scaleFactor = 1.1,
int minNeighbors = 3, int flags = 0,
Size minSize = Size(),
Size maxSize = Size(),
bool outputRejectLevels = false );
CV_WRAP bool isOldFormatCascade() const;
CV_WRAP Size getOriginalWindowSize() const;
CV_WRAP int getFeatureType() const;
void* getOldCascade();
CV_WRAP static bool convert(const String& oldcascade, const String& newcascade);
void setMaskGenerator(const Ptr<BaseCascadeClassifier::MaskGenerator>& maskGenerator);
Ptr<BaseCascadeClassifier::MaskGenerator> getMaskGenerator();
Ptr<BaseCascadeClassifier> cc;
};
(2)detectMultiScale
調(diào)用分類器內(nèi)部的成員函數(shù)detectMultiScale(多尺度檢測)
參數(shù)1:image--待檢測圖片,一般為灰度圖像加快檢測速度;
參數(shù)2:objects--被檢測物體的矩形框向量組;
參數(shù)3:scaleFactor--表示在前后兩次相繼的掃描中,搜索窗口的比例系數(shù)。默認(rèn)為1.1即每次搜索窗口依次擴(kuò)大10%;
參數(shù)4:minNeighbors--表示構(gòu)成檢測目標(biāo)的相鄰矩形的最小個數(shù)(默認(rèn)為3個),每一個人臉至少要檢測到多少次才算是真的人臉。
如果組成檢測目標(biāo)的小矩形的個數(shù)和小于 min_neighbors - 1 都會被排除。
如果min_neighbors 為 0, 則函數(shù)不做任何操作就返回所有的被檢候選矩形框,
這種設(shè)定值一般用在用戶自定義對檢測結(jié)果的組合程序上;
參數(shù)5:flags--要么使用默認(rèn)值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果設(shè)置為
CV_HAAR_DO_CANNY_PRUNING,那么函數(shù)將會使用Canny邊緣檢測來排除邊緣過多或過少的區(qū)域,因此這些區(qū)域通常不會是人臉?biāo)趨^(qū)域;決定是縮放分類器來檢測,還是縮放圖像。
參數(shù)6、7:minSize和maxSize用來限制得到的目標(biāo)區(qū)域的范圍。表示人臉的最大最小尺寸。
參數(shù)3設(shè)置測試情況,參考網(wǎng)上的,目測沒啥卵用。:
傳1.1:400張測試圖片識別率為85.3%。
傳1.2:400張測試圖片識別率為78.7%。
傳1.05: 400張測試圖片識別率為94.0%。
#define CV_HAAR_DO_CANNY_PRUNING 1 //這個值告訴分類器跳過平滑(無邊緣)區(qū)域
#define CV_HAAR_SCALE_IMAGE 2 //這個值告訴分類器不要縮放分類器,而是縮放圖像
#define CV_HAAR_FIND_BIGGEST_OBJECT 4 //告訴分類器只返回最大的目標(biāo)
#define CV_HAAR_DO_ROUGH_SEARCH 8 //它只能和上面一個參數(shù)一起使用,告訴分類器在任何窗口,只要第一個候選者被發(fā)現(xiàn)則結(jié)束搜尋。
在OpenCV的安裝目錄下的sources文件夾里的data文件夾里可以看到,文件夾的名字“haarcascades”、“hogcascades”和“l(fā)bpcascades”分別表示通過“haar”、“hog”和“l(fā)bp”三種不同的特征而訓(xùn)練出的分類器:即各文件夾里的文件。"haar"特征主要用于人臉檢測,“hog”特征主要用于行人檢測,“l(fā)bp”特征主要用于人臉識別。
(3)rect
如果創(chuàng)建一個Rect對象rect(100, 50, 50, 100),那么rect會有以下幾個功能:
rect.area(); //返回rect的面積 5000
rect.size(); //返回rect的尺寸 [50 × 100]
rect.tl(); //返回rect的左上頂點的坐標(biāo) [100, 50]
rect.br(); //返回rect的右下頂點的坐標(biāo) [150, 150]
rect.width(); //返回rect的寬度 50
rect.height(); //返回rect的高度 100
rect.contains(Point(x, y)); //返回布爾變量,判斷rect是否包含Point(x, y)點
通過faces.size()來判斷是否檢測到人臉,返回0說明未檢測到人臉,返回1表示檢測到一個人臉。依次類推,通過該參數(shù)返回判斷檢測到的人臉個數(shù)。
1.5 人臉識別
(1)FaceRecognizer
人臉識別的類FaceRecognizer
class FaceRecognizer : public Algorithm
{
public:
//! virtual destructor
virtual ~FaceRecognizer() {}
// Trains a FaceRecognizer.
virtual void train(InputArray src, InputArray labels) = 0;
// Updates a FaceRecognizer.
virtual void update(InputArrayOfArrays src, InputArray labels);
// Gets a prediction from a FaceRecognizer.
virtual int predict(InputArray src) const = 0;
// Predicts the label and confidence for a given sample.
virtual void predict(InputArray src, int &label, double &confidence) const = 0;
// Serializes this object to a given filename.
virtual void save(const string& filename) const;
// Deserializes this object from a given filename.
virtual void load(const string& filename);
// Serializes this object to a given cv::FileStorage.
virtual void save(FileStorage& fs) const = 0;
// Deserializes this object from a given cv::FileStorage.
virtual void load(const FileStorage& fs) = 0;
// Sets additional information as pairs label - info.
void setLabelsInfo(const std::map<int, string>& labelsInfo);
// Gets string information by label
string getLabelInfo(const int &label);
// Gets labels by string
vector<int> getLabelsByString(const string& str);
};
(2)opencv Ptr
OpenCV中使用的智能指針。類似于std::smart_ptr,但是在OpenCV中可以用Ptr輕松管理各種類型的指針。
//可以用Ptr<MyObjectType> ptr代替MyObjectType* ptr,MyObjectType可以是C的結(jié)構(gòu)體或C++的類,
智能指針有空再研究吧。
OpenCV筆記(Ptr) - fireae - 博客園 https://www.cnblogs.com/fireae/p/3684915.html
C++11中智能指針的原理、使用、實現(xiàn) - wxquare - 博客園 https://www.cnblogs.com/wxquare/p/4759020.html
(3)目前支持的三種算法
Eigenfaces 特征臉createEigenFaceRecognizer()
Fisherfaces createFisherFaceRecognizer()
LocalBinary Patterns Histograms局部二值直方圖 createLBPHFaceRecognizer()
(4)predict的用法
方法1:
int predict = 0;
predict = model->predict(compImage);
將照片傳入predict接口,會返回訓(xùn)練照片中最接近的照片標(biāo)簽值。train(images,labels);labels這個容器包含了所有訓(xùn)練照片的標(biāo)簽。
通過返回的標(biāo)簽值與自己預(yù)設(shè)司機(jī)的標(biāo)簽進(jìn)行比對,如果不一樣就顯示人臉識別失敗。
方法2:
通過閥值設(shè)定來控制人臉識別準(zhǔn)確度。
在創(chuàng)建人臉識別算法的過程中也可以將閥值傳遞進(jìn)去
model = createEigenFaceRecognizer(0, SIMILAR_VALUE);
這樣的話predict接口就不會返回有效數(shù)據(jù)。
如果創(chuàng)建過程中不添加。
之后可以通過對返回的predictLabel和predictConfidence進(jìn)行判斷是否人臉識別成功。
int predictLabel = -1;
double predictConfidence = 0.0;
model->predict(compImage, predictLabel, predictConfidence);
if (predictLabel == targetFaceIndex && predictConfidence <= SIMILAR_VALUE) {
cout<<"[cwr] Face recognition successful"<<endl;
s_faceRecognitionSuc++;
} else {
cout<<"[cwr] Face recognition fail"<<endl;
s_faceRecognitionFail++;
}
官網(wǎng)說明
FaceRecognizer — OpenCV 2.4.13.7 documentation https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_api.html#facerecognizer-predict
FaceRecognizer::predict
C++: int FaceRecognizer::predict(InputArray src) const = 0
C++: void FaceRecognizer::predict(InputArray src, int& label, double& confidence) const = 0
Predicts a label and associated confidence (e.g. distance) for a given input image.
The suffix const means that prediction does not affect the internal model state, so the method can be safely called from within different threads.
The following example shows how to get a prediction from a trained model:
using namespace cv;
// Do your initialization here (create the cv::FaceRecognizer model) ...
// ...
// Read in a sample image:
Mat img = imread("person1/3.jpg", CV_LOAD_IMAGE_GRAYSCALE);
// And get a prediction from the cv::FaceRecognizer:
int predicted = model->predict(img);
Or to get a prediction and the associated confidence (e.g. distance):
using namespace cv;
// Do your initialization here (create the cv::FaceRecognizer model) ...
// ...
Mat img = imread("person1/3.jpg", CV_LOAD_IMAGE_GRAYSCALE);
// Some variables for the predicted label and associated confidence (e.g. distance):
int predicted_label = -1;
double predicted_confidence = 0.0;
// Get the prediction and associated confidence from the model
model->predict(img, predicted_label, predicted_confidence);