dotnet Application Notes

Introduction

.NET is free, cross-platform, open-source developer platform for building many kinds of applications. It can run programs written in multiple languages, with C# being the most popular. It relies on a high-performance runtime that is used in production by many high-scale apps.

Installation

  1. download and extract
    1
    2
    3
    4
    cd ~
    wget ${the direct link of dotnet, for example dotnet 8.0 for arm64 or whatever your platform is}
    mkdir -p $HOME/dotnet && tar zxf dotnet-sdk-8.0.404-linux-arm64.tar.gz -C $HOME/dotnet

  2. load it automatically
    add these two lines at the end of ~/.bashrc
    1
    2
    export DOTNET_ROOT=$HOME/dotnet
    export PATH=$PATH:$HOME/dotnet
    source it to use it for the current terminal and do a checking
    1
    2
    source ~/.bashrc
    dotnet --version

To run a program

1
2
cd ${src dir of a project}
dotnet run

To compile a program to be binary file

1
2
3
cd ${src dir fo a project}
mkdir build
dotnet publish -c Release -r linux-arm64 -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false -o ./build/

运行时so动态库找不到.md

检查执行文件的依赖库

1
ldd myprogram.out

假如显示有libA和libB找不到

根据结果显示的内容,找到对应的库文件目录,更新到~/.bashrc

1
2
export LD_LIBRARY_PATH=/path/to/libA/dir:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/path/to/libB/dir:$LD_LIBRARY_PATH

dotnet中使用gRPC

dotnet简介

dotnet也就是.NET,是一个跨平台的框架

在Linux板上安装dotnet

对于arm64架构的板,需要下载对应的arm64版本的二进制文件,下载地址dotnet 8.0
把文件导入到linux后,解压,修改.bashrc, 加入如下两行

1
2
export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

logout后重新载入终端,或source .bashrc,使修改生效。

确认安装成功

1
dotnet --version

在本地Windows计算机上使用Visual Studio创建gRPC server和client项目

参考开始使用gRPC服务

在运行后,位于项目文件夹/bin/Debug/net8.0文件夹内容可以全部拷贝到linux主机,在linux主机使用以下命令运行其中的xxx.dll文件, 能达到和在windows上同样的运行效果。

1
dotnet xxx.dll

在运行client的dll时报错如下
Grpc.Core.RpcException: Status(StatusCode=”Internal”, Detail=”Error starting gRPC call. HttpRequestException: The SSL connection could not be established, see inner exception. AuthenticationException: Cannot determine the frame size or a corrupted frame was received.

临时解决方案是在客户端中使用 GrpcChannel 创建不安全的 HTTP 通道:

1
2
3
4
var channel = GrpcChannel.ForAddress("http://localhost:5000", new GrpcChannelOptions
{
HttpHandler = new HttpClientHandler()
});

初步的gRPC教程使用 C# 的 gRPC 服务

机器人路径规划

1 路径规划的方法

最直接的是离散路径规划,另外两种方法建立于离散路径规划之上,是sample-based路径规划和概率路径规划。

离散路径规划

离散路径规划将机器人工作空间明确划分为连接的图(graph),然后应用图搜索(graph serch)算法来计算最佳路径。
这种方式非常精确和彻底,因为是直接对环境进行离散化,而且可以通过调节离散化程度来改变精度。
但结果是,这种方式具有极大的计算成本,也就不适用于大型路径规划的问题。
离散路径规划具有较好的精确性,不过只适用于低维问题,在高维度路径规划问题上,使用基于样本的路径规划就更合适。

基于样本的路径规划

基于样本的路径规划通过探测工作空间并增量地构建图。结果精度没有离散路径规划高,但由于使用到的样本量相对较小其运行速度快很多。
其生成出来的路径可能不是最优的,但绝大多数情况下生成可用的路径,好于等待数小时或数天。

概率路径规划

概率路径规划与前二者主要不同之处在于,考虑机器人运动的不确定性。在一些高度受限、敏感、高风险环境特别有用。

路径规划算法的评价指标

完整:对任何可解的问题均能提供结果,对不可解的问题不提供结果。
最优:能够找到最佳路径。最佳情况可以是:最短路径、最快速度、最少风险等。

2 离散路径规划

第一步:构建连续的表达,如构建C-space(Configuration Space)
第二步:离散化
第三步:应用graph search算法

连续表达Continuous Representation

使用机器人的半径膨胀所有障碍物,然后将机器人缩小成一个点,构成C-space这使得应用路径搜索算法更容易。
C-space机器人所有位姿的集合,分为C_free和C_obs。

闵可夫斯基和Minkowski Sum

闵可夫斯基和是一个数学工具,给定障碍物和机器人的几何形状即可计算其C-space.
其背后的思想就是,将机器人几何作为笔刷,沿着障碍物描一圈轮廓,新描后内容就是C_obs.
对于凸多边形,计算极快,而对于非凸多边形,计算代价高很多。

3D Configuration Space

在原来的C-space中增加一个旋转维度,形成3D C-space.
形成的方法很简单,机器人绕Z轴旋转一个角度就是一个图层,在该角度下使用闵可夫斯基和就能得到该层的C-space,
多层的叠加就形成了3D C-space.

离散化的方法

路线图Roadmap、单元分解Cell Decomposition、梯度场Gradient Field.

可视性图法 visibility graph

可视性图法通过连接起点到所有可见障碍物的顶点生成路线图。是完整的、最短距离的算法。
其缺陷是,容错率较低,因为会有一些线段非常接近障碍物,容易发生撞击。

Voronoi Diagram

Voronoi Diagram法的路径离开障碍物。实现方式是将障碍物之间的自由空间二分,所有路径与其周围的障碍物等距。是完整算法。

Cell Decompositon(exact)

在障碍物的每一个顶点增加一条竖直线,将C-space隔成非重叠单元区域。对于非常规形状,会增加计算复杂度。
以下方法可以解决计算复杂度问题。

Approximate Cell Decompostion

将C-space分解为简单且常规的形状。同时,还支持空间的等级分解。

Simple Decomposition

一个C-space被分解为由矩形单元组成的网格,从而每个单元可以标记成空或占有。算法就能在起点和终点之间找到一条空单元连线。
这种方式比精确分解更高效,却也丢失了其完整性。因为偶尔有存在路径的图被标记为占有,把99%占用和1%占用做一样看,不实用。

Iterative Decompostion

该方法不同于直接分解,而是将空间迭代地分解为4个象限。每个象限被分类为空、占有和混合的,混合即指部分占有情况。
如果从起点到目标都找不到一串空的单元格,就将混合的单元再次划分为4个新的象限。2维里叫做四叉树分解,3维中叫八叉树分解。

Approximate Cell Decompostion常用于实践,因为计算量较Exact Cell Decompostion低很多。但是与其他的离散/组合型的
路径规划算法一样,碰到多维问题时就计算量爆炸。

势场法 Potential Field

势场法生成两个函数,一个吸引机器人去目标,一个将机器人排离障碍物,两个函数可以加起来为一个离散化的表达。
应用梯度下降算法,就能将机器人导向目的。
吸引的势场:一个在目的地全局最小的方程
排斥的势场:在障碍物周围有斜坡
其缺陷是:即不完整也不最佳

分为不知情的盲搜和启发式搜索。
盲搜:广度优先搜索BFS、深度优先搜索DFS、等成本搜索Uniform cost search
启发式搜索:A*

算法评价指标:时间复杂度、空间复杂度、泛化能力

广度优先搜索BFS

以树结构为例,从根部结点开始,将探索到的所有根放入队列尾端,然后从队列前端依次取出一个节点,将其根都放入队列尾端。
complete and optimal

深度优先搜索DFS

以树结构为例,从根部节点开始,将探索到的所有根压入栈,然后栈顶元素出栈,探索其根也压入栈。
nither complete or optimal

等成本搜索Uniform

如果把每两个节点之间的路加上一个权重,就能计算某一条路的成本。
complete and optimal

A*算法

引入一个启发函数h(n) = 到目标的距离
探索图的时候计算的成本g(n) = 当前的路径成本
算法目标是最小化函数 f(n) = g(n) + h(n)

算法流程:计算每个节点到目标的启发函数h(n), 计算起点周围点的路径成本g(n), 计算各周围点的f(n)后排序(或用priority queue),探索f(n)最小的点,
再次计算f(n)排序-探索,直到目标。
A*不保证每次都是optimal的,满足以下条件则可以达到optimal:
每条边都要比某个量长,否则会卡住;启发函数要满足三角不等式,即一边始终小于另两边之和;启发函数的值不能超过真实成本(可能导致非最优路径)。

附加资料

启发式
Path Finding Visualization
A* 变种

一些考虑

应用双向搜索,即从起点和终点一起搜,当二者汇合就产生了路径。
A*搜出来的通常是路径比较短,但是与障碍物之间很近,容易发生撞击,可以将靠近障碍物的路径的成本提高。
离散的折现状路径在现实中需要做一些smoothing,平滑的时候也要考虑与障碍物保持安全距离。

3 基于样本的路径规划

处理更高维度的问题时,比如三维地图、多自由度的机器人等,做全面的离散化较难,成本较高。
对于n维空间,每个节点的连接边有3^n-1个。
此外,在使用运动学受限的机器人时也会有困难,例如汽车不能侧向移动等。

完整系统,所有约束都只与当前姿态和时间有关。
非完整系统,有约束和微分有关,例如安全转弯半径与速度有关。路径规划会难一点。

基于样本的路径规划算法从C-space随机取样本以建立对空间的表达,具有概率完整性,在高维空间非常高效,缺陷是面对长条缝隙中的路径需要较长
时间发现,或者当作没有路径。

两种类型:概率地图方法 PRM probabilistic roadmap method、RRT rapidly exploring random tree method

PRM

从工作空间随机取样构建一个graph,跳过c-space构建和离散化。只要一个碰撞检测函数检查一个随机生成配置点是在自由空间还是在障碍物。
建图的过程叫做学习阶段:生成点,判断是否在障碍物中,在给定半径内找邻居或者k近邻,检查是否能够建立到邻居的路径,建立路径,
当真个采样点或者路径边数达到目标结束。

参数设定:迭代次数决定最终地图的精细度和计算用时;最近邻选择算法。

RRT

从起点开始,随机生成配置点,每个配置点只有一个父节点,如果配置点很长,在方向上新建一个距离短的点,可行路线连接。可以双向进行。
参数设定:取样方法(如何生成随机配置点);growth rate 距离;
可以在几毫秒内解决7维的问题,几分钟解决20维问题,效率比PRM高很多

一些考虑

在生成路径后可能是非常曲折的,这时候要用到路径平滑。
所谓的路径平滑,也就是检查相邻的两个点之间的距离,如果小于路径中的距离,就判断两个点的连线是否有障碍物,如果没有就用连线代替原路径段。

非完整:基于样本的路径规划算法不是完整的(仅概率完整),通常在复杂的环境中(例如狭长的通道)无法找到结果。
非最优:虽然可以在PRM图中用A*获取图内最优路径,但是图本身就不是环境的全面表达。
至少,基于样本的方法在多维空间中路径规划是可行的。

RRT*: 变种,通过查看子节点能否和其父节点或父节点的父节点交换,来生成更直接的路径
Anytime algorithm: 就算搜索被中止,也有路径返回,搜的越久越接近最优

4 概率路径规划

马尔可夫决策过程MDP

  • 一个状态集合:S
  • 初始状态:s_0
  • 一个动作集合:A
  • 过渡模型:T(s, a, s’),从状态s执行动作a后到达s’的概率函数,在MDP中认为此概率只与前一状态s有关而与路径无关。
  • 一个奖励集合R

概率路径规划中的MDP和强化学习RL之间的主要区别是,前者对以上的要素都已知,而在RL中,机器人只已知状态和可执行的操作,但不知道奖励和过渡模型。

策略Policy

策略是从状态到动作的映射。对于每个状态,策略都会告知机器人应该采取哪些动作。
最佳策略,表示为π∗,告知机器人在任何状态下应采取的 最佳 行动,以最大化整体回报

每个状态可以计算出其状态效用值,在某种状态下策略会选择最大效用值

CMakeList教程

一、在Windows上使用CMakeList

1、使用tdm64-gcc

google tdm64-gcc 安装

确保添加的PATH

安装cmake

确保添加到PATH
然后下载ninja.exe丢到cmake.exe文件夹内

安装make

1
choco install make

使用

在包含CMakeList.txt的文件夹内

1
2
3
mkdir build && cd build
cmake .. -G "MinGW Makefiles"
make

2、使用MSYS2

安装好MSYS2

教程有安装细节link1 link2

设定环境变量

在path中增加D:\msys64\usr\bin

使用

1
2
cmake -G "MSYS Makefiles"
make

二、在Linux系统上使用

安装库

将库安装在usr/local/lib,将可执行安装在usr/local/bin,将头文件安装在usr/local/include
这样便于使用。

库的CMakeLists代码

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 2.8)
project(mymath)
set_target_properties(mymath PROPERTIES PUBLIC_HEADER adder.h)
install(TARGETS mymath LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include
)

安装

1
2
cmake -S ..
sudo make install

使用库

CMakeLists.txt

1
2
3
4
5
cmake_minimum_required(VERSION 2.8)
project(sometest)
add_executable(testsome main.cpp)
target_link_libraries(testsome mymath) # 由于前面安装了,这里就能找到mymath

main.cpp

1
2
3
4
5
6
#include "adder.h" // 由于前面安装了,这里可以找到
#include<iostream>
int main() {
std::cout<< "2 + 3 = " << mymath::add(2,3) << std::endl;
return 0;
}

MCL 蒙特卡洛定位

蒙特卡洛定位也叫粒子滤波方法。比EKF容易编程,适应于非高斯分布噪音的数据,内存/分辨率可控。而EKF只能适用于高斯分布噪音的数据,不可调整分辨率。

粒子滤波

在整个地图上随机且均匀地生成粒子,每个粒子代表机器人可能存在的一种位姿状态,如[x, y, theta]’
每个向量还有一个权重,即[x, y, theta, weight]’,该权重是机器人实际测量pose与粒子预计测量的pose之间的差异。
带有较大权重的粒子有更大的把握在重采用resampling中留存下来。
经过若干次MCL算法迭代,和不同阶段的resampling后,粒子会收敛并预测出机器人的pose。

Bayes Filtering

estimate the state of a dynamical system from sensor measurements
使用贝叶斯Filtering可以从传感器测量值估算动态系统的状态

在机器人定位中的几个定义如下:
Dynamic System 移动机器人及它所面临的环境
State 机器人的pose,包括它的位置和方向
Measurement 感知数据(e.g. 激光扫描仪)和里程计数据(e.g. 旋转编码器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int main() {

//Given P(POS), P(DOOR|POS) and P(DOOR|¬POS)
double a = 0.0002 ; //P(POS) = 0.002
double b = 0.6 ; //P(DOOR|POS) = 0.6
double c = 0.05 ; //P(DOOR|¬POS) = 0.05

//TODO: Compute P(¬POS) and P(POS|DOOR)
double d = 1 - a; //P(¬POS)
double e = (b * a) / (a * b + d * c); //P(POS|DOOR)

//Print Result
cout << "P(POS|DOOR)= " << e << endl;

return 0;
}

蒙特卡洛滤波的例子

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
//#include "src/matplotlibcpp.h"//Graph Library
#include <iostream>
#include <string>
#include <math.h>
#include <vector>
#include <stdexcept> // throw errors
#include <random> //C++ 11 Random Numbers

//namespace plt = matplotlibcpp;
using namespace std;

// Landmarks
double landmarks[8][2] = { { 20.0, 20.0 }, { 20.0, 80.0 }, { 20.0, 50.0 },
{ 50.0, 20.0 }, { 50.0, 80.0 }, { 80.0, 80.0 },
{ 80.0, 20.0 }, { 80.0, 50.0 } };

// Map size in meters
double world_size = 100.0;

// Random Generators
random_device rd;
mt19937 gen(rd());

// Global Functions
double mod(double first_term, double second_term);
double gen_real_random();

class Robot {
public:
Robot()
{
// Constructor
x = gen_real_random() * world_size; // robot's x coordinate
y = gen_real_random() * world_size; // robot's y coordinate
orient = gen_real_random() * 2.0 * M_PI; // robot's orientation

forward_noise = 0.0; //noise of the forward movement
turn_noise = 0.0; //noise of the turn
sense_noise = 0.0; //noise of the sensing
}

void set(double new_x, double new_y, double new_orient)
{
// Set robot new position and orientation
if (new_x < 0 || new_x >= world_size)
throw std::invalid_argument("X coordinate out of bound");
if (new_y < 0 || new_y >= world_size)
throw std::invalid_argument("Y coordinate out of bound");
if (new_orient < 0 || new_orient >= 2 * M_PI)
throw std::invalid_argument("Orientation must be in [0..2pi]");

x = new_x;
y = new_y;
orient = new_orient;
}

void set_noise(double new_forward_noise, double new_turn_noise, double new_sense_noise)
{
// Simulate noise, often useful in particle filters
forward_noise = new_forward_noise;
turn_noise = new_turn_noise;
sense_noise = new_sense_noise;
}

vector<double> sense()
{
// Measure the distances from the robot toward the landmarks
vector<double> z(sizeof(landmarks) / sizeof(landmarks[0]));
double dist;

for (int i = 0; i < sizeof(landmarks) / sizeof(landmarks[0]); i++) {
dist = sqrt(pow((x - landmarks[i][0]), 2) + pow((y - landmarks[i][1]), 2));
dist += gen_gauss_random(0.0, sense_noise);
z[i] = dist;
}
return z;
}

Robot move(double turn, double forward)
{
if (forward < 0)
throw std::invalid_argument("Robot cannot move backward");

// turn, and add randomness to the turning command
orient = orient + turn + gen_gauss_random(0.0, turn_noise);
orient = mod(orient, 2 * M_PI);

// move, and add randomness to the motion command
double dist = forward + gen_gauss_random(0.0, forward_noise);
x = x + (cos(orient) * dist);
y = y + (sin(orient) * dist);

// cyclic truncate
x = mod(x, world_size);
y = mod(y, world_size);

// set particle
Robot res;
res.set(x, y, orient);
res.set_noise(forward_noise, turn_noise, sense_noise);

return res;
}

string show_pose()
{
// Returns the robot current position and orientation in a string format
return "[x=" + to_string(x) + " y=" + to_string(y) + " orient=" + to_string(orient) + "]";
}

string read_sensors()
{
// Returns all the distances from the robot toward the landmarks
vector<double> z = sense();
string readings = "[";
for (int i = 0; i < z.size(); i++) {
readings += to_string(z[i]) + " ";
}
readings[readings.size() - 1] = ']';

return readings;
}

double measurement_prob(vector<double> measurement)
{
// Calculates how likely a measurement should be
double prob = 1.0;
double dist;

for (int i = 0; i < sizeof(landmarks) / sizeof(landmarks[0]); i++) {
dist = sqrt(pow((x - landmarks[i][0]), 2) + pow((y - landmarks[i][1]), 2));
prob *= gaussian(dist, sense_noise, measurement[i]);
}

return prob;
}

double x, y, orient; //robot poses
double forward_noise, turn_noise, sense_noise; //robot noises

private:
double gen_gauss_random(double mean, double variance)
{
// Gaussian random
normal_distribution<double> gauss_dist(mean, variance);
return gauss_dist(gen);
}

double gaussian(double mu, double sigma, double x)
{
// Probability of x for 1-dim Gaussian with mean mu and var. sigma
return exp(-(pow((mu - x), 2)) / (pow(sigma, 2)) / 2.0) / sqrt(2.0 * M_PI * (pow(sigma, 2)));
}
};

// Functions
double gen_real_random()
{
// Generate real random between 0 and 1
uniform_real_distribution<double> real_dist(0.0, 1.0); //Real
return real_dist(gen);
}

double mod(double first_term, double second_term)
{
// Compute the modulus
return first_term - (second_term)*floor(first_term / (second_term));
}

double evaluation(Robot r, Robot p[], int n)
{
//Calculate the mean error of the system
double sum = 0.0;
for (int i = 0; i < n; i++) {
//the second part is because of world's cyclicity
double dx = mod((p[i].x - r.x + (world_size / 2.0)), world_size) - (world_size / 2.0);
double dy = mod((p[i].y - r.y + (world_size / 2.0)), world_size) - (world_size / 2.0);
double err = sqrt(pow(dx, 2) + pow(dy, 2));
sum += err;
}
return sum / n;
}
double max(double arr[], int n)
{
// Identify the max element in an array
double max = 0;
for (int i = 0; i < n; i++) {
if (arr[i] > max)
max = arr[i];
}
return max;
}
/*
void visualization(int n, Robot robot, int step, Robot p[], Robot pr[])
{
//Draw the robot, landmarks, particles and resampled particles on a graph

//Graph Format
plt::title("MCL, step " + to_string(step));
plt::xlim(0, 100);
plt::ylim(0, 100);

//Draw particles in green
for (int i = 0; i < n; i++) {
plt::plot({ p[i].x }, { p[i].y }, "go");
}

//Draw resampled particles in yellow
for (int i = 0; i < n; i++) {
plt::plot({ pr[i].x }, { pr[i].y }, "yo");
}

//Draw landmarks in red
for (int i = 0; i < sizeof(landmarks) / sizeof(landmarks[0]); i++) {
plt::plot({ landmarks[i][0] }, { landmarks[i][1] }, "ro");
}

//Draw robot position in blue
plt::plot({ robot.x }, { robot.y }, "bo");

//Save the image and close the plot
plt::save("./Images/Step" + to_string(step) + ".png");
plt::clf();
}
*/

int main()
{
//Practice Interfacing with Robot Class
Robot myrobot;
myrobot.set_noise(5.0, 0.1, 5.0);
myrobot.set(30.0, 50.0, M_PI / 2.0);
myrobot.move(-M_PI / 2.0, 15.0);
//cout << myrobot.read_sensors() << endl;
myrobot.move(-M_PI / 2.0, 10.0);
//cout << myrobot.read_sensors() << endl;

// Create a set of particles
int n = 1000;
Robot p[n];

for (int i = 0; i < n; i++) {
p[i].set_noise(0.05, 0.05, 5.0);
//cout << p[i].show_pose() << endl;
}

//Re-initialize myrobot object and Initialize a measurment vector
myrobot = Robot();
vector<double> z;

//Iterating 50 times over the set of particles
int steps = 50;
for (int t = 0; t < steps; t++) {

//Move the robot and sense the environment afterwards
myrobot = myrobot.move(0.1, 5.0);
z = myrobot.sense();

// Simulate a robot motion for each of these particles
Robot p2[n];
for (int i = 0; i < n; i++) {
p2[i] = p[i].move(0.1, 5.0);
p[i] = p2[i];
}

//Generate particle weights depending on robot's measurement
double w[n];
for (int i = 0; i < n; i++) {
w[i] = p[i].measurement_prob(z);
//cout << w[i] << endl;
}

//Resample the particles with a sample probability proportional to the importance weight
Robot p3[n];
int index = gen_real_random() * n;
//cout << index << endl;
double beta = 0.0;
double mw = max(w, n);
//cout << mw;
for (int i = 0; i < n; i++) {
beta += gen_real_random() * 2.0 * mw;
while (beta > w[index]) {
beta -= w[index];
index = mod((index + 1), n);
}
p3[i] = p[index];
}
for (int k=0; k < n; k++) {
p[k] = p3[k];
//cout << p[k].show_pose() << endl;
}

//#### DON'T MODIFY ANYTHING ABOVE HERE! ENTER CODE BELOW ####

//Evaluate the error by priting it in this form:
// cout << "Step = " << t << ", Evaluation = " << ErrorValue << endl;
cout << "Step = " << t << ", Evaluation = " << evaluation(myrobot, p, n) << endl;

} //End of Steps loop
return 0;
}

ROS AMCL包

Adaptive Monte Carlo Localization (AMCL)

Kalman滤波

Kalman滤波是什么及作用

一种通过预测值和测量值组合来滤除随机噪音和不确定性,得到当前状态的滤波方法。
可用于融合多个传感器、估算带噪音的数据。

例如一个机器人可以沿着X轴运动,在理论上,若以10m/s的速度从0点出发向正方向运动,该机器人在1s过后将位于10m处。
但在实际情况中,由于轮子与地面的滑移和障碍物减缓速度的情况随机发生,1s的运动后机器人的位置是一个正态分布,其均值u=10,σ与机器人控制精度有关。
与此同时,若使用传感器来测量机器人的位置,得到的结果通常也是正态分布的。

正态分布及求和计算
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
27
28
#include <iostream>
#include <math.h>

using namespace std;

double f(double mu, double sigma2, double x)
{
//Use mu, sigma2 (sigma squared), and x to code the 1-dimensional Gaussian
//Put your code here
double prob = 1.0 / sqrt(2.0 * M_PI * sigma2) * exp(-0.5 * pow((x - mu), 2.0) / sigma2);
return prob;
}

int main()
{
double sum = 0.0;
for (int i = 0; i != 20; ++i) {
double x_val = 0.0 + double(i);
double gaussian = f(5.0, 4.0, x_val);
cout << gaussian << endl;

sum += gaussian;
}
//cout << f(10.0, 4.0, 8.0) << endl;

cout << "sum is " << sum << endl; // 接近1,类似于高斯的概率密度函数积分为1
return 0;
}
命名惯例

x_t 状态 state
z_t 测量 measurement
u_t 控制 control action

测量更新 measurement update

例如,给机器人一个控制命令后,我们认为其位置(Prior belief)的高斯分布为mu,sigma2。
若此时,传感器测量得到的位置分布为v,r2,
那么构建融合的高斯分布是这样的:
新的均值:mu’ = (r2mu + sigma2v)/(r2 + sigma2)
新的方差:sigma2’ = 1/(1/r2 + 1/sigma2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <math.h>
#include <tuple>

using namespace std;

double new_mean, new_var;

tuple<double, double> measurement_update(double mean1, double var1, double mean2, double var2)
{
new_mean = (var2 * mean1 + var1 * mean2) / (var1 + var2);
new_var = 1 / (1 / var1 + 1 / var2);
return make_tuple(new_mean, new_var);
}

int main()
{
tie(new_mean, new_var) = measurement_update(10, 8, 13, 2);
printf("[%f, %f]", new_mean, new_var);
return 0;
}
状态预测

在知道当前先验位置分布mu1,sigma2(1)的时候(sigma2表示sigma的平方),给出运动控制指令的先验作用效果分布mu2,sigma2(2),
预测其状态的公式如下:
后验均值 Posterior Mean mu’ = mu1 + mu2
后验方差 Posterior Variance sigma2’ = sigma2(1) + sigma2(2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <math.h>
#include <tuple>

using namespace std;

double new_mean, new_var;

tuple<double, double> state_prediction(double mean1, double var1, double mean2, double var2)
{
new_mean = mean1 + mean2;
new_var = var1 + var2;
return make_tuple(new_mean, new_var);
}

int main()
{
tie(new_mean, new_var) = state_prediction(10, 4, 12, 4);
printf("[%f, %f]", new_mean, new_var);
return 0;
}
1D Kalman 滤波代码
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <math.h>
#include <tuple>

using namespace std;

double new_mean, new_var;

tuple<double, double> measurement_update(double mean1, double var1, double mean2, double var2)
{
new_mean = (var2 * mean1 + var1 * mean2) / (var1 + var2);
new_var = 1 / (1 / var1 + 1 / var2);
return make_tuple(new_mean, new_var);
}

tuple<double, double> state_prediction(double mean1, double var1, double mean2, double var2)
{
new_mean = mean1 + mean2;
new_var = var1 + var2;
return make_tuple(new_mean, new_var);
}

int main()
{
//Measurements and measurement variance
double measurements[5] = { 5, 6, 7, 9, 10 };
double measurement_sig = 4;

//Motions and motion variance
double motion[5] = { 1, 1, 2, 1, 1 };
double motion_sig = 2;

//Initial state
double mu = 0;
double sig = 1000;

//######TODO: Put your code here below this line######//
// Loop through all the measurments
for (int i = 0; i < sizeof(measurements)/sizeof(measurements[0]); i++){
// Apply a measurment update
tie(mu, sig) = measurement_update(mu, sig, measurements[i], measurement_sig);
printf("update: [%f, %f]\n", mu, sig);
// Apply a state prediction
tie(mu, sig) = state_prediction(mu, sig, motion[i], motion_sig);
printf("predict: [%f, %f]\n", mu, sig);
}
return 0;
}

多维度卡尔曼滤波

2d高斯

均值:一个均值组成的向量
方差:协方差矩阵,其对角线为方差,二维图像上与椭圆的长短轴相关;对角线之外的是相关项,与椭圆转动有关,正值向右转。

维度中变量的组合可是位置组合如[x, y]’,也可以是位置与速度组合如[x, x’]’

变量分为可见变量和隐藏变量,比如机器人的位置传感器获取其位置是可见变量,而其速度是可以通过位置与时间的关系得到的隐藏变量。
通过重复进行状态预测和测量更新,可以达到比单独传感更小的方差分布。

卡尔曼滤波例子
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <math.h>
#include <tuple>
#include "eigen/eigen/Core" // Eigen Library
#include "eigen/eigen/LU" // Eigen Library

using namespace std;
using namespace Eigen;

float measurements[3] = { 1, 2, 3 };

tuple<MatrixXf, MatrixXf> kalman_filter(MatrixXf x, MatrixXf P, MatrixXf u, MatrixXf F, MatrixXf H, MatrixXf R, MatrixXf I)
{
for (int n = 0; n < sizeof(measurements) / sizeof(measurements[0]); n++) {

// Measurement Update
MatrixXf Z(1, 1);
Z << measurements[n];

MatrixXf y(1, 1);
y << Z - (H * x);

MatrixXf S(1, 1);
S << H * P * H.transpose() + R;

MatrixXf K(2, 1);
K << P * H.transpose() * S.inverse();

x << x + (K * y);

P << (I - (K * H)) * P;

// Prediction
x << (F * x) + u;
P << F * P * F.transpose();
}

return make_tuple(x, P);
}

int main()
{

MatrixXf x(2, 1);// Initial state (location and velocity)
x << 0,
0;
MatrixXf P(2, 2);//Initial Uncertainty
P << 100, 0,
0, 100;
MatrixXf u(2, 1);// External Motion
u << 0,
0;
MatrixXf F(2, 2);//Next State Function
F << 1, 1,
0, 1;
MatrixXf H(1, 2);//Measurement Function
H << 1,
0;
MatrixXf R(1, 1); //Measurement Uncertainty
R << 1;
MatrixXf I(2, 2);// Identity Matrix
I << 1, 0,
0, 1;

tie(x, P) = kalman_filter(x, P, u, F, H, R, I);
cout << "x= " << x << endl;
cout << "P= " << P << endl;

return 0;
}

Kalman滤波的类型

KF-线性
EKF-非线性
UKF-高度非线性

Python advanced

decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def mydecorator(function):

def wrapper(*args, **kwargs):
function(*args, **kwargs)
print(" I am decorating your funciton!")


return wrapper

@mydecorator
def hello_world(name):
print(f"Hello {name}")

hello_world("mike")

Example 1 保存调试信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def logged(function):
def wrapper(*args, **kwargs):
value = function(*args, **kwargs)
with open("logfile.txt", "a+") as f:
fname = function.__name__
print(f"{fname} returned value {value}")
f.write(f"{fname} returned value {value}\n")
return value

return wrapper

@logged
def add(x, y):
return x + y

print(add(10, 20))

Example 2 计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import time

def timed(function):
def wrapper(*args, **kwargs):
before = time.time()
value = function(*args, **kwargs)
after = time.time()
fname = function.__name__
print(f"{fname} took {after - before} seconds to execute!")
return value

return wrapper

@timed
def myfunction(x):
result = 1
for i in range(1, x):
result *= i
return result

myfunction(1000)

generator

1
2
3
4
5
6
7
8
9
10
def infinite_sequence():
result = 1
while True:
yield result
result *= 5

values = infinite_sequence()

for i in range(5):
print(next(values))

argument parsing

1
2
3
4
5
6
7
8
import sys
# Usage: python test.py filename

filename = sys.argv[1]
message = sys.argv[2]

with open(filename, "w+") as f:
f.write(message)

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
import getopt

filename = "text.txt"
message = "Hello"

opts, args = getopt.getopt(sys.argv[1:],"f:m:", ["filename", "message"])

for opt, arg in opts:
if opt == '-f':
filename = arg
if opt == '-m':
message = arg

with open(filename, "w+") as f:
f.write(message)

encapsulation

没有直接范围隐藏数据成员

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
27
28
29
30
class Person:

def __init__(self, name, age, gender):

self.__name = name
self.__age = age
self.__gender = gender

@property
def Name(self):
return self.__name

@Name.setter # python 没有重载,只有这种
def Name(self, value):
if value == 'Bob':# 统一处理访问
self.__name = 'Defult name'
else:
self.__name = value

@staticmethod
def mymethod():
print("Hello World from static method!")

Person.mymethod()

p1 = Person('mike', 27, 'male')
print(p1.Name)

p1.Name = 'john'
print(p1.Name)

Type hinting

使用mypy做类型检查

1
mypy .\test.py

使用显式类型标注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def myfunction(myparameter:int) -> int:
return myparameter + 10

print(myfunction(8))

def dosth(param: list[int]) -> list[int]:
for i in range(len(param)):
param[i] += 1

return param

l = [1,2,3,4,5]

print(dosth(l))

Magic methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Vector:

def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
return(Vector(self.x + other.x, self.y + other.y))

def __repr__(self) -> str:
return f"X: {self.x}, Y: {self.y}"

def __call__(self):
print("Hello! I was called!")

v1 = Vector(10,20)
v2 = Vector(50,40)
v3 = v1 + v2
print(v3)
v3()

配置Qt与OpenCV

教程

碰到错误

1
Fatal error: can't write 9 bytes to section .text$_ZNSt14numeric_limitsIfE9quiet_NaNEv of CMakeFiles\opencv_perf_core.dir\perf\opencl\perf_arithm.cpp.obj: 'file too big'

解决方案

“C:\dev\opencv-4.5.2\release\CMakeFiles\3.21.0-rc2\CMakeCXXCompiler.cmake”
增加
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} “-O3”)

Python 赋值引用、拷贝

参考文章link

变量和对象之间的关系为引用

1 第一次赋值时,即创建,之后 再次赋值 将会 改变 变量的值。
2 变量名本身是没有类型的,类型只存在对象中,变量只是引用了对象。
3 所有的变量,必须 在使用前 赋值,使用未赋值的变量会产生错误。

Python对象三要素:Id,Type,Value

Id:唯一标识一个对象
Type:标识对象的类型
Value:对象的值
== 指值相等。
is 指地址相等,即指引用相等。

变量是一个系统表的元素,拥有指向对象的连接的空间。
对象是分配的一块内存,有足够的空间去表示它们所代表的值。
引用是自动形成的从变量到对象的指针。

python里面复制对象时因对象而异,如list

values[:] 复制操作是所谓的「浅复制」(shallow copy)
存在问题:当被复制的list里有引用元素时,浅复制后的list中还是会共享改引用,导致相互干扰

1
2
3
4
5
6
a = [0, [1, 2], 3]
b = a[:]
a[0] = 8
a[1][1] = 9
#a 为 [8, [1, 9], 3],
#b 为 [0, [1, 9], 3]。

正确复制嵌套元素是进行“深复制”(deep copy)

1
2
3
4
5
6
import copy

a = [0, [1, 2], 3]
b = copy.deepcopy(a)
a[0] = 8
a[1][1] = 9

引用和拷贝

没有限制条件的分片表达式(L[:])能够复制序列,但此法只能浅层复制。

字典 copy 方法,D.copy() 能够复制字典,但此法只能浅层复制。

有些内置函数,例如 list,能够生成拷贝 list(L)。

copy 标准库模块能够生成完整拷贝:deepcopy 本质上是递归 copy。