Action通信(二) MoveBase客户端和服务端的常用代码

MoveBase.action

1
2
3
4
geometry_msgs/PoseStamped target_pose
---
---
geometry_msgs/PoseStamped base_position

target_pose是目标位姿。base_position作为feedback,是base_link在world坐标系的位姿。


MoveBase的Action实际就是另一种形式的话题,会生成7个msg文件:

1
2
3
4
5
6
7
MoveBaseActionFeedback.msg  
MoveBaseActionResult.msg
MoveBaseResult.msg
MoveBaseActionGoal.msg
MoveBaseFeedback.msg
MoveBaseAction.msg
MoveBaseGoal.msg

另外有个RecoveryStatus.msg,目前没有使用过。

1
2
3
4
geometry_msgs/PoseStamped pose_stamped
uint16 current_recovery_number
uint16 total_number_of_recoveries
string recovery_behavior_name

客户端

1
2
template<class ActionSpec>
bool actionlib::SimpleActionClient< ActionSpec >::waitForServer (const ros::Duration & timeout = ros::Duration(0,0) ) [inline]

一般花点时间,比如几百毫秒实现二者的连接,在此时间内的goal会发送失败。

参数:

  • timeout Max time to block before returning. 参数为 0 is interpreted as an infinite timeout.
  • 返回 True if the server connected in the allocated time. False on timeout

所以这样写代码比较优雅:

1
2
3
4
5
while( ! ac->isServerConnected() )
{
ROS_INFO("Waiting for move_base action server to start");
ac->waitForServer(ros::Duration(1) );
}

发送goal的常用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
ac = new actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction>("move_base", true);
// 注意是 MoveBaseGoal, 成员只有 geometry_msgs/PoseStamped target_pose
move_base_msgs::MoveBaseGoal goal;
// 一系列赋值 ......
ac->sendGoal(goal);
// ac->sendGoal(goal, &doneCb, &activeCb, &feedbackCb);

bool finished_before_timeout = ac->waitForResult(ros::Duration(30.0));
if (finished_before_timeout)
{
actionlib::SimpleClientGoalState state = ac->getState();
ROS_INFO("MoveBase Action finished: %s", state.toString().c_str());
}

三个回调函数 :当action完成后,调用回调函数doneCb一次。 当action激活后,调用回调函数activeCb一次,此时还没有获得路径。 收到feedback后,不断调用回调函数feedbackCb,一直到action结束

  • bool waitForResult (const ros::Duration &timeout = ros::Duration(0, 0))

Blocks until this goal finishes. 可以指定阻塞的时间,如果是0,则无线阻塞。

  • SimpleClientGoalState getState() const

Get the state information for this goal. Possible States Are: PENDING, ACTIVE, RECALLED, REJECTED, PREEMPTED, ABORTED, SUCCEEDED, LOST

如果在服务端的回调函数里没有对goal的状态进行设置,会有下面报警

Your executeCallback did not set the goal to a terminal status. This is a bug in your ActionServer implementation. Fix your code!
For now, the ActionServer will set this goal to aborted
因为没有设置goal的最终状态,比如使用setSucceededsetAborted设置状态.

  • cancelGoal

cancelGoal()之后,getState()的状态还是ACTIVE

如果没有goal处于ACTIVE,不要使用cancelGoal,会报错但是不会让进程退出:

两个回调函数

actionlib::SimpleActionServer的成员函数

1
2
3
4
5
// Allows users to register a callback to be invoked when a new goal is available.
void registerGoalCallback (boost::function< void()> cb)

// Allows users to register a callback to be invoked when a new preempt request is available.
void registerPreemptCallback (boost::function< void()> cb)

客户端向SimpleActionServer发送新目标时,isNewGoalAvailable()为true,isPreemptRequested()也为true。 如果客户端调用cancelGoal()isNewGoalAvailable()为false,但isPreemptRequested()还是true.

问题

有时候我在move_base设置了setAborted之后,会出现报警 To transition to an aborted state, the goal must be in a preempting or active state, it is currently in state: 4 。 这是因为多次调用了 setAborted,state 4 其实就是状态 Aborted

此时如果再发送goal,就会出现报警
Your executeCallback did not set the goal to a terminalstatus. This is a bug in your ActionServer implementation. Fix your code! For now, the ActionServer will set this goal to aborted
,这打乱了 Action 的状态机,必须避免这个报警。


我使用了一段时间后,感觉缺resetGoalgetGoal函数,不太方便。

参考:
ROS Action动作通讯编程C++
Writing a Callback Based SimpleActionClientTutorials(2f)Writing(20)a(20)Callback(20)Based(20)Simple(20)Action(20)Client.html)


使用GDB解决算法切换导致的多线程问题 (一)
abstract Welcome to my blog, enter password to read.
Read more
Gazebo使用心得

Gazebo不好上手,费了一番功夫终于熟悉了,内容太多,只记录注意事项。

Gazebo里用的是 前x 左y 上z的右手坐标系 ,gazebo的栅格范围是20mX20m

启动simple_world.launch,然后在Gazebo里添加一个圆柱体,保存覆盖simple.world文件。再次启动时,报错 SpawnModel: Failure - model name sunny already exist. [Spawn service failed. Exiting. [spawn_urdf-6] process has died

网上说的方法没能解决,也不能重命名model,修改simple.world无效。 最后删除了<node pkg="gazebo_ros" type="spawn_model" name="spawn_urdf" args="-urdf -model sunny -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos) -param robot_description" /> 解决问题。

urdf 和 xacro

urdf.xacro文件里,<link name="base_link">代表机器人的主体。<geometry> <box size="0.4 0.39 0.5" />是尺寸。

gazebo插件,可以简单理解为就是连接gazebo和ros的一个桥梁,一边插件通过gazebo的api来控制仿真环境的机器人关节等数据,一边通过关节数据通过运动学模型生成里程计、雷达的scan、image等类似的信息

solidworks生成urdf

solidworks to urdf安装后,如果在solidworks里找不到,就安装最新版的,不用管版本是否对应。

solidworks只生成的URDF文件,没有xacro文件. URDF文件做一些简单修改可保存为xacro,注意:

  1. jointtype="fixed",应当为continuous
  2. joint的某些参数值错误,导致车加载差速模型后,运动错误

xacro

xacro里面的模型仍然是urdf模型,有如下变化:

  1. 创建宏定义
  2. 文件包含
  3. 提供可编程接口:常量, 变量, 数学计算, 条件语句

启动Gazebo时,机器人模型的tf关系不完整,即使xacro文件正确。还需要添加joint_state_publisherrobot_state_publisher

设置雷达时,看情况决定是否需要可视化

1
2
3
<sensor type="ray" name="lds_lfcd_sensor">
<pose>0 0 0 0 0 0</pose>
<visualize>false</visualize>

Gazebo默认的障碍物质量轻,车撞上之后会把障碍物推开。改变density选项为铁,则不会推动障碍物。


在Gazebo world中用以下方式加载模型时:

1
2
3
<include>
<uri>model://one_building</uri>
</include>

要让Gazebo能够找到uri中的自建model,需要将自己的model路径加入环境变量, export GAZEBO_MODEL_PATH=~/<path>/my_package/models:${GAZEBO_MODEL_PATH}


数字的类型

数字后面带个U,L,F的含义

  • U表示该常数用无符号整型方式存储,相当于 unsigned int
  • L表示该常数用长整型方式存储,相当于 long
  • F表示该常数用浮点方式存储,相当于 float

比如 -1UL, 其实是一个很大很大的数

OpennCV的数据类型

S = 有符号整型 U = 无符号整型 F = 浮点型

  • CV_8U - 8位无符号整数(0…255)
  • CV_32F - 32位浮点数(-FLT_MAX…FLT_MAX,INF,NAN)

  • CV_8U - 8位无符号整数(0…255)

  • CV_8S - 8位有符号整数(-128…127)

  • CV_16U - 16位无符号整数(0…65535)

  • CV_16S - 16位有符号整数(-32768…32767)

  • CV_32S - 32位有符号整数(-2147483648…2147483647)

  • CV_32F - 32位浮点数(-FLT_MAX…FLT_MAX,INF,NAN)

  • CV_64F - 64位浮点数(-DBL_MAX…DBL_MAX,INF,NAN)


障碍约束和雅格比矩阵的推导

障碍约束定义的误差函数表示机器人到障碍的最小距离。 $ \min \textrm{penaltyBelow}( dist2point ) \cdot weight $.

1
2
3
4
5
6
7
8
9
10
11
const VertexPose* bandpt = static_cast<const VertexPose*>(_vertices[0]);
// calculateDistance(const PoseSE2& current_pose, const Obstacle* obstacle) 计算机器人到障碍物的距离
// current_pose为当前机器人的位姿。
double dist = robot_model_->calculateDistance(bandpt->pose(), _measurement);
_error[0] = penaltyBoundFromBelow(dist, cfg_->obstacles.min_obstacle_dist, cfg_->optim.penalty_epsilon);

if (cfg_->optim.obstacle_cost_exponent != 1.0 && cfg_->obstacles.min_obstacle_dist > 0.0)
{
_error[0] = cfg_->obstacles.min_obstacle_dist * std::pow(_error[0] / cfg_->obstacles.min_obstacle_dist,
cfg_->optim.obstacle_cost_exponent);
}

obstacle_cost_exponentmin_obstacle_dist的设置,一般都会进入if情况。 注意 max cost (before weighting) is the same as the straight line version and that all other costs are below the straight line (for positive exponent), so it may be necessary to increase weight_obstacle and/or the inflation_weight when using larger exponents.


时间约束和kinematics约束及雅格比矩阵的推导

时间约束

EdgeTimeOptimal类太简单了,误差函数 _error[0] = timediff->dt();,优化 ,那么对其求偏导,显然只有一个矩阵,一个元素 1 : _jacobianOplusXi( 0 , 0 ) = 1;

kinematics 约束

两个误差方程,一个是 non-holonomic约束, :

初始配置和方向之间的角度 必须等于配置和方向之间的夹角,即

根据二维叉积 A×B=|A| |B|⋅sinα 得到

所以代码中的目标函数写成

另一个是 positive-drive-direction约束

两个configure,所以两个雅格比,维度明显是 2x3,两个误差方程分别对(x, y, angle)求偏导,源码里的求导很简单,还不如速度约束的求导复杂,就不写过程了。

值得注意的是绝对值的求导结果会用sign函数表示。


加速度约束和雅格比矩阵的推导

默认为5元边约束。 但是包含3个类:EdgeAcceleration, EdgeAccelerationStart, EdgeAccelerationGoal,后两个为3元边

向量的维度是2,第一个元素代表线加速度,第二个是角加速度。
EdgeAccelerationStart() and EdgeAccelerationGoal() 用于边界值

加速度约束和速度约束类似,不过变成两段圆弧和角度差、两个时间差、两个线速度和角速度。两个线速度的差除以两个时间差之和就是加速度。

1
2
3
4
5
6
7
8
9
double vel1 = dist1 / dt1->dt();
double vel2 = dist2 / dt2->dt();
const double acc_lin = (vel2 - vel1)*2 / ( dt1->dt() + dt2->dt() );
_error[0] = penaltyBoundToInterval(acc_lin,cfg_->robot.acc_lim_x, cfg_->optim.penalty_epsilon);

const double omega1 = angle_diff1 / dt1->dt();
const double omega2 = angle_diff2 / dt2->dt();
const double acc_rot = (omega2 - omega1)*2 / ( dt1->dt() + dt2->dt() );
_error[1] = penaltyBoundToInterval(acc_rot,cfg_->robot.acc_lim_theta, cfg_->optim.penalty_epsilon);

起点的加速度约束

注意:起点加速度约束和终点加速度约束都是3元边。

对于起点的加速度约束,源码有以下变化:

1
2
3
4
5
6
7
8
9
void setInitialVelocity(const geometry_msgs::Twist& vel_start)
{
_measurement = &vel_start;
}

ROS_ASSERT_MSG(cfg_ && _measurement,
"You must call setTebConfig() and setInitialVelocity() on EdgeAccelerationStart()");
vel1 = _measurement->linear.x;
omega1 = _measurement->angular.z;

其他全一样,对于终点的加速度约束,则是vel2omega2换成了_measurement的成员。那么这个_measurement从何而来?

TebOptimalPlanner::plan里有一段:

1
2
3
4
5
6
if (start_vel)
setVelocityStart(*start_vel);
if (free_goal_vel)
setVelocityGoalFree();
else
vel_goal_.first = true;

setVelocityStart给起始速度赋值:

1
2
3
4
5
6
7
8
9
// std::pair<bool, geometry_msgs::Twist>  vel_start_;
// 注意vel_start_ 和 vel_start 不同
void TebOptimalPlanner::setVelocityStart(const geometry_msgs::Twist& vel_start)
{
vel_start_.first = true;
vel_start_.second.linear.x = vel_start.linear.x;
vel_start_.second.linear.y = vel_start.linear.y;
vel_start_.second.angular.z = vel_start.angular.z;
}

然后到了AddEdgesAcceleration里添加起始加速度约束,才用到vel_start_

1
2
3
4
5
6
7
8
9
10
11
12
if (vel_start_.first)
{
EdgeAccelerationStart* acceleration_edge = new EdgeAccelerationStart;
acceleration_edge->setVertex(0,teb_.PoseVertex(0));
acceleration_edge->setVertex(1,teb_.PoseVertex(1));
acceleration_edge->setVertex(2,teb_.TimeDiffVertex(0));
// 这里就是上面的 setInitialVelocity, _measurement 就是 vel_start_.second
acceleration_edge->setInitialVelocity(vel_start_.second);
acceleration_edge->setInformation(information);
acceleration_edge->setTebConfig(*cfg_);
optimizer_->addEdge(acceleration_edge);
}

AddEdgesAcceleration然后添加普通的加速度约束,再就是终点的加速度约束,这里就奇怪了,相应的setVelocityGoal函数没有调用的地方,这里是vel_goal_.second唯一赋值的地方。即使free_goal_vel为false,也没有赋值,意思是终点的速度为0?

雅格比矩阵的推导可以参照速度约束的,不同的地方是这次把角度单独拿出来了,这样有了8个雅格比

1
2
3
4
5
6
7
8
_jacobianOplus[0].resize(2,2); // conf1
_jacobianOplus[1].resize(2,2); // conf2
_jacobianOplus[2].resize(2,2); // conf3
_jacobianOplus[3].resize(2,1); // deltaT1
_jacobianOplus[4].resize(2,1); // deltaT2
_jacobianOplus[5].resize(2,1); // angle1
_jacobianOplus[6].resize(2,1); // angle2
_jacobianOplus[7].resize(2,1); // angle3

既然拿出了角度,那么对conf的雅格比矩阵的维度就变成了 2x2,不是2x3。两个误差函数,一个是线速度的,一个是角速度的

代码中的 double aux0 = 2/sum_time_inv; 错了 应当是 double aux0 = 2/sum_time;
J[0](0,0)的推导过程
其他项以此类推


g2o的详细使用

G2O 与ceres风格不同,它是专门用于进行图优化的库,提供了大量预先定义的节点和边供用户调用。

曲线拟合的例子其实不太适合用来理解g2o,它涉及的顶点只有一个,边是一元边,有很多个。还是用图优化的例子更容易理解。

TEB算法的base_teb_edges.h, g2o_types\vertex_pose.hg2o_types\vertex_timediff.h很有学习价值,以及edge_obstacle.h, edge_shortest_path.h, edge_velocity.h

顶点

最基础的BaseVertex类,template <int D, typename T> class BaseVertex

  • D: minimal dimension of the vertex, 例如 3 for rotation in 3D. D并非是顶点(更确切的说是状态变量)的维度,而是其在流形空间(manifold)的最小表示
  • T: 节点估计值的类型,例如 Quaternion for rotation in 3D

g2o本身内部定义了一些常用的顶点类型

  • VertexSE2 : public BaseVertex<3, SE2> // 2D pose Vertex, (x,y,theta)
  • VertexSE3 : public BaseVertex<6, Isometry3> //6d vector (x,y,z,qx,qy,qz) (注意不管四元数的 w)
  • VertexPointXY : public BaseVertex<2, Vector2>
  • VertexPointXYZ : public BaseVertex<3, Vector3>
  • VertexSBAPointXYZ : public BaseVertex<3, Vector3>
  • VertexSE3Expmap : public BaseVertex<6, SE3Quat>

如果我们需要的顶点类型这里面没有,就得自己定义了

重新定义顶点一般需要考虑重写如下函数:

1
2
3
4
5
# 读盘、存盘函数,一般情况下不需要进行读/写操作的话,仅仅声明一下,作为空函数
virtual bool read(std::istream& is);
virtual bool write(std::ostream& os) const;
virtual void oplusImpl(const number_t* update);
virtual void setToOriginImpl();

setToOriginImpl 和 _estimate

setToOriginImpl:顶点重置函数,设定被优化变量的原始值。有时可以不用实现。具体来说就是初始化成员变量 _estimate _estimate的类型就是继承 BaseVertex时的模板参数

如果_estimateEigen::Vector3d,那么setToOriginImpl的内容可以是: _estimate << 0,0,0;。 如果是PoseSE2_estimate的赋值可以这样:

1
2
3
4
5
6
7
8
9
_estimate.position() = position;
_estimate.x() = x;
_estimate.y() = y;
_estimate.theta() = theta;

virtual void setToOriginImpl()
{
_estimate.setZero();
}

oplusImpl

顶点更新函数。非常重要的一个函数,主要用于优化过程中增量 △x 的计算。我们根据增量方程计算出增量之后,就是通过这个函数对估计值进行调整的,因此这个函数的内容一定要重视。

比较简单的类型是这样实现:

1
2
3
4
5
6
7
8
9
10
11
// double 也可以换成 number_t
virtual void oplusImpl( const double* update ) // 更新
{
// update为增量△m 迭代的时候,估计值 m= m + △m, m是向量(a,b,c)
_estimate += Eigen::Vector3d(update);
}

virtual void oplusImpl(const double* update)
{
_estimate.plus(update);
}

李代数的类VertexSE3Expmap,上面两个函数是

1
2
3
4
5
6
7
8
9
virtual void setToOriginImpl() {    // 重置
_estimate = SE3Quat();
}

virtual void oplusImpl(const number_t* update_) {
Eigen::Map<const Vector6> update(update_);
//更新方式 类似于左扰动?
setEstimate(SE3Quat::exp(update)*estimate());
}

g2o自带的边

  • EdgeSE3ProjectXYZ: public BaseBinaryEdge<2, Vector2d, VertexSBAPointXYZ, VertexSE3Expmap>
  • EdgeSE3ProjectXYZOnlyPose: public BaseUnaryEdge<2, Vector2d, VertexSE3Expmap>
  • EdgeSE3Expmap : public BaseBinaryEdge<6, SE3Quat, VertexSE3Expmap, VertexSE3Expmap>
仅优化一个变量的时候选择单边

以一元边为例template <int D, typename E, typename VertexXi> class BaseUnaryEdge : public BaseEdge<D,E>

  • D 表示测量值的维度
  • E 表示测量值的数据类型
  • VertexXi,VertexXj 分别表示不同顶点的类型。(一元边只有一个顶点)

例如,BaseBinaryEdge<2, Vector2D, VertexSBAPointXYZ, VertexSE3Expmap>是个二元边。2是说测量值是2维的,也就是图像的像素坐标x,y的差值,对应测量值的类型是Vector2D,两个顶点也就是优化变量分别是三维点 VertexSBAPointXYZ,和李群位姿VertexSE3Expmap。

  • read,write:分别是读盘、存盘函数,一般情况下不需要进行读/写操作的话,仅仅声明一下就可以

  • computeError函数:非常重要,是使用当前顶点的值计算的测量值与真实的测量值之间的误差

  • linearizeOplus函数:非常重要,是在当前顶点的值下,该误差对优化变量的偏导数,也就是我们说的Jacobian。

重要的成员变量:

  • _measurement:存储观测值

  • _error:存储computeError() 函数计算的误差

  • _vertices[]:存储顶点信息,比如二元边的话,_vertices[] 的大小为2,存储顺序和调用setVertex(int, vertex) 是设定的int有关(0 或1)

  • setId(int): 来定义边的编号(决定了在H矩阵中的位置)

  • setMeasurement(type) 函数来定义观测值

  • setVertex(int, vertex) 来定义顶点

  • setInformation() 来定义协方差矩阵的逆

最重要的就是computeError()linearizeOplus()两个函数了。如果我们没有给出雅可比,g2o也会进行数值求导,但是会比重写的雅可比慢。 雅克比矩阵的维度为 误差维度 × 优化变量的维度 ,两个函数一般是这样的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
virtual void computeError() override
{
// ...
_error = _measurement - Something;
}
virtual void linearizeOplus() override
{
_jacobianOplusXi(pos, pos) = something;
// ...

/*
_jocobianOplusXj(pos, pos) = something;
...
*/
}

雅格比矩阵要针对每个顶点求解,上面说的维度是每个顶点求解的维度。比如边的类型继承
1
2
class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d>
class EdgeProjection : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, VertexPose>

由于是一元边,那么只针对一个顶点求解,误差维度是边的第一个模板类型2,优化变量的维度是顶点的第一个模板参数6.

TEB算法中的class EdgeVelocity : public BaseTebMultiEdge<2, double>,是三元边,两个VertexPose顶点,类型PoseSE2。顶点VertexTimeDiff,类型double。对于前者,雅格比矩阵是 2x3,对后者是 2x1,也就是如下

1
2
3
_jacobianOplus[0].resize(2,3); // conf1
_jacobianOplus[1].resize(2,3); // conf2
_jacobianOplus[2].resize(2,1); // deltaT

对于边class G2O_TYPES_SBA_API EdgeProjectXYZ2UV : public BaseBinaryEdge<2, Vector2D, VertexSBAPointXYZ, VertexSE3Expmap>,这是二元边,雅格比包含两部分,维度分别为 2x3 和 2x6. 后一个顶点的类型是李群。可以这样写

1
2
3
4
5
// 相对 _jacobianOplus[2] 的另一种写法
_jacobianOplusXi(0,0) =
......
_jacobianOplusXj(0,0) =
......

removeEdge()

removeEdgeremoveVertex只有在ORB-SLAM中看到。

After running optimize() once, I am finding a high number of outliers. Is there any way to delete a single edge out of the graph and run optimize again, or do I have to construct it again?

The method removeEdge() removes the edge from a graph and unlinks it from all of the vertices it was attached to. After you call it on your edges, I think you need to call initializeOptimization() to reset all of g2o’s internal data structures to the new graph configuration. You should then be able to call optimize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// vector<g2o::EdgeSim3ProjectXYZ*> vpEdges12;
// 剔除一些误差大的边
// Check inliers
int nBad=0;
for(size_t i=0; i<vpEdges12.size();i++)
{
g2o::EdgeSim3ProjectXYZ* e12 = vpEdges12[i];
g2o::EdgeInverseSim3ProjectXYZ* e21 = vpEdges21[i];
if(!e12 || !e21)
continue;

if(e12->chi2()>th2 || e21->chi2()>th2)
{
size_t idx = vnIndexEdge[i];
vpMatches1[idx]=static_cast<MapPoint*>(NULL);
optimizer.removeEdge(e12);
optimizer.removeEdge(e21);
vpEdges12[i]=static_cast<g2o::EdgeSim3ProjectXYZ*>(NULL);
vpEdges21[i]=static_cast<g2o::EdgeInverseSim3ProjectXYZ*>(NULL);
nBad++;
}
}

setLevel(int level)

也是在ORB-SLAM中用到

优化完成后,对每一条边都进行检查,剔除误差较大的边(认为是错误的边),并设置setLevel为0,即下次不再对该边进行优化

setLevel(int ) is useful when you call optimizer.initializeOptimization(int ). If you assign initializeOptimization(0), the optimizer will include all edges up to level 0 in the optimization, and edges set to level >=1 will not be included

1
2
3
4
5
6
// 基于卡方检验计算出的阈值(假设测量有一个像素的偏差)
// 第二个判断条件,用于检查构成该边的MapPoint在该相机坐标系下的深度是否为正?
if(e->chi2()>5.991 || !e->isDepthPositive())
{
e->setLevel(1);// 不优化
}

范数及求导

||x|| 是 1 范数

2范数其实就是:列向量 x, 2范数为

2范数为

对 x 求导

如果是求雅格比,

也就是只需求f(x)的雅格比,再转置。


再看矩阵的范数

  • 0 范数: 矩阵中非零元素的个数

  • 1 范数: 又称为 列和范数,将每列元素进行绝对值求和,取最大值

  • 范数: 又称为 列和范数,将每列元素进行绝对值求和,取最大值

  • 2 范数: 公式为 , 其中 表示 的最大特征值。 2范数可以用来判断一个矩阵是不是病态矩阵

2范数是由向量范数诱导而来,F范数是直接定义,是两种不同的度量方式。F范数可以比较真实的矩阵和估计的矩阵值之间的误差。


C++的各种报错和报警

还是Qt的环境好,很多时候,不用编译直接就提示有错误。

  • 报错invalid new-expression of abstract class type

有的编译器也会报错 error: allocating an object of abstract class type ‘child’。说明基类中有纯虚函数没有实现,必须在派生类实现所有纯虚函数,不使用派生类时是看不出来的。

  • marked ‘override’, but does not override
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Parent
{
public:
virtual std::string getName1(int x)
{
return "Parent";
}

virtual std::string getName2(int x)
{
return "Parent";
}
};

class Child : public Parent
{
public:
virtual std::string getName1(double x) override
{
return "Sub";
}
virtual std::string getName2(int x) const override
{
return "Sub";
}
};

派生类的同名函数和基类的参数类型不同,而且多了const。如果没有override,那么是派生类新加的函数,但是添加override,编译器认为基类有同名函数,但是没发现,所以报错。

如果是重写基类方法,建议使用override标识符,让编译器检查出可能的错误,从而避免无意的错误。