在OpenCV的早期版本中有HMM人脸识别的demo,新版本已经没有了,参照早期版本,我跟踪了代码,写了些注释,与大家分享,希望对大家有用。
HMM人脸识别训练算法主要分4部分:
4, 训练
1)
计算各个状态下混高斯的均值、方差和混高斯的权重。
cvEstimateHMMStateParams( obsInfoVec, num_img, hmm);
2)
计算超态与超态之间,相邻子态之间的转移矩阵
cvEstimateTransProb( obsInfoVec, num_img, hmm);
3)
计算在每个内嵌HMM状态下的每个观测值的高斯概率。得到ehmm->obsProb。
对应于每一行(obs_x)所有观测向量,在所有超态下的所有子态发生的概率
(这个概率是每个子态下混高斯的加权),
cvEstimateObsProb( obsInfoVec[j], hmm );
4)
1,先对每一行的各个观测向量在所有超态的子态下进行viterbi算法,计算
最能解释当前行观测向量的子态序列,及其最大概率。
2, 把得到的概率最为当前行发生在各个超态下的概率,再在超态下计算viterbi算法,
计算最能解释当前行观测向量的超态序列,并给出最大概率likelihood。
3, 最能解释各个超态的子态序列和各个超态序列被放回到obsInfoVec[j]->obs_info->state
4,结果,影响的是obsInfoVec[j]中超态在行上的分布,每个超态在每一行中超态的子态中的分布。
likelihood += cvEViterbi( obsInfoVec[j], hmm );
5)各个状态(超态,内嵌子态)发生变化,重新计算各个观察值在其所在的状态中混高斯索引。
cvMixSegmL2( obsInfoVec, num_img, hmm);
3, 用k均值填充obs->mix = (int*)cvAlloc( total * sizeof(int) );
首先计算在每个子状态下,观察值的个数,之后对于每个状态下的所有观察值,
用Kmeans算法,k=3聚簇分类,把聚簇后每个向量对应的聚簇的索引所为混高斯的索引。
obs->mix[i]=k,k=1,2,3.
2, 循环每张图像,得到每张图像的观察向量,
1)CV_COUNT_OBS( &roi, &(m_dctSize), &(m_delta), &num_obs );得到DCT观察值的块信息。
Roi:图像中感兴趣的区域,如果图像已经剪切好,则次之可以设置为图像的宽度与高度。
m_dctSize:DCT采样块的大小。如:12*12。
m_delta:DCT采样的步长,如4*4。
num_obs:输出参数,结果为DCT在该图像上横向,竖向的采样个数,如20*26,即为在横向上有20个DCT的观测向量,竖向有26个DCT的观测向量。
2)创建结构用于储存图像观察向量的数组。
obsInfoVec[img_index] = cvCreateObsInfo( num_obs, vect_len );
跟踪源码,可知:
static CvStatus CV_STDCALL icvCreateObsInfo( CvImgObsInfo** obs_info,
CvSize num_obs, int obs_size )
{
int total = num_obs.height * num_obs.width;
CvImgObsInfo* obs = (CvImgObsInfo*)cvAlloc( sizeof( CvImgObsInfo) );
obs->obs_x = num_obs.width;
obs->obs_y = num_obs.height;
// total为总的DCT采样块的大小如20*26,obs_size为每个DCT块获取的向量的长度,如3*3-1
obs->obs = (float*)cvAlloc( total * obs_size * sizeof(float) );
//对于每个DCT的采样块,分配两个int空间,分别存储各个块对应的超态及其子态索引。
obs->state = (int*)cvAlloc( 2 * total * sizeof(int) );
//对于每个DCT的采样块(观察值),分配int空间,用于存储该观察值对应于那个混高斯(高//斯索引)
obs->mix = (int*)cvAlloc( total * sizeof(int) );
obs->obs_size = obs_size;
obs_info[0] = obs;
return CV_NO_ERR;
}
3)填充obs->obs = (float*)cvAlloc( total * obs_size * sizeof(float) );
if( m_suppress_intensity )
{
float* observations = (float*)malloc( num_obs.height * num_obs.width * (vect_len+1) * sizeof(float) );
//从图像中提取的观察向量即DCT系数
cvImgToObs_DCT( ipl_scaled, observations, m_dctSize, m_obsSize, m_delta ); ExtractDCT( observations, info->obs, num_obs.height * num_obs.width, vect_len );
//将观察向量从observations转到info->obs
free( observations);
}
else
{
cvImgToObs_DCT( ipl_scaled, info->obs, m_dctSize, m_obsSize, m_delta );
}
4)以HMM状态统一分割图像观测值, 填充obs->state = (int*)cvAlloc( 2 * total * sizeof(int) );k+0代表超态索引,k+1代表子态索引。
cvUniformImgSegm( info, hmm );
按照5个超态,每个超态分别为3,6,6,6,3的嵌入子态分割图像,即把每个DCT观察值块标号(包括它本身属于那个超态,属于那个子态),每个嵌入子状态的索引,,1,2,3......23,与它对应的超态无关。
1,准备工作
//用于尺度归一化
int m_useWidth = 1;
int m_useHeight = 0;
bool doRescale = 0;
int m_scaleWidth = 90;
int m_scaleHeight = 0;
//是否抑制直流分量
int m_suppress_intensity = 1;
//超态及其每个状态的混高斯
int m_stnum[32];
int m_mixnum[128];
//用DCT(离散余弦变换)系数计算的图像块的大小
CvSize m_dctSize=cvSize(12,12);
//dct窗口递进的步长
CvSize m_delta=cvSize(4,4);
//存放已经训练完毕又加载的ehmm模型
vector<CvEHMM *> vec_ehmm;
vector<string> vec_pattern_index;
//观察值的长度vect_len = 3*3-1
CvSize m_obsSize = cvSize(3,3);
int vect_len = m_obsSize.width*m_obsSize.height-1;
int InitStateAndMix()
{
m_stnum[0] = 5;
m_stnum[1] = 3;
m_stnum[2] = 6;
m_stnum[3] = 6;
m_stnum[4] = 6;
m_stnum[5] = 3;
for( int i = 0; i < 128; i++ )
{
m_mixnum[i] = 3;
}
return 1;
}
构造隐马尔科夫结构体
CvEHMM *hmm = cvCreate2DHMM( m_stnum, m_mixnum, vect_size );
vect_size是每个观察向量的维数,即m_obsSize.width*m_obsSize.height-1。