我们知道,在SLAM中,最主要也最关键的就是如何对我们设计出来的非线性问题进行求解,并计算出最优解。而在这个过程中我们用的最多的就是Ceres,之前作者在《SLAM本质剖析-Ceres》。而我们这一篇文章就来讨论一下如何使用Ceres来完成常见的优化。
下面的部分就是我们最常用的两个点到点的计算公式,其中Eigen_MAKE_ALIGNED_OPERATOR_NEW是一个Eigen库中的宏,用于启用对齐操作,以提高程序性能。
PointToPointError函数用于构造函数,它初始化了m_point_lidar(点云中的点),m_point_cam(相机中的点)和m_w(权重)变量。接下来是重载运算符,它接收一个旋转和平移向量(R_t)作为参数,并计算出旋转矩阵(R)。它通过旋转矩阵和平移向量来计算投影点,并通过计算相机中的点和点云中的点的投影点的距离来计算残差,最后将残差乘以权重。
Create函数用于创建costfunction。它接收点云中的点,相机中的点和权重作为参数,并创建一个自动微分代价函数,返回一个costfunction。最后是m_point_lidar,m_point_cam和m_w变量的定义,它们代表了点云中的点,相机中的点和权重。
// 点到点的距离损失
struct PointToPointError
{
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
PointToPointError(const Eigen::Vector3d& point_lidar,
const Eigen::Vector3d& point_cam,
const double& w):
m_point_lidar(point_lidar),
m_point_cam(point_cam),
m_w(w){}//m_point_lidar代表了点云中的点,m_point_cam代表了相机中的点,m_w代表了权重
template<typename T>
bool operator()(const T* const R_t,T* residual) const{
T rot[3*3];
ceres::AngleAxisToRotationMatrix(R_t, rot);//将旋转向量转换为旋转矩阵
Eigen::Matrix<T, 3, 3> R = Eigen::Matrix<T, 3, 3>::Identity();//初始化旋转矩阵
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
R(i, j) = rot[i+j*3];//将旋转矩阵赋值给R
}
}
Eigen::Map<const Eigen::Matrix<T,3,1>> trans(R_t+3);//将平移向量赋值给trans
residual[0] = (m_point_cam.template cast<T>() - (R * m_point_lidar.template cast<T>() + trans)).norm();//通过获取相机中的点和点云中的点的投影点,计算残差
residual[0] *= m_w;//乘以权重
return true;
}
static ceres::CostFunction* Create(const Eigen::Vector3d& point_lidar,
const Eigen::Vector3d& point_cam,
const double& w){
return (new ceres::AutoDiffCostFunction<PointToPointError, 1, 6>(
new PointToPointError(point_lidar, point_cam, w)));//创建costfunction
}
const Eigen::Vector3d m_point_lidar;
const Eigen::Vector3d m_point_cam;
const double m_w;
};
// 点到点的距离损失,方法2,主要是通过
struct LidarDistanceFactor {
LidarDistanceFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d closed_point_)
: curr_point(curr_point_), closed_point(closed_point_) {}// curr_point代表当前点,closed_point代表前一帧平面上的一个点
template <typename T>
bool operator()(const T* q, const T* t, T* residual) const {
Eigen::Quaternion<T> q_w_curr{q[3], q[0], q[1], q[2]};// 当前帧到世界坐标系的旋转
Eigen::Matrix<T, 3, 1> t_w_curr{t[0], t[1], t[2]};// 当前帧到世界坐标系的平移
Eigen::Matrix<T, 3, 1> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};// 当前点
Eigen::Matrix<T, 3, 1> point_w;// 当前点在世界坐标系下的位置
point_w = q_w_curr * cp + t_w_curr;// 当前点在世界坐标系下的位置
residual[0] = point_w.x() - T(closed_point.x());// 误差为当前点在世界坐标系下的位置的x坐标减去前一帧平面上的一个点的x坐标
residual[1] = point_w.y() - T(closed_point.y());
residual[2] = point_w.z() - T(closed_point.z());
return true;
}
static ceres::CostFunction* Create(const Eigen::Vector3d curr_point_,
const Eigen::Vector3d closed_point_) {
return (new ceres::AutoDiffCostFunction<LidarDistanceFactor, 3, 4, 3>(
new LidarDistanceFactor(curr_point_, closed_point_)));
}
Eigen::Vector3d curr_point;
Eigen::Vector3d closed_point;
};
// ceres::CostFunction *cost_function = LidarDistanceFactor::Create(curr_point, center);
// problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
struct LidarEdgeFactor是一个Ceres的代价函数类,用于计算激光雷达数据的优化问题中的边界残差。它计算了当前点到前一帧的边的投影点的距离作为误差残差。该类包含了一个构造函数,一个重载的运算符"()",以及一个静态的Create函数。
构造函数定义了四个变量:curr_point表示当前点;last_point_a代表前一帧的边上的第一个点;last_point_b代表前一帧的边上的第二个点;s代表当前点在前一帧边上的投影的位置。重载的运算符"()"定义了代价函数的计算过程,其中:
该函数通过插值的方式计算前一帧到当前帧的旋转和平移,并通过矩阵运算将当前点变换到前一帧空间,最后计算当前点到前一帧边的投影点的距离。静态的Create函数用于创建LidarEdgeFactor类的实例,并返回一个指向ceres::AutoDiffCostFunction的指针,它接受curr_point、last_point_a、last_point_b和s作为参数。
struct LidarEdgeFactor {
//计算3D点到直线的距离
LidarEdgeFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_a_,
Eigen::Vector3d last_point_b_, double s_)
: curr_point(curr_point_), last_point_a(last_point_a_), last_point_b(last_point_b_), s(s_) {// curr_point代表当前点,last_point_a代表前一帧的边上的第一个点,last_point_b代表前一帧的边上的第二个点,s代表当前点在前一帧边上的投影的位置
}
template <typename T>
bool operator()(const T* q, const T* t, T* residual) const {
Eigen::Matrix<T, 3, 1> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};// 当前点
Eigen::Matrix<T, 3, 1> lpa{T(last_point_a.x()), T(last_point_a.y()), T(last_point_a.z())};// 前一帧边上的第一个点
Eigen::Matrix<T, 3, 1> lpb{T(last_point_b.x()), T(last_point_b.y()), T(last_point_b.z())};// 前一帧边上的第二个点
// Eigen::Quaternion<T> q_last_curr{q[3], T(s) * q[0], T(s) * q[1], T(s) * q[2]};
Eigen::Quaternion<T> q_last_curr{q[3], q[0], q[1], q[2]};// 前一帧到当前帧的旋转
Eigen::Quaternion<T> q_identity{T(1), T(0), T(0), T(0)};// 单位四元数
q_last_curr = q_identity.slerp(T(s), q_last_curr);// slerp插值
Eigen::Matrix<T, 3, 1> t_last_curr{T(s) * t[0], T(s) * t[1], T(s) * t[2]};// 前一帧到当前帧的平移
Eigen::Matrix<T, 3, 1> lp;// 前一帧边上的投影点
lp = q_last_curr * cp + t_last_curr;// 前一帧边上的投影点
Eigen::Matrix<T, 3, 1> nu = (lp - lpa).cross(lp - lpb);// 当前点到前一帧边上的投影点的向量与前一帧边上的向量的叉乘
Eigen::Matrix<T, 3, 1> de = lpa - lpb;// 前一帧边上的向量
residual[0] = nu.x() / de.norm();// 误差为当前点到前一帧边上的投影点的向量与前一帧边上的向量的叉乘与前一帧边上的向量的模的比值
residual[1] = nu.y() / de.norm();
residual[2] = nu.z() / de.norm();
return true;
}
static ceres::CostFunction* Create(const Eigen::Vector3d curr_point_,
const Eigen::Vector3d last_point_a_,
const Eigen::Vector3d last_point_b_, const double s_) {
return (new ceres::AutoDiffCostFunction<LidarEdgeFactor, 3, 4, 3>( //残差维数,第一个变量维数,第二个变量维数
new LidarEdgeFactor(curr_point_, last_point_a_, last_point_b_, s_)));
}
Eigen::Vector3d curr_point, last_point_a, last_point_b;
double s;
};
// ceres::CostFunction* cost_function = LidarEdgeFactor::Create(curr_point, point_a, point_b, 1.0);
// problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
下面两个为点到面的方程,这部分主要的是第二种方法用的比较多,我们下面来主要梳理一下第二种方法。这里主要命名了一个名为LidarPlaneNormFactor函数,。它的作用是在BA(Bundle Adjustment)中对激光雷达平面的法向量进行误差优化。
该类构造函数接受三个参数:curr_point_代表当前点,plane_unit_norm_代表前一帧平面的法向量,negative_OA_dot_norm_代表前一帧平面的法向量与前一帧平面上的一个点的点乘。
在该类中有一个模板函数operator(),它定义了误差函数:对于当前帧的位姿(q为旋转四元数,t为平移向量),误差为前一帧平面的法向量与当前点在世界坐标系下的位置的点乘。
最后,该类还提供了一个静态函数Create,用于创建残差块。
//计算激光点到平面的残差,以三个点为平面
struct LidarPlaneFactor {
LidarPlaneFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_j_,
Eigen::Vector3d last_point_l_, Eigen::Vector3d last_point_m_, double s_)
: curr_point(curr_point_), last_point_j(last_point_j_), last_point_l(last_point_l_),
last_point_m(last_point_m_), s(s_) {// curr_point代表当前点,last_point_j代表前一帧的平面上的第一个点,last_point_l代表前一帧的平面上的第二个点,last_point_m代表前一帧的平面上的第三个点,s代表当前点在前一帧平面上的投影的位置
ljm_norm = (last_point_j - last_point_l).cross(last_point_j - last_point_m);// 前一帧平面上的向量
ljm_norm.normalize();// 前一帧平面上的向量的模
}
template <typename T>
bool operator()(const T* q, const T* t, T* residual) const {
Eigen::Matrix<T, 3, 1> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};// 当前点
Eigen::Matrix<T, 3, 1> lpj{T(last_point_j.x()), T(last_point_j.y()), T(last_point_j.z())};// 前一帧平面上的第一个点
// Eigen::Matrix<T, 3, 1> lpl{T(last_point_l.x()), T(last_point_l.y()),
// T(last_point_l.z())};
// Eigen::Matrix<T, 3, 1> lpm{T(last_point_m.x()), T(last_point_m.y()),
// T(last_point_m.z())};
Eigen::Matrix<T, 3, 1> ljm{T(ljm_norm.x()), T(ljm_norm.y()), T(ljm_norm.z())};// 前一帧平面上的向量
// Eigen::Quaternion<T> q_last_curr{q[3], T(s) * q[0], T(s) * q[1], T(s) * q[2]};
Eigen::Quaternion<T> q_last_curr{q[3], q[0], q[1], q[2]};// 前一帧到当前帧的旋转
Eigen::Quaternion<T> q_identity{T(1), T(0), T(0), T(0)};// 单位四元数
q_last_curr = q_identity.slerp(T(s), q_last_curr);// 前一帧到当前帧的旋转
Eigen::Matrix<T, 3, 1> t_last_curr{T(s) * t[0], T(s) * t[1], T(s) * t[2]};// 前一帧到当前帧的平移
Eigen::Matrix<T, 3, 1> lp;// 前一帧边上的投影点
lp = q_last_curr * cp + t_last_curr;// 当前点在前一帧的位置
residual[0] = (lp - lpj).dot(ljm);// 误差为当前点到前一帧平面上的投影点的向量与前一帧平面上的向量的点乘
return true;
}
static ceres::CostFunction* Create(const Eigen::Vector3d curr_point_,
const Eigen::Vector3d last_point_j_,
const Eigen::Vector3d last_point_l_,
const Eigen::Vector3d last_point_m_, const double s_) {
return (new ceres::AutoDiffCostFunction<LidarPlaneFactor, 1, 4, 3>(
new LidarPlaneFactor(curr_point_, last_point_j_, last_point_l_, last_point_m_, s_)));
}
Eigen::Vector3d curr_point, last_point_j, last_point_l, last_point_m;
Eigen::Vector3d ljm_norm;
double s;
};
// 计算激光点到平面的残差,以平面的法向量为参数
struct LidarPlaneNormFactor {
LidarPlaneNormFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d plane_unit_norm_,
double negative_OA_dot_norm_)
: curr_point(curr_point_), plane_unit_norm(plane_unit_norm_),
negative_OA_dot_norm(negative_OA_dot_norm_) {}// curr_point代表当前点,plane_unit_norm代表前一帧平面的法向量,negative_OA_dot_norm代表前一帧平面的法向量与前一帧平面上的一个点的点乘
template <typename T>
bool operator()(const T* q, const T* t, T* residual) const {
Eigen::Quaternion<T> q_w_curr{q[3], q[0], q[1], q[2]};// 当前帧到世界坐标系的旋转
Eigen::Matrix<T, 3, 1> t_w_curr{t[0], t[1], t[2]};// 当前帧到世界坐标系的平移
Eigen::Matrix<T, 3, 1> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};// 当前点
Eigen::Matrix<T, 3, 1> point_w;
point_w = q_w_curr * cp + t_w_curr;// 当前点在世界坐标系下的位置
Eigen::Matrix<T, 3, 1> norm(T(plane_unit_norm.x()), T(plane_unit_norm.y()),
T(plane_unit_norm.z()));// 前一帧平面的法向量
residual[0] = norm.dot(point_w) + T(negative_OA_dot_norm);// 误差为前一帧平面的法向量与当前点在世界坐标系下的位置的点乘
return true;
}
static ceres::CostFunction* Create(const Eigen::Vector3d curr_point_,
const Eigen::Vector3d plane_unit_norm_,
const double negative_OA_dot_norm_) {
return (new ceres::AutoDiffCostFunction<LidarPlaneNormFactor, 1, 4, 3>(
new LidarPlaneNormFactor(curr_point_, plane_unit_norm_, negative_OA_dot_norm_)));// 创建残差块
}
Eigen::Vector3d curr_point;
Eigen::Vector3d plane_unit_norm;
double negative_OA_dot_norm;
};
// ceres::CostFunction* cost_function =
// LidarPlaneNormFactor::Create(curr_point, norm, negative_OA_dot_norm);//点到平面的距离: 点与平面单位法向量的点乘
// problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
这里定义线特征的误差为一个结构体LineFeatureProjFactor。构造函数LineFeatureProjFactor用于初始化sp_,ep_,norm_vec_,direct_vec_,K_。在构造函数内部,通过计算获得KK_的值。
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我需要在rail3中使用标准注册/登录/忘记密码功能进行身份验证。是否有大多数人为此使用的插件或其他东西? 最佳答案 我不确定最常用的方法是什么-但可以肯定的是,Plataformatec的“Devise”是一个非常流行的方法:http://github.com/plataformatec/devise我已经尝试了一些authgem,对我来说,它是最简单的设置和修改以满足我的需要。它内置了密码恢复、帐户确认(如果需要)和其他一些非常方便的功能。 关于ruby-on-rails-在Rail
我在ruby表单中有一个提交按钮f.submitbtn_text,class:"btnbtn-onemgt12mgb12",id:"btn_id"我想在不使用任何javascript的情况下通过ruby禁用此按钮 最佳答案 添加disabled:true选项。f.submitbtn_text,class:"btnbtn-onemgt12mgb12",id:"btn_id",disabled:true 关于ruby-on-rails-如何在Rails中添加禁用的提交按钮,我们在St
关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby有很大不同。由于我与ruby之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?
电脑上可以截取图片吗?如果可以,该如何操作呢?相信很多小伙伴都只知道一两种截图的方式,知道的并不全面。其实,电脑上有多种方式截图的,而且非常方便。电脑怎么截图?今天我们就来教大家如何使用电脑截取图片的8种常用方式!操作环境:演示机型:Delloptiplex7050系统版本:Windows10方法一:系统自带截图具体操作:同时按下电脑的自带截图键【Windows+shift+S】,可以选择其中一种方式来截取图片:截屏有矩形截屏、任意形状截屏、窗口截屏和全屏截图。 方法二:QQ截图具体操作:在电脑登录QQ,然后同时按下【Ctrl+Alt+A】,可以任意截图你需要的界面,可以把截图的页面直接下载,
我希望Ruby的解析器会进行这种微不足道的优化,但似乎并没有(谈到YARV实现,Ruby1.9.x、2.0.0):require'benchmark'deffib1a,b=0,1whileb由于这两种方法除了在第二种方法中使用预定义常量而不是常量表达式外是相同的,因此Ruby解释器似乎在每个循环中一次又一次地计算幂常数。是否有一些Material说明为什么Ruby根本不进行这种基本优化或只在某些特定情况下进行? 最佳答案 很抱歉给出了另一个答案,但我不想删除或编辑我之前的答案,因为它下面有有趣的讨论。正如JörgWMittag所说,
我正在尝试从数据库中读取大量单元格(超过100.000个)并将它们写入VPSUbuntu服务器上的csv文件。碰巧服务器没有足够的内存。我正在考虑一次读取5000行并将它们写入文件,然后再读取5000行,等等。我应该如何重构我当前的代码以使内存不会被完全消耗?这是我的代码:defwrite_rows(emails)File.open(file_path,"w+")do|f|f该函数由sidekiqworker调用:write_rows(user.emails)感谢您的帮助! 最佳答案 这里的问题是,当您调用emails.each时,
文章目录前言约束硬约束的轨迹优化Corridor-BasedTrajectoryOptimizationBezierCurveOptimizationOtherOptions软约束的轨迹优化Distance-BasedTrajectoryOptimization优化方法前言可以看看我的这几篇Blog1,Blog2,Blog3。上次基于MinimumSnap的轨迹生成,有许多优点,比如:轨迹让机器人可以在某个时间点抵达某个航点。任何一个时刻,都能数学上求出期望的机器人的位置、速度、加速度、导数。MinimumSnap可以把问题转换为凸优化问题。缺点:MnimumSnap可以控制轨迹一定经过中间的
我对为我的RubyonRails3.1.3应用优化我的Unicorn设置的方法很感兴趣。我目前正在高CPU超大实例上生成14个工作进程,因为我的应用程序在负载测试期间似乎受CPU限制。在模拟负载测试中,每秒大约20个请求重放请求,我的实例上的所有8个内核都达到峰值,盒子负载飙升至7-8个。每个unicorn实例使用大约56-60%的CPU。我很好奇可以通过哪些方式对其进行优化?我希望能够每秒将更多请求汇集到这种大小的实例上。内存和所有其他I/O一样完全正常。在我的测试过程中,CPU越来越低。 最佳答案 如果您受CPU限制,您希望使用
1.Scenes游戏场景文件夹用于放置unity的场景文件 2.Plugins插件文件夹用于放置unity的依赖文件,例如dll 3.Scripts脚本文件夹用于放置unity的c#脚本文件 4.Resources游戏资源文件夹用于放置unity的各种游戏资源,比如images,prefabs,同时只有放到Resources文件夹的游戏资源才能使用Resource.load(资源路径不加后缀)加载到游戏内存中进行使用 5.EditorUnity编辑器扩展脚本文件夹usingUnityEditor;这个名称空间就是Unity编辑器的名称空间这个名称空间提供了扩展Unity编辑器的各种类 【你所有