face 3000 C++ 代码理解

一、代码调试

首先是训练阶段,右击属性中,选择调试,将命令参数的第一个参数设为 train
在这里插入图片描述
运行 main 函数,报错,
在这里插入图片描述
发现 argc 的值为 0,如图:
在这里插入图片描述
正常的值应该为 3,如图:
在这里插入图片描述
因为现在是训练阶段,所以从 train 函数开始找错误
在这里插入图片描述
果然 LoadImage 失败了,按 F12,跳转到 LoadImage() 函数,发现提供的图片路径与所需要的图片路径不符
提供的图片路径为:
在这里插入图片描述
而所需要的图片路径为
在这里插入图片描述
将路径改为正确路径:
在这里插入图片描述
再次运行,运行成功
在这里插入图片描述

二、train

void Train(const char* ModelName){
	std::vector<cv::Mat_<uchar> > images;
	std::vector<cv::Mat_<double> > ground_truth_shapes;
	std::vector<BoundingBox> bboxes;
    std::string file_names = "./../dataset/w300/train_test_jpgs.txt";
    // train_jpgs.txt contains all the paths for each image, one image per line
    // for example: in Linux you can use ls *.jpg > train_jpgs.txt to get the paths
    // the file looks like as below
    /*
    	1.jpg
    	2.jpg
    	3.jpg
    	...
    	1000.jpg
    */
	std::vector<std::string> names;
	//加载图片并检测人脸
	LoadImages(images, ground_truth_shapes, bboxes,names, file_names);

	Parameters params;
    params.local_features_num_ = 300;
	params.landmarks_num_per_face_ = 68;
    params.regressor_stages_ = 2;
	params.local_radius_by_stage_.push_back(0.4);
    params.local_radius_by_stage_.push_back(0.3);
    params.local_radius_by_stage_.push_back(0.2);
	params.local_radius_by_stage_.push_back(0.1);
    params.local_radius_by_stage_.push_back(0.08);
    params.local_radius_by_stage_.push_back(0.05);
	params.local_radius_by_stage_.push_back(0.05);
	params.local_radius_by_stage_.push_back(0.05);
	params.local_radius_by_stage_.push_back(0.05);
	params.local_radius_by_stage_.push_back(0.05);
	params.local_radius_by_stage_.push_back(0.05);
    params.tree_depth_ = 5;
    params.trees_num_per_forest_ = 8;
    params.initial_guess_ = 5;//扩充数据集的参数,即将每张图片扩充为几张图片

	//计算所有加载的训练图片的平均脸
	params.mean_shape_ = GetMeanShape(ground_truth_shapes, bboxes);
	CascadeRegressor cas_reg;
	//开始训练
	cas_reg.Train(images, ground_truth_shapes, bboxes, params);

	cas_reg.SaveCascadeRegressor(ModelName);
	return;
}

1.加载图片并检测人脸

//加载图片
void LoadImages(std::vector<cv::Mat_<uchar> >& images,
	std::vector<cv::Mat_<double> >& ground_truth_shapes,
	//const std::vector<cv::Mat_<double> >& current_shapes,
	std::vector<BoundingBox>& bboxes,
	std::vector<std::string>& names,
	std::string file_names){
	std::string fn_haar = "./../haarcascade_frontalface_alt2.xml";
	cv::CascadeClassifier haar_cascade;
	bool yes = haar_cascade.load(fn_haar);
	std::cout << "detector: " << yes << std::endl;
	std::cout << "loading images\n";
	std::ifstream fin;
	fin.open(file_names.c_str(), std::ifstream::in);
	// train_jpgs.txt contains all the paths for each image, one image per line
    // for example: in Linux you can use ls *.jpg > train_jpgs.txt to get the paths
    // the file looks like as below
    /*
    	1.jpg
    	2.jpg
    	3.jpg
    	...
    	1000.jpg
    */
	
	std::string name;
	int count = 0;//初始化从图片中检测到的符合要求的人脸数量
	//std::cout << name << std::endl;
	while (fin >> name){
		//读取图片
		std::cout << "reading file: " << name << std::endl;
		//std::cout << name << std::endl;
		std::string pts = name.substr(0, name.length() - 3) + "pts";
		//将读取的图片存入image
        cv::Mat_<uchar> image = cv::imread(("./../dataset/w300/trainset/" + name).c_str(), 0);
		//std::cout<<"image.size"<<image.size()<<std::endl;输出image.size(),发现每张image的尺寸都不一样
		//获取加载图片的ground_truth_shape
        cv::Mat_<double> ground_truth_shape = LoadGroundTruthShape(("./../dataset/w300/trainset/" + pts).c_str());
        std::vector<cv::Rect> faces;
		
		//检测图片中的多个人脸框
        haar_cascade.detectMultiScale(image, faces, 1.1, 2, 0, cv::Size(30, 30));
		//std::cout<<"after"<<faces.size()<<std::endl;face.size()大小不一,有的为1,有的为4
		//遍历检测每个人脸框,如果人脸框包含ground_truth_shape,则count++
        for (int i = 0; i < faces.size(); i++){
            cv::Rect faceRec = faces[i];
            if (ShapeInRect(ground_truth_shape, faceRec)){ 
            	// check if the detected face rectangle is in the ground_truth_shape
                images.push_back(image);
				names.push_back(pts);
                ground_truth_shapes.push_back(ground_truth_shape);
                BoundingBox bbox;
                bbox.start_x = faceRec.x;
                bbox.start_y = faceRec.y;
                bbox.width = faceRec.width;
                bbox.height = faceRec.height;
                bbox.center_x = bbox.start_x + bbox.width / 2.0;
                bbox.center_y = bbox.start_y + bbox.height / 2.0;
                bboxes.push_back(bbox);
                count++;//从图片中检测到的符合要求的人脸数量
                if (count%100 == 0){
                    std::cout << count << " images loaded\n";
                }
                break;
            }
         }

	}
	//std::cout<<"加载图片数量"<<count<<std::endl;
	std::cout << "get " << bboxes.size() << " faces\n";
	fin.close();
}

2.计算所有加载的训练图片的平均脸

// get the mean shape, 每个landmark的坐标点范围为[-1, 1]x[-1, 1]
//计算平均脸
cv::Mat_<double> GetMeanShape(const std::vector<cv::Mat_<double> >& all_shapes,
	const std::vector<BoundingBox>& all_bboxes) {
	//通常选择第一个人脸模型作为对齐标准
	cv::Mat_<double> mean_shape = cv::Mat::zeros(all_shapes[0].rows, 2, CV_32FC1);
	for (int i = 0; i < all_shapes.size(); i++)
	{
		////将人脸模型坐标归一化到[-1.1]
		mean_shape += ProjectShape(all_shapes[i], all_bboxes[i]);
	}
	mean_shape = 1.0 / all_shapes.size()*mean_shape;
	//std::cout<<"训练图片的平均脸"<<mean_shape<<std::endl;
	return mean_shape;
}

3.开始训练

3.1扩充数据集
//扩充数据集
	std::cout << "augment data sets" << std::endl;
	cv::RNG random_generator(current_time);
	for (int i = 0; i < images_.size(); i++){
		//params_.initial_guess_ 决定将每张图片扩充为几张图片
		for (int j = 0; j < params_.initial_guess_; j++)
		{
			int index = 0;
			do {
				//在0到图片数范围内随机产生index,且index服从均匀分布
				index = random_generator.uniform(0, images_.size());
			}while(index == i);//index!=i的时候就跳出循环,即随机产生一个不等于i的index,然后用产生的这张图片的形状作为第i张图片的初始化形状
			//push_back的作用是在在vector尾部加入一个数据
			augmented_images_index.push_back(i);
			augmented_ground_truth_shapes.push_back(ground_truth_shapes_[i]);
			augmented_bboxes.push_back(bboxes_[i]);
			//取出随机产生图片的真实形状,通过相应处理后作为第i张图片的初始形状
			cv::Mat_<double> temp = ground_truth_shapes_[index];
			temp = ProjectShape(temp, bboxes_[index]);
			temp = ReProjection(temp, bboxes_[i]);
			augmented_current_shapes.push_back(temp);
		}
        augmented_images_index.push_back(i);
        augmented_ground_truth_shapes.push_back(ground_truth_shapes_[i]);
        augmented_bboxes.push_back(bboxes_[i]);
		//将之前计算的平均脸作为第i张图片原始的初始化形状
        augmented_current_shapes.push_back(ReProjection(params_.mean_shape_, bboxes_[i]));
	}
    std::cout << "augmented size: " << augmented_current_shapes.size() << std::endl;
3.2 计算回归目标即真实形状增量
// calculate the regression targets
	std::cout << "calculate regression targets" << std::endl;
    #pragma omp parallel for
	//计算每个人脸的真实形状增量
	for (int i = 0; i < augmented_current_shapes.size(); i++){
		regression_targets[i] = ProjectShape(augmented_ground_truth_shapes[i], augmented_bboxes[i])
			- ProjectShape(augmented_current_shapes[i], augmented_bboxes[i]);
		cv::Mat_<double> rotation;
		double scale;
		//计算平均形状与各初始化形状之间的相似变换,即rotations和scales
		getSimilarityTransform(params_.mean_shape_, ProjectShape(augmented_current_shapes[i], augmented_bboxes[i]), rotation, scale);
		cv::transpose(rotation, rotation);
		//根据rotations和scales得到统一坐标的真实形状增量
		regression_targets[i] = scale * regression_targets[i] * rotation;
		getSimilarityTransform(ProjectShape(augmented_current_shapes[i], augmented_bboxes[i]), params_.mean_shape_, rotation, scale);
		rotations_[i] = rotation;
		scales_[i] = scale;
	}
3.3 各阶段随机森林的训练
随机选取 300 对像素点

在各 landmark 半径区域内随机选取 300 对像素点,并计算它们之间的差值,叫做 pixel_difference_features,即像素差特征点。

//在半径范围内随机选取local_features_num_对像素点a和b
	for (int i = 0; i < local_features_num_; i++){
		double x, y;
		do{
			x = rd.uniform(-local_radius_, local_radius_);
			y = rd.uniform(-local_radius_, local_radius_);
		} while (x*x + y*y > local_radius_*local_radius_);
		cv::Point2f a(x, y);

		do{
			x = rd.uniform(-local_radius_, local_radius_);
			y = rd.uniform(-local_radius_, local_radius_);
		} while (x*x + y*y > local_radius_*local_radius_);
		cv::Point2f b(x, y);

		local_position_[i] = FeatureLocations(a, b);
	}
	//计算刚刚选取的像素对的差值
	//std::cout << "get pixel differences" << std::endl;
	cv::Mat_<int> pixel_differences(local_features_num_, augmented_images_index.size()); // matrix: features*images
	for (int i = 0; i < augmented_images_index.size(); i++){
		
		cv::Mat_<double> rotation = rotations[i];
		double scale = scales[i];
		//getSimilarityTransform(ProjectShape(augmented_current_shapes[i], augmented_bboxes[i]),mean_shape_, rotation, scale);
		//将选取的像素点对坐标与平均脸坐标对齐,并计算像素点对之间的像素差值
		for (int j = 0; j < local_features_num_; j++){
			FeatureLocations pos = local_position_[j];
			double delta_x = rotation(0, 0)*pos.start.x + rotation(0, 1)*pos.start.y;
			double delta_y = rotation(1, 0)*pos.start.x + rotation(1, 1)*pos.start.y;
			delta_x = scale*delta_x*augmented_bboxes[i].width / 2.0;
			delta_y = scale*delta_y*augmented_bboxes[i].height / 2.0;
			int real_x = delta_x + augmented_current_shapes[i](landmark_index_, 0);
			int real_y = delta_y + augmented_current_shapes[i](landmark_index_, 1);
			real_x = std::max(0, std::min(real_x, images[augmented_images_index[i]].cols - 1)); // which cols
			real_y = std::max(0, std::min(real_y, images[augmented_images_index[i]].rows - 1)); // which rows
			int tmp = (int)images[augmented_images_index[i]](real_y, real_x); //real_y at first

			delta_x = rotation(0, 0)*pos.end.x + rotation(0, 1)*pos.end.y;
			delta_y = rotation(1, 0)*pos.end.x + rotation(1, 1)*pos.end.y;
			delta_x = scale*delta_x*augmented_bboxes[i].width / 2.0;
			delta_y = scale*delta_y*augmented_bboxes[i].height / 2.0;
			real_x = delta_x + augmented_current_shapes[i](landmark_index_, 0);
			real_y = delta_y + augmented_current_shapes[i](landmark_index_, 1);
			real_x = std::max(0, std::min(real_x, images[augmented_images_index[i]].cols - 1)); // which cols
			real_y = std::max(0, std::min(real_y, images[augmented_images_index[i]].rows - 1)); // which rows
			pixel_differences(j, i) = tmp - (int)images[augmented_images_index[i]](real_y, real_x); 
		}
	}
随机森林数据集重构,相当于有放回抽取
// train Random Forest
	// construct each tree in the forest
	//随机森林数据集的重构,相当于有放回抽取
	double overlap = 0.4;
	int step = floor(((double)augmented_images_index.size())*overlap / (trees_num_per_forest_ - 1));
	trees_.clear();
	all_leaf_nodes_ = 0;
	for (int i = 0; i < trees_num_per_forest_; i++){
		int start_index = i*step;
		int end_index = augmented_images_index.size() - (trees_num_per_forest_ - i - 1)*step;

在这里插入图片描述

树的构建

在这里插入图片描述

随机产生临时阈值
// random generate threshold
			std::vector<int> data;
			data.reserve(images_indexes.size());
			for (int i = 0; i < images_indexes.size(); i++){
				//将新划分的数据集(100张)中所有的图片的某个landmark的300对像素点差值存入data
				data.push_back(pixel_differences(j, images_indexes[i]));
			}
			std::sort(data.begin(), data.end());//排序
			//用于选取阈值的临时索引
			int tmp_index = floor((float)(images_indexes.size()*(0.5 + 0.9*(rd.uniform(0.0, 1.0) - 0.5))));
			//根据临时索引从data中选取对应值作为临时阈值
			int tmp_threshold = data[tmp_index];
根据临时阈值对重构后的数据集进行划分

若小于临时阈值,则被临时划分到左子树,并取出该图片的真实形状增量,反之则被划分到右子树。

for (int i = 0; i < images_indexes.size(); i++){
				int index = images_indexes[i];
				//小于随机产生的阈值则被划分到左边
				if (pixel_differences(j, index) < tmp_threshold){
					tmp_left_indexes.push_back(index);
					// do with regression target
					double value = regression_targets_->at(index)(landmark_index_, 0);
					Ex_2_lc += pow(value, 2);
					Ex_lc += value;
					value = regression_targets_->at(index)(landmark_index_, 1);
					Ey_2_lc += pow(value, 2);
					Ey_lc += value;
				}
				//如果大于阈值就被划分到右边
				else{
					tmp_right_indexes.push_back(index);
					double value = regression_targets_->at(index)(landmark_index_, 0);
					Ex_2_rc += pow(value, 2);
					Ex_rc += value;
					value = regression_targets_->at(index)(landmark_index_, 1);
					Ey_2_rc += pow(value, 2);
					Ey_rc += value;
				}
			}
计算方差,选择使得方差减小最大的像素差值点作为真正的分裂节点
if (tmp_left_indexes.size() == 0){ 
				var_lc = 0.0;
			} else{
				//计算方差,DX=EX^2-(EX)^2
				var_lc = Ex_2_lc / tmp_left_indexes.size() - pow(Ex_lc / tmp_left_indexes.size(), 2)
					+ Ey_2_lc / tmp_left_indexes.size() - pow(Ey_lc / tmp_left_indexes.size(), 2);
			}
			if (tmp_right_indexes.size() == 0){
				var_rc = 0.0;
			} else{
				var_rc = Ex_2_rc / tmp_right_indexes.size() - pow(Ex_rc / tmp_right_indexes.size(), 2)
					+ Ey_2_rc / tmp_right_indexes.size() - pow(Ey_rc / tmp_right_indexes.size(), 2);
			}
			var_red = -var_lc*tmp_left_indexes.size() - var_rc*tmp_right_indexes.size();
			//选取使得方差减小最大的像素差值点作为分裂节点
			if (var_red > var){
				var = var_red;
				threshold = tmp_threshold;
				feature_index = j;
				left_indexes = tmp_left_indexes;
				right_indexes = tmp_right_indexes;
			}
		}
	}
对上一过程进行递归
//递归构造树
		node->left_child_ = BuildTree(selected_indexes, pixel_differences, left_indexes, current_depth + 1);
		node->right_child_ = BuildTree(selected_indexes, pixel_differences, right_indexes, current_depth + 1);

构建树的完整代码

//树的构建
Node* RandomForest::BuildTree(std::set<int>& selected_indexes, cv::Mat_<int>& pixel_differences, std::vector<int>& images_indexes, int current_depth){
	//std::cout<<"images_index.size:"<<images_indexes.size()<<std::endl;
	if (images_indexes.size() > 0){ // the node may not split under some cases
		Node* node = new Node();
		node->depth_ = current_depth;
		node->samples_ = images_indexes.size();
		std::vector<int> left_indexes, right_indexes;
		if (current_depth == tree_depth_){ // the node reaches max depth
			node->is_leaf_ = true;
			node->leaf_identity = all_leaf_nodes_;
			all_leaf_nodes_++;
			return node;
		}
		//寻找分裂节点
		int ret = FindSplitFeature(node, selected_indexes, pixel_differences, images_indexes, left_indexes, right_indexes);
		// actually it won't enter the if block, when the random function is good enough
		//当前数据集已不可划分,是一个包含所有数据的叶子节点
		if (ret == 1){ // the current node contain all sample when reaches max variance reduction, it is leaf node
			node->is_leaf_ = true;
			node->leaf_identity = all_leaf_nodes_;
			all_leaf_nodes_++;
			return node;
		}

		//if (current_depth + 1 < tree_depth_){
		//递归构造树
		node->left_child_ = BuildTree(selected_indexes, pixel_differences, left_indexes, current_depth + 1);
		node->right_child_ = BuildTree(selected_indexes, pixel_differences, right_indexes, current_depth + 1);
		//}
		return node;
	}
	else{ // this case is not possible in this data structure
		return NULL;
	}
}
3.4 获取每层的全局二值特征点
std::cout << "Get Global Binary Features" << std::endl;

    struct feature_node **global_binary_features;
    global_binary_features = new struct feature_node* [augmented_current_shapes.size()];
	
	//初始化每张图片的全局二值特征
    for(int i = 0; i < augmented_current_shapes.size(); ++i){
        global_binary_features[i] = new feature_node[params_.trees_num_per_forest_*params_.landmarks_num_per_face_+1];
    }
    int num_feature = 0;//初始化全局二值特征点的数量
    for (int i=0; i < params_.landmarks_num_per_face_; ++i){
        num_feature += rd_forests_[i].all_leaf_nodes_;//每个landmark所对应的随机森林的所有叶子节点数量之和
    }
    #pragma omp parallel for
	//遍历每张图片
    for (int i = 0; i < augmented_current_shapes.size(); ++i){
        int index = 1;
        int ind = 0;
        const cv::Mat_<double>& rotation = rotations_[i];
        const double scale = scales_[i];
        const cv::Mat_<uchar>& image = images[augmented_images_index[i]];
        const BoundingBox& bbox = augmented_bboxes[i];
        const cv::Mat_<double>& current_shape = augmented_current_shapes[i];
		//每个landmark
    	for (int j = 0; j < params_.landmarks_num_per_face_; ++j){
			//随机森林里的每棵树
    		for (int k = 0; k < params_.trees_num_per_forest_; ++k){
				
                Node* node = rd_forests_[j].trees_[k];
				//如果这个节点不是叶子结点的话就判断它下一步的走向
                while (!node->is_leaf_){
                    FeatureLocations& pos = node->feature_locations_;
                    double delta_x = rotation(0, 0)*pos.start.x + rotation(0, 1)*pos.start.y;
                    double delta_y = rotation(1, 0)*pos.start.x + rotation(1, 1)*pos.start.y;
                    delta_x = scale*delta_x*bbox.width / 2.0;
                    delta_y = scale*delta_y*bbox.height / 2.0;
                    int real_x = delta_x + current_shape(j, 0);
                    int real_y = delta_y + current_shape(j, 1);
                    real_x = std::max(0, std::min(real_x, image.cols - 1)); // which cols
                    real_y = std::max(0, std::min(real_y, image.rows - 1)); // which rows
                    int tmp = (int)image(real_y, real_x); //real_y at first

                    delta_x = rotation(0, 0)*pos.end.x + rotation(0, 1)*pos.end.y;
                    delta_y = rotation(1, 0)*pos.end.x + rotation(1, 1)*pos.end.y;
                    delta_x = scale*delta_x*bbox.width / 2.0;
                    delta_y = scale*delta_y*bbox.height / 2.0;
                    real_x = delta_x + current_shape(j, 0);
                    real_y = delta_y + current_shape(j, 1);
                    real_x = std::max(0, std::min(real_x, image.cols - 1)); // which cols
                    real_y = std::max(0, std::min(real_y, image.rows - 1)); // which rows
					//小于阈值就到左边
                    if ((tmp - (int)image(real_y, real_x)) < node->threshold_){
                        node = node->left_child_;// go left
                    }
					//否则在右边
                    else{
                        node = node->right_child_;// go right
                    }
                }
                global_binary_features[i][ind].index = index + node->leaf_identity;//rd_forests_[j].GetBinaryFeatureIndex(k, images[augmented_images_index[i]], augmented_bboxes[i], augmented_current_shapes[i], rotations_[i], scales_[i]);
    			global_binary_features[i][ind].value = 1.0;
                ind++;
               // std::cout<< global_binary_features[i][ind].index << " ";
    		}
            index += rd_forests_[j].all_leaf_nodes_;
    	}
        if (i%2000 == 0 && i > 0){
            std::cout << "extracted " << i << " images" << std::endl;
        }
        global_binary_features[i][params_.trees_num_per_forest_*params_.landmarks_num_per_face_].index = -1;
        global_binary_features[i][params_.trees_num_per_forest_*params_.landmarks_num_per_face_].value = -1.0;
    }
3.5 全局回归并预测回归目标
//每一层的全局回归
	std::cout << "Global Regression of stage " << stage_ << std::endl;
    linear_model_x_.resize(params_.landmarks_num_per_face_);
    linear_model_y_.resize(params_.landmarks_num_per_face_);
    double** targets = new double*[params_.landmarks_num_per_face_];
    for (int i = 0; i < params_.landmarks_num_per_face_; ++i){
        targets[i] = new double[augmented_current_shapes.size()];
    }
    #pragma omp parallel for
    for (int i = 0; i < params_.landmarks_num_per_face_; ++i){
		if(i%10==0)
			std::cout << "regress landmark " << i << std::endl;
        for(int j = 0; j< augmented_current_shapes.size();j++){
			//获取每张图片每个landmark的回归目标的横坐标
            targets[i][j] = regression_targets[j](i, 0);
			
        }
        prob->y = targets[i];
        check_parameter(prob, regression_params);
		//回归模型,就是那个求W的公式的具体实现
        struct model* regression_model = train(prob, regression_params);
		//横坐标的线性模型
        linear_model_x_[i] = regression_model;
        for(int j = 0; j < augmented_current_shapes.size(); j++){
			//获取每张图片每个landmark的回归目标的纵坐标
            targets[i][j] = regression_targets[j](i, 1);
        }
        prob->y = targets[i];
        check_parameter(prob, regression_params);
        regression_model = train(prob, regression_params);
		//纵坐标的线性模型
        linear_model_y_[i] = regression_model;

    }
    for (int i = 0; i < params_.landmarks_num_per_face_; ++i){
        delete[] targets[i];// = new double[augmented_current_shapes.size()];
    }
    delete[] targets;
	//预测回归目标
	std::cout << "predict regression targets" << std::endl;

    std::vector<cv::Mat_<double> > predict_regression_targets;
    predict_regression_targets.resize(augmented_current_shapes.size());
    #pragma omp parallel for
    for (int i = 0; i < augmented_current_shapes.size(); i++){
        cv::Mat_<double> a(params_.landmarks_num_per_face_, 2, 0.0);
        for (int j = 0; j < params_.landmarks_num_per_face_; j++){
			//预测横坐标增量
            a(j, 0) = predict(linear_model_x_[j], global_binary_features[i]);
			//预测纵坐标增量
            a(j, 1) = predict(linear_model_y_[j], global_binary_features[i]);
        }
        cv::Mat_<double> rot;
        cv::transpose(rotations_[i], rot);
		//与平均脸坐标统一的预测形状增量
        predict_regression_targets[i] = scales_[i] * a * rot;
        if (i%2000 == 0 && i > 0){
             std::cout << "predict " << i << " images" << std::endl;
        }
    }
    std::cout << "\n";

训练终于完成了,接下来就级联回归器保存起来以便测试时调用
在这里插入图片描述

三、test

void Test(const char* ModelName, const char* name){
	CascadeRegressor cas_load;
	cas_load.LoadCascadeRegressor(ModelName);
	TestImage(name, cas_load);
    return;
}

void Test(const char* ModelName){
	CascadeRegressor cas_load;
	cas_load.LoadCascadeRegressor(ModelName);//加载训练阶段得到的级联回归器
	std::vector<cv::Mat_<uchar> > images;//待测试图片
	std::vector<cv::Mat_<double> > ground_truth_shapes;//待测试图片的真实形状
	std::vector<BoundingBox> bboxes;//人脸框
	std::vector<std::string> names;//测试图片名
	std::string file_names = "./../dataset/w300/test_jpgs.txt";//存放测试图片名的文件名
	LoadImages(images, ground_truth_shapes, bboxes,names, file_names);//加载测试图片
	
  //  struct timeval t1, t2;
   // gettimeofday(&t1, NULL);
	string str = ModelName;
	double mean_error = 0;//初始化mean_error
	std::vector<cv::Mat_<double> > regression_shapes;//定义回归得到的形状
	std::ofstream fout;
	fout.open((str + "_result.txt").c_str(), std::fstream::out);

	for (int i = 0; i < images.size(); i++){
		//将平均形状在全局坐标下重建作为初始回归形状
		cv::Mat_<double> current_shape = ReProjection(cas_load.params_.mean_shape_, bboxes[i]);
		//通过级联回归器得到的预测形状
        cv::Mat_<double> res = cas_load.Predict(images[i], current_shape, bboxes[i]);//, ground_truth_shapes[i]);
		regression_shapes.push_back(res);
		//计算预测人脸形状与真实人脸形状之间的误差
		double err = CalculateError(ground_truth_shapes[i], res);
        cout << "error: " << err << std::endl;
		fout << i << "	" << err << endl;
		//计算平均误差
		mean_error = mean_error + err;
	}
	
	cout << "mean_error is : " << mean_error <<"	"<< mean_error / images.size() << endl;
	fout << "regression error  is : " << mean_error / images.size() << endl;
	//遍历每张图片
	for (int i = 0; i < names.size(); i++)
	{
		cv::Mat_<double> tmp_mat = cv::Mat::zeros(68, 2, CV_64F);
		string file_name ="./data/"+ names[i];
		tmp_mat = regression_shapes[i];
		fout.open(file_name, std::fstream::out);
		//对于每张图片的每个landmark,将其回归形状的横纵坐标输出到文件中
		for (int i = 0; i < 68; i++){
			fout << tmp_mat(i, 0) << "	" << tmp_mat(i, 1) << std::endl;
			cout << tmp_mat(i, 0) << "	" << tmp_mat(i, 1) << std::endl;
		}
		fout.close();
	}
	
	fout.close();

  //  gettimeofday(&t2, NULL);
  //  double time_full = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;
  //  cout << "time full: " << time_full << " : " << time_full/images.size() << endl;
	return;
}

四、开始测试

测试之前需要先把图片加载路径修改一下,如图:
在这里插入图片描述

测试开始
在这里插入图片描述
测试结果
在这里插入图片描述
返回的是 68 个 landmark 的坐标,用记事本打开如图所示:
在这里插入图片描述

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值