一、APAP算法优点
APAP算法是结合3d投影去做的极致算法,有利于和3d融合的技术
二、APAP 算法关键
APAP 算法叫做As-Projective-As-Possible,包含了柱面投影,球面投影,特征提取,特征匹配,单应性矩阵估计,APAP变换,图像融合等核心算法,前期必须用多种不同的投影算法解决非直线相机的问题,对于监控图像来说,最大的问题就是桶形畸变和鱼眼畸变,在拼接上难度比较大,很多系统直接经过畸变矫正以后再拼接,实际上矫正以后的图像非常难拼接,矫正图像的视差比不矫正更大,所以又必须使用各种投影算法去解决视差问题。APAP算法本身是一种后处理,第一步就是需要做预处理,也就是投影矫正,做好再开始做APAP算法。这方面包含多种矫正算法,尤其是球面投影矫正。
柱面和球面矫正需要用到的工具有很多, 目前有两种方法:
- 3d建模,将顶点和纹理映射
- 在2d平面上去做3d数据公式的计算,再返回来投影到2d上
讲述这两个方法的好处和缺点,1 3d建模可以解决直观的问题,效率高,使用者必须熟悉掌握3d的核心知识, 2 使用2d做3d数据计算,这样可以解决不用3d建模的步骤,但是不直观 ,我们采用后者的方法,这样做的好处是:
- 1 节省学习3d建模的时间
- 2 先掌握基础数学建模 ,后期再3d建模
- 3 3d建模投影后取平面投影的时间
- 4 跳过复杂阶段先进入核心问题
1 柱面矫正
柱面矫正包含两个算法,
a)柱面内壁投影
b) 投影变换,八字倾斜矫正
2 球面矫正
a) 球面内壁投影
b) 双投影变换,倾斜矫正
3 球面
1 特征提取与匹配:使用 SIFT 算法提取特征点并进行匹配
使用SIFT特征和kNN匹配,通过Lowe's比率测试筛选优质匹配点。
2 全局单应性计算 计算全局单应性矩阵 g_H
这一步和普通拼接算法一样,使用RANSAC 拟合全局单应性矩阵,在这之前还需要使用更多的计算让图像变为直线相机的图像,然后做投影算法,让图像符合可以拼接,也就是说,如果我们的图像不符合拼接,那后面的局部单应性计算就没有用。其中会有两种投影算法我们必须掌握,一种就是柱面投影,一种就是球面投影算法。
3 APAP 局部单应性计算
APAP算法中,局部单应性优化是核心步骤,其能量函数需同时满足全局一致性和局部平滑性,具体做法是将图像划分为网格,为每个网格点计算局部单应性矩阵,数学计算使用以下公式:

∣∣H i −H global ∣∣2||H~i~ - H~global~||^2∣∣H i −H global ∣∣2 代表的是全局一致性,强制Hi接近全局单应Hglobal,而∣∣H i −H j ∣∣2||H~i~ - H~j~||^2∣∣H i −H j ∣∣2 代表着局部平滑,要求得极值,
则对函数求导
4 图像变形与融合:
基于局部单应性矩阵对图像进行变形,并进行线性融合,做法是将图像划分为网格,对每个网格顶点优化局部单应性Hlocal,平衡全局一致性和局部对齐,实现能量函数最小化,这里面还有一个关键的地方是图像是多头做,还是单头做,如果做单头,视差大的图像可能矫正拉伸变形严重,不大的情况就应该单头做
5 拼接点刚性变换,纹理融合
使用三维的顶点和纹理拖拽,这一步也是必须有的,但是自动化比较难,必须有手动的辅助,完全手动拉伸则耗费精力,做法应该是点击增加对应的可选点,增加纹理顶点细分,当然,这样做肯定要增加计算量了,所有顶点也必须重新划分。最简单的做法就是顶点确定,点击对应顶点,让已经存在的顶点进行位置变换。
6 多频段融合
MultiBandBlender消除拼接缝
二、 数学推导(最小二乘问题)
将单应性矩阵 Hi 展开为9维向量, 由于能量函数为:

对每个 h i求导,令导数为零,得到线性方程组:
(1+λ∣N(i)∣)h i −λ∑i=N(i)n∣∣hi−hj∣∣2(1+λ|N(i)|) h~i~ - λ \sum_{i=N(i)}^{n} ||h_i-h_j||^2 (1+λ∣N(i)∣)h i −λi=N(i)∑n∣∣hi−hj∣∣2
将所有网格顶点的方程联立,构建稀疏线性系统 AH=b,其中:
A 是系数矩阵(稀疏对称正定)。
H 是所有 hi 的拼接向量。
b 是右侧常数项(与 hglobal 相关)
三、分步骤求解
我们使用最基本的线性方程求解,这个方程为 AX + b = 0,那么这里的未知就是X,实际上就是所有临近域的单应性矩阵h
(1) 单应性矩阵向量化
c
Mat flattenHomography(const Mat& H) {
return H.reshape(1, 9).clone(); // 3x3 -> 9x1
}
(2) 构建稀疏线性系统
使用Eigen库(高效稀疏矩阵运算):
c
#include <Eigen/Sparse>
void buildLinearSystem(const vector<Point2f>& gridVertices,
const Mat& H_global, double lambda,
Eigen::SparseMatrix<double>& A,
Eigen::VectorXd& b) {
int n = gridVertices.size();
A.resize(9 * n, 9 * n);
b.resize(9 * n);
// 全局单应性向量
Mat h_global = flattenHomography(H_global);
Eigen::Map<Eigen::VectorXd> h_global_eigen(h_global.ptr<double>(), 9);
// 填充A和b
for (int i = 0; i < n; i++) {
// 第一项:H_i = H_global
for (int k = 0; k < 9; k++) {
A.coeffRef(9 * i + k, 9 * i + k) += 1.0;
b(9 * i + k) = h_global_eigen(k);
}
// 第二项:相邻约束 (简化版:4邻域)
vector<int> neighbors = getNeighbors(i, gridVertices);
for (int j : neighbors) {
for (int k = 0; k < 9; k++) {
A.coeffRef(9 * i + k, 9 * i + k) += lambda;
A.coeffRef(9 * i + k, 9 * j + k) -= lambda;
}
}
}
}
(3) 求解线性系统
使用Eigen的共轭梯度法(适合稀疏矩阵),注意以下中有伪代码
c
vector<Mat> solveLocalHomographies(const Eigen::SparseMatrix<double>& A,
const Eigen::VectorXd& b, int n) {
Eigen::ConjugateGradient<Eigen::SparseMatrix<double>> solver;
solver.compute(A);
Eigen::VectorXd h = solver.solve(b);
// 将解转换为单应性矩阵列表
vector<Mat> H_locals(n);
for (int i = 0; i < n; i++) {
Mat Hi(3, 3, CV_64F, h.data() + 9 * i);
//注意伪代码,这里需要真实计算
H_locals[i] = Hi.clone();
}
return H_locals;
}
(4) 整合到APAP类中
c
vector<Mat> optimizeAllHomographies(const vector<Point2f>& gridVertices,
const Mat& H_global, double lambda) {
Eigen::SparseMatrix<double> A;
Eigen::VectorXd b;
buildLinearSystem(gridVertices, H_global, lambda, A, b);
return solveLocalHomographies(A, b, gridVertices.size());
}
四、算法实现
主要思想流程如下:
c
Mat stitch(const Mat& img1, const Mat& img2) {
// Step 1: 特征提取与匹配
vector<KeyPoint> kp1, kp2;
Mat desc1, desc2;
vector<DMatch> matches;
extractAndMatch(img1, img2, kp1, kp2, desc1, desc2, matches);
// Step 2: 计算全局单应性
Mat H_global = findGlobalHomography(kp1, kp2, matches);
// Step 3: 网格划分与局部单应性优化
Mat warped;
localWarp(img1, img2, H_global, kp1, kp2, matches, warped);
// Step 4: 图像融合
Mat result;
blendImages(warped, img2, result);
return result;
}
localwrap
c
void APAPStitcher::localWarp(const Mat& img1, const Mat& img2, const Mat& H_global,
const vector<KeyPoint>& kp1, const vector<KeyPoint>& kp2,
const vector<DMatch>& matches, Mat& warped) {
// 初始化网格顶点
vector<Point2f> gridVertices = createGridVertices(img1.size());
// 优化所有局部单应性
vector<Mat> H_locals = optimizeAllHomographies(gridVertices, H_global, lambda);
// 应用网格变形
applyMeshWarp(img1, gridVertices, H_locals, warped);
}
APAP算法 如何适应大模型代理
且听下回分解