线性方程组
一个m个方程,n个未知数的方程组定义如下
把上式中的系数与一个m*n的阵列联系起来,称这个了阵列为方程组的系数矩阵(coefficient matrix):
如果在系数矩阵的右侧添加一列方程组的右端项,则得到新的矩阵, 称这个矩阵为方程组的增广矩阵(augmented maxtrix):
令:
因此定义一般形式为:
- r(A) < r(A|B),方程组无解
- r(A) = r(A|B)=n,方程组有唯一解
- r(A) = r(A|B) < n,方程组有无穷解
- r(A) > r(A|B),这种情况不存在
其中r()代表矩阵的秩,A|B是增广矩阵,n是X未知数个数.
线性最小二乘
以一个简单的直线拟合的例子说明线性最小二乘
假设方程\(y = kx + b\), 给定n组观测数据\((x, y)\)求解系数\(X = [k, b]^T\).
解为:
矩阵分解
LU 分解
将系数矩阵A转变成等价两个矩阵L和U的乘积 ,其中L和U分别是单位下三角矩阵和上三角矩阵。当A的所有顺序主子式都不为0时,矩阵A可以分解为A=LU(所有顺序主子式不为0,矩阵不一定不可以进行LU分解)。其中L是下三角矩阵,U是上三角矩阵。

求解方程组
当系数矩阵A完成了LU分解后,方程组Ax = b就可以化为L(Ux) = b,等价于求解两个方程组Ly = b和Ux = y
Example
/**
******************************************************************************
* @file
* @author maky <chengwei920412@outlook.com>
* @version
* @date 2018-12-24 23:16:58
* @brief
******************************************************************************
* @attention
*
*
******************************************************************************
*/
#include <iostream>
#include <Eigen/Eigen>
int main(int argc, char * argv[])
{
Eigen::Matrix4d A;
A << 6., 2., 1., -1.,
2., 4., 1., 0.,
1., 1., 4., -1.,
-1., 0., -1., 3.;
auto solver = A.lu();
{
Eigen::Vector4d B(1., 0., 0., 0.);
auto x = solver.solve(B);
std::cout << x.transpose() << std::endl;
}
{
Eigen::Vector4d B(0., 1., 0., 0.);
auto x = solver.solve(B);
std::cout << x.transpose() << std::endl;
}
{
Eigen::Vector4d B(0., 0., 1., 0.);
auto x = solver.solve(B);
std::cout << x.transpose() << std::endl;
}
{
Eigen::Vector4d B(0., 0., 0., 1.);
auto x = solver.solve(B);
std::cout << x.transpose() << std::endl;
}
return 0;
}
QR 分解
QR 分解是把矩阵分解成一个正交矩阵Q(\(Q^TQ = I\))与一个上三角矩阵R的积。QR 分解经常用来解线性最小二乘法问题。QR 分解也是特定特征值算法即QR算法的基础.

其中, Q 是一个标准正交方阵, R 是上三角矩阵。
线性最小二乘
对于矩阵\(A_{m \times n} \, \, (m \ge n)\), 存在一个单位列正交矩阵\(Q_{m \times n}\)和一个上三角矩阵\(R_{n \times n}\)使得:
我们找一个向量xx使得\(\lVert Ax - b \rVert\)最小,首先把矩阵Q扩充为一个正交矩阵,于是有
所以\(\lVert Ax - b \rVert\)最小也就是取\(\begin{Vmatrix}R\mathbf x-Q^T\mathbf b\end{Vmatrix}\)最小。所以最小二乘解为 :
SVD 分解
LLT 分解(Cholesky 分解)
Cholesky 分解是把一个对称正定的矩阵表示成一个下三角矩阵L和其转置的乘积的分解。它要求矩阵的所有特征值必须大于零,故分解的下三角的对角元也是大于零的。Cholesky分解法又称平方根法,是当A为实对称正定矩阵时,LU三角分解法的变形。
定理: 若\(A \in R^{n \times n}\)对称正定,则存在一个对角元为正数的下三角矩阵\(L \in R^{n \times n}\), 使得\(A = LL^{T}\)成立。
Cholesky 分解在计算马氏距离时的作用
对于协方差矩阵,是实对称半正定矩阵,如果其对角线元素全部为正,则协方差矩阵是实对称正定矩阵,因此可进行Cholesky分解。
在计算样本X中两个个特征向量的距离时,可以采用马氏距离表示:
直接对协方差矩阵求逆计算复杂度较高,因此使用Cholesky分解:
LDLT 分解
Example
/**
******************************************************************************
* @file
* @author maky <chengwei920412@outlook.com>
* @version
* @date 2018-12-25 23:16:58
* @brief
******************************************************************************
* @attention
*
*
******************************************************************************
*/
#include <iostream>
#include <cmath>
#include <random>
#include <vector>
#include <array>
#include <set>
#include <memory>
#include <algorithm>
#include <Eigen/Eigen>
#include <opencv2/opencv.hpp>
template <class ModelT, class SampleT, int min_sample>
class RandomSampleConsensus
{
protected:
typedef ModelT ModelT;
typedef SampleT SampleT;
enum { min_sample_ = min_sample };
public:
class IModel {
protected:
enum { min_sample_ = min_sample };
typedef SampleT SampleT;
public:
IModel() {}
virtual ~IModel() {}
public:
virtual bool generate(const std::array<std::shared_ptr<SampleT>, min_sample_> &samples) = 0;
virtual bool evaluate(const std::set<std::shared_ptr<SampleT>> &samples, float threshold) = 0;
public:
const std::set<std::shared_ptr<SampleT>> &getInliers() const { return inliers_; }
int getWeight() { return inliers_.size(); }
protected:
std::set<std::shared_ptr<SampleT>> inliers_;
};
public:
RandomSampleConsensus(float threshold, float confidence = 0.995, float probability_of_inliers = 0.5, int iteration_total = 0)
{
reset(threshold, confidence, probability_of_inliers, iteration_total);
}
~RandomSampleConsensus() {}
public:
void reset(float threshold, float confidence = 0.995, float probability_of_inliers = 0.5, int iteration_total = 0)
{
threshold_ = threshold;
iteration_total_ = iteration_total;
if (iteration_total_ <= 0) {
iteration_total_ = std::log(1 - confidence) / std::log(1 - std::pow(probability_of_inliers, min_sample_));
}
models_.clear();
for (int pos = 0; pos < iteration_total_; pos++) {
models_.push_back(std::make_shared<ModelT>());
}
std::srand((unsigned int)(std::time(nullptr)));
}
bool evaluate(const std::set<std::shared_ptr<SampleT>> &samples)
{
if (!samples.size() || samples.size() < min_sample) {
return false;
}
for (int pos = 0; pos < iteration_total_; pos++) {
auto &model = models_.at(pos);
std::set<std::shared_ptr<SampleT>> remaining = samples;
std::array<std::shared_ptr<SampleT>, min_sample_> model_sample;
randomGenerateMinSample(remaining, model_sample);
if (!model->generate(model_sample)) {
return false;
}
if (!model->evaluate(remaining, threshold_)) {
return false;
}
}
// sort models by inliers count.
std::sort(models_.begin(), models_.end(), [](const auto &left, const auto &right)
{
if (left->getWeight() > right->getWeight()) {
return true;
}
return false;
});
return true;
}
const std::vector<std::shared_ptr<ModelT>> getModels(void) { return models_; }
protected:
bool randomGenerateMinSample(std::set<std::shared_ptr<SampleT>> samples, std::array<std::shared_ptr<SampleT>, min_sample_> &model_sample)
{
int index = std::rand() % samples.size();
for (auto iter = model_sample.begin(); iter != model_sample.end(); iter++) {
int index = std::rand() % samples.size();
auto sample = samples.begin();
std::advance(sample, index);
(*iter) = (*sample);
samples.erase(sample);
}
return true;
}
private:
int iteration_total_;
float threshold_;
std::vector<std::shared_ptr<ModelT>> models_;
};
class LineModel : public RandomSampleConsensus<LineModel, Eigen::Vector2d, 2>::IModel
{
public:
LineModel() {}
virtual ~LineModel() {}
public:
virtual bool generate(const std::array<std::shared_ptr<SampleT>, min_sample_> &samples)
{
if (!samples[0] || !samples[1]) {
return false;
}
// y = kx + b;
k_ = (samples[0]->y() - samples[1]->y()) / (samples[0]->x() - samples[1]->x());
b_ = samples[0]->y() - k_ * samples[0]->x();
model_sample_ = samples;
return true;
}
virtual bool evaluate(const std::set<std::shared_ptr<SampleT>> &samples, float threshold)
{
for (auto iter : samples) {
if (!iter) {
return false;
}
if (threshold > distance(iter)) {
inliers_.insert(iter);
}
}
return true;
}
float distance(std::shared_ptr<SampleT> sample)
{
// http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
// kx - y + b = 0
return std::fabs(k_ * sample->x() + (-1.) * sample->y() + b_) / std::sqrt(k_ * k_ + (-1. * -1.));
}
const std::array<std::shared_ptr<SampleT>, min_sample_> getModelSample() const { return model_sample_; }
private:
float k_, b_;
std::array<std::shared_ptr<SampleT>, min_sample_> model_sample_;
};
double slope(int x0, int y0, int x1, int y1)
{
return (double)(y1 - y0) / (x1 - x0);
}
void draw_line(cv::Mat& output, cv::Point a, cv::Point b, cv::Scalar color, int LineWidth)
{
double s = slope(a.x, a.y, b.x, b.y);
cv::Point p(0, 0), q(output.cols, output.rows);
p.y = -(a.x - p.x) * s + a.y;
q.y = -(b.x - q.x) * s + b.y;
cv::line(output, p, q, color, LineWidth, cv::LINE_AA, 0);
}
void least_squares(const std::set<std::shared_ptr<Eigen::Vector2d>> &observation, float &k, float &b)
{
Eigen::MatrixXd A(observation.size(), 2);
Eigen::VectorXd B(observation.size());
int pos = 0;
for (auto iter : observation) {
A(pos, 0) = iter->x();
A(pos, 1) = 1.;
B(pos, 0) = iter->y();
pos++;
}
//Eigen::Vector2d x = (A.transpose() * A).inverse() * A.transpose() * B;
Eigen::Vector2d x = A.colPivHouseholderQr().solve(B);
k = x.x();
b = x.y();
}
int main(int argc, char * argv[])
{
while (true) {
int canvas_size = 800;
int points_size = 500;
int outlier_size = 50;
cv::Mat canvas(canvas_size, canvas_size, CV_8UC3, cv::Scalar::all(255));
std::random_device seed;
std::mt19937 random = std::mt19937(seed());
std::uniform_int_distribution<int> range(0, canvas_size - 1);
std::set<std::shared_ptr<Eigen::Vector2d>> observations;
{
std::normal_distribution<double> noise(0, 25);
for (int pos = 0; pos < points_size; pos++)
{
int value = range(random);
cv::Point point(floor(value + noise(random)), floor(value + noise(random)));
cv::circle(canvas, point, floor(canvas_size / 100) + 3, cv::Scalar(0, 0, 0), 2, cv::LINE_AA);
observations.insert(std::make_shared<Eigen::Vector2d>(point.x, point.y));
}
}
{
std::normal_distribution<double> noise(0, 200);
for (int pos = 0; pos < outlier_size; pos++) {
int value = range(random);
cv::Point point(floor(value + noise(random)), floor(value + noise(random)));
cv::circle(canvas, point, floor(canvas_size / 100) + 3, cv::Scalar(0, 0, 0), 2, cv::LINE_AA);
observations.insert(std::make_shared<Eigen::Vector2d>(point.x, point.y));
}
}
RandomSampleConsensus<LineModel, Eigen::Vector2d, 2> line_ransac(20);
line_ransac.evaluate(observations);
auto model = line_ransac.getModels().front();
auto inliers = model->getInliers();
if (inliers.size())
{
for (auto& inlier : inliers)
{
cv::Point point(floor(inlier->x()), floor(inlier->y()));
cv::circle(canvas, point, floor(canvas_size / 100), cv::Scalar(0, 255, 0), -1, cv::LINE_AA);
}
}
auto line = model->getModelSample();
{
auto begin = line[0];
auto end = line[1];
if (line[0] && line[1])
{
cv::Point begin(line[0]->x(), line[0]->y());
cv::Point end(line[1]->x(), line[1]->y());
draw_line(canvas, begin, end, cv::Scalar(0, 0, 255), 4);
}
}
{
float k_e = 0, b_e = 0;
least_squares(inliers, k_e, b_e);
auto begin = line[0];
auto end = line[1];
if (line[0] && line[1])
{
cv::Point begin(line[0]->x(), line[0]->x() * k_e + b_e);
cv::Point end(line[1]->x(), line[1]->x() * k_e + b_e);
draw_line(canvas, begin, end, cv::Scalar(255, 0, 0), 2);
}
}
cv::imshow("RANSAC", canvas);
char key = cv::waitKey(100);
if (key == 27) {
return 0;
}
}
return 0;
}
如下图,其中,黑色为观测值,绿色点为inliers,红色线ransac模型, 蓝色线为最小二乘拟合的直线。

Reference
http://www-users.math.umn.edu/~lerman/math5467/svd.pdf
https://baike.baidu.com/item/lu%E5%88%86%E8%A7%A3/764245?fr=aladdin
https://baike.baidu.com/item/QR%E5%88%86%E8%A7%A3/8918473?fr=aladdin
https://blog.csdn.net/wangshuailpp/article/details/80209863
https://blog.csdn.net/kokerf/article/details/72437294
https://www.cnblogs.com/liufuqiang/p/5663175.html