公司新闻

SeetaFace教程(三)制作Demo:明星脸相似度测试

记得很久之前百度魔图有一个计算你和哪个明星最像的功能,还是挺有趣的,现在我们可以用SeetaFace来实现这个功能,其中对SeetaFace库的调用就使用教程(二)中封装的类。


在新建工程之前,我们首先需要一个明星面部的数据库。在网上找了一段时间,并没有发现现成的可以下载。后来看到了百度的这个页面:

感觉这些图片看起来合适,于是就写了个python脚本爬了一下,大概爬了10000+的图片。如果有人需要这个明星图片数据,可以在这里下载:链接: pan.baidu.com/s/1slVIBd 密码: x6s5


我们新建一个项目,配好环境,在项目根目录新建一个images文件夹,然后把所有爬到的图片复制进去。


我们的程序要有两个部分,第一要把所有图片的特征提取出来,并保存成一个文件,第二就是根据一张新的图片去找最相近的Top K个明星。


为了达到这个目的,我们先把原始的float数组特征封装成结构体Feature,主要是添加了图片所在位置的信息。然后将多个Feature封装成FeatureGroup类,主要是为了实现可以保存到文件,再从文件恢复,以及找寻Top K三个功能。


Feature结构的声明,filename是这个特征对应的图片位置,data保存真正的特征,similarity_with_goal主要是为了后面找最相近的Top K使用:

struct Feature{
	string filename;
	float* data;
	float similarity_with_goal;
	friend bool operator< (Feature f1, Feature f2){
        return f1.similarity_with_goal < f2.similarity_with_goal;
    }
};

再看FeatureGroup类的声明:

class FeatureGroup{
public:
	FeatureGroup(int feat_dims, SeetaFace* seeta_face);
	FeatureGroup(string model_file, SeetaFace* seeta_face);
	std::vector<Feature> feats;
	bool AddFeature(float* feat, string filename);
	bool SaveModel(string model_file);
	int GetFeatureDims();
	bool FindTopK(int k, float* feat, std::vector<Feature>& result);
	~FeatureGroup();
private:
	int feat_dims;
	SeetaFace* seeta_face;
};

bool SaveModel(string model_file);是把所有特征保存起来。而构造函数FeatureGroup(string model_file, SeetaFace* seeta_face);可以从文件中恢复。


std::vector<Feature> feats;存放所有的特征。使用AddFeature添加特征,使用FindTopK找寻和目标图片特征最像的K张图片。


具体实现在文末贴出。


程序编译运行后,输入1会检测项目目录images下所有图片,并生成所有图片中的人脸特征。


注意:如果使用Debug模式生成,速度会非常慢。建议从头开始用release模式编译所有库,并在release模式下生成,速度会大大提高。


程序运行结束后,会自动生成test.index文件保存所有特征。


现在就可以找一些图片来测试了,这里为了方便起见,还是直接用明星自己的图片来测试。


随便找了一张梁朝伟的图片:


程序运行后选择2,加载数据库完成后输入图片路径测试,检测成功了:


我们再找一张梁朝伟的图片来检测,这次的图片略微夸张:


也检测出来了,说明还是比较靠谱的!



下面粘贴出所有代码供参考:


SeetaFace.h文件:


#include <string>
#include<iostream>
#include<vector>
using std::string;
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include "face_detection.h"
#include "face_alignment.h"
#include "face_identification.h"
#include <fstream>
#include <io.h>
#include<queue>
class Detector : public seeta::FaceDetection{
public:
	Detector(const char * model_name);
};


class SeetaFace{
public:
	SeetaFace();
	Detector* detector; 
	seeta::FaceAlignment* point_detector; 
	seeta::FaceIdentification* face_recognizer;
	bool GetFeature(string filename, float* feat);
	float* NewFeatureBuffer();
	float FeatureCompare(float* feat1, float* feat2);
	int GetFeatureDims();


};


struct Feature{
	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, SeetaFace* seeta_face);
	FeatureGroup(string model_file, SeetaFace* seeta_face);
	std::vector<Feature> feats;
	bool AddFeature(float* feat, string filename);
	bool SaveModel(string model_file);
	int GetFeatureDims();
	bool FindTopK(int k, float* feat, std::vector<Feature>& result);
	~FeatureGroup();
private:
	int feat_dims;
	SeetaFace* seeta_face;
};

SeetaFace.cpp文件:


#include "SeetaFace.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);
}


SeetaFace::SeetaFace(){
	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* SeetaFace::NewFeatureBuffer(){
	return new float[this->face_recognizer->feature_size()];
}


bool SeetaFace::GetFeature(string filename, float* feat){
	//如果有多张脸,输出第一张脸,返回true
	//如果没有脸,输出false
	//read pic greyscale
	cv::Mat src_img = cv::imread(filename, 0);
    seeta::ImageData src_img_data(src_img.cols, src_img.rows, src_img.channels());
    src_img_data.data = src_img.data;


	//read pic color
	cv::Mat src_img_color = cv::imread(filename, 1);
	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;


	std::vector<seeta::FaceInfo> faces = this->detector->Detect(src_img_data);
	int32_t face_num = static_cast<int32_t>(faces.size());
	if (face_num == 0)
	{
	    return false;
	}
	seeta::FacialLandmark points[5];
	this->point_detector->PointDetectLandmarks(src_img_data, faces[0], points);


	this->face_recognizer->ExtractFeatureWithCrop(src_img_data_color, points, feat);


	return true;
};


int SeetaFace::GetFeatureDims(){
	return this->face_recognizer->feature_size();
}


float SeetaFace::FeatureCompare(float* feat1, float* feat2){
	return this->face_recognizer->CalcSimilarity(feat1, feat2);
}


FeatureGroup::FeatureGroup(int feat_dims, SeetaFace* seeta_face){
	this->feat_dims = feat_dims;
	this->seeta_face = seeta_face;
}


int FeatureGroup::GetFeatureDims(){
	return this->feat_dims;
}


bool FeatureGroup::AddFeature(float* feat, string filename){
	Feature temp;
	float* new_feat = new float[this->feat_dims];
	memcpy(new_feat, feat, sizeof(new_feat)*this->feat_dims);
	temp.data=new_feat;
	temp.filename=filename;
	this->feats.push_back(temp);
	return true;
}


bool FeatureGroup::SaveModel(string model_file){
	std::ofstream file;
	file.open(model_file);
	file<<int(this->feats.size())<<std::endl;
	file<<this->feat_dims<<std::endl;
	for (int i=0; i<int(this->feats.size()); i++){
		file<<this->feats[i].filename<<std::endl;
		for(int j=0;j<this->feat_dims;j++)
			file<<this->feats[i].data[j]<<" ";
		file<<std::endl;
	}
	file.close();
	return true;
}


FeatureGroup::FeatureGroup(string model_file, SeetaFace* seeta_face){
	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 temp;
		file.getline(buffer, 1000);
		while (buffer[0]=='\\0' || buffer[0]==' '){
			file.getline(buffer, 1000);
		}
		temp.filename=buffer;
		new_feat = new float[this->feat_dims];
		for(int j=0;j<this->feat_dims;j++)
			file>>new_feat[j];
		temp.data=new_feat;
		this->feats.push_back(temp);
	}
	file.close();
	this->seeta_face = seeta_face;
}


FeatureGroup::~FeatureGroup(){
	for (int i=0; i<int(this->feats.size()); i++)
		delete [](this->feats[i].data);
}


bool FeatureGroup::FindTopK(int k, float* feat, std::vector<Feature>& result){
	std::cout<<"Calculating Similarities..."<<std::endl;
	for (int i=0; i<int(this->feats.size()); i++){
		this->feats[i].similarity_with_goal = this->seeta_face->FeatureCompare(this->feats[i].data, feat);
	}
	std::cout<<"Finding TopK..."<<std::endl;
	std::priority_queue<Feature> q;
	for (int i=0; i<int(this->feats.size()); i++)
		q.push(this->feats[i]);
	for (int i=0;i<k;i++){
		if(q.empty()) return true;
		result.push_back(q.top());
		q.pop();
	}
	return 0;
}


bool GetFilenameUnderPath(string file_path, std::vector<string>& files){
    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;
}


int main(int argc, char* argv[]){
	int choice;
	std::cout<<"Input 1 to Build Database Index, 2 to Test Image:"<<std::endl;
	std::cin>>choice;


	if(choice==1){
		std::vector<string> filenames;
		GetFilenameUnderPath(".\\\\images", filenames);
		std::cout<<"Detected  "<<filenames.size()<<"  images...."<<std::endl; 
		SeetaFace sf;
		FeatureGroup feature_group(sf.GetFeatureDims(), &sf);
		float* feat=sf.NewFeatureBuffer();
		for (int i=0; i<int(filenames.size());i++){
			if (sf.GetFeature(filenames[i], feat))
				feature_group.AddFeature(feat, filenames[i]);
			if ((i+1) % 1 == 0)
				std::cout<<(i+1)<<" / "<<int(filenames.size())<<std::endl;
		}
		feature_group.SaveModel("test.index");


		std::cout<<"Finished."<<std::endl;
	}else{
		SeetaFace sf;
		string pic_file;
		std::cout<<"Loading Database..."<<std::endl;
		FeatureGroup feature_group("test.index", &sf);
		float* feat=sf.NewFeatureBuffer();
		while(true){
			std::vector<Feature> result;
			std::cout<<"Please Input Your Filename:  ";
			std::cin>>pic_file;
			if(sf.GetFeature(pic_file, feat)==false){
				std::cout<<"Wrong Filename or Can't Detect Face.."<<std::endl;
				continue;
			}
			feature_group.FindTopK(10, feat, result);
			for (int i=0;i<int(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;
}

平台注册入口