第一部分 levmar的安装与使用
Levenberg-Marquardt算法是求解非线性问题的一个非常好用的算法。该算法属于信赖域算法的一种,关于信赖域算法的解释可以参考这一博主的解释:关于信赖域算法理解,个人感觉很好。
Levenberg-Marquardt算法是一个开源的算法,其文件下载地址如下:
http://www.netlib.org/clapack/CLAPACK-3.1.1-VisualStudio.zip 下载clapack
http://www.ics.forth.gr/~lourakis/levmar/levmar-2.5.tgz 下载LEVMAR
http://www.cmake.org/files/v2.8/cmake-2.8.0-win32-x86.exe 下载cmake
利用cmake将源码组织成一个工程,其具体安装可参照这篇博客:levmar安装
下面说明下dlevmar_dif()函数的参数内容。
dlevmar_dif()函数共有11个参数:
参数1:void (*func)(double *p, double *hx, int m, int n, void *adata)
就是求解的函数,需要自己构造。以其demo中wood函数为例:
void wood(double *p, double *x, int m, int n, void *data) /* 非线性目标函数f(x1,x2,x3,x4) */
{
register int i;
for(i=0; i
x[i+1]=1.0 - p[0]; // f(x1,x2,x3,x4) = 1.0 - x1;
x[i+2]=sqrt(90.0)*(p[3] - p[2]*p[2]); // f(x1,x2,x3,x4) = sqrt(90.0)*(x4 - x3*x3);
x[i+3]=1.0 - p[2]; // f(x1,x2,x3,x4) = 1 - x3;
x[i+4]=sqrt(10.0)*(p[1]+p[3] - 2.0); // f(x1,x2,x3,x4) = sqrt(10.0) *(x2 + x4 - 2);
x[i+5]=(p[1] - p[3])/sqrt(10.0); // f(x1,x2,x3,x4) = (x2 - x4)/sqrt(10.0)
}
}
参数2:double *p
输入输出参数,输入初始迭代的参数向量,输出最佳逼近的向量。
参数3:double *x
该参数为期望值向量,即为逼近的目标。
参数4:int m
参数向量P的维数。
参数5:int n
期望向量维数
参数6:int itmax
该参数表示最大迭代次数
参数7:double opts[5]
迭代过程中的一些阈值,包括最初计算阻尼系数时需要的系数、迭代结束需要的系数。如果不知道怎么设可以为NULL,代码有默认配置。
参数8:double info[LM_INFO_SZ]
返回迭代结束的一些信息,如迭代次数、结束原因等。
参数9:double *work
该参数从源码看,并没什么用,可以设为NULL。
参数10:double *covar
该参数为正交矩阵系数(记得不清了,在论文有提到,但我在自己写代码的时候没有用到),该参数可设为NULL。
参数11:void *adata
该参数为是为了当有特殊需求时才传的参数,该参数与(func*)中的void *adata一致,目前没看出怎么用,暂时设为NULL。
第二部分 自己动手编写levmar算法
可以参考原论文:下载地址
可以参考K. Madsen等人的《Methods for non-linear least squares problems》写得很不错,推荐看这论文:下载地址
具体原理我就不多啰嗦,论文解释得还是挺清楚的,自我感觉需要啰嗦一下下的小问题:
1. 雅克比矩阵的求解,可根据导数的定义来近似求解雅克比矩阵,雅克比矩阵:
jac = f'(x) = [ f(x)-f(x- delta * x) ]/(x- delta * x))
2. 海森矩阵的求解,海森矩阵的计算近似等于:雅克比矩阵的转置乘以雅克比矩阵 ==> Hessen = jacT * jac
3. 线性系统求解“步进”,求解参数向量的“步进”Dp,这是一个求解线性系统问题;求解线性系统问题有许多方法和工具,这里我为了简单就使用了opencv中的 cvSlove()函数求解。
根据其提供伪代码配合理论就可以很快编写出自己的levmar函数了。
啰嗦下因为采用的求解线性系统的方法不一样,所以在迭代次数和结果上与原版levmar算有一点点差异,迭代次数可能相差几次,及结果会相差一点(一般相差不会超过0.01)。
levmar伪代码如下: