假装异步加载中...
24 Apr 2023

论文翻译LIO-SAM Tightly-coupled Lidar Inertial Odometry viaSmoothing and Mapping

概要:

我们提出了一种通过平滑和映射实现激光惯性测距仪紧密耦合的姿态估计框架LIO-SAM,可以实时准确地估计移动机器人的运动轨迹和创建地图。LIO-SAM在因子图的基础上表述了激光惯性测距仪的姿态估计,允许从不同源头将大量的相对测量和绝对测量(包括闭环)作为因子引入系统。从惯性测量单元(IMU)预积分获得的运动估计消除点云的失真,并为激光测距仪姿态优化提供初值。获得的激光测距仪解决方案用于估计IMU的偏差。为确保实时高性能,我们边缘化旧的激光扫描以进行姿态优化,而不是将激光扫描与全局地图匹配。与全局尺度相比,局部尺度的扫描匹配显著提高了系统的实时性能,关键帧的选择性引入和高效的滑动窗口方法(将新关键帧注册到固定大小的“子关键帧”集)也是如此。提出的方法在不同尺度和环境下从三个平台收集的数据集上进行了广泛评估。

I. INTRODUCTION

状态估计、定位和映射对移动机器人成功的智能化至关重要,它们是反馈控制、避障和规划等许多能力的基本先决条件。利用视觉感知和激光感知,已经投入了大量精力来实现支持移动机器人6DoF状态估计的高性能实时同步定位和映射(SLAM)。基于视觉的方法通常使用单目相机或立体相机,通过连续图像之间的三角测量来确定相机运动。虽然基于视觉的方法特别适合场景识别,但由于其易受初始化、照明和范围的影响,单独使用时无法可靠地支持自动导航系统。另一方面,基于激光的方法在很大程度上不受照明变化的影响。特别是随着Velodyne VLS-128和Ouster OS1-128等长距离、高分辨率3D激光雷达的出现,激光雷达更适合直接捕捉3D空间环境的细节。因此,本文重点关注基于激光的状态估计和映射方法。

在过去的二十年中,提出了许多基于激光的状态估计和映射方法。其中,在[1]中提出的激光测距仪姿态估计和映射(LOAM)方法用于低漂移和实时状态估计和映射,是最广泛使用的方法之一。LOAM使用激光测距仪和惯性测量单元(IMU),实现最先进的性能,自发布以来一直位列KITTI姿态基准网站[2]上基于激光的最高方法。尽管LOAM获得成功,但它存在一些限制——通过在全局体素地图中保存数据,难以执行闭环检测和纳入其他绝对测量(如GPS)进行姿态校正。在特征丰富的环境中,这种体素地图变得越来越密时,其在线优化过程的效率会降低。LOAM在大规模测试中也会出现漂移,因为在其核心它是一个基于扫描匹配的方法。

在本文中,我们提出了一种通过平滑和映射实现激光惯性测距仪紧密耦合的姿态估计框架LIO-SAM,以解决上述问题。我们假设点云消失畸变的非线性运动模型,使用原始IMU测量估计激光扫描期间的传感器运动。除了消除点云畸变外,估计的运动还为激光测距仪姿态优化提供初值。然后,获得的激光测距仪解决方案用于在因子图中估计IMU的偏差。

通过引入全局因子图进行机器人运动轨迹估计,我们可以高效地利用激光和IMU测量值进行传感器融合,在机器人姿态之间进行场景识别,并在可用时引入绝对测量值,如GPS定位和罗盘方位。这些来自各种来源的因子用于对图进行联合优化。此外,我们边缘化旧的激光扫描以进行姿态优化,而不是像LOAM那样将扫描与全局地图匹配。与全局尺度相比,局部尺度的扫描匹配显著提高了系统的实时性能,关键帧的选择性引入和高效的滑动窗口方法(将新关键帧注册到固定大小的“子关键帧”集)也是如此。我们工作的主要贡献可以总结如下:

  • 建立在因子图之上的紧密耦合的激光惯性测距仪框架,适用于多传感器融合和全局优化。
  • 一个高效的、基于局部滑动窗口的扫描匹配方法,通过选择性选择新关键帧并将其注册到固定大小的先验子关键帧集,实现实时性能。
  • 该框架在各种尺度、车辆和环境下进行了广泛验证。

激光测距仪姿态估计通常通过使用ICP [3]和GICP [4]等扫描匹配方法找到两个连续帧之间的相对变换来完成。由于计算效率高,基于特征的匹配方法已经成为一种流行的替代方法。例如,在[5]中提出了一种基于平面匹配的方法进行实时激光测距仪姿态估计。假设在结构化环境中操作,它从点云中提取平面,并通过求解最小二乘问题匹配这些平面。在[6]中提出了一种基于collar line-based的方法进行姿态估计。在这种方法中,从原始点云中随机生成线段,并用于以后的配准。然而,由于现代3D激光雷达的旋转机构和传感器运动,激光扫描的点云经常会发生畸变。仅使用激光进行姿态估计并不理想,因为使用畸变的点云或特征进行配准最终会导致大的漂移。

因此,激光通常与其他传感器(如IMU和GPS)一起使用进行状态估计和映射。这种利用传感器融合的设计方案通常可以分为两类:松散耦合融合和紧密耦合融合。在LOAM [1]中,IMU用于消除激光扫描的畸变,并为扫描匹配提供运动先验。但是,IMU没有参与算法的优化过程。因此,LOAM可以归类为一种松散耦合方法。在[7]中提出了一种轻量级的用于地面车辆地图任务[8]的地面优化激光测距仪姿态估计和映射(LeGO-LOAM)方法。其IMU测量值的融合与LOAM相同。松散耦合融合的更流行的方法是使用扩展卡尔曼滤波器(EKF)。例如,[9][13]在机器人状态估计的优化阶段使用EKF融合来自激光、惯性和可选GPS的测量值。

紧密耦合的系统通常可以提供更高的精度,目前是正在进行研究的主要焦点[14]。在[15]中,利用预积分的IMU测量消除点云畸变。在[16]中提出了一种机器人中心的激光惯性状态估计器R-LINS。R-LINS使用误差状态Kalman滤波器以紧密耦合的方式递归纠正机器人的状态估计。由于缺乏用于状态估计的其他可用传感器,它在长时间导航中会出现漂移。在[17]中引入了一种紧密耦合的激光惯性测距仪姿态估计和映射框架LIOM。LIOM是LIO映射的缩写,它联合优化来自激光和IMU的测量值,与LOAM相比,其精度类似或更高。由于LIOM旨在处理所有传感器测量值,所以无法实现实时性能——在我们的测试中,其运行速度约为实时的0.6倍。

III. LIDAR INERTIAL ODOMETRY VIA SMOOTHING AND MAPPING

A. System Overview

我们首先定义整篇论文中使用的坐标系和符号。我们用W表示世界坐标系,B表示机器人体坐标系。为了方便,我们还假设IMU坐标系与机器人体坐标系重合。机器人状态x可以写成:

\[x = [R^T, p^T, v^T, b^T]^T (1)\]
其中R ∈ SO(3)是旋转矩阵,p ∈ $R^3$ 是位置向量,v是速度,b是IMU偏差。从B到W的变换T ∈ SE(3)表示为T = [R p]。

Fig.1

Fig. 1: The system structure of LIO-SAM. The system receives input from a 3D lidar, an IMU and optionally a GPS. Four types of factorsare introduced to construct the factor graph.: (a) IMU preintegration factor, (b) lidar odometry factor, (c) GPS factor, and (d) loop closurefactor. The generation of these factors is discussed in Section III.

该系统的概述如图1所示。该系统从3D激光雷达、IMU和可选的GPS接收传感器数据。我们的目标是利用这些传感器的观测值估计机器人的状态和其轨迹。这个状态估计问题可以表述为最大后验概率(MAP)问题。我们使用因子图来建模这个问题,因为与贝叶斯网相比,因子图更适合进行推理。假设高斯噪声模型,我们问题的MAP推理等同于求解非线性最小二乘问题[18]。 注意,在不失一般性的前提下,该系统也可以纳入其他传感器的测量,如高度计的高度或罗盘的方位。我们为构建因子图引入四种因子类型和一种变量类型。这个变量代表机器人在特定时间的状态,被分配到图的节点。四种因子类型为:

(a) IMU预积分因子,

(b) 激光测距仪姿态因子

(c) GPS因子

(d) 闭环因子。

当机器人姿态变化超过用户定义的阈值时,向图中添加新的机器人状态节点x。在插入新节点时,使用增量平滑和映射与贝叶斯树(iSAM2)[19]对因子图进行优化。 生成这些因子的过程在以下各节中描述。

B. IMU预积分因子

IMU的角速度和加速度测量值使用公式2和3定义:

\[\hat{w}_t = w_t + b^w_t + n^w_t (2) \\ \hat{a}_t = R^{BW}_t(a_t - g) + b^a_t + n^a_t (3)\]

其中 $\hat{w}_t$ 和 $\hat{a}_t$ 是时刻t在B(Body)坐标系中的原始IMU测量值。$\hat{w}_t$ 和 $\hat{a}_t$ 受缓慢变化的偏差 $b_t$ 和白噪声 $n_t$ 的影响。$R^{BW}_t$ 是从W(Wrold)坐标系到B(Body)坐标系的旋转矩阵。gW(World)坐标系中的恒定重力向量。 我们现在可以使用IMU的测量值推断机器人的运动。机器人在时刻 $t + ∆t$ 的速度、位置和旋转可以计算如下:

\[v_{t+∆t} = v_t + g∆t + R_t(\hat{a}_t − b^a_t − n^a_t )∆t(4)\] \[p_{t+∆t} = p_t + v_t∆t + \frac{1}{2}g∆t^2 + \frac{1}{2}R_t(\hat{a}_t − b^a_t − n^a_t )∆t^2(5)\] \[R_{t+∆t} = R_texp((\hat{w}_t - b^w_t - n^w_t )∆t),(6)\]

其中

\[R_t = R^{WB}_t = R^{BW^T}_t\]

这里我们假设B的角速度和加速度在上述积分期间保持不变。 然后,我们应用[20]中提出的IMU预积分方法来获得两时间步之间的相对机体运动。在时刻i和j之间,预积分测量值 $∆v_{ij}$ 、$∆p_{ij}$ 和 $∆R_{ij}$ 可以使用下式计算:

\[∆v_{ij} = R^T_i (v_j − v_i − g∆t_{ij}) (7)\] \[∆p_{ij} = R^T_i (p_j − p_i − v_i∆t_{ij} − \frac{1}{2}g∆t^2_{ij})(8)\] \[∆R_{ij} = R^T_i R_j (9)\]

由于空间限制,我们引用[20]中的描述,详细推导公式7和9。除了效率高外,应用IMU预积分还自然地给我们带来一种约束——IMU预积分因子。IMU偏差与因子图中的激光测距仪因子一起联合优化

C. 激光测距仪姿态因子

当接收到新激光扫描时,我们首先执行特征提取。通过评估局部区域内点的粗糙度来提取边缘和平面特征。粗糙度值较大的点被分类为边缘特征。类似地,由较小的粗糙度值被归类为平面特征。我们将在时刻i的激光扫描中提取的边缘和平面特征分别表示为 $F^e_i$ 和 $F^p_i$。

在时刻i提取的所有特征组成激光帧 $F_i$ ,其中

\[F_i=\{F^e_i, F^p_i \}\]

注意,一个激光帧F表示为B(Body)坐标系下。特征提取过程的更详细描述可以在[1]或[7]中找到(如果使用距离图像)。

使用每个激光帧计算并添加因子到图中在计算上是不可行的,所以我们采用关键帧选择的概念,这在视觉SLAM领域广泛使用。使用简单但有效的启发法,当与前一状态$x_i$相比,机器人姿态的变化超过用户定义的阈值时,我们选择激光帧$F_{i+1}$作为关键帧。新保存的关键帧$F_{i+1}$与因子图中的新机器人状态节点$x_{i+1}$相关联。两个关键帧之间的激光帧被丢弃。这样添加关键帧不仅在地图密度和内存消耗之间达到平衡,而且有助于维持一个相对稀疏的因子图,这适合实时非线性优化。 在我们的工作中,添加新关键帧的位置和旋转变化阈值分别选择为1米和10°。

假设我们想要向因子图添加一个新状态节点$x_{i+1}$。与此状态相关联的激光关键帧是$F_{i+1}$。生成激光测距仪因子的步骤如下:

1). 用于体素地图的子关键帧:

我们实现滑动窗口方法来创建包含固定数量最近激光扫描的点云地图。不是优化两个连续激光扫描之间的变换,我们提取n个最近的关键帧,我们称之为子关键帧,用于估计。子关键帧集合 \(\{F_{i−n}, ..., F_i\}\) 然后使用与之关联的变换 \(\{T_{i−n}, ..., T_i\}\) 转换到W坐标系下。转换后的子关键帧合并到体素地图$M_i$中。由于我们在前面的特征提取步骤中提取两种类型的特征,$M_i$由两个子体素地图组成,边缘特征体素地图${M^e_i}$ 和平面特征体素地图 ${M^p_i}$。激光帧和体素地图之间的关系如下:

\[{M}_{i}=\{M_{i}^{e}, {M}_{i}^{p}\}\]

其中

\[{M}_{i}^{e}='F_{i}^{e} \cup'{F}_{i-1}^{e} \cup \ldots \cup '{F}_{i-n}^{e}\] \[{M}_{i}^{p} = '{F}_{i}^{p} \cup '{F}_{i-1}^{p} \cup \ldots \cup '{F}_{i-n}^{p}\]

$‘F^e_i$ 和 $‘F^p_i$ 是转换到W坐标系后的边缘和平面特征。$M_{i}^{e}$ 和 $M_{i}^{p}$ 然后下采样以消除落在同一个体素单元中的重复特征。在本文中,n被选择为25。 $M_{i}^{e}$ 和 $M_{i}^{p}$ 的下采样分辨率分别为0.2米和0.4米。

2). 扫描匹配:

我们通过扫描匹配将新获得的激光帧$F_{i+1}$,也是${F^e_{i+1}, F^p_{i+1}}$,与$M_i$匹配。可以利用各种扫描匹配方法,如[3]和[4]来完成此目的。这里我们选择[1]中的方法,因为其计算效率高和在各种困难环境下的鲁棒性。 我们首先将${F^e_{i+1}, F^p_{i+1}}$从B转换到W,得到${‘F^e_{i+1}, ‘F^p_{i+1}}$。这个初始变换是使用来自 $ \widetilde{T}{i+1}$ 得到的。对于 $‘F^e{i+1}$ 或 $‘F^p_{i+1}$ 中的每个特征,我们然后在 $M_{i}^{e}$ 或 $M_{i}^{p}$ 中找到其边缘或平面对应项。为了简洁起见,这里省略了找到这些对应项的详细步骤,但在[1]中有详细描述。

3)相对变换:

特征与其边缘或平面补丁对应物之间的距离可以使用以下公式计算:

\[d_{e_k} = \frac {| (p^e_{i+1,k} - p^e_{i,u}) \times (p^e_{i+1,k} - p^e_{i,v}) |} {| p^e_{i,u} - p^e_{i,v} |} (10)\\ d_{p_k} = \frac {| (p^e_{i+1,k} - p^e_{i,u}) (p^e_{i,u} - p^e_{i,v}) \times (p^e_{i,u} - p^e_{i,w}) |} {| (p^e_{i,u} - p^e_{i,v}) \times (p^e_{i,u} - p^e_{i,w}) |} (11)\]

这里 k,u,v 和 w 是匹配对集合中(ircorresponding sets)的特征索引(feature indices)。对于 $′F^e_{i+1}$ 中的边缘特征 $p^e_{i+1,k}$,$p^e_{i,u}$ 和 $p^e_{i,v}$ 是在 $M^e_i$ 中形成相应边缘线的点。对于 $′F^p_{i+1}$ 中的平面特征 $p^p_{i+1,k}$,$p^p_{i,u}, p^p_{i,v}$和$p^p_{i,w}$ 在$M^p_i$中形成相应的平面片。然后使用Gauss-Newton方法最小化能量函数来求解最佳变换:

\[\min_{T_{i+1}} \left \{ \sum_{ {p^e_{i+1,k}} \in {′F^e_{i+1}}} {d_{e_k}} + \sum_{ p^p_{i+1,k} \in ′F^p_{i+1}}{d_{p_k}} \right \}\]

最终,我们可以获得 $x_i$和$x_{i+1}$ 之间的相对变换$∆T_{i,i+1}$,这是连接这两个姿态的激光测距仪里程计因子:

\[∆T_{i,i+1} = T^T_i T_{i+1}(12)\]

我们注意到获得$∆T_{i,i+1}$的另一种方法是将子关键帧变换到$x_i$的坐标系中。换句话说,我们将$F_{i+1}$匹配到以$x_i$的坐标系表示的体素地图。通过这种方式,可以直接获得真实的相对变换$∆T_{i,i+1}$。因为变换后的特征$′F^e_i$和$′F^p_i$可以被多次重用,我们选择使用Sec III-C.1描述的方法,以提高计算效率。

D. GPS Factor

尽管只利用IMU预积分和激光测距仪里程计因子可以获得可靠的状态估计和映射,但系统在长时间导航任务中仍会产生漂移。为解决这个问题,我们可以引入提供绝对测量的传感器来消除漂移。这样的传感器包括高度计、罗盘和GPS。为了说明问题,这里我们讨论GPS,因为它被广泛用于实际导航系统中。 当我们接收到GPS测量时,我们首先使用[21]中提出的方法将其变换到本地笛卡尔坐标系。在向因子图添加新节点时,我们随后将一个新的GPS因子与该节点关联。如果GPS信号与激光帧没有硬件同步,我们根据激光帧的时间戳线性插值GPS测量。

Fig.2

Fig. 2: Datasets are collected on 3 platforms: (a) a custom-builthandheld device, (b) an unmanned ground vehicle - ClearpathJackal, (c) an electric boat - Duffy 21.

我们注意到,当有GPS接收时不必不断添加GPS因子,因为激光惯性里程计的漂移速度很慢。在实践中,我们只在估计位置协方差大于接收的GPS位置协方差时添加GPS因子。

E. 闭环因子

由于利用了因子图,闭环可以无缝地组合进所提出的系统。为了说明问题,我们描述并实现一种简单但有效的基于欧几里得距离的闭环检测方法。我们也注意到我们提出的框架与其他闭环检测方法兼容,如[22]和[23],它们生成点云描述符并使用它进行场所识别。

当一个新状态 $x_{i+1}$ 被添加到因子图时,我们首先在图中搜索,找到在欧几里得空间中与$x_{i+1}$ 接近的先前状态。如图1所示,例如,$x_3$是返回的候选项之一。然后我们尝试使用扫描匹配将$F_{i+1}$与子关键帧${F_{3−m},…,F_3,…,F_{3+m}}$匹配。请注意, $F_{i+1}$ 和过去的子关键帧首先被变换到W坐标系下,然后进行扫描匹配。我们获得相对变换$∆T_{3,i+1}$,并将其作为闭环因子添加到图中。在本文中,我们选择索引m为12,闭环的搜索距离从新状态 $x_{i+1}$ 为15米。

在实践中,我们发现添加闭环因子对于校正机器人的高度漂移特别有用,尤其是当GPS是唯一的绝对传感器时。这是因为GPS的高度测量非常不准确,在没有闭环的情况下,我们的测试中达到100米的高度误差。

IV. EXPERIMENTS

我们现在描述一系列实验来定性和定量地分析我们提出的框架。本文使用的传感器套件包括Velodyne VLP16激光雷达、MicroStrain 3DM-GX5-25 IMU和Reach M GPS。为验证,我们在各种规模、平台和环境中收集了5个不同的数据集。这些数据集分别称为Rotation、Walking、Campus、Park和Amsterdam。传感器安装平台如图2所示。前三个数据集使用MIT校园定制的手持设备收集。Park数据集使用Clearpath Jackal无人地面车(UGV)在植被覆盖的公园收集。最后一个数据集Amsterdam通过在船上安装传感器并在阿姆斯特丹的运河上巡航来收集。这些数据集的详细信息见表I。

表I:数据集详细信息

数据集 扫描 高度变化(米) 轨迹长度(米) 最大旋转速度(◦/s)
Rotation 582 0 0 213.9
Walking 6502 0.3 801 133.7
Campus 9865 1.0 1437 124.8
Park 24691 19.0 2898 217.4
Amsterdam 107656 0 19065 17.2

我们将提出的LIO-SAM框架与LOAM和LIOM进行比较。在所有的实验中,LOAM和LIO-SAM被强制实时运行。另一方面,LIOM被给予无限的时间来处理每个传感器测量。所有方法都是用C++实现的,在装有Intel i7-10710U CPU的笔记本电脑上使用机器人操作系统(ROS)[24]在Ubuntu 下执行。我们注意到仅使用CPU进行计算,没有启用并行计算。我们的LIO-SAM实现可在Github上免费获得。实验的补充细节,包括所有测试的完整可视化,

A. Rotation数据集

Fig.3

图3:Rotation测试中LOAM和LIO-SAM的映射结果。LIOM无法产生有意义的结果。

在此测试中,我们主要评估当只向因子图中添加IMU预积分和激光测距仪里程计因子时,我们的框架的鲁棒性。Rotation数据集是通过用户手持传感器套件并在站立时执行一系列激进的旋转动作收集的。此测试中遇到的最大旋转速度为133.7°/s。测试环境中有许多结构,如图3(a)所示。LOAM和LIO-SAM获得的地图分别如图3(b)和(c)所示。因为LIOM使用来自[25]的相同初始化管道,它继承了视觉惯性SLAM的相同初始化敏感性,无法使用此数据集适当初始化。由于无法产生有意义的结果,LIOM的地图未显示。如图所示,与LOAM的地图相比,LIO-SAM的地图保留了环境更多细致的结构细节。这是因为LIO-SAM即使在机器人快速旋转时也能精确地在SO(3)中注册每个激光雷达帧。

B. Walking数据集

Fig.4

图4:使用步行数据集的LOAM、LIOM和LIO-SAM的映射结果。

在此测试中,图4(b)中的LOAM地图在遇到激进的旋转时多次发散。LIOM优于LOAM。然而,其地图如图4(c)所示,由于不准确的点云配准,显示出许多模糊结构。LIO-SAM产生的地图与Google Earth图像一致,而无需使用GPS。

此测试旨在评估当系统在SE(3)中进行激进的平移和旋转时,我们方法的性能。此数据集中遇到的最大平移和旋转速度分别为1.8 m/s和213.9°/s。在数据收集期间,用户手持图2(a)所示的传感器套件,快速穿过MIT校园(图4(a))。在此测试中,图4(b)所示的LOAM地图在遇到激进旋转时在多个位置发散。在此测试中,LIOM优于LOAM。然而,其地图如图4(c)所示,在各个位置仍略有发散,且由许多模糊结构组成。因为LIOM旨在处理所有传感器测量,它仅以0.56×实时速度运行,而其他方法以实时速度运行。最后,LIO-SAM优于两种方法,产生的地图与可用的Google Earth图像一致。

C. Campus Dataset

TABLE II: End-to-end translation error (meters)

Dataset LOAM LIOM LIO-odom LIO-GPS LIO-SAM
Campus 192.43 Fail 9.44 6.87 0.12
Park 121.74 34.60 36.36 2.93 0.04
Amsterdam Fail Fail Fail 1.21 0.17

此测试旨在显示引入GPS和环路闭合因子的好处。为此,我们故意禁用向图中插入GPS和环路闭合因子。当禁用GPS和环路闭合因子时,我们的方法称为LIO-odom,它仅利用IMU预积分和激光测距仪里程计因子。当使用GPS因子时,我们的方法称为LIO-GPS,它使用IMU预积分、激光测距仪里程计和GPS因子进行图构造。LIO-SAM在可用时使用所有因子。

Fig.5

Fig. 5: Results of various methods using the Campus dataset thatis gathered on the MIT campus. The red dot indicates the start andend location. The trajectory direction is clock-wise. LIOM is notshown because it fails to produce meaningful results.

为收集此数据集,用户使用手持设备在MIT校园内四处走动,然后返回相同的位置。由于映射区域内有许多建筑物和树木,GPS接收通常不可用,大部分时间不准确。过滤掉不一致的GPS测量后,GPS可用的区域如图5(a)中的绿色段所示。这些区域对应于不是被建筑物或树木包围的几个区域。

LOAM、LIO-odom、LIOGPS和LIO-SAM的估计轨迹如图5(a)所示。由于LIOM无法适当初始化和产生有意义的结果,因此未显示LIOM的结果。如图所示,与所有其他方法相比,LOAM的轨迹明显发散。在没有GPS数据校正的情况下,LIO-odom的轨迹在地图右下角开始明显发散。在GPS数据的帮助下,LIO-GPS可以在可用时修正偏差。但是,在数据集的后续部分中,GPS数据不可用。因此,由于漂移,当机器人返回起始位置时,LIO-GPS无法闭环。另一方面,LIO-SAM可以通过向图中添加环路闭合因子来消除漂移。LIO-SAM的地图与Google Earth图像对齐良好,如图5(b)所示。当机器人返回起点时,所有方法的相对平移误差如表II所示。

D. Park Dataset

Fig.6

Fig. 6: Results of various methods using the Park dataset that isgathered in Pleasant Valley Park, New Jersey. The red dot indicatesthe start and end location. The trajectory direction is clock-wise.

在此测试中,我们将传感器安装在UGV上,驱动车辆沿森林徒步小道行驶。机器人在行驶40分钟后返回其初始位置。UGV在三种道路表面上驾驶:沥青路面、草地覆盖的地面和覆盖着泥土的小道。由于缺乏悬架,当在非沥青道路上行驶时,机器人会遭受低幅度但高频率的振动。 为模拟具有挑战性的映射场景,我们仅在机器人位于开阔地区时使用GPS测量,如图6(a)中的绿色段所示。这样的映射场景代表机器人必须映射多个GPS否定区域的任务,并定期返回GPS可用区域以纠正漂移。

与前面的测试结果类似,LOAM、LIOM和LIO-odom由于没有绝对校正数据而遭受显着漂移。此外,LIOM仅以0.67×实时速度运行,而其他方法以实时速度运行。 虽然LIO-GPS和LIO-SAM的轨迹在水平面上重合,但它们的相对平移误差不同(表II)。因为没有可靠的绝对高度测量,LIO-GPS在高度上遭受漂移,并且无法在返回机器人的初始位置时闭合环路。LIO-SAM没有此类问题,因为它利用环路闭合因子消除漂移。

E. Amsterdam Dataset

最后,我们在船上安装传感器套件,并在阿姆斯特丹的运河上巡航3个小时。虽然在此测试中传感器的运动相对平稳,但映射运河仍然具有挑战性,有几个原因。

许多桥梁跨越运河,形成退化情景,因为当船在它们下方时几乎没有有用的特征,类似于移动通过一条长长的、没有特征的走廊。由于没有地面,平面特征的数量也显著减少。当直接阳光在传感器视野内时,我们观察到激光雷达的许多错误检测,这在数据收集期间约占20%的时间。我们也因上方的桥梁和城市建筑物而只获得间歇性的GPS接收。

由于这些挑战,LOAM、LIOM和LIO-odom在此测试中都无法产生有意义的结果。与公园数据集中遇到的问题类似,LIO-GPS由于高度漂移而无法在返回机器人的起始位置时闭环,这进一步激发我们在LIO-SAM中使用环路闭合因子。

F. Benchmarking Results

TABLE III: RMSE translation error w.r.t GPS

Dataset LOAM LIOM LIO-odom LIO-GPS LIO-SAM
Park 47.31 28.96 23.96 1.09 0.96

由于仅在公园数据集中获得全面GPS覆盖,我们显示相对于GPS测量历史的均方根误差(RMSE)结果,将其视为真实值。此RMSE误差不考虑z轴上的误差。如表III所示,LIO-GPS和LIO-SAM与GPS真值取得相似的RMSE误差。注意,如果我们向这两种方法提供完全访问所有GPS测量,则可以进一步将这两种方法的误差降低至少一个数量级。

然而,在许多映射设置中并非始终可获得完全的GPS访问权限。我们的意图是设计一个鲁棒的系统,可以在各种具有挑战性的环境中运行。

表IV显示了三种竞争方法在所有五个数据集中注册一个激光雷达帧的平均运行时间。在所有测试中,LOAM和LIO-SAM被迫实时运行。换句话说,如果运行时间超过100ms,而激光雷达的旋转速率为10Hz,则会丢弃一些激光雷达帧。LIOM获得无限时间来处理每个激光雷达帧。如表所示,LIO-SAM的运行时间明显少于其他两种方法,这使其更适合部署在低功耗嵌入式系统上。

TABLE IV: Runtime of mapping for processing one scan (ms)

Dataset LOAM LIOM LIO-SAM Stress test
Rotation 83.6 Fail 41.9 13×
Walking 253.6 339.8 58.4 13×
Campus 244.9 Fail 97.8 10×
Park 266.4 245.2 100.5
Amsterdam Fail Fail 79.31

们还通过以超过实时的速度向LIO-SAM提供数据来对其进行压力测试。当LIO-SAM与数据回放速度为1×实时时的结果相比,在没有故障的情况下达到相似的性能时,记录最大数据回放速度,并在表IV的最后一列中显示。如表所示,LIO-SAM能够以高达13×的速度处理数据超过实时。

我们注意到,LIO-SAM的运行时间更大程度上受特征图的密度影响,而不是因子图中的节点数和因子数的影响。例如,公园数据集是在特征丰富的环境中收集的,植被导致大量特征,而阿姆斯特丹数据集产生更稀疏的特征图。虽然公园测试的因子图由4,573个节点和9,365个因子组成,但阿姆斯特丹测试的图由23,304个节点和49,617个因子组成。

尽管如此,LIO-SAM在阿姆斯特丹测试中使用的时间少于在公园测试中的运行时间。

Fig.7

Fig. 7: Map of LIO-SAM aligned with Google Earth.

V. CONCLUSIONS AND DISCUSSION

我们提出了LIO-SAM,一种通过平滑和映射进行紧密耦合的激光惯性里程计框架,用于在复杂环境中进行实时状态估计和映射。通过在因子图上形式化激光惯性里程计,LIO-SAM特别适合多传感器融合。附加的传感器测量可以轻易地作为新因子并入框架。提供绝对测量的传感器,如GPS、磁罗盘或高度表,可以用于消除长时间积累的或在特征贫乏环境中的激光惯性里程计的漂移。场景识别也可以轻易地并入系统。为改进系统的实时性能,我们提出了滑动窗口方法,该方法边缘化旧的激光雷达帧以进行扫描匹配。关键帧被选择添加到因子图中,并且仅当生成激光里程计和环路闭合因子时,才将新关键帧注册到固定大小的子关键帧集。这种在局部范围内而不是全局范围内进行的扫描匹配有助于LIO-SAM框架的实时性能。该提出的方法在各种环境下收集的数据集上进行了全面评估。结果表明,与LOAM和LIOM相比,LIO-SAM可以达到相似或更高的精度。未来的工作涉及在无人机上测试提出的系统。

REFERENCES

[1] J. Zhang and S. Singh, “Low-drift and Real-time Lidar Odometry andMapping,” Autonomous Robots, vol. 41(2): 401-416, 2017.

[2] A. Geiger, P. Lenz, and R. Urtasun, “Are We Ready for AutonomousDriving? The KITTI Vision Benchmark Suite”, IEEE InternationalConference on Computer Vision and Pattern Recognition, pp. 33543361, 2012.

[3] P.J. Besl and N.D. McKay, “A Method for Registration of 3D Shapes,”IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 14(2): 239-256, 1992.

[4] A. Segal, D. Haehnel, and S. Thrun, “Generalized-ICP,” Proceedingsof Robotics: Science and Systems, 2009.

[5] W.S. Grant, R.C. Voorhies, and L. Itti, “Finding Planes in LiDARPoint Clouds for Real-time Registration,” IEEE/RSJ InternationalConference on Intelligent Robots and Systems, pp. 4347-4354, 2013.

[6] M. Velas, M. Spanel, and A. Herout, “Collar Line Segments for FastOdometry Estimation from Velodyne Point Clouds,” IEEE International Conference on Robotics and Automation, pp. 4486-4495, 2016.

[7] T. Shan and B. Englot, “LeGO-LOAM: Lightweight and Groundoptimized Lidar Odometry and Mapping on Variable Terrain,”IEEE/RSJ International Conference on Intelligent Robots and Systems,pp. 4758-4765, 2018.

[8] T. Shan, J. Wang, K. Doherty, and B. Englot, “Bayesian GeneralizedKernel Inference for Terrain Traversability Mapping,” In Conferenceon Robot Learning, pp. 829-838, 2018.

[9] S. Lynen, M.W. Achtelik, S. Weiss, M. Chli, and R. Siegwart, “ARobust and Modular Multi-sensor Fusion Approach Applied to MAVNavigation,” IEEE/RSJ International Conference on Intelligent Robotsand Systems, pp. 3923-3929, 2013.

[10] S. Yang, X. Zhu, X. Nian, L. Feng, X. Qu, and T. Mal, “A RobustPose Graph Approach for City Scale LiDAR Mapping,” IEEE/RSJInternational Conference on Intelligent Robots and Systems, pp. 11751182, 2018.

[11] M. Demir and K. Fujimura, “Robust Localization with Low-MountedMultiple LiDARs in Urban Environments,” IEEE Intelligent Transportation Systems Conference, pp. 3288-3293, 2019.

[12] Y. Gao, S. Liu, M. Atia, and A. Noureldin, “INS/GPS/LiDAR Integrated Navigation System for Urban and Indoor Environments usingHybrid Scan Matching Algorithm,” Sensors, vol. 15(9): 23286-23302,2015.

[13] S. Hening, C.A. Ippolito, K.S. Krishnakumar, V. Stepanyan, and M. Teodorescu, “3D LiDAR SLAM integration with GPS/INS for UAVsin urban GPS-degraded environments,” AIAA Infotech@AerospaceConference, pp. 448-457, 2017.

[14] C. Chen, H. Zhu, M. Li, and S. You, “A Review of Visual-InertialSimultaneous Localization and Mapping from Filtering-Based andOptimization-Based Perspectives,” Robotics, vol. 7(3):45, 2018.

[15] C. Le Gentil,, T. Vidal-Calleja, and S. Huang, “IN2LAMA: InertialLidar Localisation and Mapping,” IEEE International Conference onRobotics and Automation, pp. 6388-6394, 2019.

[16] C. Qin, H. Ye, C.E. Pranata, J. Han, S. Zhang, and Ming Liu, “RLINS: A Robocentric Lidar-Inertial State Estimator for Robust andEfficient Navigation,” arXiv:1907.02233, 2019.

[17] H. Ye, Y. Chen, and M. Liu, “Tightly Coupled 3D Lidar InertialOdometry and Mapping,” IEEE International Conference on Roboticsand Automation, pp. 3144-3150, 2019.

[18] F. Dellaert and M. Kaess, “Factor Graphs for Robot Perception,”Foundations and Trends in Robotics, vol. 6(1-2): 1-139, 2017.

[19] M. Kaess, H. Johannsson, R. Roberts, V. Ila, J.J. Leonard, and F. Dellaert, “iSAM2: Incremental Smoothing and Mapping Using theBayes Tree,” The International Journal of Robotics Research, vol. 31(2): 216-235, 2012.

[20] C. Forster, L. Carlone, F. Dellaert, and D. Scaramuzza, “On-ManifoldPreintegration for Real-Time Visual-Inertial Odometry,” IEEE Transactions on Robotics, vol. 33(1): 1-21, 2016.

[21] T. Moore and D. Stouch, “A Generalized Extended Kalman FilterImplementation for The Robot Operating System,” Intelligent Autonomous Systems, vol. 13: 335-348, 2016.

[22] G. Kim and A. Kim, “Scan Context: Egocentric Spatial Descriptor forPlace Recognition within 3D Point Cloud Map,” IEEE/RSJ International Conference on Intelligent Robots and Systems, pp. 4802-4809,2018.

[23] J. Guo, P. VK Borges, C. Park, and A. Gawel, “Local Descriptor forRobust Place Recognition using Lidar Intensity,” IEEE Robotics andAutomation Letters, vol. 4(2): 1470-1477, 2019.

[24] M. Quigley, K. Conley, B. Gerkey, J. Faust, T. Foote, J. Leibs,R. Wheeler, and A.Y. Ng, “ROS: An Open-source Robot OperatingSystem,” IEEE ICRA Workshop on Open Source Software, 2009.

[25] T. Qin, P. Li, and S. Shen, “Vins-mono: A Robust and VersatileMonocular Visual-Inertial State Estimator,” IEEE Transactions onRobotics, vol. 34(4): 1004-1020, 2018.

[继续阅读]

23 Apr 2023

论文翻译LVI-SAM Tightly-coupled Lidar-Visual-Inertial Odometry via Smoothing and Mapping

摘要——我们提出了一个通过平滑和映射实现紧密耦合的激光雷达-视觉-惯性测距技术的框架,称为LVI-SAM。LVI-SAM能够以高精度和强鲁棒性实时完成状态估计和地图构建。LVI-SAM建立在一个因子图上,并由两个子系统组成:一个是视觉-惯性系统(VIS),另一个是激光雷达-惯性系统(LIS)。这两个子系统以紧密耦合的方式设计,其中VIS利用LIS的估计结果以便进行初始化。

通过使用激光雷达测量数据来提取视觉特征的深度信息,可以提高VIS的准确性。反过来,LIS利用VIS的估计结果进行初始匹配。首先,VIS识别闭环,然后LIS进一步精细调整。LVI-SAM可以在其中一个子系统失效的情况下工作,这提高了它在无纹理和无特征环境中的鲁棒性。我们对LVI-SAM进行了广泛的评估,使用了多种平台和不同尺度和环境的数据集。我们的实现代码可在https://git.io/lvi-sam获取。

I. INTRODUCTION

同时定位和地图构建(SLAM)是许多移动机器人导航任务所必需的基础功能。在过去的二十年中,使用SLAM在具有单一感知传感器(如激光雷达或相机)的具有挑战性的环境中进行实时状态估计和地图构建已取得了巨大成功。基于激光雷达的方法可以在远距离捕捉环境的细节。然而,在没有结构的环境中,例如长走廊或平坦的开放区域,这种方法通常会失败。虽然基于视觉的方法特别适合场所识别,并在纹理丰富的环境中表现良好,但它们对照明变化、快速运动和初始化非常敏感。因此,激光雷达和视觉方法通常分别与惯性测量单元(IMU)耦合,以增加它们各自的鲁棒性和精度。激光雷达-惯性系统可以帮助纠正点云畸变并解决短时间内特征缺失的问题。IMU测量可以恢复度量尺度和姿态以辅助视觉惯性系统。为了进一步提高系统性能,激光雷达、相机和IMU测量的融合越来越受到关注。

我们的工作与视觉惯性测距(VIO),激光惯性测距(LIO)和激光-视觉惯性测距(LVIO)密切相关。我们注意到,本文不考虑非惯性系统,尽管我们知道已有成功的非惯性激光-视觉系统,如12。视觉惯性测距(VIO)主要分为两大类:基于滤波器的方法和基于优化的方法。基于滤波器的方法通常使用扩展卡尔曼滤波器(EKF)通过相机和IMU的测量来传播系统状态。基于优化的方法维护一个滑动窗口估计器,最小化视觉重投影误差以及IMU测量误差。在我们的工作中,我们仅考虑单目相机。在最受欢迎的公开VIO Pipeline中,MSCKF 3 , RVIO4和OpenVINS 5是基于滤波器的,而OKVIS 6和VINS-Mono 7是基于优化的。尽管OKVIS使用立体相机表现出卓越的性能,但它没有针对单目相机进行优化。VINS-Mono在滑动窗口设置中执行非线性优化,并使用单目相机实现了最先进的准确性 8

基于它们的设计方案,激光惯性里程计可以分为两大类:松耦合方法和紧耦合方法。其中LOAM 9和LeGO-LOAM 10是松耦合系统,因为IMU测量结果不会在优化步骤中使用。紧耦合系统通常提供更高的精度和鲁棒性,目前是持续研究的重点11。在公开可用的紧耦合系统中,LIO-mapping 12采用7的优化流程并最小化IMU和激光雷达测量残差。由于LIO-mapping被设计为优化所有测量值,因此无法实现实时性能。LIO-SAM 13通过引入激光雷达关键帧的滑动窗口,限制计算复杂度,并利用因子图进行联合IMU和激光雷达约束优化。专为地面车辆设计的LINS 14使用误差状态卡尔曼滤波器以递归方式修正机器人状态。

最近,由于其在传感器降级任务中的鲁棒性,激光雷达-视觉-惯性系统受到越来越多的关注1516提出了一种紧耦合LVIO系统,采用顺序处理流程,从粗到细解决状态估计问题。最粗略的估计从IMU预测开始,然后进一步通过VIO和LIO进行优化。16目前在KITTI基准测试17上实现了最先进的精度。基于MSCKF框架,18提供在线的空间和时间多传感器校准。1618的实现不是公开的。我们的工作与上述工作不同,我们利用因子图进行全局优化,可以通过回路闭合检测定期消除机器人的漂移。

本文提出了一个实时状态估计和建图的紧耦合激光雷达-视觉-惯性测量单元平滑与映射(LVI-SAM)框架。LVI-SAM基于一个因子图,由一个视觉惯性系统(VIS)和一个激光雷达惯性系统(LIS)两个子系统组成。两个子系统可以独立地运行,当一个系统出现故障时,或者当检测到足够的特征时,两个子系统可以联合工作。VIS执行视觉特征跟踪,并可选地使用激光雷达帧提取特征深度。通过优化视觉重投影误差和IMU测量误差来获得的视觉里程计,作为激光雷达扫描匹配的初始估计,并引入约束到因子图中。使用IMU测量进行点云去畸变后,LIS提取激光雷达边缘和平面特征,并将其匹配到滑动窗口中维护的特征地图。LIS中的估计系统状态可以发送到VIS中以促进其初始化。对于环路闭合,首先由VIS确定候选匹配,然后由LIS进一步优化。来自视觉里程计、激光雷达里程计、IMU预积分和环路闭合的约束在因子图中联合优化。最后,优化的IMU偏差项被利用以传播IMU测量以获得IMU速率下的姿态估计。我们工作的主要贡献如下:

  • 基于因子图的紧密耦合LVIO框架,实现了多传感器融合和通过场所识别实现全局优化。

  • 我们的框架通过故障检测来避免子系统的失败,使其对传感器退化具有鲁棒性。

  • 我们的框架经过广泛验证,使用跨越不同尺度、平台和环境的数据进行了测试。

我们的工作在系统层面上是独特的,代表了VIO和LIO最先进技术的独特集成,实现了提高鲁棒性和准确性的LVIO系统。我们希望我们的系统可以作为一个坚实的基础,让其他人可以轻松地在此基础上推进激光雷达-视觉-惯性测距技术的最新进展。

II. LIDAR VISUAL INERTIAL ODOMETRY VIA SMOOTHING AND MAPPING

A.系统概述

Fig.1

图1:LVI-SAM的系统结构。该系统接收来自3D激光雷达、相机和IMU的输入,可以分为两个子系统:视觉惯性系统(VIS)和激光雷达惯性系统(LIS)。VIS和LIS可以独立运行,同时利用彼此的信息来提高系统的准确性和稳健性。该系统以IMU速率输出姿态估计。

本文提出的激光雷达-视觉-惯性系统,接收来自3D激光雷达、单目相机和IMU的输入,如图1所示。我们的框架由两个关键子系统组成:视觉-惯性系统(VIS)和激光-惯性系统(LIS)。VIS处理图像和IMU测量值,其中激光测量值是可选的。通过最小化视觉和IMU测量的联合残差获得视觉里程计。LIS提取激光特征,并通过将提取的特征与特征地图匹配来执行激光里程计。特征地图以滑动窗口方式维护,以实现实时性能。最后,通过使用iSAM2 19在因子图中联合优化IMU预积分约束、视觉里程计约束、激光里程计约束和环路闭合约束的贡献,解决可以被表述为最大后验(MAP)问题的状态估计问题。需要注意的是,LIS中采用的多传感器图优化旨在减少数据交换并提高系统效率。

B. Visual-Inertial System

Fig.2

图2: 我们的视觉惯性系统框架。该系统优化IMU预积分、没有深度信息的视觉测量和具有深度信息的视觉测量的残差。

我们为我们的VIS(视觉惯性系统)采用了7的Pipeline,如图2所示。使用角点检测器20检测视觉特征并通过Kanade-Lucas-Tomasi算法21进行跟踪。在VIS初始化后,我们使用视觉里程计对激光雷达帧进行配准,并获取用于特征深度估计的稀疏深度图像。系统在滑动窗口设置下执行束调整,其中系统状态 $x ∈ X$ 可以写成:

\[x = [R, p, v, b]\]

$R ∈ SO(3)$ 是旋转矩阵,$p ∈ R^3$ 是位移向量,$v$ 是速度,$b = [b_{a},b_w ]$ 是IMU偏差。$b_a$和$b_w$分别是加速度和角速度的偏差向量。从传感器体坐标系B到世界坐标系W的变换 $T ∈ SE(3)$ 表示为 $T = [R,p]$。在接下来的几节中,我们将详细介绍如何改善VIS初始化和特征深度估计的过程。由于篇幅限制,我们将读者引用到[7]以获取更多细节,如残差的实现。

1). 初始化:基于优化的VIO通常由于在初始化时解决高度非线性问题而导致发散。初始化的质量严重依赖于两个因素:初始传感器运动和IMU参数的准确性。在实践中,我们发现[7]在传感器行驶速度较小或恒定时常常无法初始化。这是因为当加速度激励不够大时,度量尺度不可观测。IMU参数包括缓慢变化的偏置和白噪声,影响原始加速度和角速度测量。在初始化时这些参数的良好猜测有助于优化更快地收敛。

为了提高我们的VIS初始化的鲁棒性,我们利用了LIS估计的系统状态x和IMU偏置b。因为深度可以直接从激光雷达中观测到,我们首先初始化LIS并获得x和b。然后我们基于图像时间戳对它们进行插值和关联到每个图像关键帧。请注意,IMU偏置被假定在两个图像关键帧之间保持不变。最后,LIS估计的x和b被用作VIS初始化的初始猜测,这显著提高了初始化速度和鲁棒性。有关使用和不使用LIS进行VIS初始化的比较,请参见补充视频:

2). 特征深度关联:在VIS初始化之后,我们使用估计的视觉里程计将激光雷达帧注册到相机帧。由于现代的3D激光雷达通常产生稀疏的扫描,我们堆叠多个激光雷达帧以获取密集的深度图。为了将特征与深度值关联起来,我们首先将视觉特征和激光雷达深度点投影到以相机为中心的单位球上。然后,使用极坐标将深度点进行下采样并存储,以保持球面上的恒定密度。我们通过使用视觉特征的极坐标搜索二维K-D树来查找最近的三个球面上的深度点。最后,特征深度是由视觉特征和相机中心Oc所形成的直线长度,在笛卡尔空间中与由三个深度点形成的平面相交。该过程的可视化可以在图3(a)中找到,其中特征深度是虚线直线的长度。

Fig.3

图3: 视觉特征深度关联.

我们进一步通过检查最近的三个深度点之间的距离来验证关联的特征深度。这是因为来自不同时间戳的叠加激光雷达帧可能导致深度模糊,来自不同物体的深度点混杂在一起。图3(b)展示了这种情况的示例。绿色表示在时间ti观测到的深度点,灰色表示在tj时相机移动到新位置观察到的深度点。然而,由于激光雷达帧的叠加,ti时观察到的深度点(由虚线灰圆圈表示)可能仍然在tj时可见。使用来自不同物体的深度点关联特征深度会导致不准确的估计。与16类似,我们通过检查特征的最大深度点距离来拒绝这样的估计。如果最大距离大于2m,则特征没有深度关联。

Fig.4

图4: Registed 深度图和视觉特征。在(a)和(c)中,深度图的颜色变化表示深度变化。在(b)和(d)中,绿色点是成功与深度相关联的视觉特征。未能通过深度关联过程的特征为红色。.

一个展示Registered深度图和视觉特征的演示如图4所示。在图4(a)和(c)中,使用视觉里程计注册的深度点被投影到相机图像上。在图4(b)和(d)中,成功关联深度的视觉特征被标为绿色。请注意,尽管深度图在图4(a)中覆盖了大部分图像,但由于验证检查失败,许多位于窗户角落的特征在4(b)中缺乏深度关联。

3). 故障检测:由于激烈运动、光照变化和无纹理环境等原因,VIS容易出现故障。当机器人经历激烈运动或进入无纹理环境时,跟踪特征点的数量会大大减少。特征点不足可能导致优化发散。我们还注意到当VIS发生故障时,会估计出大的IMU偏差。因此,当跟踪的特征点数量低于阈值或估计的IMU偏差超过阈值时,我们报告VIS故障。我们的系统需要进行主动故障检测,以确保其故障不会破坏LIS的功能。一旦检测到故障,VIS将重新初始化并通知LIS。

4). 闭环检测: 我们利用DBoW222进行闭环检测。对于每个新的图像关键帧,我们提取BRIEF描述符[23]并将其与之前提取的描述符进行匹配。由DBoW2返回的闭环候选图像的时间戳将发送到LIS进行进一步验证。

C. Lidar-Inertial System

Fig.5

Fig. 5: 我们的激光雷达惯性系统框架。该系统维护一个包含四种约束类型的因子图。

如图5所示,所提出的激光惯性系统是从13改编而来,为全局姿态优化维护一个因子图。在该图中,添加了四种类型的约束:IMU预积分约束、视觉里程计约束、激光里程计约束和闭环约束,并进行联合优化。激光里程计约束是从扫描匹配中得出的,我们将当前激光关键帧与全局特征地图进行匹配。闭环约束的候选项首先由VIS提供,然后通过扫描匹配进一步优化。我们维护一个激光关键帧的滑动窗口,用于特征地图,以保证有界的计算复杂度。当机器人姿态变化超过阈值时,选择一个新的激光关键帧。在键帧之间的间歇性激光帧被丢弃。在选择新的激光关键帧时,将一个新的机器人状态x添加到因子图中作为节点。以这种方式添加关键帧不仅可以在记忆消耗和地图密度之间达到平衡,而且有助于维护相对稀疏的因子图以进行实时优化。由于空间限制,我们将读者引用到13中查看具体实现细节。在接下来的章节中,我们将重点介绍改进系统鲁棒性的新程序。

1). 初始猜测(Initial guess):我们发现初始猜测(Initial guess)在扫描匹配的成功中起着至关重要的作用,特别是当传感器经历激烈的运动时。在LIS初始化之前和之后,初始猜测的来源是不同的。

在LIS初始化之前,我们假设机器人从静态位置开始,速度为零。然后,我们假设偏差和噪声为零值,对原始IMU测量进行积分。两个激光雷达关键帧之间积分的平移和旋转变化产生了用于扫描匹配的初始猜测。我们发现,在初始线速度小于10 m/s且角速度小于180◦/s的挑战性条件下,该方法可以成功初始化系统。一旦LIS被初始化,我们在因子图中估计IMU偏差、机器人姿态和速度。然后,我们将它们发送到VIS以帮助其初始化。

在LIS初始化之后,我们可以从两个来源获得初始猜测:带有校正偏差的IMU测量值和VIS。如果可用,我们使用视觉惯性测距作为初始猜测。如果VIS报告失败,则切换到IMU测量值作为初始猜测。

这些程序提高了在丰富纹理和无纹理环境中的初始猜测的准确性和鲁棒性。

Fig.6

Fig. 6: Degenerate mapping scenarios where scan-matching is illconstrained. In (a) and (c), the lidar is placed facing the ground. In (b) and (d), the lidar is in a flat, open, and structure-lessenvironment. White points indicate the contents of the lidar scan. Color variation indicates elevation change.

2). 故障检测:虽然激光雷达在远距离可以捕捉到环境的精细细节,但它仍会遇到扫描匹配不稳定的降级场景,这些场景如图6所示。我们采用来自文献[24]的方法进行LIS故障检测。扫描匹配中的非线性优化问题可以被表述为迭代地解决一个线性问题:

\[\min_{T} || AT − b ||^2\]

其中A和b是在T处进行线性化后得到的。当ATA的最小特征值小于一个阈值时,LIS在第一次迭代优化时报告故障。当发生故障时,不会将激光里程约束添加到因子图中。关于这些假设的详细分析,请参见文献[24]。

III. 实验

我们现在描述一系列实验,以验证所提出的框架在三个自行收集的数据集(称为Urban、Jackal和Handheld)上的有效性。这些数据集的详细信息将在以下部分提供。我们的数据采集传感器包括Velodyne VLP-16激光雷达、FLIR BFS-U3-04S2M-CS相机、MicroStrain 3DM-GX5-25 IMU和一个Reach RS+ GPS(用于地面真实数据)。我们将所提出的框架与开源解决方案进行比较,这些解决方案包括VINS-Mono、LOAM、LIO-mapping、LINS和LIO-SAM。所有方法都是用C++实现,并在一台搭载Intel i7-10710U的Ubuntu Linux笔记本电脑上执行。我们的LVISAM实现和数据集可以在下面的链接中找到2。

A. 消融研究

我们利用Urban数据集展示了系统中每个模块的设计如何影响所提出的框架的性能。该数据集包括建筑物、停放和行驶的汽车、行人、骑车人和植被等,由操作员携带传感器组件步行采集。我们还故意将传感器套件放置在具有挑战性的位置(图6(a))以验证系统在降级场景中的鲁棒性。由于密集的高架植被,该区域无法使用GPS。我们在同一位置开始和结束数据采集过程,以验证端到端的平移和旋转误差,并提供表I中的结果。

Fig.7

图7:Urban数据集的消融研究轨迹。

TABLE I: End-to-end translation and rotation errors.

Errortype A1(w/o depth) A1(w/ depth) A2 A3(w/o depth) A3(w/ depth) A4
Translation (m) 239.19 142.12 290.43 45.42 32.18 0.28
Rotation (degree) 60.72 39.52 116.50 6.41 7.66 5.77

1)A1-将激光雷达的特征深度信息纳入视觉惯性测量的影响:我们禁用LIS中的扫描匹配,并仅依靠VIS进行位姿估计。带有和不带有启用深度注册的结果在图7中标记为A1。轨迹方向为顺时针。当将深度与视觉特征相关联时,端到端姿态误差显示在表I中大大降低。

2)A2-包含视觉惯性测量的影响:我们禁用VIS,并仅使用LIS进行位姿估计。轨迹标记为A2。在遇到降级场景时,轨迹发散了几次。

3)A3-将激光雷达的特征深度信息纳入激光雷达-视觉惯性测量的影响:现在我们同时使用VIS和LIS,并切换VIS中的深度注册模块,以比较所得到的LVIO轨迹。通过深度协助视觉特征,平移误差进一步减少了29%,从45.42米降至32.18米。请注意,在此测试中,我们禁用了环路闭合检测,以验证系统的纯里程计模式。

4)A4-包含视觉环路闭合检测的影响:通过启用VIS中的环路闭合检测功能,消除了系统的漂移。当在Figure7中启用每个模块时的最终轨迹标记为A4。

Fig.8

图8:采集Jackal和Handheld数据集的卫星图像。 数据集中白点表示GPS可用性。

B. Jackal数据集

Jackal数据集是通过将传感器组件安装在Clearpath Jackal无人地面车(UGV)上收集的。我们手动驾驶机器人在一个富有特色的环境中行驶,起点和终点相同。该环境如图8(a)所示,包括建筑物、植被和各种道路表面。GPS接收区域用白色点标记。我们比较了各种方法,并在图9(a)中展示了它们的轨迹。我们进一步通过手动禁用和启用循环闭合功能来验证这些方法的准确性。基于GPS测量结果作为“地面真实值”,LVI-SAM方法的平均均方根误差(RMSE)最低。LINS方法从LeGOLOAM [10]中改编而来,专门设计用于UGV操作,其最低端到端平移误差也在所有方法中取得了最佳结果。而LVI-SAM方法再次取得了最低的端到端旋转误差。

C. Handheld数据集

Handheld数据集是由操作员携带传感器组件在几个开放的田野中行走收集的,如图8(b)所示。数据集也起点和终点相同。我们通过穿过一个开放的棒球场增加了数据集的挑战性,该棒球场位于图像的中上方。当穿过这个球场时,相机和激光雷达所观测到的主要特征是草和地面平面,分别如图6(b)和(d)所示。由于前面提到的退化问题,所有基于激光雷达的方法都无法产生有意义的结果。LVI-SAM方法成功完成了测试,无论循环闭合是否启用,都实现了三个基准测试标准中的最低误差。详见表II。

Fig.9

图9:使用Jackal和Handheld数据集的各种方法的轨迹。 绿色线条描绘了每种比较方法的结果轨迹。 作为基准的GPS定位测量用红点表示。 由于LOAM,LIO-mapping,LINS和LIO-SAM无法生成有意义的结果,因此没有显示它们的轨迹。

表II:使用各种方法对Jackal和Handheld数据集进行定量比较。

Dataset Error type VINS(w/o loop) VINS(w/ loop) LOAM LIO-mapping LINS(w/o loop) LINS(w/ loop) LIO-SAM(w/o loop) LIO-SAM(w/ loop) LVI-SAM(w/o loop) LVI-SAM(w/ loop)
Jackal RMSE w.r.t GPS (m) 8.58 4.49 44.92 127.05 3.95 0.77 3.54 1.52 4.05 0.67
Jackal Translation (m) 10.82 11.86 61.73 123.22 7.37 0.09 5.48 0.12 4.69 0.11
Jackal Rotation (degree) 9.98 12.79 61.41 139.23 4.80 2.07 2.18 2.64 2.28 1.52
Handheld RMSE w.r.t GPS (m) 87.53 73.07 Fail Fail Fail Fail 53.62 Fail 7.87 0.83
Handheld Translation (m) 40.65 1.87 Fail Fail Fail Fail 58.91 Fail 7.57 0.27
Handheld Rotation (degree) 53.48 52.09 Fail Fail Fail Fail 20.17 Fail 24.8 23.82

IV. 结论

我们提出了LVI-SAM,一种通过平滑和映射实现紧密耦合的激光雷达-视觉-惯性测距系统的框架,用于在复杂环境中进行实时状态估计和建图。所提出的框架由两个子系统组成:一个视觉-惯性系统和一个激光雷达-惯性系统。这两个子系统设计为紧密耦合,以提高系统的鲁棒性和准确性。通过对各种规模、平台和环境的数据集进行评估,我们的系统表现出与现有公开方法相当或更好的准确性。我们希望我们的系统能够作为其他人可以轻松构建的坚实基础,以推进激光雷达-视觉-惯性测距技术的发展。

致谢

本工作得到荷兰阿姆斯特丹高级城市解决方案研究所的支持。

REFERENCES

[1] J. Graeter, A. Wilczynski, and M. Lauer, “LIMO: Lidar-MonocularVisual Odometry,” IEEE/RSJ International Conference on IntelligentRobots and Systems (IROS), pp. 7872–7879, 2018.

[2] Y.-S. Shin, Y. S. Park, and A. Kim, “DVL-SLAM: Sparse DepthEnhanced Direct Visual-LiDAR SLAM,” Autonomous Robots, vol. 44,no. 2, pp. 115–130, 2020.

[3] A. I. Mourikis and S. I. Roumeliotis, “A Multi-state Constraint KalmanFilter for Vision-aided Inertial Navigation,” IEEE International Conference on Robotics and Automation (ICRA), pp. 3565–3572, 2007.

[4] M. Bloesch, S. Omari, M. Hutter, and R. Siegwart, “Robust VisualInertial Odometry using A Direct EKF-based Approach,” IEEE/RSJinternational conference on intelligent robots and systems (IROS), pp.298–304, 2015.

[5] P. Geneva, K. Eckenhoff, W. Lee, Y. Yang, and G. Huang, “OpenVINS:A Research Platform for Visual-Inertial Estimation,” IROS Workshopon Visual-Inertial Navigation: Challenges and Applications, 2019.

[6] S. Leutenegger, S. Lynen, M. Bosse, R. Siegwart, and P. Furgale,“Keyframe-based Visual-Inertial Odometry using Nonlinear Optimization,” The International Journal of Robotics Research, vol. 34, no. 3,pp. 314–334, 2015.

[7] T. Qin, P. Li, and S. Shen, “VINS-Mono: A Robust and VersatileMonocular Visual-Inertial State Estimator,” IEEE Transactions onRobotics, vol. 34, no. 4, pp. 1004–1020, 2018.

[8] J. Delmerico and D. Scaramuzza, “A Benchmark Comparison ofMonocular Visual-Inertial Odometry Algorithms for Flying Robots,”IEEE International Conference on Robotics and Automation (ICRA),pp. 2502–2509, 2018.

[9] J. Zhang and S. Singh, “Low-drift and Real-time Lidar Odometry andMapping,” Autonomous Robots, vol. 41, no. 2, pp. 401–416, 2017.

[10] T. Shan and B. Englot, “LeGO-LOAM: Lightweight and GroundOptimized Lidar Odometry and Mapping on Variable Terrain,”IEEE/RSJ International Conference on Intelligent Robots and Systems(IROS), pp. 4758–4765, 2018.

[11] C. Chen, H. Zhu, M. Li, and S. You, “A Review of Visual-InertialSimultaneous Localization and Mapping from Filtering-based andOptimization-based Perspectives,” Robotics, vol. 7, no. 3, p. 45, 2018.

[12] H. Ye, Y. Chen, and M. Liu, “Tightly Coupled 3D Lidar InertialOdometry and Mapping,” IEEE International Conference on Roboticsand Automation (ICRA), pp. 3144–3150, 2019.

[13] T. Shan, B. Englot, D. Meyers, W. Wang, C. Ratti, and D. Rus, “LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing andMapping,” IEEE/RSJ International Conference on Intelligent Robotsand Systems (IROS), pp. 4758–4765, 2020.

[14] C. Qin, H. Ye, C. E. Pranata, J. Han, S. Zhang, and M. Liu, “LINS:A Lidar-Inertial State Estimator for Robust and Efficient Navigation,”IEEE International Conference on Robotics and Automation (ICRA),pp. 8899–8906, 2020.

[15] C. Debeunne and D. Vivet, “A Review of Visual-LiDAR Fusion basedSimultaneous Localization and Mapping,” Sensors, vol. 20, no. 7, p.2068, 2020.

[16] J. Zhang and S. Singh, “Laser-Visual-Inertial Odometry and Mappingwith High Robustness and Low Drift,” Journal of Field Robotics,vol. 35, no. 8, pp. 1242–1264, 2018.

[17] A. Geiger, P. Lenz, C. Stiller, and R. Urtasun, “Vision Meets Robotics:The KITTI Dataset,” The International Journal of Robotics Research,vol. 32, no. 11, pp. 1231–1237, 2013.

[18] X. Zuo, P. Geneva, W. Lee, Y. Liu, and G. Huang, “LIC-Fusion:LiDAR-Inertial-Camera Odometry,” arXiv preprint arXiv:1909.04102,2019.

[19] M. Kaess, H. Johannsson, R. Roberts, V. Ila, J. J. Leonard, andF. Dellaert, “iSAM2: Incremental Smoothing and Mapping using theBayes Tree,” The International Journal of Robotics Research, vol. 31,no. 2, pp. 216–235, 2012.

[20] J. Shi et al., “Good Features to Track,” IEEE Conference on ComputerVision and Pattern Recognition, pp. 593–600, 1994.

[21] B. D. Lucas, T. Kanade et al., “An Iterative Image RegistrationTechnique with an Application to Stereo Vision,” 1981.

[22] D. G´alvez-L´opez and J. D. Tardos, “Bags of Binary Words forFast Place Recognition in Image Sequences,” IEEE Transactions onRobotics, vol. 28, no. 5, pp. 1188–1197, 2012.

[23] M. Calonder, V. Lepetit, C. Strecha, and P. Fua, “Brief: Binary robustindependent elementary features,” European conference on computervision, pp. 778–792, 2010.

[24] J. Zhang, M. Kaess, and S. Singh, “On Degeneracy of Optimizationbased State Estimation Problems,” IEEE International Conference onRobotics and Automation (ICRA), pp. 809–816, 2016.

[继续阅读]

26 Nov 2016

Markdoown 语法

1. 基本语法

2. 流程图(flowchar.js)

2.1 定义流程图元素

tag=>type: content:>url

  1. tag就是一个标签,在第二段连接元素时用;

  2. type是这个标签的类型,有6种类型,分别为:
    • start
    • end
    • operation
    • subroutine
    • condition
    • inputoutput
  3. content就是在框框中要写的内容,中英文均可,但有一点需要特别注意,就是type后的冒号与文本之间一定要有个空格,没空格会出问题;

  4. url就是一个连接,与框框中的文本相绑定.

2.2 连接流程图元素

连接流程图元素阶段的语法就简单多了,直接用 -> 来连接两个元素,需要注意的是condition类型,因为他有yes和no两个分支,所以要写成

cond(yes)->io->e
cond(no)->sub(right)->op

2.3 示例

st=>start: Start i=0
e=>end
op1=>operation: i++
sub1=>subroutine: i = i * i
cond=>condition: i > 10000 ?

io=>inputoutput: savefile
st->op1->cond
cond(yes)->io->e
cond(no)->sub1(right)->op1
st=>start:  i = 0
e=>end
op1=>operation: i++
sub1=>subroutine: i = i * i
cond=>condition: i > 10000 ?
io=>inputoutput: savefile

st(right)->op1->cond
cond(no, right)->sub1(right)->op1
cond(yes)->io->e

3 序列图(js-sequence-diagrams

3.1 语法规则

语法规则

Andrew->China: Says Hello Note right of China: China thinks\nabout it China–>Andrew: How are you? Andrew-»China: I am good thanks!

3.2 示例

Andrew->China: Says Hello
Note right of China: China thinks\nabout it
China-->Andrew: How are you?
Andrew->>China: I am good thanks!
Andrew->China: Says Hello
Note right of China: China thinks\nabout it
China-->Andrew: How are you?
Andrew->>China: I am good thanks!
[继续阅读]

25 Nov 2016

Vulkan 介绍

1. 环境搭建

1.1 Windows环境

  • 下载LunarG® Vulkan™ SDK,链接地址
  • Cmake安装、Python 3安装
  • 更新驱动nvidia, 和安装VulkanRT

1.2 Android环境

  • 下载Android StudioNDK r12+
  • 编译shaderc(可选), APP_STL可以是gnustl_static, gnustl_shared, c++_static,或者c++_shared
    cd <ndk-root>/sources/third_party/shaderc/
    ../../../ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk APP_STL:=c++_shared APP_ABI=all libshaderc_combined
    
  • 下载官方demo git clone https://github.com/googlesamples/vulkan-basic-samples.git
  • 更新gslang
    cd LunarGSamples
    update_external_sources.bat -s -g
    

    这里会从github上下载最新的gslang,并用msbuild.exe编译

  • 生成Android Studio工程
    cd API-samples
    cmake -DANDROID=ON -DANDROID_ABI=[armeabi-v7a|arm64-v8a| x86|x86_64|all(default)]
    

    从github下载下来的工程直接cmake会出错,因为在utils工程的CMakeList.txt有两个变量没有赋值(ANDROID_NDK和UTILS_NAME)

    add_library(native_app_glue STATIC
      ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
    #ANDROID_NDK 没有赋值导致上面的路径无效
    target_include_directories(${UTILS_NAME} PRIVATE
                            ${ANDROID_NDK}/sources/android/native_app_glue
                            ${CMAKE_CURRENT_SOURCE_DIR}/../android/vulkan_wrapper
                            ${CMAKE_CURRENT_SOURCE_DIR}/../data
                            ${ANDROID_NDK}/sources/third_party/shaderc/include)
    #UTILS_NAME 没有赋值导致target_include_directories的语法错误
    

    修改方法,直接在LunarGSamples/API-Samples/utils/CMakeLists.txt,添加一下内容:

    set(UTILS_NAME "utils") #随意命名,不是空字符串就行
    if(WIN32)
      set(ANDROID_NDK "E:/3.1_Android_IDE/android-ndk-r13b")
    else()
      set(ANDROID_NDK "/home/billyzheng/tool/android-ndk-r13b")
    endif()
    
  • 编译Demo 用Android Studio 导入工程LunarGSamples/API-Samples/android(File > Import project) 编译APK(Build> Build APK)

2. LunarG SDK 介绍

  1. Vulkan Loader:
  2. Validation Layers 分析GPU内存、API参数、多线程API调用、纹理和渲染目标格式等等
  3. Vulkan Trace Tools:

3. 工具使用

3.1 SPIR-V

有三种方式生成SPIR-V

  • Shared tools
  • Single tool set for a single ISV
  • Simplicity

4. API

4.1 概念

4.2 接口

4.3 扩展

Khronos-approved Extensions

  • VK_KHR_android_surface
  • VK_KHR_display
  • VK_KHR_display_swapchain
  • VK_KHR_mir_surface
  • VK_KHR_surface
  • VK_KHR_swapchain
  • VK_KHR_wayland_surface
  • VK_KHR_win32_surface
  • VK_KHR_xcb_surface
  • VK_KHR_xlib_surface

    Multivendor Extensions

  • VK_EXT_debug_marker
  • VK_EXT_debug_report
  • VK_EXT_validation_flags

    AMD Vendor Extensions

  • VK_AMD_draw_indirect_count
  • VK_AMD_gcn_shader
  • VK_AMD_gpu_shader_half_float
  • VK_AMD_negative_viewport_height
  • VK_AMD_rasterization_order
  • VK_AMD_shader_ballot
  • VK_AMD_shader_explicit_vertex_parameter
  • VK_AMD_shader_trinary_minmax

    Imagination Vendor Extensions

  • VK_IMG_filter_cubic
  • VK_IMG_format_pvrtc (Registered, but not currently documented)

    NVIDIA Vendor Extensions

  • VK_NV_dedicated_allocation
  • VK_NV_external_memory
  • VK_NV_external_memory_capabilities
  • VK_NV_external_memory_win32
  • VK_NV_glsl_shader
  • VK_NV_win32_keyed_mutex

5. Samples

  1. 【Khronos】 Vulkan-Samples

6. 参考资料

  1. 【Khronos】官方首页
  2. 【Khronos】Vulkan标准
  3. 【Khronos】SPIR-V 语言
  4. Vulkan in 30 minutes
  5. A Brief Overview Of Vulkan API
[继续阅读]

25 Oct 2012

windows平台兼容IPv6的Socket编程

内容如题,只是为了贴点代码

#include <WinSock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <ws2def.h>
#include "commontype.h"
#if (_WIN32_WINNT == 0x0500)
#include <tpipv6.h>
#endif	
#pragma comment(lib, "Ws2_32.lib")

typedef struct sockaddr_storage Address;
typedef struct _socket
{
	SOCKET sock;
	int32 family;
	int8 *addr;
	int32 port;
	Address *remote;
	Address *local;
	int32 state;
}Net_Socket_Stru;
#include "logger.h"

#define PortLength 20

LPTSTR PrintError(int ErrorCode)
{
    static TCHAR Message[1024];

    // If this program was multithreaded, we'd want to use
    // FORMAT_MESSAGE_ALLOCATE_BUFFER instead of a static buffer here.
    // (And of course, free the buffer when we were done with it)

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
                  FORMAT_MESSAGE_MAX_WIDTH_MASK,
                  NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                  Message, 1024, NULL);
    return Message;
}
int Net_ModuleInit()
{
	int RetVal;
	WSADATA wsaData;
	if ((RetVal = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) {
        LogWrite("WSAStartup failed with error %d: %s\n",
                RetVal, PrintError(RetVal));
        WSACleanup();
        return Return_ERROR;
    }
	return Return_OK;
}
int32 Net_CreateSendSock(const int8* addr,const int32 port,int32 socktype,Net_Socket_Stru *netSock)
{
	SOCKET sock;
	struct addrinfo hints;
	struct addrinfo *addr_result;
	struct addrinfo *rp;
	int8 cPortBuff[PortLength];
	int32 retcode;
	netSock->sock = -1;
	memset(&cPortBuff, 0, PortLength);
	memset(&hints, 0, sizeof(struct addrinfo));
 	hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
 	hints.ai_socktype = socktype; /* Datagram socket */
 	hints.ai_flags = 0;             /* For wildcard IP address */
 	hints.ai_protocol = 0;          /* Any protocol */
 	hints.ai_canonname = NULL;
 	hints.ai_addr = NULL;
 	hints.ai_next = NULL;
	sprintf(cPortBuff,"%u",port);
	if(0!=getaddrinfo(addr,cPortBuff,&hints,&addr_result))
	{
		LogWrite("getaddrinfo error");
		return -1;
	}
	for(rp=addr_result;rp!=NULL;rp=rp->ai_next)
	{
        //ai - family , res - > ai - socktype , res- > ai - protocol
		sock = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol);
		if(sock==-1)
		{
			continue;
		}
		if(socktype==SOCK_DGRAM)
		{
			retcode = sendto(sock,addr,strlen(addr),0,rp->ai_addr,rp->ai_addrlen);
			if(retcode != SOCKET_ERROR)
				break;
		}
		if(socktype==SOCK_STREAM)
		{
			retcode = connect(sock,rp->ai_addr,rp->ai_addrlen);
			if(retcode != SOCKET_ERROR)
				break;
		}
		LogWrite("******************************************");
		LogWrite("family:%d\n",rp->ai_family);
        LogWrite("socktype:%d\n",rp->ai_socktype);
        LogWrite("protocol:%d\n",rp->ai_protocol);
		LogWrite("create socket error with errorcode:%d\n",WSAGetLastError());
		closesocket(sock);
		sock = -1;
	}
	if(sock != -1)
	{
		LogWrite("******************************************");
		LogWrite("family:%d\n",rp->ai_family);
		LogWrite("socktype:%d\n",rp->ai_socktype);
		LogWrite("protocol:%d\n",rp->ai_protocol);
		LogWrite("create socket success\n");
		netSock->family = rp->ai_family;
		netSock->addr = (int8 *) malloc(sizeof(int8)*strlen(addr));
		memcpy(netSock->addr,addr,strlen(addr));
		netSock->port = port;
		netSock->local = (Address*) malloc(sizeof(Address));
		memset(netSock->local,0,sizeof(Address));
		netSock->remote = (Address*) malloc(sizeof(Address));
		memcpy(netSock->remote,rp->ai_addr,sizeof(Address));
	}
	netSock->sock = sock;
	freeaddrinfo(addr_result);
    return 0;
}
int32 Net_CreateReciveSock(const int8* addr,const int32 port,int32 socktype,Net_Socket_Stru *netSock)
{
	SOCKET sock;
	struct addrinfo hints;
	struct addrinfo *addr_result;
	struct addrinfo *rp;
	int8 cPortBuff[PortLength];
	int32 retcode;	
	netSock->sock = -1;
	memset(&cPortBuff, 0, PortLength);
	memset(&hints, 0, sizeof(struct addrinfo));
 	hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
 	hints.ai_socktype = socktype; /* Datagram socket */
 	hints.ai_flags = 0;             /* For wildcard IP address */
 	hints.ai_protocol = 0;          /* Any protocol */
 	hints.ai_canonname = NULL;
 	hints.ai_addr = NULL;
 	hints.ai_next = NULL;
	sprintf(cPortBuff,"%u",port);
	if(0!=getaddrinfo(addr,cPortBuff,&hints,&addr_result))
	{
		LogWrite("getaddrinfo error");
		retcode = -1;
		return retcode;
	}
	for(rp=addr_result;rp!=NULL;rp=rp->ai_next)
	{
        //ai - family , res - > ai - socktype , res- > ai - protocol
        printf("******************************************");
		printf("family:%d\n",rp->ai_family);
        printf("socktype:%d\n",rp->ai_socktype);
        printf("protocol:%d\n",rp->ai_protocol);

		sock = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol);
		if(sock==-1)
		{
			continue;
		}

		retcode = bind(sock,rp->ai_addr,rp->ai_addrlen);
		if(retcode != SOCKET_ERROR)
			break;
		LogWrite("******************************************");
		LogWrite("family:%d\n",rp->ai_family);
        LogWrite("socktype:%d\n",rp->ai_socktype);
        LogWrite("protocol:%d\n",rp->ai_protocol);
		LogWrite("create socket error with errorcode:%d\n",WSAGetLastError());
		closesocket(sock);
		sock = -1;
		retcode = -1;
	}
	if(sock != -1)
	{
		LogWrite("******************************************");
		LogWrite("family:%d\n",rp->ai_family);
		LogWrite("socktype:%d\n",rp->ai_socktype);
		LogWrite("protocol:%d\n",rp->ai_protocol);
		LogWrite("create socket success\n");
		netSock->addr = (int8 *) malloc(sizeof(int8)*strlen(addr));
		memcpy(netSock->addr,addr,strlen(addr));
		netSock->port = port;
		netSock->local = (Address*) malloc(sizeof(Address));
		memcpy(netSock->local,rp->ai_addr,sizeof(Address));
		retcode = -1;
	}
	netSock->sock = sock;
	freeaddrinfo(addr_result);
    return retcode;
}
void Net_ReleaseSock(Net_Socket_Stru *netSock)
{

	closesocket(netSock->sock);
	netSock->sock = -1;
	free(netSock->local);
	netSock->local =(Address*)NULL;
	free(netSock->remote);
	netSock->remote =(Address*)NULL;
	free(netSock->addr);
	netSock->addr = (int8*)NULL;
}
int32 Net_SendUdpPacket(Net_Socket_Stru *netSock,const int8 *buffer,int32 length)
{
	int result;
	result = sendto(netSock->sock,buffer,length,0,(sockaddr*)netSock->remote,sizeof(Address));
	return result;
}
int32 Net_SendTcpPacket(Net_Socket_Stru *netSock,const int8 *buffer,int32 length)
{
	int result;
	result = send(netSock->sock,buffer,length,0);
	return result;
}
int32 Net_RecieveUdpPacket(Net_Socket_Stru *netSock,int8 *buffer,int32 length)
{
	int result;
	int addlen;
	memset(buffer,0,length);
	result = recvfrom(netSock->sock,buffer,length,0,(sockaddr*)netSock->remote,&addlen);
	return result;
}
int32 Net_RecieveTcpPacket(Net_Socket_Stru *netSock,int8 *buffer,int32 length)
{
	int result;
	memset(buffer,0,length);
	result = recv(netSock->sock,buffer,length,0);
	return result;
}

 
好像上面的代码有点问题,这里就不更正了,一起找找碴嘛

[继续阅读]

24 Oct 2012

让人费解的空指针

今天看到这样一个宏:

#define OFFSETOF(type, field) \
(((char*) &((type *)0)->field) - ((char*) (type *) 0))

乍一看,懵了,查看其使用方法:

struct thread {
	ulong_t stack_ptr;		/* saved stack pointer (this must be the first field!) */
	volatile u32_t num_ticks;       /* number of ticks thread has been running */
	void *stack;                    /* kernel stack */
	struct thread *parent;          /* parent thread */
	struct process *proc;           /* process the thread belongs to (null for kernel-only) */
	thread_state_t state;           /* state of thread in lifecycle */
	int exitcode;                   /* thread's exit code */
	int refcount;                   /* num threads that will wait for this one */
	struct thread_queue waitqueue;  /* wait queue for thread lifecycle events */
	DEFINE_LINK(thread_queue, thread);
};
OFFSETOF(struct thread, stack_ptr);

 

大致明白了type是一般是一个结构体,field是其结构体成员,这个宏的意思是:求结构体成员在结构体中的偏移。一个问题解决了,可是又来了一个问题:(type*)0不是一个空指针吗,为什么对其取field成员不会报错?

维基百科关于offset的说明,其中有一点是:the macro relied on the compiler being not especially picky about pointers; it obtained the offset of a member by specifying a hypothetical structure that begins at address zero.

大概意思是这个宏计算偏移是通过指定一个假想的从地址0开始的结构体

While this works correctly in many compilers, it has undefined behavior according to the C standard, since it involves a dereference of a null pointer (although, one might argue that no dereferencing takes place, because the whole expression is calculated at compile time).

大概意思是虽然这个宏在很多编译器里能正常运行,由于指定了空指针,按照C语言标准会有不可预期的结果出现,尽管这个表达式的值在编译时计算出来。

综上所述,我的理解是这个宏的正确性依赖于编译器,虽然它是在编译时计算。

最后,我对这里的空指针的理解,这里有空指针,它指向了内存地址为0的内存单元,虽然不可写,但是这个指针保存它的结构,也就是编译器对这个具有类型的空指针维护有它的符号表,可以通过这个指针计算出其成员地址,这个在编译时是可通过的,所以我们在&((type *)0)->field)时,没有任何问题,只是我们对这种内存单元进行读写操作,OS对其有保护,会抛出段保护错误。当然如果我们的程序运行在内核空间,那就难说了。

参考资料:
维基百科空指针:http://en.wikipedia.org/wiki/Pointer_(computer_programming)#Null_pointer
维基百科offset宏:http://en.wikipedia.org/wiki/Offsetof
[继续阅读]

20 Oct 2012

Linux内核的原子操作

目录

  1. 基本概念
  2. 原子整数操作
  3. 原子位操作


1、基本概念

原子操作可以保证指令以原子的方式执行,执行过程不被打断。它通过把读取和修改变量的行为包含在一个单步中执行,从而防止了竞争的发生,保证操作结果总是一致的。

例如:

int i=9;

线程1:

i++

i=9 OR i=8

线程2

i–;

i=9 OR i=8

两个线程并发的执行,导致结果不确定性。原子操作的作用和信号量机制是一样,都是为了防止同时访问临界资源,保证结果的一致性。大多数硬件体系结构要么本来就支持简单的原子操作,要么就为音频执行提供了锁内在总线的指令,例如x86平台上,就支持CPU锁总线操作,汇编指令前缀“LOCK”就可以将总线锁作,直到指令结束时锁打开;而有些硬件体系结构本身就不太支持原子操作,比如SPARC,但是Linux内核通过一些方法,做到了原子操作。

原子操作在Linux内核里分为原子整数操作和原子位操作,下面我们来看看这两个操作用法。

2、原子整数操作

针对整数的原子操作只能对atomic_t类型的数据进行处理,之所以没有用C语言的int类型,主要有两个原因:

1、让原子函数只接受atomic_t类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用,防止该类型数据不会传给其它非原子操作

2、使用atomic_t类型确保编译器不对相应的值进行访问优化

3、在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。

尽管Linux的整型数据都是32位的,但是使用atomic_t的代码只能将将该类型的数据当作24位来用,我们看看atomic_t的数据

|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|

|      带符号24位整形数据        |   锁   |

|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|

|10987654321098765432109876543210|

| 3         2         1          |

|            32位atmoic_t         |

|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|

我们看到atomic_t中嵌入了一个8bit的锁,因为SPARC体系结构对原子操作缺乏指令级的支持,所以只能使用利用该锁来避免对原子类型数据的并发访问,对于原子操作的使用,看下面例子:

int a=3;
atomic_t v=ATOMIC_INIT(a);
atomic_add(&v,a);
atomic_inc(&v);

在上面例子中,我们先定义并初始化了一个atomic_t变量,再对其进行了加法、自加操作。在Linux内核中提供了一系统的原子整数操作函数。

原子整数操作 描述
ATOMIC_INIT(int i) 在声明一个atmoic_t变量时,将它初始化为i
ATOMIC_INIT(int i) 在声明一个atmoic_t变量时,将它初始化为i
int atmoic_read(atmoic_t *v) 原子地读取整数变量v
void atmoic_set(atmoic_t *v,int i) 原子地设置v值为i
void atmoic_add(atmoic_t *v,int i) 原子地从v值加i
void atmoic_sub(atmoic_t *v,int i) 原子地从v值减i
void atmoic_inc(atmoic_t *v) 原子地从v值加1
void atmoic_dec(atmoic_t *v) 原子地从v值减1
int atmoic_sub_and_test(int i,atmoic_t *v) 原子地从v值减i,如果结果等于0返回真,否则返回假
int atmoic_add_negative(int i,atmoic_t *v) 原子地从v值减i,如果结果是负数返回真,否则返回假
int atmoic_dec_and_test(atmoic_t *v) 原子地给v减1,如果结果等于0返回真,否则返回假
int atmoic_inc_and_test(atmoic_t *v) 原子地给v加1,如果结果等于0返回真,否则返回假

原子操作最常见的用途就是实现计数器,使用复杂的锁机制来保护一个单纯的计数是很笨拙的,原子操作比起复杂的同步方法来说,给系统带来的开销小,对高速缓存行的影响也小。

3、原子位操作

除了原子整数操作外,内核还提供了一组针对位这一级数据进行操作的函数,位操作函数是对普通的内在地址进行操作的,它的参数是一个指针和一个位号。由于是对普通的指针进程操作,所以没有像atomic_t这样的类型约束。

unsigned long word = 0;
set_bit(0,&word);
clear_bit(0,&word);
change_bit(0,&word);//翻转第0位的值
原子整数操作 描述
void set_bit(int nr,void *addr) 原子地设置addr所指对象的第nr位
void clear_bit(int nr,void *addr) 原子地清空addr所指对象的第nr位
void change_bit(int nr,void *addr) 原子地翻转addr所指对象的第nr位
int test_and_set_bit(int nr,void *addr) 原子地设置addr所指对象的第nr位,并返回原先的值
int test_and_clear_bit(int nr,void *addr) 原子地清空addr所指对象的第nr位,并返回原先的值
int test_and_change_bit(int nr,void *addr) 原子地翻转addr所指对象的第nr位,并返回原先的值
int test_bit(int nr,void *addr) 原子地返回addr所指对象的第nr位
[继续阅读]

11 Sep 2012

JS加载本地库

太让人震惊了,JS可以加载本地库文件。
在Firefox下面

Components.utils.import("resource://gre/modules/ctypes.jsm");
 
var lib = ctypes.open("C:\\WINDOWS\\system32\\user32.dll");
 
/* Declare the signature of the function we are going to call */
var msgBox = lib.declare("MessageBoxW",
                         ctypes.winapi_abi,
                         ctypes.int32_t,
                         ctypes.int32_t,
                         ctypes.jschar.ptr,
                         ctypes.jschar.ptr,
                         ctypes.int32_t);
var MB_OK = 0;
 
var ret = msgBox(0, "Hello world", "title", MB_OK);
 
lib.close();

 

详细见:JS-ctypes

[继续阅读]

03 Sep 2012

内核实现理论篇之软盘存储

磁盘按层次分为磁面、磁道、扇区。扇区是磁盘的最小存储单位,一个扇区有512B大小,BIOS有的13H中断02号功能提供读取磁盘功能,03号功能提供写磁盘功能。

表4.1 13H中断02号功能 |中断号 |功能号(AH) |入口参数 |出口参数 |作用 | |—– |—– |—– |—– |—– | | 13H | 02H | AL=扇区数,DH=磁头号,CH=柱面号,CL=扇区号,DL=驱动器号,ES:BX=缓冲区地址|CF=0操作成功,AH=00H,AL=传输的扇区数;CF=1操作失败,AH=状态代码 | 读取指定的扇区到缓冲区 | | 13H | 03H |AL=扇区数,DH=磁头号,CH=柱面号,CL=扇区号,DL=驱动器号,ES:BX=缓冲区地址|CF=0操作成功,AH=00H,AL=传输的扇区数;CF=1操作失败,AH=状态代码|把缓冲区数据写入到指定扇区|

对于,对于1.44MB的软盘来说,一共有两个磁面,每个磁面有80个磁道,每个磁道有18个扇区。即28018*512=1.44MB。其磁头、柱面、扇区号的计算方法,如图4-1所示。

扇区号计算方法

对于线性的扇区号F,分解为柱面号-磁头号-扇区号。之前所以柱面号在高位,是因为,磁盘在柱面是交叉存储的,也就是一个磁头的一个磁道写满之后,又写下一个磁头的磁道。读取扇区函数read_sector代码:

;************************************************
;功    能:	read扇区
;入口参数:	保存在堆栈中,依次为
;		 	1.扇区号
;		 	2.扇区数
;		 	3.缓冲区地址
;出口参数:	无	
;************************************************
read_sector:
	push	bp
	mov 	bp,sp
	pusha
	mov 	[num_retries],byte 0	
.loop:		
;扇区号(AX)	
	mov 	ax,	[bp+Seg_Outer_Offset]
;扇区(CL):sector=L%18	
	div		Sec_PerCylinder
	mov 	cl,al
;柱面(CH):cylinder=L/18/2
	mov 	al,ah
	xor 	ah,ah
	div 	2
	mov 	ch,ah
;柱头(DH): head=L/18%2
	mov 	dh,al
;读取扇区数(AL)
	xor 	ax,ax
	mov 	ax,[bp+Seg_Outer_Offset+2]	
;功能号(AH):02H
	xor 	ah,ah
	mov 	ah,02h
;驱动器(DL):00h
	mov 	dl,0
;缓冲区(BX)
	mov 	bx,[bp+Seg_Outer_Offset+4]
	int 	13h

	jnc 	.done
	inc		byte [num_retries]
	cmp		byte [num_retries],3;最多只重复读取3次
	jne		.loop
.done:
	popa
	pop bp
	ret
[继续阅读]

04 Aug 2012

ELF文件格式

ELF,Excuteable and Linkable Format,文件的结构图如下所示:

 

ELF文件概览

ELF文件概览

文件由4部分组成:ELF头,Program Headers, Sections 和 Section Headers。只有ELF Header位置是固定的,其余部分位置、大小等信息由ELF头中的各项值来决定。ELF Header的格式如下所示

#define	EI_NIDENT	16
typedef	struct{
	unsigned char 	e_ident[EI_NIDENT];
	Elf32_Harlf		e_type;
	Elf32_Harlf		e_machine;
	Elf32_Word		e_version;
	Elf32_Addr 		e_entry;
	Elf32_Off		e_phofff;
	Elf32_Off		e_shoff;
	Elf32_Word		e_flags;
	Elf32_Harlf		e_ehsize;
	Elf32_Harlf		e_phentsize;
	Elf32_Harlf		e_phnum
	Elf32_Harlf		e_ehentsize;
	Elf32_Harlf		e_ehnum;
	Elf32_Harlf		e_shstrndx;
}Elf32_Ehdr;
[继续阅读]

30 Jul 2012

secureCRT 文件上传

  • 下载
    wget http://freeware.sgi.com/source/rzsz/rzsz-3.48.tar.gz
  • 解压
    tar -zxvf rzsz-3.48.tar.gz
  • 安装
    make posix
    cp rz sz /usr/bin

然后在secureCRT中,使用rz,sz就可以上传文件了。
以上是要在Linux有权限上网的情况下,如果Linux不能上网,那么就得用sftp把文件上传Linux。
secureCRT自带sftp,使用以下命令上传
sftp>put [local_file] [remote_diretory]
与之对应是用sftp下载
sftp>get [remote_file] [local_diretory]
参考:

  1. Linux系统手动安装rzsz 软件包:http://www.51cto.com/art/200712/62867.htm
  2. sftp使用:http://wenku.baidu.com/view/42054b878762caaedd33d48a.html

前一篇secureCRT的使用《远程管理Linux之命令界面

[继续阅读]

09 Apr 2012

内核实现理论篇之进程基本概念

进程是计算机中已运行程序的实体,是操作系统进行资源分配和调度的一个独立单位。多任务操作系统中存在多个进程,通过时间共享(在微观上一个或若干个时间片内处理器由一个进程占有,时间片用完则进行调度),以实现在一个处理器上多个程序宏观并发执行。

目录:

  1. 进程的提出
  2. 基本概念
  3. 进程状态
  4. 进程原语
  5. 进程实体

1、进程的提出
并发、共享、虚拟和异步是操作系统的四个基本特征,计算机上的并发性是指在同一个时间里运行多个程序。在单核计算机里,只有一个处理器,操作系统把处理器的使用按时间片分配给各个运行中的程序,当时间片小到一程度,宏观上看来就是在同一时间内运行多个程序,以达到并发。显然这里的并发是宏观上的并发,微观上是分时的。

2、基本概念
那么“运行中的程序”就是进程,所以进程概念的提出是实现了在单核计算机里并发运行多个程序。进程是在系统中能独立运行并作为资源资源分配的基本单位。进程是运行中的程序,一个活动的实体,它包含程序的指令、数据、堆栈以及一些寄存器的值。而与之对应一个概念“程序”,我们可以理解为静态的二进制(或其它中间语言)的代码和数据。当程序被调入内存后,即程序运行后,就成了进程,被操作系统所调度、管理。

进程与程序关系

图1 进程与程序关系

3、进程的状态
处理器在一个时间片里只能有一个进程在运行,其它的进程都处于等调度状态中。那么进程就有状态,最简单的二状态模型,如图2所示。

二状态模型

图2 二状态模型

在这个模型中,一个进程只有两种状态,一种是运行状态(即当前运行),一种是非运行状态(即待调度)。运行状态的进程,单独占有处理器,执行其代码指令。运行状态的进程释放处理器,切换为非运行状态有三种情景:1.主动释放,如请求的资源无法得到分配;2.时间片耗完;3.被中断,如优先级高的进程抢占。非运行状态的进程等待当前运行状态的进程释放处理器,然后调度程序根据调度算法从进程队列中选择一个进程,占有处理器并切换状态。

对于待调度状态的进程,操作系统得有一个数据结构将其组织进行调度。所有待调度的进程组成了一队列,进程队列,如图3所示。

二模型进程队列

图3 二状态进程队列

为了使操作系统具有更好的性能,在二状态模型基础上,一种更为稍复杂的五状态模型被提了出来,如图4所示。

五状态模型

图4 五状态模型

此模型在二状态模型上多加了三种状态:创建、退出、阻塞。

  • 创建状态:进程刚被创建还没有被调入进程池中
  • 就绪状态:等待合适时机,被调度运行的进程
  • 运行状态:正在运行的进程
  • 阻塞状态:等待某事件发生的进程,如键盘输入进程一般处于阻塞,等待键盘将其激活
  • 退出状态:从进程池中调出的进程

有此操作系统还有挂起状态,此处就不作论述。

4、进程原语

  • 创建进程原语
  • 销毁进程原语
  • 阻塞进程原语
  • 唤醒进程原语

5、进程实体
进程在OS中,包含代码段、数据段、堆栈段及进程控制块(PCB)。

  • 代码段:要执行的代码
  • 数据段:进程执行中要用到的、可修改的数据。包括程序的数据、用户栈和可修改的数据
  • 系统段:每个进程都 有一个或多个系统栈,用于保存参数、过程调用地址和系统调用地址。注:每个特权都与之对应着一个系统栈,详细的会在保护模式里会提到。
  • 进程控制块(PCB):可能包括以下信息
    • 进程标识
      • 内部标识 即进程ID,方便系统使用
      • 父进程标识 父进程ID
      • 用户标识 由创建者提供,用户访问时使用
    • 处理器状态
      • 用户可见寄存器 处于用户模式下的处理器执行机器指令可以访问的寄存器,即通用寄存器(EAX,EBX……)
      • 控制和状态寄存器 程序计数器(PC),条件码,状态信息(EFLAGS)
      • 栈指针
    • 进程控制信息
      • 调度和状态信息 如进程状态、优先级、调度信息(依赖于调度算法)、事件(阻塞时等待发生的事件标识)
      • 数据结构 进程可以以队列、环、或某些别的结构形式与其他进程进行链接,如进程处在就绪队列中;子进程与父进程的关系
      • 进程间通信 与两个独立进程间的通信相关的有各种标记、信号和消息。进程控制块中维护着某些或全部此类信息
      • 进程特权 进程根据其可以访问的内存空间以及可以执行的指令类型被 赋予各种特权
      • 存储管理 描述分配给进程的虚拟内存的段表和页表的指针
      • 资源的所有权和使用情况 进程占有的资源以及使用情况,如文件、处理器、IO或其它

进程在系统中以进程实体(Process Image)出现,其中进程控制块是进程管理和进程调度的主要部分,进程控制块又描述了进程占有的资源:内存、文件、设备。操作系统中进程实体和资源关系如图5所示。

进程与资源

图5 进程与资源

[继续阅读]

08 Apr 2012

Javascript画一个函数图像

这里写了一段代码,本想实现用javascript画了一个函数图像。直接上代码:
************华丽的分隔线************

<html>
<head>
<title>8023</title>
</head>
<body>
</body>
<script type="text/javascript">
var Each=function(data,func){
	for(var i=0;i<data.length;i++)
		func(data,i);
};
//画个函数图像
function drawSomething(width,height){
	var backColor={'R':255,'G':255,'B':255};
	var frontColor={'R':255,'G':0,'B':0};
	var canvas=initialCanvas(width,height);	
		//修正width,height
		width=canvas.width;
	height=canvas.height;
	var g=canvas.getContext("2d");
	var imgd;
	if (g.createImageData) {
		imgd = g.createImageData(width, height);
	} else if (g.getImageData) {
		imgd = g.getImageData(0, 0, width, height);
	} else {
		imgd = {'width' : width, 'height' : height, 'data' : new Array(width*height*4)};
	}
	imgd = g.getImageData(0, 0, width, height);
	
	var center={'x':width/2,'y':height/2}
	//修改每一个像素,性能很差,将就实现一下啦
	Each(imgd.data,function(data,index){
		var position=GetPostion(index,width,height,center);
		if(isInSide(position,width,height)){
			setPix(data,index,frontColor);
		}
		else{
			setPix(data,index,backColor);
		}
	});
	g.putImageData(imgd,0,0);
}
//设置像素颜色
function setPix(pix,i,color,alpha)
{
	var n=i*4;
	pix[n  ]=color.R;
	pix[n+1]=color.G;
	pix[n+2]=color.B;
	if(alpha!=null)
		pix[n+3]=alpha;
	else pix[n+3]=255;
}
//初始化Canvas
function initialCanvas(width,height){
	var elem=document.createElement("canvas");
	elem.width=width;
        elem.height=height;
	elem.style.width=width+"px";
	elem.style.height=height+"px";
	document.body.insertBefore(elem,document.body.childNodes[0]);
	return elem;
}
//判断是否在图形内,这是一个函数图像
function isInSide(position,width,height){
	var x=position.x;
	var y=position.y;
	x=x*20.0/width/1.0;
	y=y*20.0/height/1.0;
	if(17*x*x-16*Math.abs(x)*y+7*y*y-225<0)
		return 1;
	return 0;
}
//将索引转换为坐标
//center为坐标原点位置,这里只考虑原点在canvas内部
//因为Y轴方向变了,转换不严密
function GetPostion(index,width,height,center){
	var y = (height - index / width)- center.y;
	var x = index % width- center.x;
	return {'x':x,'y':y};
}
window.onload=function(){
	var width=64;
	var height=48;
	drawSomething(width,height);
};
</script>
</html>

************华丽的分隔线************

哈哈,猜猜你能看到什么?点击查看
************华丽的分隔线************
这里要的做是用函数17x^2-16|x|y+17y^2-225=0来画一个心形
怎么用JS画一个函数呢?我看了一下HTML5的Canvas可以直接操作画布是的像素,一个很笨拙的方法产生,直接操作从像素从着手,一个像素一个像素的画。
嗯,纯属心血来潮,哈哈。
参考:

1、函数来自matrix67博客: http://www.matrix67.com/blog/archives/2247
2、HTML5 Canvas教程很详细的:http://blog.bingo929.com/html-5-canvas-the-basics-html5.html

************华丽的分隔线************
PS:还要优化一下,坐标转换出了点问题,图像位置不对,画布设置大了一点就有问题

[继续阅读]

29 Mar 2012

UML学习笔记

UML不是一种方法学,而是一种描述语言。作为面一种描述语言,在OOA、OOD、OOP中,有着非常重要的作用。在项目初期的需求分析、功能分析中,在设计阶段的模块设计、类设计及对象状态,到了编程的时候可以很快的从UML转换为源代码。

对小项目,UML的优势不明显,无须过多的需求分析,所有的一想就出来了。但是面对复杂的业务时,这种描述语言的存在(此处仅针对UML),使得思路清晰。

UML图分为三类:

  1. 静态图:包括用例图、对象图、类图、组件图、部署图
  2. 动态图:协作图、序列图、活动图、状态图
  3. 物理图:文件、数据库等

 

1、用例图

帮助开发团队以一种可视化的方式来理解系统的功能需求。

什么是用例?用例是指系统的功能,是系统某个功能的执行动作的集合。从用户的观点告诉系统要做的一些特定的事情。

符号:椭圆(用例)、人形符号(用户或角色)

2、类图和对象图

类间关系:

  • 继承
  • 关联
  • 依赖
  • 聚合
  • 组合

3、序列图

序列图用来显示具体用例图的详细流程,显示流程中不同对象的交互关系,以及相互的各种调用。

序列图有两个维度:

  • 水平:对象实例之间的交互
  • 垂直:以发生时间顺序显示消息/调用的序列

对象之间的交互:

  • 调用(call)
  • 返回(return)
  • 发送(send)
  • 创建(create)
  • 销毁(destroy)

4、状态图

状态图表示某个类所处的不同状态和该类的状态转换信息。

符号:

  • 初始起点 ========»实心圆
  • 状态之间的转换====»带箭头的线段
  • 状态 ===========»圆角矩形
  • 判断点==========»空心圆
  • 终止点==========»内部包含实心圆的圆

PS:每个类都有状态,但不一定有状态图

5、活动图

活动图是状态图的一个变体,用来描述执行算法的工作流程中涉及的活动,描述一组顺序或并发的活动。活动状态代表一个活动: 一个工作流步骤或一个操作的执行。

泳道(swimlane)表示实际执行活动的对象。

6、组件图

组件图担任物理视图,它的用途是显示系统中的软件与其他软件的依赖关系。

7、部署图

部署图表示该软件系统如何部署到硬件环境中。

 

PS:呃,本文纯属笔记。看到最后其实发现什么都没有讲清楚,所以还是好好看书。但是不得不强调一点题外话:UML只是面向对象技术中的描述语言,真正重要的还是要对“面向对象”理解。

书目:

《面向对象设计与分析》——纯粹对面向对象讲解的书

《UML参考书》——UML图详解

《设计模式》——你懂的

[继续阅读]

27 Mar 2012

微内核的实现

毕业设计是微内核的分析与实现。
其实说到分析还没有那个水平,为了题目好看才加上去的。

先前一直在看书,也没有更新博客。目前基础知识也算告一段落了,不甚仔细的看了一遍。有些地方不是很明白,再看看别人的代码,再理解反思。准备写一系列博文:先是基础篇,梳理理论知识;然后是实战篇,纪录coding过程。
基础知识主要有以下几个部分:

  1. 操作系统原理 (《计算机操作系统》汤子赢)
    • 进程概念(Process Description)
    • 进程同步(Concurrency)
    • 进程通信(IPC)
    • 进程调度(Process Scheduling)
    • 内存分配(Memory Allocation)
    • 内存管理(Memory Management)
    • 虚拟内存(Virtual Memory)
  2. 保护模式  (《80X86汇编语言程序设计教程》杨季文)
    • 386的寄存器
    • 描述符分类
    • 操作系统类指令
    • 保护之分段管理机制
    • 保护之特权级变换
    • 输入输出保护
  3. 系统引导及初始化
    • 系统引导  以前有一篇博文,很简单 《简易引导扇区》
    • 初始化  实模式和保护模式之间的切换

其他参考资料:

  1. 《自己动手写操作系统》 于渊
  2. 《操作系统精髓与设计原理》 william stallings
  3. 《IA-32 intel架构软件开发人员手册》

 

PS:本着毕业设计,并限于个人能力,以上内容仅供参考。此不涉及内容:文件系统,图形界面,设备驱动;基于386、单核心(single processor)、多任务(multi task),输入输出仅限于键盘、显示器。

[继续阅读]

02 Dec 2011

Javascript打造右键菜单

JAVASCRIPT代码

var $G = function (id) {
    if (typeof id === "string")
        return document.getElementById(id);
    else
        return id;
};
var extend = function (a, b) {
    if (typeof b !== "object")
        return a;
    for (var key in b) {
        a[key] = b[key];
    }
    return a;
};
_obj = $G("div_RightMenu");
var config = {
    obj: _obj,
    showMenu: function (menu, top, left) {
        menu.style.top = top + "px";
        menu.style.left = left + "px";
        menu.style.display = "block";
    },
    hiddenMenu: function (menu) {
        menu.style.display = "none";
    }
};

var getOffset = function (menu, x, y) {
    var result = {};
    menu._width = menu.offsetWidth;
    menu._height = menu.offsetHeight;
    if (x + menu._width &gt; window.innerWidth)
        x = window.innerWidth - menu._width;
    if (y + menu._height &gt; window.innerHeight)
        y = window.innerHeight - menu._height;
    result.left = document.body.scrollLeft + x;
    result.top = document.body.scrollTop + y;
    return result;
};

function LoadMenu(con) {
    config = extend(config, con);
    var menu = config.obj;
    document.oncontextmenu = function (event) {
        event = event ¦¦ window.event;
        var position = getOffset(menu, event.clientX, event.clientY);
        config.showMenu(menu, position.top, position.left);
        event.preventDefault();
    };
    document.onclick = function () {
        config.hiddenMenu(menu);
    };
}

HTML代码

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Index</title>
    <style>
        *{margin:0;padding:0;}
        .div_RightMenu
        {
            z-index: 30000;
            text-align: left;
            cursor: default;
            position: absolute;
            background-color: #FAFFF8;
            width: 160px;
            height: auto;
            border: 1px solid #333333;
            display: none;
            filter: progid:DXImageTransform.Microsoft.Shadow(Color=#333333,Direction=
120,strength=5);
        }

        .divMenuItem
        {
            height: 17px;
            color: Black;
            font-family: 宋体;
            vertical-align: middle;
            font-size: 10pt;
            margin-bottom: 3px;
            cursor: hand;
            padding-left: 30px;
            padding-top: 2px;
        }
        .mask
        {
            z-index:10;
            width:;
            height:99999px;
            position:absolute;
            background-color:Black;
            filter:alpha(opacity=0.7);
            opacity:0.5;
        }
    </style>
</head>
<body style="position: relative">
    <div id="div_RightMenu" class="div_RightMenu">
            <div class="divMenuItem">
                我的首页</div>
            <div class="divMenuItem">
                删除记录</div>
            <div class="divMenuItem">
                详细信息</div>
            <div class="divMenuItem">
                刷新</div>
            <hr>
            <div class="divMenuItem">
                加入收藏夹</div>
            <div class="divMenuItem">
                复制</div>
            <div class="divMenuItem">
                全选</div>
            <hr>
            <div class="divMenuItem">
                联系作者</div>
            <div class="divMenuItem">
                关于此功能</div>
            <div class="divMenuItem" style="margin-bottom: 0px;">
                属性</div>
        </div>
    <div id="mask" class="mask">
        <div style=" background-color:Blue;"></div>
    </div>
    <div>
        心上十八添一目,
        单身贵族尔相思.
        春来人去无日月,
        一人相伴到白头.
        高兴只有一对脚,
        东西南北路遥遥.
        有人为伍吾口多,
        两人共枕非夫妻.
              猜8个字
    </div>
</body>
<script src="../../Scripts/showMenu.js"></script>
    <script>
        LoadMenu({});
    </script>
</html>

[继续阅读]

02 Dec 2011

HTML5教程

HTML5正在逐步完善,Adobe放弃了手机上的Flash,更是坚定了HTML5在移动终端的地位。

有很多同学想学HTML5,这里推荐一个PPT。

原版是英文的,地址http://slides.html5rocks.com/

directguo翻译了一下:http://directguo.com/html5/#slide1

我也弄了一个中文镜像:http://html5.cnsystem.org

英文镜像:http://html5.cnsystem.org/en

建议访问英文镜像,中文镜像有些问题,图片显示不出来啊这些。

Good luck!

 

[继续阅读]

25 Nov 2011

EMACS简单操作

文件操作
创建 C-x C-f
打开 C-x C-f
重命名 M-x rename-buffer
删除
保存 C-x C-s
全部保存 C-x s

移动光标
分为行,列,页,词,句移动
上一行/下一行 C-p C-n
上一列/下一列 C-b C-f
上一页/下一页 M-v C-v
上一词/下一词 M-b M-f
句 首/句 尾 M-a M-e
行 首/行 尾 C-a C-e

文本编辑
删除
删除光标前的一个字符
C-d 删除光标后的一个字符

M-移除光标前的一个词
M-d 移除光标后的一个词

C-k 移除从光标到“行尾”间的字符
M-k 移除从光标到“句尾”间的字符

插入
1、直接输入
2、C-u 字符 (当字符为数字和空格,回车,注意)
3、插入空行
撤销 C-x u
查找
C-s 是向前搜索,C-r 是向后搜索
替换
M-x replace-string
窗格
添加垂直 C-x 3
添加水平 C-x 2
关闭其它 C-x 1

[继续阅读]

20 Nov 2011

什么是TTL(Time To Live)

在前面《PING解决上网问题》里说到了,用Ping命令解决无法上网问题,在回显的内容中有一项是TTL=53

那什么是TTL呢?Time To Live,存活时间。就是说一个IP报文在网络中传递的时间。为什么会有这个概念呢?有些情况下,目的地不可达的时候,例如某网站主机死掉了,用户发送的报文找不到目的地,但是总不能一直在网络里拼命的找吧,这样网络一定给挤爆。所以用TTL来将过期的报文给抛弃掉。但是TTL并不是时间,而与IP报文传输经过路由器的个数有关,IP报文有个初始的TTL值(下面会提到),然后没经过一个路由器减一,当TTL小于0的时候,该报文就被抛弃。

那么TTL有什么用呢?

1、查看访问速度的一个指标

在选空间的时候,此方法可以做为空间衡量标准之一。经过的路由器越多,访问速度就越慢。那么PING一下,从TTL就可以看到这个空间访问速度如何,至少从路由耗时没有那么多。

2、分析目标机操作系统

UNIX 及类 UNIX 操作系统 ICMP 回显应答的 TTL 字段值为 255

Compaq Tru64 5.0 ICMP 回显应答的 TTL 字段值为 64

WINXP-32bit 回显应答的 TTL 字段值为 64

微软 Windows NT/2K/2003操作系统 ICMP 回显应答的 TTL 字段值为 128

微软Windows 2008操作系统 ICMP 回显应答的 TTL 字段值为64

微软 Windows 95 操作系统 ICMP 回显应答的 TTL 字段值为 32(这个没人用了吧)

上面一些系统初始的TTL值,比如在命令行回显的TTL为124,那么基本可以确定为window2003了,NT、2K现在基本人用了。当然这个方法,得出来的结果不一定准确,假如命令行回显的TTL为53,那么是64-11=53还是128-75=63?

当然一般不会经过75个路由器的。

[继续阅读]

20 Nov 2011

HTML5之WebSock

今年暑假实习的时候,导师叫我看看HTML5。感觉HTML5真的很棒,HTML4.0时代很麻烦的事情,通通在浏览器解决了。

/************/

不想写了,明天再写。

主要提醒自己,明天看看node.js,不知道什么时候出了个node.exe,还能集成到IIS里,明天看看

WebSock真是不错的东西

API:http://nodejs.org/docs/v0.5.5/api/http.htm

 

Soctt博客:《Installing and Running node.js applications within IIS on Windows – Are you mad?》如在IIS里运行

JeffZhao文章:《基于Node.js、Express和Jscex开发的ToDo网站示例

[继续阅读]

19 Nov 2011

window注册表TCP/IP相关设置

1)设置生存时间
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

DefaultTTL REG_DWORD 0-0xff(0-255 十进制,默认值128)

说明:指定传出IP数据包中设置的默认生存时间(TTL)值.TTL决定了IP数据包在到达
目标前在网络中生存的最大时间.它实际上限定了IP数据包在丢弃前允许通过的路由
器数量.有时利用此数值来探测远程主机操作系统.

2)防止ICMP重定向报文的攻击
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

EnableICMPRedirects REG_DWORD 0x0(默认值为0x1)

说明:该参数控制Windows 2000是否会改变其路由表以响应网络设备(如路由器)发送给它
的ICMP重定向消息,有时会被利用来干坏事.Win2000中默认值为1,表示响应ICMP重定向报
文.

3)禁止响应ICMP路由通告报文
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Inter
faces\interface

PerformRouterDiscovery REG_DWORD 0x0(默认值为0x2)

说明:“ICMP路由公告”功能可造成他人计算机的网络连接异常,数据被窃听,计算机被
用于流量攻击等严重后果.此问题曾导致校园网某些局域网大面积,长时间的网络异常.
因此建议关闭响应ICMP路由通告报文.Win2000中默认值为2,表示当DHCP发送路由器发
现选项时启用.

4)防止SYN洪水攻击
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

SynAttackProtect REG_DWORD 0x2(默认值为0x0)

说明:SYN攻击保护包括减少SYN-ACK重新传输次数,以减少分配资源所保留的时
间.路由缓存项资源分配延迟,直到建立连接为止.如果synattackprotect=2,
则AFD的连接指示一直延迟到三路握手完成为止.注意,仅在TcpMaxHalfOpen和
TcpMaxHalfOpenRetried设置超出范围时,保护机制才会采取措施.

5) 禁止C$、D$一类的缺省共享
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters

AutoShareServer、REG_DWORD、0x0

6) 禁止ADMIN$缺省共享
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters

AutoShareWks、REG_DWORD、0x0

7) 限制IPC$缺省共享
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

restrictanonymous REG_DWORD 0x0 缺省
0x1 匿名用户无法列举本机用户列表
0x2 匿名用户无法连接本机IPC$共享
说明:不建议使用2,否则可能会造成你的一些服务无法启动,如SQL Server

8)不支持IGMP协议
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

IGMPLevel REG_DWORD 0x0(默认值为0x2)

说明:记得Win9x下有个bug,就是用可以用IGMP使别人蓝屏,修改注册表可以修正这个
bug.Win2000虽然没这个bug了,但IGMP并不是必要的,因此照样可以去掉.改成0后用
route print将看不到那个讨厌的224.0.0.0项了.

9)设置arp缓存老化时间设置
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services:\Tcpip\Parameters

ArpCacheLife REG_DWORD 0-0xFFFFFFFF(秒数,默认值为120秒)
ArpCacheMinReferencedLife REG_DWORD 0-0xFFFFFFFF(秒数,默认值为600)

说明:如果ArpCacheLife大于或等于ArpCacheMinReferencedLife,则引用或未引用的ARP
缓存项在ArpCacheLife秒后到期.如果ArpCacheLife小于ArpCacheMinReferencedLife,
未引用项在ArpCacheLife秒后到期,而引用项在ArpCacheMinReferencedLife秒后到期.
每次将出站数据包发送到项的IP地址时,就会引用ARP缓存中的项。

10)禁止死网关监测技术
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services:\Tcpip\Parameters

EnableDeadGWDetect REG_DWORD 0x0(默认值为ox1)

说明:如果你设置了多个网关,那么你的机器在处理多个连接有困难时,就会自动改用备份
网关.有时候这并不是一项好主意,建议禁止死网关监测.

11)不支持路由功能
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services:\Tcpip\Parameters

IPEnableRouter REG_DWORD 0x0(默认值为0x0)

说明:把值设置为0x1可以使Win2000具备路由功能,由此带来不必要的问题.

12)做NAT时放大转换的对外端口最大值
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services:\Tcpip\Parameters

MaxUserPort REG_DWORD 5000-65534(十进制)(默认值0x1388–十进制为5000)

说明:当应用程序从系统请求可用的用户端口数时,该参数控制所使用的最大端口数.正常
情况下,短期端口的分配数量为1024-5000.将该参数设置到有效范围以外时,就会使用最
接近的有效数值(5000或65534).使用NAT时建议把值放大点.

13)修改MAC地址
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\

找到右窗口的说明为网卡的目录,
比如说是{4D36E972-E325-11CE-BFC1-08002BE10318}

展开之,在其下的0000,0001,0002…的分支中找到DriverDesc的键值为你网卡的说明,
比如说DriverDesc的值为Intel(R) 82559 Fast Ethernet LAN on Motherboard
然后在右窗口新建一字符串值,名字为Networkaddress,内容为你想要的MAC值,比如说
是004040404040
然后重起计算机,ipconfig /all看看.

[继续阅读]

19 Nov 2011

PING解决上网问题

先看一张图片:

ping

ping mail.qq.com

我们常用Ping命令来看某计算机是否可达,特别是当我无法上网的时候。

举例:某办公室电脑不能上网了

1、首先PING局域网内的电脑,如果PING不同,说明局域网组网有问题,检查一下网线是否插好了,是否存在网络环路。

2、如果局域网的电脑能PING通了,如果有网关,在PING一下网关,以防交换机死机(温度过高,或者拥塞),因为有些是整栋的局域网,办公室是子网,网关是连接外网出口。

3、如果用代理服务器上网的,在PING一下代理是否可达。

4、最后是PING你要访问的网站,看是否会显示IP地址,查看是否DNS解析错误,或者无法解析。

大部分用户都是选择自动获取的,有些时候无法获取DNS服务器地址,所以无法解析。这时我们可以设置DNS,每个省都有DNS服务器,可以百度一下,或者设置为8.8.8.8 。不过建议还是自动获取,曾经我们的网站就是因为所设置的DNS出问题了导致无法解析。

关于DNS,每台电脑本机都有DNS缓存记录(试想每次都查询DNS服务器,上网该多慢啊),我们用

ipconfig /displaydns

可以查看本机的DNS缓存;用

ipconfig /flushdns可以刷新DNS缓存

总之PING命令是用来检测目标机是否可达,帮我们找到问题的关键所在。

然后补充一点IP地址的常识:*.*.*.* ,符号*代表一个小于255的整数,加上子网掩码就可以从逻辑上划分子网。通常子网掩码是255.255.255.0,也就是说前三个*的一样,逻辑上就在局域网里了。当然,还得物理上在一个局域网里,通常连接到同一个路由器和交换机的电脑,物理上都在一个局域网里。通常我们玩游戏的时候,局域网里没有玩家,有可能是IP不在一个网段,还有可能是防火墙问题。

[继续阅读]

18 Nov 2011

JavaScript 基本语法

今天下了一本《JavaScript高级程序设计》来看,终于明白以前一些不明白的问题。

在说这那些不明白的问题之前,不得不说一下JavaScript与EMCAScript。

ECMAScript是由ECMA-262标准化的脚本语言的名称。JavaScript和JScript与ECMAScript相容,但包含超出ECMAScript的功能。JavaScript是ECMA-262标准的实现和扩展。正是因为JavaScript符合ECMA-262标准,才让它看起来有点晦涩。ECMA-262标准和C、C++差别,我们用C、C++的编程习惯去看待JavaScript才会看得云里雾里。

1.变量

JavaScript分原始值和引用值。原始类型Undefined,Null,Number,String,Boolean。Undefined类型只有一个值undefined,变量没有初始化即为undefined。

JavaScript中变量是弱类型。

变量在初始化时可以不用声明,为全局变量。如oTest=”asdfasdf”,这里没有用var声明oTest,但oTest是一个全局变量。

2.Function

JavaScript中函数没有重载。

argument对象。在函数代码中,使用特殊对象argument,开发者可以无需指出参数名,也可访问它们。argument[0]访问第一个参数,argument[1]访问第二个。。。。如

function alertArg() {
  var n=argument.length;
  for(var i=0;i<n;i++) 
    alert(argument[i]);
}

alertArg(‘hello’,’zhengbo’)//—-将弹出’hello’消息框,然后再弹出’zhengbo’消息框。

Function类。在JavaScript里,所有的东西都可以看成对象,函数也不例外。

var function_name=new Function(argument1,argument2,……argument1N,functionbody);

每个argument都是一个参数,最后一个参数是函数主体。所以下面两种函数声明是等效的。

function sayHi(name){ alert(“hello,”+name); }

var sayHi=new Function( name, ” alert( \” hello, \” +name); “) ;

这里函数sayHi是一个对象,也是一个引用类型变量,而sayHi引用函数的地址。既然sayHi是一个地址,自然可以做为参数。如

function callAnthorFn(fnArg,sName) { fnArg(sName); }

callAnthorFn(sayHi,”zhengbo”);//–输出hello,zhengbo

闭包。所谓闭包,是指词法表示包括不必计算的变量的函数,也就是说,该函数能使用函数外定义的变量。

[继续阅读]

18 Nov 2011

翻墙初体验

一直以来对墙外的世界都没有什么兴趣,觉得墙外没有什么吸引力

最近在捣鼓免费空间,给自己一个地方写博客,发现很多地方要国外的IP

于是,想翻墙吧(虽然不是必需的),看别人都翻得那么火热,自己也翻一把。

先是朋友介绍的自由门,但是服务器用不了,登不上去。

简单的就是找网页代理,但是速度很慢,不稳定,特别是脚本多的网站。

今天下午终于找到一个方法,就是用SSH

先是申请一个SSH账号,点击这里申请(只是两天)

然后下载一个软件MyEnTunnel

 

使用MyEnTunnel , 并进行配置

cnsystem'blog

cnsystem'blog

然后将本机sock5代理设为localhost:7070

这里端口号7070就是上面设置的,和SSH服务器通信的端口号

原理是:用SSH与国外服务器建立一条PPP隧道,将网页请求从7070端口出去。

对于GOOGLE的一些网站可以修改HOST文件

HOST文件在C:\Windows\System32\drivers\etc目录下,然后管理员权限打开

添加IP/Domain

我的HOST如下

209.85.147.109 pop.gmail.com
209.85.147.109 smtp.gmail.com
203.208.46.161 docs.google.com
209.85.225.102 groups.google.com
74.125.127.139 spreadsheets.google.com
74.125.127.100 services.google.com
74.125.127.100 writely.google.com
74.125.127.100 sites.google.com
209.85.225.104 reader.google.com
74.125.127.101 calendar.google.com
203.208.46.148 lh1.googleusercontent.com
203.208.46.148 lh2.googleusercontent.com
203.208.46.148 lh3.googleusercontent.com
203.208.46.148 lh4.googleusercontent.com
203.208.46.148 lh5.googleusercontent.com
203.208.46.148 lh6.googleusercontent.com
203.208.46.148 lh7.googleusercontent.com
203.208.46.148 s1.googleusercontent.com
203.208.46.148 s2.googleusercontent.com
203.208.46.148 images1-focus-opensocial.googleusercontent.com
203.208.46.148 images2-focus-opensocial.googleusercontent.com
203.208.46.148 images3-focus-opensocial.googleusercontent.com
203.208.46.132 webcache.googleusercontent.com
203.208.46.148 picasaweb.google.com
203.208.46.148 plus.google.com (GOOGLE+,谷歌的SNS)
203.208.46.148 talkgadget.google.com

有些理解可能错,就写到这里了,回头再好好看看

[继续阅读]

17 Nov 2011

简易引导扇区

这里讲一讲引导扇区

计算机电源打开后,先进行加电自检,然后寻找启动盘,计算机会检查以0xAA55结束的盲区,BIOS认为它是一个引导扇区。

除了以0xAA55结束之外,还应该包含一段少于512B的执行码(包括代码及数据)。

一旦发现了引导扇区后,就会将这512B的内容装载到内存的0000:7c00处,然后跳转到0000:7c00处将控制权彻底交给这段引导代码。

见下面代码:

引导扇区占512B,以AA55结尾。</p>
org 07c00h //对齐07c00h
mov ax,cs
mov ds,ax
mov es,ax
call DispStr
jmp $   //死循环
DispStr: //输出字符P到屏幕上
mov ax,BootMessage
mov bp,ax
mov cx,16
mov ax,01301h
mov bx,000ch
mov dl,0
int 10h
ret
BootMessage: db "Hello,Os World!"
times 510-($-$$) db 0   //未使用的字节以0填充
dw 0xaa55 //以0XAA55结尾

用NASM编译
nasm boot.asm -o boot.bin
然后新建一个虚拟机,从软盘启动,软盘装载boot.bin
启动之后,一行红色的Hello,Os World!
激动ing

[继续阅读]

14 Sep 2011

远程管理Linux之命令界面

客户端:SecureCRT

服务端:开启SSH服务

客户端就不说,只是下载一个软件就是了。Linux服务器要开启SSH或Telnet,我用的是Ubuntu,开启SSH服务。具体见下文:

网上有很多介绍在Ubuntu下开启SSH服务的文章,但大多数介绍的方法测试后都不太理想,均不能实现远程登录到Ubuntu上,最后分析原因是都没有真正开启ssh-server服务。最终成功的方法如下:

sudo apt-get install openssh-server

Ubuntu缺省安装了openssh-client,所以在这里就不安装了,如果你的系统没有安装的话,再用apt-get安装上即可。

然后确认sshserver是否启动了:

ps -e grep ssh

如果只有ssh-agent那ssh-server还没有启动,需要/etc/init.d/ssh start,如果看到sshd那说明ssh-server已经启动了。

ssh-server配置文件位于/ etc/ssh/sshd_config,在这里可以定义SSH的服务端口,默认端口是22,你可以自己定义成其他端口号,如222。然后重启SSH服务:

sudo /etc/init.d/ssh restart

ssh连接:ssh linuxidc@192.168.1.1

  1. 首先在服务器上安装ssh的服务器端。

$ sudo aptitude install openssh-server

  1. 启动ssh-server。

$ /etc/init.d/ssh restart

  1. 确认ssh-server已经正常工作。

$ netstat -tlp

tcp6 0 0 *:ssh *:* LISTEN –

看到上面这一行输出说明ssh-server已经在运行了。

  1. 在客户端通过ssh登录服务器。假设服务器的IP地址是192.168.0.103,登录的用户名是hyx。

$ ssh -l hyx 192.168.0.103

接下来会提示输入密码,然后就能成功登录到服务器上了

解决中文乱码问题:

(1)/var/lib/locales/supported.d/local文件中添加一行:zh_CN.UTF-8 UTF-8,执行sudo locale-gen下载文件

(2)在/etc/environment中增加两行分别为:LANG=”zh_CN.UTF-8″和LC_ALL=”zh_CN.UTF-8″

(3)~/.profile中增加两行分别为:export LANG=”zh_CN.UTF-8″和export LC_ALL=”zh_CN.UTF-8″,执行.profile

(4)在SecureCRT里设置,见下图

[继续阅读]

14 Sep 2011

GDB调试说明

GDB可算做是Linux-GCC系列编译器附带的Debug工具.

要用GDB调试程序只需要在Shell-Command下输如类似 gdb xxx.exe即可.

(这里强调一点, 在编译的时候, 需要加入参数-g来支持debug, 例如gcc -g xxx.c -o xxx.exe)

出现gdb提示后, 可以先用 “l ” (list) 来查看src, 然后在某行加上断点 “b 21” (break at line 21)

接下来, 运行程序 “r” (run) 即可, 这时程序会自动停留到断点处,

如果想继续运行, 则键入 “c” (continue)

查看变量用 p ( p var1)

设置变量用 set (set var1=”abc”)

跳到某行用 jump (jump 21)

进入一个Function内部, 用 s (step into)

Step-by-Step运行则用 n (next)

print 检查各个变量的值(gdb) print p (p为变量名)

whatis 显示某个变量的类型 (gdb) whatis p

断点(breakpoint)

break line-number 使程序恰好在执行给定行之前停止。

break function-name 使程序恰好在进入指定的函数之前停止。

break line-or-function if condition 如果condition(条件)是真,程序到达指定行或函数时停止。

break routine-name 在指定例程的入口处设置断点

(gdb) break filename:line-number 如果该程序是由很多原文件构成的,在各个原文件中设置断点

(gdb) break filename:function-name

要想设置一个条件断点,可以利用break if命令,如下所示:

(gdb) break line-or-function if expr 例:(gdb) break 46 if testsize==100

countinue 命令断点继续运行

显示当前gdb的断点信息: (gdb) info break

删除指定的某个断点: (gdb) delete breakpoint 1 该命令将会删除编号为1的断点,

如果不带编号参数,将删除所有的断点 (gdb) delete breakpoint

禁止使用某个断点 (gdb) disable breakpoint 1 该命令将禁止断点 1,同时断点信息的 (Enb)域将变为 n

允许使用某个断点 (gdb) enable breakpoint 1 该命令将允许断点 1,同时断点信息的 (Enb)域将变为 y

清除原文件中某一代码行上的所有断点 (gdb)clean number

注:number 为原文件的某个代码行的行号

[继续阅读]