这个文章我一年前写的,没有写完,我觉得有时间还是要把它研究透的,先发出来,以后再补充。
- 引用的实现及论文概述
引用的论文《FaceAlignmentByExplicitShapeRegression》;
因为当前实现的训练和测试是分离的,因此,代码学习按照原有论文的实现进行代码学习,分为训练和测试两个部分进行分析。
https://github.com/soundsilence/FaceAlignment
-
class及全局变量
- class及关联关系图
-
数据流图及处理流图
-
主数据流图
- 训练的数据流图
-
主数据流图
-
-
- 测试的数据流图
-
-
ShapeRegressor类相关处理
-
- ShapeRegressor类的训练处理Train
-
函数声明:void ShapeRegressor::Train(const vector<Mat_<uchar> >& images,
const vector<Mat_<double> >& ground_truth_shapes,
const vector<BoundingBox>& bounding_box,
int first_level_num, int second_level_num,
int candidate_pixel_num, int fern_pixel_num,
int initial_num)
输入:
- Images:类型vector<Mat_<uchar> >;所有要进行训练的N张图片,训练demo程序中使用了1345张图片;
- ground_truth_shapes:vector<Mat_<double> >类型;所有要进行训练的N张图片的坐标点,其中每张图片的坐标点集是一个Mat_<double>的对象,存储的数据landmark_num*2,其中landmark_num是每张图片内的坐标点的个数。当前训练demo程序中每张图片使用了29个坐标点。
- bounding_box:vector<BoundingBox>类型;所有要进行训练的N张图片的人脸所在的边界框,其中每张图片是一个BoundingBox对象,存储的数据有“start_x&start_y&width&height¢roid_x¢roid_y”,其中“centroid_x¢roid_y”是由“start_x&start_y&width&height”计算得出的。
- first_level_num:第一层回归元的个数;当前训练demo程序中的第一层回归元使用了10个。
- second_level_num:第二层回归元的个数;当前训练demo程序第二层回归元的个数500个;
- candidate_pixel_num:被选作特征的像素的个数;在当前训练demo程序中,该值为400;
- fern_pixel_num:一个fern中的像素对的个数;在当前训练demo程序中,该值为5;
- initial_num:每个输入图像的初始形状的个数;在当前训练demo程序中,该值为20;
输出:
处理流程:
- 基于原始图像,生成随机投影图像集共生成投影坐标点集的个数是N*initial_num。遍历所有输入图像,循环initial_num次(在当前训练demo程序中,该值为20),每次处理如下:
- 随机生成一个index,该index值不能跟当前的图像索引一致,该index作为输入图像集的一个随机索引。
- 分别push当前原始图像&坐标点&boundbox到局部变量augmented_images&augmented_ground_truth_shapes&augmented_bounding_box中。
- 获取当前随机index对应图像的坐标点集Mat_<double>类型的对象temp。
- 对随机index对应图像的坐标点集tmp基于它对应的边框进行投影(ProjectShape函数),产生新的坐标点集Mat_<double>类型的对象temp。
- 基于第(4)步产生的tmp基于当前图像对应的边框进行重投影(ReProjectShape函数),产生新的坐标点集Mat_<double>类型的对象temp。
- 将新的投影坐标点集temp保存到current_shapes。
- 从训练的形状中获取平均形状。通过每个输入图像的坐标点集Mat_<double>类型的对象基于该图像对应的boundbox进行投影操作后生成的坐标点集,然后求平均得到。
- 训练fern cascades。循环first_level_num次(在当前的训练demo中是10),每次的处理如下:
- 训练当前的fern cascade,通过调用FernCascade类的Train操作实现,并输出预测的形状vector<Mat_<double> >类型的对象prediction。后面详细讲解。
- 更新当前的形状。
- 根据(1)输出的预测结果prediction,加上当前预测使用的投影坐标点集current_shapes的对应点集基于对应的augmented_bounding_box中的boundbox对象的投影,得到更新的current_shapes。
- 基于上一步更新的current_shapes,基于对应的augmented_bounding_box中的boundbox对象进行重投影,得到更新的current_shapes。
- 结束。
异常:
说明:这个是训练操作的主流程。
-
- ShapeRegressor类的Write操作
函数声明:void ShapeRegressor::Write(ofstream& fout)
输入:文件ofstream
输出:写入的文件内容
处理流程:该操作主要是保存模型到文件中。
- 保存first_level_num_变量,当前demo使用值为10.
- 保存landmark_num_变量,当前demo使用值为29;
- 保存训练集的所有图片的boundbox;
- 保存训练集的所有图片的坐标集;
- 循环调用FernCascade的Write操作,保存相应的级联-fern的模型;
说明:
-
-
- ShapeRegressor类的预测处理
-
-
FernCascade类的相关处理
- FernCascade类的Train操作
函数声明:vector<Mat_<double> > FernCascade::Train(const vector<Mat_<uchar> >& images,
const vector<Mat_<double> >& current_shapes,
const vector<Mat_<double> >& ground_truth_shapes,
const vector<BoundingBox> & bounding_box,
const Mat_<double>& mean_shape,
int second_level_num,
int candidate_pixel_num,
int fern_pixel_num,
int curr_level_num,
int first_level_num)
输入:
- Images:vector<Mat_<uchar> >类型;所有要进行训练的图片,经过ShapeRegressor类的训练处理Train之前的处理后,图像数量是原始图像数量的initial_num倍;也就是每个原始图像复制了initial_num-1份。共initial_num*N张图像。
- current_shapes:vector<Mat_<double> >类型;经过前面处理的坐标点集。
- ground_truth_shapes:vector<Mat_<double> >类型;该图像的原始坐标点集。跟Images一样被复制了initial_num-1份。
- bounding_box:vector<BoundingBox>类型;该图像的原始boundbox。跟Images一样被复制了initial_num-1份。
- mean_shape:Mat_<double>类型。所有图像的坐标集的均值。
- second_level_num:第二层回归元的个数;当前训练demo程序第二层回归元的个数500个;
- candidate_pixel_num:选作特征的像素的个数;在当前训练demo程序中,该值为400;
- fern_pixel_num:一个fern中的像素对的个数;在当前训练demo程序中,该值为5;
- curr_level_num:当前是第一层回归元的第几个。从1开始,一直到first_level_num。
- first_level_num:第一层回归元的个数;当前训练demo程序中的第一层回归元使用了10个。
输出:
返回值:vector<Mat_<double> > 类型对象;本次fern cascade训练的坐标集结果。
处理流程:
- 获取回归目标集vector<Mat_<double> >类型的对象regression_target。遍历当前坐标集vector<Mat_<double> >类型的对象current_shapes(共initial_num*N个),进行如下操作:对应于论文中的“2.2 原始回归元---Primitive regressor”。
- 计算出当前坐标集对象的回归目标regression_targets[i]。对当前索引的原始坐标集ground_truth_shapes内的坐标点集对象基于相应的原始边界boundbox内的对象进行投影操作减去当前索引的之前处理后坐标集current_shapes的对象基于相应的原始边界boundbox内的对象进行投影操作。
- 计算出该current_shapes对应的坐标集对象相对于平均坐标集的缩放的比例scale和旋转的角度rotation。以全局平均坐标集mean_shape和当前索引的之前处理后坐标集current_shapes的对象基于相应的原始边界boundbox内的对象进行投影操作后的坐标集作为输入,调用函数SimilarityTransform得到。
- 对当前获取的角度rotation,调用函数transpose,进行转置操作。
- 根据scale和rotation,调整当前坐标集对象对应的回归目标regression_targets[i].
- 取出来candidate_pixel_num个像素相对坐标candidate_pixel_locations及对应的最近坐标点在mean_shape上的索引nearest_landmark_index。循环candidate_pixel_num次(当前训练demo,该值为400),进行如下处理:对应于论文“2.3索引形状(图像)特征---Shape-indexed (image) features”。
- 获取两个(-1.0,1.0)区间的随机值x&y,如果x*x + y*y > 1.0,则重新获取,指导该i次循环能获取到满足条件的x,y;
- 在全局平均坐标集mean_shape中,找到最近的坐标点索引;
- 候选的像素candidate_pixel_locations横坐标&纵坐标分别是x&y与mean_shape的最近的坐标点的横坐标和纵坐标差值,也就是这一步计算出来的坐标是局部坐标;
- nearest_landmark_index是mean_shape的最近的坐标点所在的索引;
- 为每个图像获取候选像素点对应的像素值(强度)。遍历所有图像(共N*initial_num个图像),进行如下处理:
- 将该图像当前最新更新的坐标集current_shapes基于该图像的boundbox进行投影操作保存到temp中;
- 将temp与全局平均坐标集mean_shape,调用函数SimilarityTransform,获取到相对平均坐标集的scale和rotation。
- 循环candidate_pixel_num次(当前训练demo,该值为400),进行如下操作:
- 根据候选像素candidate pixel的横纵坐标和第(2)步所获取的scale&rotation及对应的boundbox,计算出重投影的横纵坐标project_x&project_y(也就是,还原出原始坐标);
- 根据当前候选像素candidate pixel对应的索引,获取到当前最新更新的坐标集current_shapes上对应的坐标点,并计算出真实坐标real_x&real_y。
- 把真实坐标real_x&real_y对应的images图像的像素值存放到 vector<vector<double> > 类型的densities对象中。
- 获取第一张图像的候选像素的强度的两两之间的协方差保存在covariance中。(对应于论文中“2.4基于相关性的特征选择---Correlation-based feature selection”的一部分)。
- 训练fern。循环second_level_num次(在当前实现的demo中为500),进行如下处理;
- 调用Fern类内的操作Train,进行fern的训练,输出vector<Mat_<double> > 类型对象prediction,并返回vector<Mat_<double> >类型的对象temp;关于Fern类内的操作Train后续详解;
- 基于temp,进行prediction和回归目标集regression_targets的更新
- 进行prediction更新。遍历的size次,进行如下处理:
- 对当前更新的current_shapes基于相应的boundbox进行投影操作;
- 将(1)投影操作的结果与全局平均形状mean_shape,调用函数SimilarityTransform,获取到相应的角度rotation和缩放scale;
- 对角度rotation进行转置;
- 基于角度和缩放对prediction进行更新;
- 返回prediction。
说明:
-
- FernCascade类的Write操作
函数声明:void FernCascade::Write(ofstream& fout)
输入:文件ofstream
输出:写入的文件内容
处理流程:该操作主要是保存模型到文件中。
- 保存second_level_num_变量,当前demo使用值为400.
- 循环调用Fern的Write操作,保存相应的fern的模型;
说明:
-
Fern类的相关处理
- Fern类的Train操作
函数声明:vector<Mat_<double> > Fern::Train(
const vector<vector<double> >& candidate_pixel_intensity,
const Mat_<double>& covariance,
const Mat_<double>& candidate_pixel_locations,
const Mat_<int>& nearest_landmark_index,
const vector<Mat_<double> >& regression_targets,
int fern_pixel_num)
输入:
- candidate_pixel_intensity:vector<vector<double> >类型;真实坐标real_x&real_y对应的images图像的像素值,共有candidate_pixel_num*initial_num*N个像素点(当前训练demo,candidate_pixel_num值为400)。
- Covariance: const Mat_<double>类型;candidate_pixel_intensity(candidate_pixel_num个像素点)中的两两之间的协方差,因为当前训练的demo的candidate_pixel_num值为400,因此,对象是一个400*400的矩阵。
- candidate_pixel_locations:const Mat_<double>类型;是候选像素的相对坐标点集,候选像素个数是candidate_pixel_num个,当前demo使用的值为400。
- nearest_landmark_index: const Mat_<int>类型;候选像素坐标点的在mean_shape的最近的坐标点所在的索引。也就是该相对坐标点对应的原点坐标的索引。
- regression_targets:const vector<Mat_<double> >类型;回归目标;当前图像的形状与预测形状间的差值,共initial_num*N个。对应于论文中的“2.2 原始回归元---Primitive regressor”。
- fern_pixel_num: int 类型;一个fern中的像素对的个数。在当前训练demo程序中,该值为5;
输出:
返回值:vector<Mat_<double> >;本次训练结果。
处理流程:
- 根据像素强度和回归目标之间的相关性,从候选像素集candidate_pixel_intensity中选择出像素对,其中像素对索引selected_pixel_index_,像素对的坐标selected_pixel_locations_,像素对对应的局部坐标系的原点的索引selected_nearest_landmark_index_。(对应于论文的“2.4 基于相关性的特征选择---Correlation-based feature selection”及“3.实现细节---Implementation details,运行回归元多次(在我们的实现中是5次),并选择中间结果作为最终估算结果.”)循环fern_pixel_num次(当前训练demo程序中,该值为5),进行如下处理:
- 产生随机方向random_direction并调用函数normalize进行归一化;
- 对回归目标 regression_targets使用随机方向random_direction,生成回归目标的映射结果projection_result。遍历回归目标 regression_targets内所有元素(共initial_num*N个),每个与random_direction相乘,得到映射结果projection_result。
- 计算出真实坐标real_x&real_y对应的images图像的像素值candidate_pixel_intensity与回归目标的映射结果projection_result之间的协方差。遍历candidate_pixel_num次,调用函数calculate_covariance,对像素值candidate_pixel_intensity与回归目标的映射结果projection_result之间的协方差运算,结果存放在covariance_projection_density中。
- 找到最大相关性的两个像素索引,该索引也是回归目标 regression_targets对应的索引。双循环遍历candidate_pixel_num次(当前demo值为400),对上一步得到的不同方向的协方差covariance_projection_density之间相减,最后得到最大的差值的一对像素索引。
- 记录candidate_pixel_intensity中选择出像素对,其中像素对索引selected_pixel_index_,像素对的坐标selected_pixel_locations_,像素对对应的局部坐标系的原点的索引selected_nearest_landmark_index_。
- 在当前一对像素索引的candidate_pixel_intensity对应的vector内找到像素对于强度差别最大的值,并计算出当前这对像素索引对应的门限值,存放在threshold_对应的索引中。
- 决策每个形状的bin存放在对应的shapes_in_bin中,其中bin的个数是2的fern_pixel_num次幂(在当前demo中fern_pixel_num为5,因此为32个)。遍历回归目标regression_targets内的所有元素,循环fern_pixel_num次,如果selected_pixel_index_对应的候选的像素对差值大于门限值,则记录下来,最后保存在对应的shapes_in_bin的vector中。也就是用“位或”的方式作为索引值把当前满足条件的回归目标regression_targets的索引记录在shapes_in_bin中。
- 获取bin的输出bin_output_及返回本次的prediction。循环bin_num次(当前demo的值是32),进行如下的处理:
- 循环当前索引指向的shapes_in_bin的vector,对第2)计算处理的满足条件的回归目标(即fern中的bin)进行求和tmp。
- 获取bin_output_的输出。进行bin_output_[i]=(1.0/((1.0+1000.0/bin_size) * bin_size)) * temp。???,这个不明白这个公式的依据是???
- 利用tmp,更新prediction。
- 返回prediction。
说明:
-
- Fern类的Write操作
函数声明:void Fern::Write(ofstream& fout)
输入:文件ofstream
输出:写入的文件内容
处理流程:该操作主要是保存模型到文件中。
- 保存fern_pixel_num_变量,当前demo使用值为5.
- 保存landmark_num_变量值,当前demo使用值为29,一张图像的坐标点个数;
- 保存该fern选择的像素对坐标selected_pixel_locations_;
- 保存像素对对应的局部坐标的原点索引selected_nearest_landmark_index_
- 保存每个像素对对应的门限值;
- 保存该fern对应的bin output变量bin_output_;