这个文章我一年前写的,没有写完,我觉得有时间还是要把它研究透的,先发出来,以后再补充。

  1. 引用的实现及论文概述

引用的论文《FaceAlignmentByExplicitShapeRegression》;

因为当前实现的训练和测试是分离的,因此,代码学习按照原有论文的实现进行代码学习,分为训练和测试两个部分进行分析。

https://github.com/soundsilence/FaceAlignment

  1. class及全局变量
    1. class及关联关系图

FaceAlignmentByExplicitShapeRegression,代码学习

  1. 数据流图及处理流图
    1. 主数据流图
      1. 训练的数据流图

FaceAlignmentByExplicitShapeRegression,代码学习

      1. 测试的数据流图

FaceAlignmentByExplicitShapeRegression,代码学习

  1. ShapeRegressor类相关处理
      1. 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;

输出:

处理流程:

  1. 基于原始图像,生成随机投影图像集共生成投影坐标点集的个数是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。
  1. 从训练的形状中获取平均形状。通过每个输入图像的坐标点集Mat_<double>类型的对象基于该图像对应的boundbox进行投影操作后生成的坐标点集,然后求平均得到。
  2. 训练fern cascades。循环first_level_num次(在当前的训练demo中是10),每次的处理如下:
  • 训练当前的fern cascade,通过调用FernCascade类的Train操作实现,并输出预测的形状vector<Mat_<double> >类型的对象prediction。后面详细讲解。
  • 更新当前的形状。
  1. 根据(1)输出的预测结果prediction,加上当前预测使用的投影坐标点集current_shapes的对应点集基于对应的augmented_bounding_box中的boundbox对象的投影,得到更新的current_shapes。
  2. 基于上一步更新的current_shapes,基于对应的augmented_bounding_box中的boundbox对象进行重投影,得到更新的current_shapes。
  1. 结束。

 

异常:

说明:这个是训练操作的主流程。

    1. ShapeRegressor类的Write操作

函数声明:void ShapeRegressor::Write(ofstream& fout)

输入:文件ofstream

输出:写入的文件内容

处理流程:该操作主要是保存模型到文件中。

  1. 保存first_level_num_变量,当前demo使用值为10.
  2. 保存landmark_num_变量,当前demo使用值为29;
  3. 保存训练集的所有图片的boundbox;
  4. 保存训练集的所有图片的坐标集;
  5. 循环调用FernCascade的Write操作,保存相应的级联-fern的模型;

说明:

 

      1. ShapeRegressor类的预测处理

 

  1. FernCascade类的相关处理
    1. 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训练的坐标集结果。

处理流程:

  1. 获取回归目标集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].
  1. 取出来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的最近的坐标点所在的索引;
  1. 为每个图像获取候选像素点对应的像素值(强度)。遍历所有图像(共N*initial_num个图像),进行如下处理:
  • 将该图像当前最新更新的坐标集current_shapes基于该图像的boundbox进行投影操作保存到temp中;
  • 将temp与全局平均坐标集mean_shape,调用函数SimilarityTransform,获取到相对平均坐标集的scale和rotation。
  • 循环candidate_pixel_num次(当前训练demo,该值为400),进行如下操作:
  1. 根据候选像素candidate pixel的横纵坐标和第(2)步所获取的scale&rotation及对应的boundbox,计算出重投影的横纵坐标project_x&project_y(也就是,还原出原始坐标);
  2. 根据当前候选像素candidate pixel对应的索引,获取到当前最新更新的坐标集current_shapes上对应的坐标点,并计算出真实坐标real_x&real_y。
  3. 把真实坐标real_x&real_y对应的images图像的像素值存放到 vector<vector<double> > 类型的densities对象中。
  1. 获取第一张图像的候选像素的强度的两两之间的协方差保存在covariance中。(对应于论文中“2.4基于相关性的特征选择---Correlation-based feature selection”的一部分)。
  2. 训练fern。循环second_level_num次(在当前实现的demo中为500),进行如下处理;
  • 调用Fern类内的操作Train,进行fern的训练,输出vector<Mat_<double> > 类型对象prediction,并返回vector<Mat_<double> >类型的对象temp;关于Fern类内的操作Train后续详解;
  • 基于temp,进行prediction和回归目标集regression_targets的更新
  1. 进行prediction更新。遍历的size次,进行如下处理:
  • 对当前更新的current_shapes基于相应的boundbox进行投影操作;
  • 将(1)投影操作的结果与全局平均形状mean_shape,调用函数SimilarityTransform,获取到相应的角度rotation和缩放scale;
  • 对角度rotation进行转置;
  • 基于角度和缩放对prediction进行更新;
  1. 返回prediction。

说明:

    1. FernCascade类的Write操作

函数声明:void FernCascade::Write(ofstream& fout)

输入:文件ofstream

输出:写入的文件内容

处理流程:该操作主要是保存模型到文件中。

  1. 保存second_level_num_变量,当前demo使用值为400.
  2. 循环调用Fern的Write操作,保存相应的fern的模型;

说明:

 

  1. Fern类的相关处理
    1. 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> >;本次训练结果。

处理流程:

  1. 根据像素强度和回归目标之间的相关性,从候选像素集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_对应的索引中。
  1. 决策每个形状的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中。
  2. 获取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。
  1. 返回prediction。

说明:

    1. Fern类的Write操作

函数声明:void Fern::Write(ofstream& fout)

输入:文件ofstream

输出:写入的文件内容

处理流程:该操作主要是保存模型到文件中。

  1. 保存fern_pixel_num_变量,当前demo使用值为5.
  2. 保存landmark_num_变量值,当前demo使用值为29,一张图像的坐标点个数;
  3. 保存该fern选择的像素对坐标selected_pixel_locations_;
  4. 保存像素对对应的局部坐标的原点索引selected_nearest_landmark_index_
  5. 保存每个像素对对应的门限值;
  6. 保存该fern对应的bin output变量bin_output_;

相关文章:

  • 2021-05-22
  • 2022-02-25
  • 2021-06-07
  • 2021-08-01
  • 2021-06-03
  • 2022-01-19
  • 2022-02-16
  • 2021-08-18
猜你喜欢
  • 2021-10-11
  • 2021-04-24
  • 2021-08-17
  • 2021-09-14
  • 2021-04-17
  • 2022-02-24
相关资源
相似解决方案