文章目录
一、直接法的引出
2.1 特征点法的缺点
- 关键点的提取有描述子的计算耗时;
- 只使用特征点,丢弃了大部分可能有用的信息;
- 相机会运动到特征缺失的地方,这里找不到足够的特征点;
- 只能用于构建稀疏特征点(稀疏地图)。
2.2 光流法
- 保留关键点,把匹配描述子替换成光流跟踪,估计相机运动时仍使用对极几何、PnP或ICP算法,需要角点。
- 光流是一种描述像素随时间在图像间运动的方法。
- 根据使用像素的多少,光流法可以分成:
1)稀疏光流(计算部分像素运动):以Lucas-Kanade光流为代表,即L-K光流
2)稠密光流(计算所有像素运动):以Horn-Schunck光流为代表,即H-S光流 - 灰度不变假设:同一空间点的像素灰度值在不同图像是不变的。
- L-K光流常用来跟踪角点的运动,L-K光流的具体算法过程:P208-210
2.3 直接法
- 在光流中,我们首先确定特征点的位置,然后用光流跟踪确定相机运动,但是很难保证全局最优,因此引入直接法。
- 直接法:无需计算关键点和描述子,根据图像的像素灰度信息同时估计相机运动和点的投影,随机选点,无需角点。
- 假设P是一个位置已知的空间点,根据P的来源,直接法分成:
1)稀疏直接法:来自稀疏关键点;
2)半稠密直接法:来自部分像素,只考虑带有梯度的像素点,舍弃像素梯度不明显的地方;
3)稠密直接法:来自所有像素。 - 直接法的思路是,根据空间点P在当前相机的像素下,通过位姿估计值寻找另一个相机的像素位置,因为存在光度误差,我们需要进行优化。
- 求解直接法最后等价于求解一个优化问题,计算出雅克比矩阵,然后通过使用g2o或者Ceres这些优化库,也可以手写高斯牛顿法或者列文伯格-马夸尔特法,来计算增量,迭代求解。
- 直接法的具体算法过程:P219-220,包括计算雅克比矩阵、增量等。
二、光流法实践
2.1 理论
我们在第一张图片提取角点,然后用光流跟踪他们在第二张图片的位置。
采用3种方法:
1)OpenCV自带的LK光流;
2)基于高斯牛顿法的光流:因为光流可以看做一个优化问题,通过最小化灰度误差估计最优的像素偏移;
3)多层光流:使用图像金字塔。
2.2 CMakeList.txt
cmake_minimum_required(VERSION 2.8)
project(ch8)
set(CMAKE_BUILD_TYPE "Release")
add_definitions("-DENABLE_SSE")
set(CMAKE_CXX_FLAGS "-std=c++14 -msse4")
find_package(OpenCV REQUIRED)
find_package(Sophus REQUIRED)
find_package(Pangolin REQUIRED)
include_directories(
${OpenCV_INCLUDE_DIRS}
${G2O_INCLUDE_DIRS}
${Sophus_INCLUDE_DIRS}
"/usr/include/eigen3/"
${Pangolin_INCLUDE_DIRS}
)
add_executable(optical_flow optical_flow.cpp)
target_link_libraries(optical_flow ${OpenCV_LIBS} ${FMT_LIBRARIES} fmt)
add_executable(direct_method direct_method.cpp)
target_link_libraries(direct_method ${OpenCV_LIBS} ${Pangolin_LIBRARIES} ${FMT_LIBRARIES} fmt)
2.3 代码展示
//
// Created by Xiang on 2017/12/19.
//
#include <opencv2/opencv.hpp>
#include <string>
#include <chrono>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <opencv2/imgproc/types_c.h>
using namespace std;
using namespace cv;
string file_1 = "/home/robot/桌面/slambook2-master/ch8/LK1.png"; // first image
string file_2 = "/home/robot/桌面/slambook2-master/ch8/LK2.png"; // second image
/// 光流跟踪器和接口Optical flow tracker and interface
class OpticalFlowTracker {
public:
OpticalFlowTracker(
const Mat &img1_,
const Mat &img2_,
const vector<KeyPoint> &kp1_,
vector<KeyPoint> &kp2_,
vector<bool> &success_,
bool inverse_ = true, bool has_initial_ = false) :
img1(img1_), img2(img2_), kp1(kp1_), kp2(kp2_), success(success_), inverse(inverse_),
has_initial(has_initial_) {}
void calculateOpticalFlow(const Range &range);
private:
const Mat &img1;
const Mat &img2;
const vector<KeyPoint> &kp1;
vector<KeyPoint> &kp2;
vector<bool> &success;
bool inverse = true;
bool has_initial = false;
};
/**单层光流
* single level optical flow
* @param [in] img1 the first image
* @param [in] img2 the second image
* @param [in] kp1 keypoints in img1
* @param [in|out] kp2 keypoints in img2, if empty, use initial guess in kp1
* @param [out] success true if a keypoint is tracked successfully
* @param [in] inverse use inverse formulation?
*/
void OpticalFlowSingleLevel(
const Mat &img1,
const Mat &img2,
const vector<KeyPoint> &kp1,
vector<KeyPoint> &kp2,
vector<bool> &success,
bool inverse = false,
bool has_initial_guess = false
);
/**多层光流,金字塔的比例默认设置为2
* multi level optical flow, scale of pyramid is set to 2 by default
* the image pyramid will be create inside the function
* @param [in] img1 the first pyramid
* @param [in] img2 the second pyramid
* @param [in] kp1 keypoints in img1
* @param [out] kp2 keypoints in img2
* @param [out] success true if a keypoint is tracked successfully
* @param [in] inverse set true to enable inverse formulation
*/
void OpticalFlowMultiLevel(
const Mat &img1,
const Mat &img2,
const vector<KeyPoint> &kp1,
vector<KeyPoint> &kp2,
vector<bool> &success,
bool inverse = false
);
/**从参考图像中获取灰度值(双线性插值)
* get a gray scale value from reference image (bi-linear interpolated)
* @param img
* @param x
* @param y
* @return the interpolated value of this pixel
*/
inline float GetPixelValue(const cv::Mat &img, float x, float y) {
// boundary check
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= img.cols - 1) x = img.cols - 2;
if (y >= img.rows - 1) y = img.rows - 2;
float xx = x - floor(x);
float yy = y - floor(y);
int x_a1 = std::min(img.cols - 1, int(x) + 1);
int y_a1 = std::min(img.rows - 1, int(y) + 1);
return (1 - xx) * (1 - yy) * img.at<uchar>(y, x)
+ xx * (1 - yy) * img.at<uchar>(y, x_a1)
+ (1 - xx) * yy * img.at<uchar>(y_a1, x)
+ xx * yy * img.at<uchar>(y_a1, x_a1);
}
int main(int argc, char **argv) {
// 1. 读取图像
Mat img1 = imread(file_1, 0);
Mat img2 = imread(file_2, 0);
// 2. 提取第一幅图的关键点,在此使用GFTT算法
vector<KeyPoint> kp1;
Ptr<GFTTDetector> detector = GFTTDetector::create(500, 0.01, 20); // maximum 500 keypoints
detector->detect(img1, kp1);
// 3. 跟踪第二张图像中的这些关键点
// 1)首先在验证图片中使用单级LK
vector<KeyPoint> kp2_single;
vector<bool> success_single;
OpticalFlowSingleLevel(img1, img2, kp1, kp2_single, success_single);
// 2)然后测试多级LK
vector<KeyPoint> kp2_multi;
vector<bool> success_multi;
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
OpticalFlowMultiLevel(img1, img2, kp1, kp2_multi, success_multi, true);
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "optical flow by gauss-newton: " << time_used.count() << endl;
// 3)使用opencv的光流进行验证
vector<Point2f> pt1, pt2;
for (auto &kp: kp1) pt1.push_back(kp.pt);
vector<uchar> status;
vector<float> error;
t1 = chrono::steady_clock::now();
cv::calcOpticalFlowPyrLK(img1, img2, pt1, pt2, status, error);
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "optical flow by opencv: " << time_used.count() << endl;
// 4. 绘制这些函数的差异
Mat img2_single;
cv::cvtColor(img2, img2_single, CV_GRAY2BGR);
for (int i = 0; i < kp2_single.size(); i++) {
if (success_single[i]) {
cv::circle(img2_single, kp2_single[i].pt, 2, cv::Scalar(0, 250, 0), 2);
cv::line(img2_single, kp1[i].pt, kp2_single[i].pt, cv::Scalar(0, 250, 0));
}
}
Mat img2_multi;
cv::cvtColor(img2, img2_multi, CV_GRAY2BGR);
for (int i = 0; i < kp2_multi.size(); i++) {
if (success_multi[i]) {
cv::circle(img2_multi, kp2_multi[i].pt, 2, cv::Scalar(0, 250, 0), 2);
cv::line(img2_multi, kp1[i].pt, kp2_multi[i].pt, cv::Scalar(0, 250, 0));
}
}
Mat img2_CV;
cv::cvtColor(img2, img2_CV, CV_GRAY2BGR);
for (int i = 0; i < pt2.size(); i++) {
if (status[i]) {
cv::circle(img2_CV, pt2[i], 2, cv::Scalar(0, 250, 0), 2);
cv::line(img2_CV, pt1[i], pt2[i], cv::Scalar(0, 250, 0));
}
}
cv::imshow("tracked single level", img2_single);
cv::imshow("tracked multi level", img2_multi);
cv::imshow("tracked by opencv", img2_CV);
cv::waitKey(0);
return 0;
}
// 单层光流
void OpticalFlowSingleLevel(
const Mat &img1,
const Mat &img2,
const vector<KeyPoint> &kp1,
vector<KeyPoint> &kp2,
vector<bool> &success,
bool inverse, bool has_initial) {
kp2.resize(kp1.size());//将第二个图像的特征点数量设置和第一个一样
success.resize(kp1.size());
OpticalFlowTracker tracker(img1, img2, kp1, kp2, success, inverse, has_initial);
parallel_for_(Range(0, kp1.size()),
std::bind(&OpticalFlowTracker::calculateOpticalFlow, &tracker, placeholders::_1));//计算光流
}
// 计算光流
void OpticalFlowTracker::calculateOpticalFlow(const Range &range) {
// parameters
int half_patch_size = 4;
int iterations = 10;
for (size_t i = range.start; i < range.end; i++) {
auto kp = kp1[i];
// 1. 设置dx dy
double dx = 0, dy = 0;
if (has_initial) {
dx = kp2[i].pt.x - kp.pt.x;
dy = kp2[i].pt.y - kp.pt.y;
}
double cost = 0, lastCost = 0;
bool succ = true; // indicate if this point succeeded
// 2. 高斯牛顿法
Eigen::Matrix2d H = Eigen::Matrix2d::Zero(); // hessian
Eigen::Vector2d b = Eigen::Vector2d::Zero(); // bias
Eigen::Vector2d J; // jacobian
for (int iter = 0; iter < iterations; iter++) { //迭代10次
if (inverse == false) {
H = Eigen::Matrix2d::Zero();
b = Eigen::Vector2d::Zero();
} else {
// only reset b
b = Eigen::Vector2d::Zero();
}
cost = 0;
// 计算 cost 和 jacobian
for (int x = -half_patch_size; x < half_patch_size; x++)
for (int y = -half_patch_size; y < half_patch_size; y++) {
double error = GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y) -
GetPixelValue(img2, kp.pt.x + x + dx, kp.pt.y + y + dy);; // Jacobian
if (inverse == false) {
J = -1.0 * Eigen::Vector2d(
0.5 * (GetPixelValue(img2, kp.pt.x + dx + x + 1, kp.pt.y + dy + y) -
GetPixelValue(img2, kp.pt.x + dx + x - 1, kp.pt.y + dy + y)),
0.5 * (GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y + 1) -
GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y - 1))
);
} else if (iter == 0) {
// 在逆模式中,J在所有迭代中保持不变
// 注意,当dx,dy更新时,这个J不会改变,所以我们可以存储它,只计算错误
// in inverse mode, J keeps same for all iterations
// NOTE this J does not change when dx, dy is updated, so we can store it and only compute error
J = -1.0 * Eigen::Vector2d(
0.5 * (GetPixelValue(img1, kp.pt.x + x + 1, kp.pt.y + y) -
GetPixelValue(img1, kp.pt.x + x - 1, kp.pt.y + y)),
0.5 * (GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y + 1) -
GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y - 1))
);
}
// 计算 H, b and set cost;
b += -error * J;
cost += error * error;
if (inverse == false || iter == 0) {
// also update H
H += J * J.transpose();
}
}
// 得到 H * 更新量 = b 的 更新量
Eigen::Vector2d update = H.ldlt().solve(b);
if (std::isnan(update[0])) {
// sometimes occurred when we have a black or white patch and H is irreversible
cout << "update is nan" << endl;
succ = false;
break;
}
if (iter > 0 && cost > lastCost) {
break;
}
// 更新 dx, dy
dx += update[0];
dy += update[1];
lastCost = cost;
succ = true;
if (update.norm() < 1e-2) {
// converge
break;
}
}
success[i] = succ; // 如果这个点成功就设置成功,反之
// 设置 kp2
kp2[i].pt = kp.pt + Point2f(dx, dy);
}
}
// 多层光流
void OpticalFlowMultiLevel(
const Mat &img1,
const Mat &img2,
const vector<KeyPoint> &kp1,
vector<KeyPoint> &kp2,
vector<bool> &success,
bool inverse) {
// parameters
int pyramids = 4;
double pyramid_scale = 0.5;
double scales[] = {1.0, 0.5, 0.25, 0.125};
// 构建金字塔
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
vector<Mat> pyr1, pyr2; // image pyramids
for (int i = 0; i < pyramids; i++) {
if (i == 0) { // 第一个放入最底层
pyr1.push_back(img1);
pyr2.push_back(img2);
} else { // 其余要进行缩放
Mat img1_pyr, img2_pyr;
cv::resize(pyr1[i - 1], img1_pyr,
cv::Size(pyr1[i - 1].cols * pyramid_scale, pyr1[i - 1].rows * pyramid_scale));
cv::resize(pyr2[i - 1], img2_pyr,
cv::Size(pyr2[i - 1].cols * pyramid_scale, pyr2[i - 1].rows * pyramid_scale));
pyr1.push_back(img1_pyr);
pyr2.push_back(img2_pyr);
}
}
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "build pyramid time: " << time_used.count() << endl;
// 特征点缩放
vector<KeyPoint> kp1_pyr, kp2_pyr;
for (auto &kp:kp1) {
auto kp_top = kp;
kp_top.pt *= scales[pyramids - 1];
kp1_pyr.push_back(kp_top);
kp2_pyr.push_back(kp_top);
}
// 对每层调用单层直接法
for (int level = pyramids - 1; level >= 0; level--) {
// from coarse to fine
success.clear();
t1 = chrono::steady_clock::now();
OpticalFlowSingleLevel(pyr1[level], pyr2[level], kp1_pyr, kp2_pyr, success, inverse, true);
t2 = chrono::steady_clock::now();
auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "track pyr " << level << " cost time: " << time_used.count() << endl;
if (level > 0) {
for (auto &kp: kp1_pyr)
kp.pt /= pyramid_scale;
for (auto &kp: kp2_pyr)
kp.pt /= pyramid_scale;
}
}
for (auto &kp: kp2_pyr)
kp2.push_back(kp);
}
2.4 结果展示
build pyramid time: 0.001775
track pyr 3 cost time: 0.000317644
track pyr 2 cost time: 0.00025403
track pyr 1 cost time: 0.000238879
track pyr 0 cost time: 0.000227848
optical flow by gauss-newton: 0.00309121
optical flow by opencv: 0.00202506
三、直接法实践
3.1 理论
求解直接法最后等价于求解一个优化问题,计算出雅克比矩阵,然后通过使用g2o或者Ceres这些优化库,也可以手写高斯牛顿法或者列文伯格-马夸尔特法,来计算增量,迭代求解。
这里我们使用手写高斯牛顿法,演示稀疏的直接法,分成两种方式:
1)单层直接法
2)多层直接法:图像金字塔,每层调用单层直接法
3.2 CMakeList.txt
3.3 代码展示
#include <opencv2/opencv.hpp>
#include <sophus/se3.hpp>
#include <boost/format.hpp>
#include <pangolin/pangolin.h>
#include <opencv2/imgproc/types_c.h>
using namespace std;
typedef vector<Eigen::Vector2d, Eigen::aligned_allocator<Eigen::Vector2d>> VecVector2d;
// 相机内参
double fx = 718.856, fy = 718.856, cx = 607.1928, cy = 185.2157;
// baseline
double baseline = 0.573;
// paths
string left_file = "/home/robot/桌面/slambook2-master/ch8/left.png";
string disparity_file = "/home/robot/桌面/slambook2-master/ch8/disparity.png";
boost::format fmt_others("/home/robot/桌面/slambook2-master/ch8/%06d.png"); // other files
// useful typedefs
typedef Eigen::Matrix<double, 6, 6> Matrix6d;
typedef Eigen::Matrix<double, 2, 6> Matrix26d;
typedef Eigen::Matrix<double, 6, 1> Vector6d;
/// class for accumulator jacobians in parallel
class JacobianAccumulator {
public:
JacobianAccumulator(
const cv::Mat &img1_,
const cv::Mat &img2_,
const VecVector2d &px_ref_,
const vector<double> depth_ref_,
Sophus::SE3d &T21_) :
img1(img1_), img2(img2_), px_ref(px_ref_), depth_ref(depth_ref_), T21(T21_) {
projection = VecVector2d(px_ref.size(), Eigen::Vector2d(0, 0));
}
/// accumulate jacobians in a range
void accumulate_jacobian(const cv::Range &range);
/// get hessian matrix
Matrix6d hessian() const { return H; }
/// get bias
Vector6d bias() const { return b; }
/// get total cost
double cost_func() const { return cost; }
/// get projected points
VecVector2d projected_points() const { return projection; }
/// reset h, b, cost to zero
void reset() {
H = Matrix6d::Zero();
b = Vector6d::Zero();
cost = 0;
}
private:
const cv::Mat &img1;
const cv::Mat &img2;
const VecVector2d &px_ref;
const vector<double> depth_ref;
Sophus::SE3d &T21;
VecVector2d projection; // projected points
std::mutex hessian_mutex;
Matrix6d H = Matrix6d::Zero();
Vector6d b = Vector6d::Zero();
double cost = 0;
};
/**
* 使用多层直接法进行位姿态估计
* @param img1
* @param img2
* @param px_ref
* @param depth_ref
* @param T21
*/
void DirectPoseEstimationMultiLayer(
const cv::Mat &img1,
const cv::Mat &img2,
const VecVector2d &px_ref,
const vector<double> depth_ref,
Sophus::SE3d &T21
);
/**
* 使用单层直接法进行位姿态估计
* @param img1
* @param img2
* @param px_ref
* @param depth_ref
* @param T21
*/
void DirectPoseEstimationSingleLayer(
const cv::Mat &img1,
const cv::Mat &img2,
const VecVector2d &px_ref,
const vector<double> depth_ref,
Sophus::SE3d &T21
);
// 双线性插值bilinear interpolation
inline float GetPixelValue(const cv::Mat &img, float x, float y) {
// 边界检查boundary check
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= img.cols) x = img.cols - 1;
if (y >= img.rows) y = img.rows - 1;
uchar *data = &img.data[int(y) * img.step + int(x)];
float xx = x - floor(x);
float yy = y - floor(y);
return float(
(1 - xx) * (1 - yy) * data[0] +
xx * (1 - yy) * data[1] +
(1 - xx) * yy * data[img.step] +
xx * yy * data[img.step + 1]
);
}
int main(int argc, char **argv) {
// 1.图像读取
cv::Mat left_img = cv::imread(left_file, 0);
cv::Mat disparity_img = cv::imread(disparity_file, 0);//rgb-d相机采集的数据
// 2. 让我们随机选取第一张图像中的像素,并在第一张图像的帧中生成一些3d点
cv::RNG rng;
int nPoints = 2000;
int boarder = 20;
VecVector2d pixels_ref;
vector<double> depth_ref;
// 3. 在ref中生成像素并加载深度数据
for (int i = 0; i < nPoints; i++) {
int x = rng.uniform(boarder, left_img.cols - boarder); // 不要拾取靠近边界的像素
int y = rng.uniform(boarder, left_img.rows - boarder); // 不要拾取靠近边界的像素
int disparity = disparity_img.at<uchar>(y, x); //从rgb-d相机采集的图像中提取某个像素点距离
// 根据这个式子计算深度,本人不太懂原理
double depth = fx * baseline / disparity; // 深度=焦距*基线/距离
depth_ref.push_back(depth); // 深度
pixels_ref.push_back(Eigen::Vector2d(x, y));// 像素
}
// 4. 估计01-05.png的位姿
Sophus::SE3d T_cur_ref;
for (int i = 1; i < 6; i++) { // 1~10
cv::Mat img = cv::imread((fmt_others % i).str(), 0);
// 1)单层直接法
DirectPoseEstimationSingleLayer(left_img, img, pixels_ref, depth_ref, T_cur_ref);
// 2)多层直接法
// DirectPoseEstimationMultiLayer(left_img, img, pixels_ref, depth_ref, T_cur_ref);
}
return 0;
}
void DirectPoseEstimationSingleLayer(
const cv::Mat &img1,
const cv::Mat &img2,
const VecVector2d &px_ref,
const vector<double> depth_ref,
Sophus::SE3d &T21) {
const int iterations = 10;
double cost = 0, lastCost = 0;
auto t1 = chrono::steady_clock::now();
JacobianAccumulator jaco_accu(img1, img2, px_ref, depth_ref, T21);
// 1. 计算出H b 更新量,并更新到估计值上
for (int iter = 0; iter < iterations; iter++) {
// 1) 构建Hdx=b
jaco_accu.reset();
cv::parallel_for_(cv::Range(0, px_ref.size()),
std::bind(&JacobianAccumulator::accumulate_jacobian, &jaco_accu, std::placeholders::_1));
// 上式中的accumulate_jacobian用来计算雅可比
Matrix6d H = jaco_accu.hessian();
Vector6d b = jaco_accu.bias();
// 2) 解决更新并进行估计
Vector6d update = H.ldlt().solve(b);; //求出dx
T21 = Sophus::SE3d::exp(update) * T21; // 左乘更新
cost = jaco_accu.cost_func();
if (std::isnan(update[0])) {
// 当有黑色或白色斑块并且H是不可逆的时候,有时发生
cout << "update is nan" << endl;
break;
}
if (iter > 0 && cost > lastCost) {
cout << "cost increased: " << cost << ", " << lastCost << endl;
break;
}
if (update.norm() < 1e-3) {
// converge
break;
}
lastCost = cost;
cout << "iteration: " << iter << ", cost: " << cost << endl;
}
// 2. 输出位姿和时间
cout << "T21 = \n" << T21.matrix() << endl;
auto t2 = chrono::steady_clock::now();
auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "单层直接法所用时间: " << time_used.count() << endl;
// 3. 在此处绘制投影像素
cv::Mat img2_show;
cv::cvtColor(img2, img2_show, CV_GRAY2BGR);
VecVector2d projection = jaco_accu.projected_points();
for (size_t i = 0; i < px_ref.size(); ++i) {
auto p_ref = px_ref[i];
auto p_cur = projection[i];
if (p_cur[0] > 0 && p_cur[1] > 0) {
cv::circle(img2_show, cv::Point2f(p_cur[0], p_cur[1]), 2, cv::Scalar(0, 250, 0), 2);
cv::line(img2_show, cv::Point2f(p_ref[0], p_ref[1]), cv::Point2f(p_cur[0], p_cur[1]),
cv::Scalar(0, 250, 0));
}
}
cv::imshow("current", img2_show);
cv::waitKey();
}
void JacobianAccumulator::accumulate_jacobian(const cv::Range &range) {
// 这里的误差和雅可比计算公式按照P220
// parameters
const int half_patch_size = 1;
int cnt_good = 0;
Matrix6d hessian = Matrix6d::Zero();
Vector6d bias = Vector6d::Zero();
double cost_tmp = 0;
for (size_t i = range.start; i < range.end; i++) {
// 1. 计算第二幅图像中的投影P219的p1和p2
// point_ref表示第一张图片像素坐标P1
Eigen::Vector3d point_ref =
depth_ref[i] * Eigen::Vector3d((px_ref[i][0] - cx) / fx, (px_ref[i][1] - cy) / fy, 1);
// point_cur表示第二张图片的投影的下像素坐标P2
Eigen::Vector3d point_cur = T21 * point_ref;
if (point_cur[2] < 0) // 深度<0无效
continue;
// 2. 求出雅可比矩阵一面的一些参数u v
float u = fx * point_cur[0] / point_cur[2] + cx, v = fy * point_cur[1] / point_cur[2] + cy;
if (u < half_patch_size || u > img2.cols - half_patch_size || v < half_patch_size ||
v > img2.rows - half_patch_size)
continue;
// 求出雅可比矩阵一面的一些参数 X Y Z Z^2 1/Z 1/Z^2
projection[i] = Eigen::Vector2d(u, v);
double X = point_cur[0], Y = point_cur[1], Z = point_cur[2],
Z2 = Z * Z, Z_inv = 1.0 / Z, Z2_inv = Z_inv * Z_inv;
cnt_good++;
// 3. 计算误差和雅可比
for (int x = -half_patch_size; x <= half_patch_size; x++)
for (int y = -half_patch_size; y <= half_patch_size; y++) {
// 1)e=I1(p1)-I2(P2) P219 公式8.11
double error = GetPixelValue(img1, px_ref[i][0] + x, px_ref[i][1] + y) -
GetPixelValue(img2, u + x, v + y);
// 2)求出J
Matrix26d J_pixel_xi;
Eigen::Vector2d J_img_pixel;
J_pixel_xi(0, 0) = fx * Z_inv;
J_pixel_xi(0, 1) = 0;
J_pixel_xi(0, 2) = -fx * X * Z2_inv;
J_pixel_xi(0, 3) = -fx * X * Y * Z2_inv;
J_pixel_xi(0, 4) = fx + fx * X * X * Z2_inv;
J_pixel_xi(0, 5) = -fx * Y * Z_inv;
J_pixel_xi(1, 0) = 0;
J_pixel_xi(1, 1) = fy * Z_inv;
J_pixel_xi(1, 2) = -fy * Y * Z2_inv;
J_pixel_xi(1, 3) = -fy - fy * Y * Y * Z2_inv;
J_pixel_xi(1, 4) = fy * X * Y * Z2_inv;
J_pixel_xi(1, 5) = fy * X * Z_inv;
J_img_pixel = Eigen::Vector2d(
0.5 * (GetPixelValue(img2, u + 1 + x, v + y) - GetPixelValue(img2, u - 1 + x, v + y)),
0.5 * (GetPixelValue(img2, u + x, v + 1 + y) - GetPixelValue(img2, u + x, v - 1 + y))
);
// total jacobian
Vector6d J = -1.0 * (J_img_pixel.transpose() * J_pixel_xi).transpose();
// 3)利用高斯牛顿求出H * dx = b中的H b
hessian += J * J.transpose();
bias += -error * J;
cost_tmp += error * error;
}
}
if (cnt_good) {
// set hessian, bias and cost
unique_lock<mutex> lck(hessian_mutex);
H += hessian;
b += bias;
cost += cost_tmp / cnt_good;
}
}
void DirectPoseEstimationMultiLayer(
const cv::Mat &img1,
const cv::Mat &img2,
const VecVector2d &px_ref,
const vector<double> depth_ref,
Sophus::SE3d &T21) {
// 1. 定义参数
int pyramids = 4; // 金字塔层数
double pyramid_scale = 0.5; //每层图片缩放倍率
double scales[] = {1.0, 0.5, 0.25, 0.125}; //每层内参缩放倍率
// 2. 构建金字塔
vector<cv::Mat> pyr1, pyr2; // image pyramids
for (int i = 0; i < pyramids; i++) {
if (i == 0) { //第一张直接放到金字塔底部
pyr1.push_back(img1);
pyr2.push_back(img2);
} else { //其余每层在上层的基础上缩放
cv::Mat img1_pyr, img2_pyr;
cv::resize(pyr1[i - 1], img1_pyr,
cv::Size(pyr1[i - 1].cols * pyramid_scale, pyr1[i - 1].rows * pyramid_scale));
cv::resize(pyr2[i - 1], img2_pyr,
cv::Size(pyr2[i - 1].cols * pyramid_scale, pyr2[i - 1].rows * pyramid_scale));
pyr1.push_back(img1_pyr);
pyr2.push_back(img2_pyr);
}
}
// 3. 开始执行
double fxG = fx, fyG = fy, cxG = cx, cyG = cy; // 备份初始内参
for (int level = pyramids - 1; level >= 0; level--) {
// 每层的关键点也要缩放
VecVector2d px_ref_pyr;
for (auto &px: px_ref) {
px_ref_pyr.push_back(scales[level] * px);
}
// 不同金字塔级别中的内参的缩放
fx = fxG * scales[level];
fy = fyG * scales[level];
cx = cxG * scales[level];
cy = cyG * scales[level];
// 每层用单层直接法计算
DirectPoseEstimationSingleLayer(pyr1[level], pyr2[level], px_ref_pyr, depth_ref, T21);
}
}
3.4 结果展示
- 单层直接法:分别对五张图片计算出外参T,图片几乎类似这里只显示一张。
iteration: 0, cost: 4.0882e+06
iteration: 1, cost: 3.89309e+06
iteration: 2, cost: 3.80651e+06
iteration: 3, cost: 3.73133e+06
iteration: 4, cost: 3.66689e+06
iteration: 5, cost: 3.62699e+06
iteration: 6, cost: 3.60182e+06
cost increased: 3.60262e+06, 3.60182e+06
T21 =
0.999999 -0.000516409 0.000938655 -0.0107857
0.000513252 0.999994 0.00336124 -0.0199524
-0.000940385 -0.00336075 0.999994 -0.115379
0 0 0 1
单层直接法所用时间: 0.00370231
iteration: 0, cost: 4.51727e+06
iteration: 1, cost: 4.44097e+06
iteration: 2, cost: 4.40338e+06
iteration: 3, cost: 4.38437e+06
iteration: 4, cost: 4.36851e+06
iteration: 5, cost: 4.3375e+06
iteration: 6, cost: 4.31656e+06
iteration: 7, cost: 4.28746e+06
iteration: 8, cost: 4.28125e+06
cost increased: 4.29874e+06, 4.28125e+06
T21 =
1 -0.00023899 -0.000423502 0.0318101
0.000240825 0.999991 0.0043384 -0.0140133
0.000422461 -0.0043385 0.99999 -0.0616333
0 0 0 1
单层直接法所用时间: 0.00604826
iteration: 0, cost: 6.27375e+06
iteration: 1, cost: 6.21322e+06
iteration: 2, cost: 6.09078e+06
iteration: 3, cost: 5.94502e+06
iteration: 4, cost: 5.8199e+06
iteration: 5, cost: 5.7109e+06
iteration: 6, cost: 5.66204e+06
iteration: 7, cost: 5.63571e+06
iteration: 8, cost: 5.56997e+06
iteration: 9, cost: 5.12689e+06
T21 =
0.999966 -0.00781871 -0.00278991 0.0766677
0.00783866 0.999943 0.00721305 -0.0372605
0.00273336 -0.00723467 0.99997 -0.128187
0 0 0 1
单层直接法所用时间: 0.00568748
iteration: 0, cost: 6.13602e+06
iteration: 1, cost: 6.11936e+06
iteration: 2, cost: 6.09768e+06
iteration: 3, cost: 6.00533e+06
iteration: 4, cost: 6.00515e+06
iteration: 5, cost: 5.99342e+06
iteration: 6, cost: 5.9524e+06
iteration: 7, cost: 5.90819e+06
iteration: 8, cost: 5.89092e+06
iteration: 9, cost: 5.87545e+06
T21 =
0.999941 -0.0108718 0.000193771 0.0288788
0.0108699 0.999909 0.00799969 -0.0580119
-0.000280724 -0.00799711 0.999968 -0.256918
0 0 0 1
单层直接法所用时间: 0.00389448
iteration: 0, cost: 7.42899e+06
iteration: 1, cost: 7.412e+06
cost increased: 7.41608e+06, 7.412e+06
T21 =
0.999924 -0.0123424 -0.000713318 0.0351923
0.0123486 0.999878 0.00951657 -0.0727886
0.000595773 -0.00952466 0.999954 -0.231601
0 0 0 1
单层直接法所用时间: 0.00221729
2. 多层直接法:由于篇幅原因,这里只展示对第一张图片的金字塔多层优化,这里是4层金字塔。
iteration: 0, cost: 1.59493e+06
iteration: 1, cost: 652954
iteration: 2, cost: 263926
iteration: 3, cost: 166670
cost increased: 172304, 166670
T21 =
0.999991 0.00226062 0.00346118 -0.00358483
-0.00226947 0.999994 0.00255482 0.00317976
-0.00345538 -0.00256266 0.999991 -0.720607
0 0 0 1
单层直接法所用时间: 0.00162608
iteration: 0, cost: 178898
cost increased: 180789, 178898
T21 =
0.999989 0.00303756 0.00346181 0.00140036
-0.00304538 0.999993 0.00225747 0.00627627
-0.00345493 -0.00226798 0.999991 -0.72901
0 0 0 1
单层直接法所用时间: 0.00266667
iteration: 0, cost: 248601
iteration: 1, cost: 229994
T21 =
0.999991 0.00251351 0.00346626 -0.00271352
-0.00252162 0.999994 0.00233523 0.00243136
-0.00346037 -0.00234395 0.999991 -0.73472
0 0 0 1
单层直接法所用时间: 0.00587702
iteration: 0, cost: 348714
T21 =
0.999991 0.00248082 0.00343393 -0.00374045
-0.00248837 0.999994 0.00219446 0.0030455
-0.00342847 -0.00220298 0.999992 -0.732343
0 0 0 1
单层直接法所用时间: 0.00270028
iteration: 0, cost: 1.31076e+06
iteration: 1, cost: 918151
iteration: 2, cost: 604174
iteration: 3, cost: 384708
iteration: 4, cost: 269610
iteration: 5, cost: 235605
cost increased: 239882, 235605
T21 =
0.99997 0.0010374 0.00769571 0.00572471
-0.00107293 0.999989 0.00461427 0.000557331
-0.00769083 -0.00462238 0.99996 -1.46166
0 0 0 1
单层直接法所用时间: 0.00987296