多传感器融合定位: 基于图优化的建图——数据流梳理

此篇文章仅仅为了梳理Sensor Fusion for Localization & Mapping :第六章graph-optimization优化数据流。

源码参考:https://github.com/AlexGeControl/Sensor-Fusion。

一、节点:data_pretreat_flow

订阅数据:

1.点云数据
2.imu数据
3.速度数据/速度、角速度,话题 /kitti/oxts/gps/vel
4.imu、lidar  tf link 转化矩阵

发布数据:

1.去畸变后点云数据 话题 cloud_topic
2.同步时间戳后imu数据
3.同步后gps位置、速度数据, 话题 /synced_pos_vel
4.雷达坐标系下,位姿、速度数据 /synced_gnss(相当于gnss/imu在lidar坐标系的里程计数据)

二、节点:front_end_node

订阅数据:

去畸变雷达数据 话题 cloud_topic

发布数据:

雷达估计位姿 话题 odom_topic

1.读数据

2.判断是否有数据

3.判断数据的有效性

4.执行

判断是否首次执行
首次执行,单位矩阵设置为初始位姿,
非首次执行:

(1)根据pcl库,移除无效点云

pcl::removeNaNFromPointCloud

参数:输入点云,输出有效点云,输出定义了frame:包含点云数据和点云位姿

注:
using POINT = pcl::PointXYZ
uing CLOUD = pcl::PointCloud<POINT>
uing CLOUD_PTR = CLOUD ::Ptr
CLOUD  cloud 和 CLOUD_PTR  cloud_ptr
则两者可以相互转化 cloud = *cloud_ptr 
只是为了说明pcl::PointCloud<POINT>::Ptr 为指针类型。。。

(2)对点云数据进行滤波

CloudData::CLOUD_PTR filtered_cloud_ptr(new CloudData::CLOUD());
frame_filter_ptr_->Filter(current_frame_.cloud_data.cloud_ptr, filtered_cloud_ptr);

(3)建立局部地图

if (local_map_frames_.size() == 0) {
    
    
        current_frame_.pose = init_pose_;
        UpdateWithNewFrame(current_frame_);
        cloud_pose = current_frame_.pose;
        return true;
}

这一步的目的是为了把关键帧的点云保存下来,由于用的是共享指针,所以直接复制只是复制了一个指针而已,此时无论你放多少个关键帧在容器里,这些关键帧点云指针都是指向的同一个点云。

bool FrontEnd::UpdateWithNewFrame(const Frame& new_key_frame) {
    
    
    Frame key_frame = new_key_frame;
    key_frame.cloud_data.cloud_ptr.reset(new CloudData::CLOUD(*new_key_frame.cloud_data.cloud_ptr));
    
    // keep only the latest local_frame_num_ frames:
    local_map_frames_.push_back(key_frame);
    while (local_map_frames_.size() > static_cast<size_t>(local_frame_num_)) {
    
    
        local_map_frames_.pop_front();
    }

    // transform all local frame measurements to map frame
    // to create local map:
    local_map_ptr_.reset(new CloudData::CLOUD());
    CloudData::CLOUD_PTR transformed_cloud_ptr(new CloudData::CLOUD());
    //这一段相当于对点云的拼接,依次将局部地图local_map_frames_中点云拼接,形成local_map
    /*
    举例:将第一帧点云local_map_frames_.at(0)通过转换矩阵 local_map_frames_.at(0).pose
    得到第二帧点云坐标系下点云transformed_cloud_ptr,然后将拼接相加得到新点云。
    其中, local_map_frames_.at(0).pose是通过前后两帧点云求得的位姿转化矩阵(align)。
    local_map_ptr_ 指向最终拼接完成的点云地图,即局部地图。
    */
    for (size_t i = 0; i < local_map_frames_.size(); ++i) {
    
    
        pcl::transformPointCloud(
            *local_map_frames_.at(i).cloud_data.cloud_ptr, 
            *transformed_cloud_ptr, 
            local_map_frames_.at(i).pose
        );

        *local_map_ptr_ += *transformed_cloud_ptr;
    }

    // scan-to-map matching:
    // set target as local map:
    //local_map_frames_大小小于10帧,直接输入点云作为配准点云,大于10帧,滤波后点云(局部地图)作为配准点云
    if (local_map_frames_.size() < 10) {
    
    
        registration_ptr_->SetInputTarget(local_map_ptr_);
    } else {
    
    
        CloudData::CLOUD_PTR filtered_local_map_ptr(new CloudData::CLOUD());
        local_map_filter_ptr_->Filter(local_map_ptr_, filtered_local_map_ptr);
        registration_ptr_->SetInputTarget(filtered_local_map_ptr);
    }

    return true;
}

(4)点云匹配求位姿(NDT)

NDT:1.根据target cloud 划分为栅格,统计落在各栅格中的点,根据各栅格中的点,计算各栅格均值、协方差、构建高斯分布; 2.不断迭代将source cloud 旋转、平移到target cloud ,计算联合概率,直到联合概率最大,得到最终的R,t.(本质上还是一个凸优化问题)
使用正态分布变换NDT进行配准,基本流程:

1.setInputSource   设置需要配准的点云
2.setInputTarget  设置点云配准目标
3.align(output, init_pose)   参数:配准,计算输入点云与目标点云的之间的转换矩阵final_transformation_,
并同时对输入点云input_做转换,并输出到output中。
CloudData::CLOUD_PTR result_cloud_ptr(new CloudData::CLOUD());
registration_ptr_->ScanMatch(filtered_cloud_ptr, predict_pose, result_cloud_ptr, current_frame_.pose);
cloud_pose = current_frame_.pose;

(5)基于匀速模型更新点云预测

step_pose = last_pose.inverse() * current_frame_.pose;
predict_pose = current_frame_.pose * step_pose;
last_pose = current_frame_.pose;
predice_pose其实作为align点云配准的初始估计。提升点云配准的速度和精度。

(6)更新关键帧

按照前后两帧位姿相距大于设定距离key_frame_distance_取关键帧。

if (fabs(last_key_frame_pose(0,3) - current_frame_.pose(0,3)) + 
    fabs(last_key_frame_pose(1,3) - current_frame_.pose(1,3)) +
    fabs(last_key_frame_pose(2,3) - current_frame_.pose(2,3)) > key_frame_distance_) {
    
    
    UpdateWithNewFrame(current_frame_);
    last_key_frame_pose = current_frame_.pose;
}

三、节点lio_back_end_node

订阅

1.去畸变点云数据
2.局部点云地图位姿(雷达里程计)
3.imu预积分位姿
4.轮速计预积分位姿
5.同步的gps全局位置
6.闭环关键帧位姿

发布

1.优化后局部点云位姿
2.关键帧位姿
3.优化后关键帧序列轨迹

1.读数据

2.增加回环位姿用于后端优化

(1)当检测到回环时,增加回环约束,调用AddPRVAGRelativePoseEdge接口。

InsertLoopClosurePose();
bool LIOBackEnd::InsertLoopPose(const LoopPose& loop_pose) {
    
    
    if (!graph_optimizer_config_.use_loop_close)
        return false;

    // get vertex IDs:
    const int vertex_index_i = loop_pose.index0;
    const int vertex_index_j = loop_pose.index1;
    // get relative pose measurement:
    Eigen::Matrix4d relative_pose = loop_pose.pose.cast<double>();
    // add constraint, lidar frontend / loop closure detection:
    graph_optimizer_ptr_->AddPRVAGRelativePoseEdge(
        vertex_index_i, vertex_index_j, 
        relative_pose, graph_optimizer_config_.close_loop_noise
    );

    // update loop closure count:
    ++counter_.loop_closure;
    
    LOG(INFO) << "Add loop closure: " << loop_pose.index0 << "," << loop_pose.index1 << std::endl;

    return true;
}

AddPRVAGRelativePoseEdge接口是封装的一个g2o双边接口,输入为顶点、回环相对位姿测量和信息矩阵。
AddPRVAGRelativePoseEdge具体实现:

a.初始化

初始化回环相对位姿边

g2o::EdgePRVAGRelativePose *edge(new g2o::EdgePRVAGRelativePose());

而不同的边又封装成类,如EdgePRVAGRelativePose,在类构造函数中实现,参数分别为errorDim, errorType, Vertex1Type, Vertex2Type。

class EdgePRVAGRelativePose : public g2o::BaseBinaryEdge<6, Vector6d, g2o::VertexPRVAG, g2o::VertexPRVAG> {
    
    
public:
	EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    static const int INDEX_P = 0;
    static const int INDEX_R = 3;

	EdgePRVAGRelativePose() 
	: g2o::BaseBinaryEdge<6, Vector6d, g2o::VertexPRVAG, g2o::VertexPRVAG>() {
    
    
	}

	virtual void computeError() override {
    
    
        const g2o::VertexPRVAG* v0 = static_cast<const g2o::VertexPRVAG*>(_vertices[0]);
        const g2o::VertexPRVAG* v1 = static_cast<const g2o::VertexPRVAG*>(_vertices[1]);

		const Eigen::Vector3d &pos_i = v0->estimate().pos;
		const Sophus::SO3d    &ori_i = v0->estimate().ori;

		const Eigen::Vector3d &pos_j = v1->estimate().pos;
		const Sophus::SO3d    &ori_j = v1->estimate().ori;

		const Eigen::Vector3d &pos_ij = _measurement.block<3, 1>(INDEX_P, 0);
		const Eigen::Vector3d &ori_ij = _measurement.block<3, 1>(INDEX_R, 0);

		_error.block(INDEX_P, 0, 3, 1) = ori_i.inverse() * (pos_j - pos_i) - pos_ij;
		_error.block(INDEX_R, 0, 3, 1) = (Sophus::SO3d::exp(ori_ij).inverse()*ori_i.inverse()*ori_j).log();
    }

    virtual void setMeasurement(const Vector6d& m) override {
    
    
		_measurement = m;
	}

	virtual bool read(std::istream& is) override {
    
    		
		Vector6d v;
		is >> v(INDEX_P + 0) >> v(INDEX_P + 1) >> v(INDEX_P + 2)
		   >> v(INDEX_R + 0) >> v(INDEX_R + 1) >> v(INDEX_R + 2);

    	setMeasurement(v);

		for (int i = 0; i < information().rows(); ++i) {
    
    
			for (int j = i; j < information().cols(); ++j) {
    
    
				is >> information()(i, j);
				// update cross-diagonal element:
				if (i != j) {
    
    
					information()(j, i) = information()(i, j);
				}
			}
		}

		return true;
	}

	virtual bool write(std::ostream& os) const override {
    
    
    	Vector6d v = _measurement;

		os << v(INDEX_P + 0) << " " <<v(INDEX_P + 1) << " " << v(INDEX_P + 2) << " "
		   << v(INDEX_R + 0) << " " <<v(INDEX_R + 1) << " " << v(INDEX_R + 2) << " ";

		for (int i = 0; i < information().rows(); ++i)
			for (int j = i; j < information().cols(); ++j)
				os << " " << information()(i, j);

		return os.good();
	}
};

b.设置顶点

g2o::VertexPRVAG* vertex_i = dynamic_cast<g2o::VertexPRVAG*>(graph_ptr_->vertex(vertex_index_i));
g2o::VertexPRVAG* vertex_j = dynamic_cast<g2o::VertexPRVAG*>(graph_ptr_->vertex(vertex_index_j));
edge->vertices()[0] = vertex_i;
edge->vertices()[1] = vertex_j;

c.设置测量(用于完成边误差计算)

// b. set measurement
Vector6d measurement = Vector6d::Zero();
// b.1. position:
measurement.block<3, 1>(g2o::EdgePRVAGRelativePose::INDEX_P, 0) = relative_pose.block<3, 1>(0, 3);
// b.2. orientation, so3:
measurement.block<3, 1>(g2o::EdgePRVAGRelativePose::INDEX_R, 0) = Sophus::SO3d(
	Eigen::Quaterniond(relative_pose.block<3, 3>(0, 0).cast<double>())
).log();
edge->setMeasurement(measurement);

d.设置信息矩阵

// c. set information matrix:
Eigen::MatrixXd information_matrix = CalculateSe3EdgeInformationMatrix(noise);
edge->setInformation(information_matrix);

e.设置鲁棒核函数

// d. set loss function:
if (need_robust_kernel_) {
    
    
	AddRobustKernel(edge, robust_kernel_name_, robust_kernel_size_);
}

将上述信息用于g2o优化

graph_ptr_->addEdge

(2)更新回环计数

++counter_.loop_closure

3.若有点云数据、局部地图点云位姿、gnss、imu则,更新updateBackEnd()

(1)判断数据是否有效,通过时间戳判断

(2)进入后端优化

如果首次执行,则将雷达里程计在map frame 的位姿作为初始位姿(将雷达里程计在map frame 的位姿与惯导位姿对齐)

if (!odometry_inited) {
    
    
	// the origin of lidar odometry frame in map frame as init pose:
	odom_init_pose = current_gnss_pose_data_.pose * current_laser_odom_data_.pose.inverse();

	odometry_inited = true;
}

(3)更新IMU预积分UpdateIMUPreIntegration();

UpdateIMUPreIntegration();

a.首次进行预积分进行初始化

重置imu状态

ResetState(init_imu_data);

将预积分状态量、方差、雅克比矩阵赋初值

b.更新状态UpdateState

aa.中值积分计算预积分状态量
bb.更新F、B矩阵,计算方差P
P = F*P*F + B*Q*B
cc.更新雅克比矩阵J
J = F*J

(4)更新轮速计预积分

UpdateOdoPreIntegration()

a.首次进行预积分进行初始化

重置imu状态,将预积分状态量、方差、雅克比矩阵赋初值

ResetState(init_imu_data);

b.更新状态UpdateState

aa.中值积分计算预积分状态量
bb.更新F、B矩阵,计算方差P
P = F*P*F + B*Q*B
cc.更新雅克比矩阵J
J = F*J

可以看出轮速计预积分和imu预积分一样,只是预积分状态量不一样,预积分对状态量雅克比矩阵不一样,其它完全一致。

(5)执行优化

输入参数:当前帧点云、当前帧点云位姿、当前帧gnss位置、当前帧imu数据

a.重置参数 ResetParam()

将变量标志has_new_key_frame_、has_new_optimized_置为false。

b.判断是否为新的关键帧MaybeNewKeyFrame()

MaybeNewKeyFrame()
aa.如果是第一帧:

初始化imu预积分和轮速计预积分。并将关键帧标志has_new_optimized_置为true。

if (key_frames_deque_.size() == 0) {
    
    

	// 初始化IMU预积分
	// init IMU pre-integrator:
	if ( imu_pre_integrator_ptr_ ) {
    
    
		imu_pre_integrator_ptr_->Init(imu_data);
	}

	// 初始化编码器预积分
	// init odometer pre-integrator:
	if ( odo_pre_integrator_ptr_ ) {
    
    
		gnss_odom.GetVelocityData(velocity_data);
		odo_pre_integrator_ptr_->Init(velocity_data);
	}

	last_laser_pose = laser_odom;
	last_gnss_pose = gnss_odom;

	has_new_key_frame_ = true;
}
bb.判断是否为新的关键帧

判断当前帧与上一帧是否足够远,如果大于设定距离,则说明当前帧是新的关键帧,则需要重置预积分(相当于判断当前帧为关键帧时做的预处理)。
重置预积分:传入参数当前帧imu数据,预积分变量。先更新imu预积分,将预积分结果赋值给预积分变量imu_pre_integration,再重置预积分更新相关变量,并将关键帧标志has_new_optimized_置为true。
从这里可以看出预积分不断进行,但每次新来一帧关键帧会重置预积分。

// whether the current scan is far away enough from last key frame:
if (fabs(laser_odom.pose(0,3) - last_laser_pose.pose(0,3)) + 
	fabs(laser_odom.pose(1,3) - last_laser_pose.pose(1,3)) +
	fabs(laser_odom.pose(2,3) - last_laser_pose.pose(2,3)) > key_frame_distance_) {
    
    
	
	// 如果是新的关键帧,则需要重置预积分器
	if ( imu_pre_integrator_ptr_ ) {
    
    
		imu_pre_integrator_ptr_->Reset(imu_data, imu_pre_integration_); 
	}

	if ( odo_pre_integrator_ptr_ ) {
    
    
		gnss_odom.GetVelocityData(velocity_data);
		odo_pre_integrator_ptr_->Reset(velocity_data, odo_pre_integration_); 
	}

	last_laser_pose = laser_odom;
	last_gnss_pose = gnss_odom;

	has_new_key_frame_ = true;
}

轮速计预积分和imu预积分处理方式一致。

cc.若标志has_new_optimized_为true,上一步判断当前帧为关键帧
aaa.将当前关键帧点云保存下来
// a. first write new key scan to disk:
std::string file_path = key_frames_path_ + "/key_frame_" + std::to_string(key_frames_deque_.size()) + ".pcd";
pcl::io::savePCDFileBinary(file_path, *cloud_data.cloud_ptr);
bbb.将当前关键帧下,雷达里程计、gnss分别赋值分别给key_frame_deque_, key_gnss_deque_。
current_key_gnss_.time = current_key_frame_.time = laser_odom.time;
current_key_gnss_.index = current_key_frame_.index = key_frames_deque_.size();

// b. create key frame index for lidar scan, relative pose measurement:
current_key_frame_.pose = laser_odom.pose;

// c. create key frame index for GNSS measurement, full LIO state:
current_key_gnss_.pose = gnss_odom.pose;
current_key_gnss_.vel.v = gnss_odom.vel.v;
current_key_gnss_.vel.w = gnss_odom.vel.w;

// add to cache for later evo evaluation:
key_frames_deque_.push_back(current_key_frame_);
key_gnss_deque_.push_back(current_key_gnss_);
c.如果MaybeNewKeyFrame()判断当前帧为新的关键帧,增加节点和边AddNodeAndEdge()

通过AddNodeAndEdge()增加节点和边,相当于添加不同测量的约束

aa.如果没有全局约束gnss,则将第一帧关键帧固定,若有gnss则不固定第一帧关键帧。固定第一帧关键帧表示,第一帧不做优化。
if (!graph_optimizer_config_.use_gnss && graph_optimizer_ptr_->GetNodeNum() == 0) {
    
    
	graph_optimizer_ptr_->AddPRVAGNode(current_key_frame_, true);
} else {
    
    
	graph_optimizer_ptr_->AddPRVAGNode(current_key_gnss_, false);
}

添加顶点,被封装为g2o增加节点接口AddPRVAGNode()
初始化
g2o::VertexPRVAG *vertex(new g2o::VertexPRVAG());
VertexPRVAG是继承g2o顶点BaseVertex<15, PRVAG>参数表示:stateDim, stateType

class VertexPRVAG : public g2o::BaseVertex<15, PRVAG> {
    
    
public:
	EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    virtual void setToOriginImpl() override {
    
    
        _estimate = PRVAG();
    }

    virtual void oplusImpl(const double *update) override {
    
             //  顶点更新
        //
        // TODO: do update
        //
        _estimate.pos  +=  Eigen::Vector3d(
            update[PRVAG::INDEX_POS + 0],  update[PRVAG::INDEX_POS + 1],  update[PRVAG::INDEX_POS + 2]  
        );
        _estimate.ori    =  _estimate.ori * Sophus::SO3d::exp(
            Eigen::Vector3d(
                update[PRVAG::INDEX_ORI + 0],  update[PRVAG::INDEX_ORI + 1],  update[PRVAG::INDEX_ORI + 2]  
            )
        );
        _estimate.vel += Eigen::Vector3d(
                update[PRVAG::INDEX_VEL + 0],  update[PRVAG::INDEX_VEL + 1],  update[PRVAG::INDEX_VEL + 2]  
        );
        Eigen::Vector3d d_b_a_i(
                update[PRVAG::INDEX_B_A + 0],  update[PRVAG::INDEX_B_A + 1],  update[PRVAG::INDEX_B_A + 2]  
        );
        Eigen::Vector3d d_b_g_i(
                update[PRVAG::INDEX_B_G + 0],  update[PRVAG::INDEX_B_G + 1],  update[PRVAG::INDEX_B_G + 2]  
        );

        _estimate.b_a += d_b_a_i ;
        _estimate.b_g += d_b_g_i;               

        updateDeltaBiases(d_b_a_i, d_b_g_i);
    }

    bool isUpdated(void) const {
    
     return _is_updated; }

    void updateDeltaBiases(
        const Eigen::Vector3d &d_b_a_i, 
        const Eigen::Vector3d &d_b_g_i
    ) {
    
    
        std::lock_guard<std::mutex> l(_m);

        _is_updated = true;

        _d_b_a_i += d_b_a_i;
        _d_b_g_i += d_b_g_i;
    }

    void getDeltaBiases(Eigen::Vector3d &d_b_a_i, Eigen::Vector3d &d_b_g_i) {
    
    
        std::lock_guard<std::mutex> l(_m);

        d_b_a_i = _d_b_a_i;
        d_b_g_i = _d_b_g_i;

        _d_b_a_i = _d_b_g_i = Eigen::Vector3d::Zero();

        _is_updated = false;
    }

    virtual bool read(std::istream &in) {
    
     return true; }

    virtual bool write(std::ostream &out) const {
    
     return true; }

private:
    std::mutex _m;
    bool _is_updated = false;

    Eigen::Vector3d _d_b_a_i = Eigen::Vector3d::Zero();
    Eigen::Vector3d _d_b_g_i = Eigen::Vector3d::Zero();
};

设置顶点编号
因为之前已经添加过回环约束顶点

vertex->setId(graph_ptr_->vertices().sizes());

设置测量(用于完成边误差计算),此处可以在自定义边中完成setMeasure纯虚函数具体实现。

// b. set vertex state:
g2o::PRVAG measurement;

measurement.time = lio_key_frame.time;

measurement.pos = lio_key_frame.pose.block<3, 1>(0, 3).cast<double>();
measurement.ori = Sophus::SO3d(
	Eigen::Quaterniond(lio_key_frame.pose.block<3, 3>(0, 0).cast<double>())
);
// transform linear velocity from body frame to navigation frame:
measurement.vel = measurement.ori * lio_key_frame.vel.v.cast<double>();
measurement.b_a = lio_key_frame.bias.accel.cast<double>();
measurement.b_g = lio_key_frame.bias.gyro.cast<double>();

vertex->setEstimate(measurement);

设置是否需要固定顶点

// for first vertex:
if (need_fix) {
    
    
	vertex->setFixed(true);
}

添加顶点

graph_ptr_->addVertex(vertex);
bb.设置连接的顶点
// get num. of vertices:
const int N = graph_optimizer_ptr_->GetNodeNum();
// get vertex IDs:
const int vertex_index_i = N - 2;
const int vertex_index_j = N - 1;
cc.添加雷达里程计相对位姿约束
if ( N > 1 ) {
    
    
	// get relative pose measurement:
	Eigen::Matrix4d relative_pose = (last_key_frame_.pose.inverse() * current_key_frame_.pose).cast<double>();
	// add constraint, lidar frontend / loop closure detection:
	graph_optimizer_ptr_->AddPRVAGRelativePoseEdge(
		vertex_index_i, vertex_index_j, 
		relative_pose, graph_optimizer_config_.odom_edge_noise
	);
}
dd.添加gnss先验单边约束
if ( graph_optimizer_config_.use_gnss ) {
    
    
	// get prior position measurement:
	Eigen::Vector3d pos = current_key_gnss_.pose.block<3, 1>(0, 3).cast<double>();
	// add constraint, GNSS position:
	graph_optimizer_ptr_->AddPRVAGPriorPosEdge(
		vertex_index_j, 
		pos, graph_optimizer_config_.gnss_noise
	);
}
ee.添加imu预积分约束
if ( N > 1 && graph_optimizer_config_.use_imu_pre_integration ) {
    
    
	// add constraint, IMU pre-integraion:
	graph_optimizer_ptr_->AddPRVAGIMUPreIntegrationEdge(
		vertex_index_i, vertex_index_j,
		imu_pre_integration_
	);
}
ff.添加轮式里程计约束
if ( N > 1 && graph_optimizer_config_.use_odo_pre_integration ) {
    
    
	// add constraint, odo pre-integraion:
	graph_optimizer_ptr_->AddPRVAGOdoPreIntegrationEdge(
		vertex_index_i, vertex_index_j,
		odo_pre_integration_
	);
}
gg.保留当前关键帧,且计数
last_key_frame_ = current_key_frame_;
++counter_.key_frame;
d.执行优化判断 MaybeOptimized()

aa.执行优化条件:有足够的关键帧或者有足够的回环,则need_optimize置为true
当前设置关键帧数量为50帧,回环帧数量10帧。
bb.若need_optimize为真,则执行优化

graph_optimizer_ptr_->Optimize()

和前面设置顶点和边一样,调用优化也封装成接口Optimize()

bool G2oGraphOptimizer::Optimize() {
    
    
    static int optimize_cnt = 0;

    if(graph_ptr_->edges().size() < 1) {
    
    
        return false;
    }

    TicToc optimize_time;

    graph_ptr_->initializeOptimization();
    graph_ptr_->computeInitialGuess();
    graph_ptr_->computeActiveErrors();
    graph_ptr_->setVerbose(false);

    double chi2 = graph_ptr_->chi2();

    int iterations = graph_ptr_->optimize(max_iterations_num_);

    LOG(INFO) << std::endl 
              << "------ Finish Iteration " << ++optimize_cnt << " of Backend Optimization -------" << std::endl
              << "\tNum. Vertices: " << graph_ptr_->vertices().size() << std::endl 
              << "\tNum. Edges: " << graph_ptr_->edges().size() << std::endl
              << "\tNum. Iterations: " << iterations << "/" << max_iterations_num_ << std::endl
              << "\tTime Consumption: " << optimize_time.toc() << std::endl
              << "\tCost Change: " << chi2 << "--->" << graph_ptr_->chi2()
              << std::endl << std::endl;

    return true;
}
cc.优化完成设置优化标志has_new_optimized_ 为true
has_new_optimized_ = true;

4.发布数据

(1)发布激光里程计
(2)有新关键帧时
a.发布当前点云数据
b.发布关键帧,包括关键帧位姿
c.发布gnss数据,包括惯导位姿、速度
(3)优化完成时
发布优化后关键帧、包括位姿

至此完成。

猜你喜欢

转载自blog.csdn.net/u010196944/article/details/131687624