之前的一篇文章,已经给大家介绍了SeetaFace的基础运用--人脸检测,这仅仅只是第一步,我们最关键的地方还是使用这个模型来进行人脸识别,下面就介绍一下这个模型后续的一些模块的功能和运用。
其中第二步应该是进行人脸相似度的比对实验,这个比较好实现,只要之前第一步实现了,这个还是很简单的,有兴趣的同学可以参照这篇文章(SeetaFace教程(二)封装常用功能:提取一幅图像中的人脸特征、计算特征相似性)进行操作
其实这个实验也可以按照知乎作者@何之源的第三篇介绍文档来操作,但不知哪儿飘来的bug,让熟练找bug的我到最后也不知道调试bug是哪儿来的。于是后面就转换了另一个实现。下面介绍一下我在以上作者基础上实验中踩的坑,不感兴趣的可以直接跳过看后面的内容。
一切按照作者的描述配置好以后,我自己也根据依赖再次检查一遍之后,感觉没问题,那就跑咯
第一次踩坑:无法遍历图片文件,明明有文件,却检测到0个对象
解决:直接debug,发现好像文件路径不对,直接放绝对路径!再跑!
第二次踩坑:换绝对路径后运行时抛异常!。。。这个bug思考良久,之间有很多尝试
解决:最后发现是win10下下面代码中while 循环体当中的_findnext(hFile, &fileinfo) == 0函数有毛病,它的第一个参数要改为long long类型的。。。
bool GetFilenameUnderPath(string file_path, std::vector& files) {
long long hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(file_path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//如果是目录,迭代之
//如果不是,加入列表
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
GetFilenameUnderPath(p.assign(file_path).append("\\").append(fileinfo.name), files);
}
else
{
char *ext = strrchr(fileinfo.name, '.');
if (ext) {
ext++;
if (_stricmp(ext, "jpg") == 0 || _stricmp(ext, "png") == 0)
files.push_back(p.assign(file_path).append("\\").append(fileinfo.name));
}
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
return true;
}
第三次踩坑:获取图片以及读取图片特征的时候,抛异常!
未解决:再次debug,发现每次能够读取文件夹当中的第一张图片,第二张就不行了,开始想着是不是没有释放变量,导致堆内对象的冲突,看了看,应该不是,那个函数调用完以后就结束了生命周期,不存在没有回收的情况,而后。。。吃饭去了,懒得找了,找了另外一个项目,功能一样,代码似乎跟这个差不多,没细看,能跑就好了,耐心没了(-)
正式内容
参考文章:传送门
之前第一次跑的没问题的话,可以直接按照这后面的介绍来:
总体步骤
新建win32控制台项目,此时需要opencv,我在测试时使用的是opencv2.4.10,其他的应该也行。
① 将项目属性改为x64
② 在配置属性-VC++目录中,将opencv的包含目录和库目录修改好,记得使用x64的lib库
③ 链接器-输入,加入FaceIdentification.lib FaceAlignment.lib FaceDetection.lib opencv_core2410.lib opencv_highgui2410.lib opencv_imgproc2410.lib
④ 与这6个lib文件相对于的dll文件放入可执行文件目录下即可。
⑤ 将seetaface源码中的三个model,放在一起,存于项目文件夹下即可。images文件夹中为一些测试图片
两个头文件:
- FaceRecognition.h
#ifndef FACERECOGNITION_H
#define FACERECOGNITION_H
#include
#include
using std::string;
#include
#include
#include "face_detection.h"
#include "face_alignment.h"
#include "face_identification.h"
class Detector : public seeta::FaceDetection {
public:
Detector(const char * model_name);
};
class FaceRecognition {
public:
FaceRecognition();
float* NewFeatureBuffer();
float FeatureCompare(float* feat1, float* feat2);
int GetFeatureDims();
bool GetFeature(string filename, float* feat);
~FaceRecognition();
public:
Detector* detector;
seeta::FaceAlignment* point_detector;
seeta::FaceIdentification* face_recognizer;
};
#endif // !FACERECOGNITION_H
- FaceGroup.h
#ifndef FEATUREGROUP_H
#define FEATUREGROUP_H
#include
#include
#include
#include
#include "FaceRecognition.h"
struct Feature
{
std::string filename;
float* data;
float similarity_with_goal;
friend bool operator < (Feature f1, Feature f2)
{
return f1.similarity_with_goal < f2.similarity_with_goal;
}
};
class FeatureGroup
{
public:
FeatureGroup(int feat_dims, FaceRecognition* fr);
FeatureGroup(string model_file, FaceRecognition* fr);
bool AddFeature(float* feat, string filename);
bool SaveModel(string model_file);
int GetFeatureDims();
bool FindTopK(int k, float* feat, std::vector& result);
~FeatureGroup();
public:
std::vector features;
private:
int feat_dims;
FaceRecognition* fr;
};
#endif //FEATUREGROUP_H
三个CPP文件
- FaceRecognition.cpp
#include "FaceRecognition.h"
Detector::Detector(const char* model_name) :seeta::FaceDetection(model_name)
{
this->SetMinFaceSize(40);
this->SetScoreThresh(2.f);
this->SetImagePyramidScaleFactor(0.8f);
this->SetWindowStep(4, 4);
}
FaceRecognition::FaceRecognition()
{
this->detector = new Detector("model/seeta_fd_frontal_v1.0.bin");
this->point_detector = new seeta::FaceAlignment("model/seeta_fa_v1.1.bin");
this->face_recognizer = new seeta::FaceIdentification("model/seeta_fr_v1.0.bin");
}
float* FaceRecognition::NewFeatureBuffer()
{
return new float[this->face_recognizer->feature_size()];
}
int FaceRecognition::GetFeatureDims()
{
return this->face_recognizer->feature_size();
}
float FaceRecognition::FeatureCompare(float* feat1, float* feat2)
{
return this->face_recognizer->CalcSimilarity(feat1, feat2);
}
bool FaceRecognition::GetFeature(string filename, float* feat)
{
//load image and convert to gray
cv::Mat src_img_color = cv::imread(filename, 1);
cv::Mat src_img_gray;
cv::cvtColor(src_img_color, src_img_gray, CV_BGR2GRAY);
//convert to ImageData type
seeta::ImageData src_img_data_color(src_img_color.cols, src_img_color.rows, src_img_color.channels());
src_img_data_color.data = src_img_color.data;
seeta::ImageData src_img_data_gray(src_img_gray.cols, src_img_gray.rows, src_img_gray.channels());
src_img_data_gray.data = src_img_gray.data;
//Detect faces
std::vector faces = this->detector->Detect(src_img_data_gray);
int32_t face_num = static_cast(faces.size());
if (face_num == 0)
{
std::cout << "Faces are not detected." << std::endl;
return false;
}
//Detect 5 facial landmarks
seeta::FacialLandmark points[5];
this->point_detector->PointDetectLandmarks(src_img_data_gray, faces[0], points);
//Extract face identity feature
this->face_recognizer->ExtractFeatureWithCrop(src_img_data_color, points, feat);
return true;
}
FaceRecognition::~FaceRecognition()
{
if (detector)
delete detector;
if (point_detector)
delete point_detector;
if (face_recognizer)
delete face_recognizer;
}
- FeatureGroup.cpp
#include "FeatureGroup.h"
FeatureGroup::FeatureGroup(int feat_dims, FaceRecognition* fr)
{
this->feat_dims = feat_dims;
this->fr = fr;
}
FeatureGroup::FeatureGroup(string model_file, FaceRecognition* fr)
{
std::ifstream file;
file.open(model_file);
int size;
float* new_feat;
char* buffer = new char[1000];
//从模型读取数据
file >> size;
file >> this->feat_dims;
for (int i = 0; i < size; i++)
{
Feature tmp;
file.getline(buffer, 1000);
while (buffer[0] == '\0' || buffer[0] == ' ')
{
file.getline(buffer, 1000);
}
tmp.filename = buffer;
new_feat = new float[this->feat_dims];
for (int j = 0; j < this->feat_dims; j++)
{
file >> new_feat[j];
}
tmp.data = new_feat;
this->features.push_back(tmp);
}
file.close();
this->fr = fr;
}
int FeatureGroup::GetFeatureDims()
{
return this->feat_dims;
}
bool FeatureGroup::AddFeature(float* feat, string filename)
{
Feature tmp;
float* new_feat = new float[this->feat_dims];
//memcpy(new_feat, feat, sizeof(new_feat) * this->feat_dims);// 这句话执行有问题,改成下面的循环
for (int i = 0; i < this->feat_dims; i++)
{
new_feat[i] = feat[i];
}
tmp.data = new_feat;
tmp.filename = filename;
this->features.push_back(tmp);
return true;
}
bool FeatureGroup::SaveModel(string model_file)
{
std::ofstream file;
file.open(model_file);
file << int(this->features.size()) << std::endl; //先输出特征的个数
file << this->feat_dims << std::endl; //再输出特征的维数
//依次写入数据
for (int i = 0; i < int(this->features.size()); i++)
{
file << this->features[i].filename << std::endl;
for (int j = 0; j < this->feat_dims; j++)
{
file << this->features[i].data[j] << " ";
}
file << std::endl;
}
file.close();
return true;
}
bool FeatureGroup::FindTopK(int k, float* feat, std::vector& result)
{
std::cout << "Calculating Similarities..." << std::endl;
for (int i = 0; i < int(this->features.size()); i++)
{
this->features[i].similarity_with_goal = this->fr->FeatureCompare(this->features[i].data, feat);
}
std::cout << "Finding Topk..." << std::endl;
std::priority_queue q;
for (int i = 0; i < int(this->features.size()); i++)
{
q.push(this->features[i]);
}
for (int i = 0; i < k; i++)
{
if (q.empty())
return true;
result.push_back(q.top());
q.pop();
}
return true;
}
FeatureGroup::~FeatureGroup() //如果出错,再回来修改
{
for (int i = 0; i < int(this->features.size()); i++)
{
delete[](this->features[i].data);
}
}
- main.cpp
#include "FaceRecognition.h"
#include "FeatureGroup.h"
/*
* given two images and calculate the similarity
*/
int faceverification()
{
FaceRecognition fr;
float* feat1 = fr.NewFeatureBuffer();
float* feat2 = fr.NewFeatureBuffer();
std::vector> pic_pair;
pic_pair.push_back(std::make_pair("0_1.jpg", "0_2.jpg"));
pic_pair.push_back(std::make_pair("1_1.jpg", "1_2.jpg"));
pic_pair.push_back(std::make_pair("2_1.jpg", "2_2.jpg"));
double averageDetectTime = 0.0;
for (size_t i = 0; i < pic_pair.size(); i++)
{
long t0 = cv::getTickCount();
fr.GetFeature(pic_pair[i].first, feat1);
fr.GetFeature(pic_pair[i].second, feat2);
float sim = fr.FeatureCompare(feat1, feat2);
std::cout << "similarity:" << sim << std::endl;
long t1 = cv::getTickCount();
double secs = (t1 - t0) / cv::getTickFrequency();
averageDetectTime += secs;
std::cout << "Image pair " << i << " takes " << secs << " seconds to detect,alignment and verification !" << std::endl;;
}
std::cout << std::endl << "All verification takes " << averageDetectTime << " secs!" << std::endl;
std::cout << "The average verification times for one image takes " << double(averageDetectTime / pic_pair.size() / 2) << " secs !" << std::endl;
delete feat1;
delete feat2;
return 0;
}
bool GetAllFileNames(string file_path, std::vector& files) {
intptr_t hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(file_path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//如果是目录,迭代之
//如果不是,加入列表
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
GetAllFileNames(p.assign(file_path).append("\\").append(fileinfo.name), files);
}
else
{
char *ext = strrchr(fileinfo.name, '.');
if (ext) {
ext++;
if (_stricmp(ext, "jpg") == 0 || _stricmp(ext, "png") == 0)
files.push_back(p.assign(file_path).append("\\").append(fileinfo.name));
}
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
return true;
}
int main(int argc, char* argv[])
{
int choice;
std::cout << "Input 1 to build database index, 2 to test image: ";
std::cin >> choice;
if (1 == choice)
{
std::vector filenames;
GetAllFileNames("./images", filenames);
std::cout << "Detected " << filenames.size() << " images..." << std::endl;
FaceRecognition fr;
FeatureGroup fg(fr.GetFeatureDims(), &fr);
float * feat = fr.NewFeatureBuffer();
double averageExtractFeatureTime = 0.0;
for (int i = 0; i < filenames.size(); i++)
{
long t0 = cv::getTickCount();
if (fr.GetFeature(filenames[i], feat))
{
fg.AddFeature(feat, filenames[i]);
}
long t1 = cv::getTickCount();
double secs = (t1 - t0) / cv::getTickFrequency();
averageExtractFeatureTime += secs;
if ((i + 1) % 5 == 0)
{
std::cout << i + 1 << " / " << int(filenames.size()) << " has been extracted!" << std::endl;
}
}
std::cout << std::endl << "All verification takes " << averageExtractFeatureTime << " secs!" << std::endl;
std::cout << "The average extract feature times for one image takes " << double(averageExtractFeatureTime / filenames.size() / 2) << " secs !" << std::endl;
fg.SaveModel("feature.index");
std::cout << "Feature Extraction has been finished!" << std::endl;
}
else if (2 == choice)
{
FaceRecognition fr;
string pic_file_path;
std::cout << "Loading DataBase..." << std::endl;
FeatureGroup fg("feature.index", &fr);
std::cout << "Database has been loaded!" << std::endl;
float * feat = fr.NewFeatureBuffer();
while (true)
{
std::vector result;
std::cout << "please input your filename:";
std::cin >> pic_file_path;
if (!fr.GetFeature(pic_file_path, feat))
{
std::cout << "Wrong Filename or Can't detect face..." << std::endl;
continue;
}
fg.FindTopK(10, feat, result);
for (int i = 0; i < result.size(); i++)
{
std::cout << "Top " << i + 1 << " : " << result[i].filename << " Similarity: " << result[i].similarity_with_goal << std::endl;
}
}
std::cout << std::endl;
}
return 0;
}
大体文件结构如下
其中有些冗余的东西,但大体如下
测试
images文件夹下如下图所示,提取七个人的特征,最后做个相似度排行
测试图片如下:
输出结果:
有人故意拿比较难辩别的图片做了测试,效果还是很好的,具体我没做测试,有兴趣的童鞋可以去探究一下