判断文件是否存在
1
2
3
4
5
6
7
8
#include <fstream>

std::ifstream fin(file_name);
if (!fin) {
printf("file not exist");
return false;
}
fin.close();

C++17中有std::filesystem::path,相当于Boost的filesystem模块,只需要set(CMAKE_CXX_STANDARD 17)

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
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;


int main()
{
std::filesystem::path test_path("/home/user/test.yaml");
//判断路径是否存在
std::cout << "is_exist = " << std::filesystem::exists(test_path) << std::endl;

// 判断路径为绝对路径还是相对路径
std::cout << "is_abs = " << test_path.is_absolute() << std::endl;
std::cout << "is_relative = " << test_path.is_relative() << std::endl;
// 取得不带扩展名的文件名
std::cout << "stem = " << test_path.stem() << std::endl;
// 取得扩展名,如果没有,则为空
std::cout << "extension = " << test_path.extension() << std::endl;
// 取得文件名
std::cout << "filename = " << test_path.filename() << std::endl;

std::cout << "----------------------------------------" << std::endl;

std::filesystem::path test_dir("/home/user/Pictures");
//判断是文件还是文件夹
std::cout << "is_file = " << std::filesystem::is_regular_file(test_path) << std::endl;
std::cout << "is_dir = " << std::filesystem::is_directory(test_dir) << std::endl;

//关于路径拼接
std::filesystem::path new_path = test_dir / "test.txt";
std::cout << "new_path = " << new_path << std::endl;

//取得当前绝对工作路径 编译后可执行文件的路径文件夹
std::filesystem::path workPath = std::filesystem::current_path();
std::cout << "current exe path = " << workPath << std::endl;

//递归遍历指定路径下所有文件,文件夹名字也会返回
std::filesystem::recursive_directory_iterator iterDir(test_dir);
for (auto &it: iterDir)
{
//打印绝对路径
std::cout << it << std::endl;
// 不打印路径而是打印文件名
std::cout << it.path().filename() << std::endl;
}

//给一个相对路径,返回绝对路径
std::filesystem::path path_test("./");
std::cout << "abs_path = " << std::filesystem::absolute(path_test) << std::endl;


// 删除文件, 路径不存在不报错
// std::filesystem::remove(test_dir / "deepfakes"/"yolov7.pdf");

// 拷贝文件夹, 删除文件夹
// std::filesystem::copy(test_dir, test_dir.parent_path() / "works", std::filesystem::copy_options::recursive);
// std::filesystem::remove_all(test_dir.parent_path() / "works");
return 0;
}


exception的使用

一次使用时,出现报错:

1
2
3
4
5
6
warning: exception of type ‘std::bad_alloc’ will be caught
catch (bad_alloc e){ cout << e.what() << endl;}
^~~~~

warning: by earlier handler for ‘std::exception’
catch (exception e){ cout << e.what() << endl;}

catch语句块不能随意乱安排。派生类型的异常放在前面,基类异常应该放在最后

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
try {
throw DerivedException();
}
catch (DerivedException& e) { // Handle derived class first
std::cerr << "Caught DerivedException: " << e.what() << std::endl;
}
catch (BaseException& e) { // Then handle base class
std::cerr << "Caught BaseException: " << e.what() << std::endl;
}
return 0;
}

catch(...) 是一个通配符,能够捕获任何未被其他 catch 块处理的异常。避免滥用 catch(...),尽量捕获特定类型的异常,避免掩盖问题。


yaml-cpp的使用

使用yaml-cpp读文件

1
2
3
4
5
6
7
8
9
10
YAML::Node gps_root;
try {
gps_root = YAML::LoadFile("/home/user/111.yaml");
} catch (YAML::ParserException &ex) {
std::cerr << "gps.yaml parse failed: " << ex.what() << std::endl;
exit(-1);
} catch (YAML::BadFile &ex) {
std::cerr << "gps.yaml load failed: " << ex.what() << std::endl;
exit(-1);
}

所用函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Node LoadFile(const std::string& filename) {
std::ifstream fin(filename.c_str());
if (!fin) {
throw BadFile();
}
return Load(fin);
}

Node Load(std::istream& input)
{
Parser parser(input);
NodeBuilder builder;
if (!parser.HandleNextDocument(builder)) {
return Node();
}

return builder.Root();
}

如果读文件失败,会抛出异常,不会运行到Load,所以无法用IsDefined函数判断是否读文件成功。


无法到达目标时,搜索新的目标点
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
double planner::distance(double x1,double y1,double x2,double y2)
{
return sqrt( (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) );
}

bool planner::isAroundFree(unsigned int mx, unsigned int my)
{
if(mx <= 1 || my <= 1 || mx >= this->costmap_->getSizeInCellsX()-1 || my >= this->costmap_->getSizeInCellsY()-1)
return false;
int x,y;
for(int i=-1;i<=1;i++)
{
for(int j=-1;j<=1;j++)
{
x = static_cast<int>(mx) + i;
y = static_cast<int>(my) + j;
if(this->costmap_->getCost(static_cast<unsigned int>(x),static_cast<unsigned int>(y)) != costmap_2d::FREE_SPACE)
return false;
}
}
return true;
}

// tolerance:搜索半径,单位米。
// in:输入的点
// out:搜索到的可行点
void planner::getNearFreePoint(const geometry_msgs::PoseStamped in,
geometry_msgs::PoseStamped& out,
double tolerance)
{
out = in;
unsigned int grid_size = static_cast<unsigned int>(tolerance/costmap_->getResolution() + 0.5);
if(grid_size<1)
{
out = in;
return;
}

unsigned int mx0,my0;
if(costmap_->worldToMap(in.pose.position.x,in.pose.position.y,mx0,my0))
{
if(this->isAroundFree(mx0,my0))
return;
unsigned int minx,maxx,miny,maxy;
double wx = 0.0,wy = 0.0;
double min_move_cost = 10000000.0;
minx = mx0-grid_size>0?mx0-grid_size:0;
maxx = mx0+grid_size<costmap_->getSizeInCellsX()?mx0+grid_size:costmap_->getSizeInCellsX();
miny = my0-grid_size>0?my0-grid_size:0;
maxy = my0+grid_size<costmap_->getSizeInCellsY()?my0+grid_size:costmap_->getSizeInCellsY();
for(unsigned int i=minx;i<=maxx;i++)
{
for(unsigned int j=miny;j<=maxy;j++)
{
costmap_->mapToWorld(i,j,wx,wy);
double current_move_cost = this->distance(in.pose.position.x,in.pose.position.y,wx,wy);
if(!this->isAroundFree(i,j) || current_move_cost > tolerance)
continue;
if(min_move_cost > current_move_cost)
{
min_move_cost = current_move_cost;
out.pose.position.x = wx;
out.pose.position.y = wy;
}
}
}
}
}

proto基本使用

安装

安装Protobuf需要两个zip文件,以 3.0.0 为例,到github下载页面下载 protoc-3.0.0-linux-x86_64.zipprotobuf-cpp-3.0.0.zip

安装前者:

1
2
3
4
5
6
7
8
9
10
# Unzip
unzip protoc-3.2.0-linux-x86_64.zip -d protoc3

sudo mv protoc3/bin/* /usr/local/bin/

sudo mv protoc3/include/* /usr/local/include/

# change owner,也可不执行
sudo chwon [user] /usr/local/bin/protoc
sudo chwon -R [user] /usr/local/include/google

再安装后者

1
2
3
4
5
6
7
8
9
10
11
12
13
# 解压后执行
./autogen.sh

./configure # 或者 ./configure --prefix=/usr

make
sudo make install
sudo ldconfig

protoc --version
# 输出:libprotoc 3.0.0

locate libprotobuf.so


所用protobuf的版本是 3.12.4

Test.proto文件:

1
2
3
4
5
6
7
8
9
syntax = "proto3";
// 包名:在生成对应的C++文件时,将被替换为名称空间,在代码中会有体现
package Test;

message Pose {
float x = 1;
float y = 2;
float yaw = 3;
}

根据proto文件生成.h 和 .cc文件

1
./protoc --proto_path=/home/user/qt_projects --cpp_out=/home/user/qt_projects/proto_gen /home/user/qt_projects/Test.proto

简略形式: protoc message.proto --cpp_out=.


CMake中的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.5)

project(test_proto LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(/home/user/zmqpp-4.2.0/src/zmqpp)
include_directories(/home/user/protobuf/src)

LINK_DIRECTORIES(/home/user/x86/zmqpp-4.2.0/lib/)
LINK_DIRECTORIES(/home/user/x86/protobuf/lib)
# 上面生成的cc文件
add_executable(test_proto main.cpp Test.pb.cc)
target_link_libraries(test_proto zmq protobuf)

注意 #include "Test.pb.h"

可以这样使用

1
2
3
4
Test::Pose*  pose_msg = new Test::Pose();
pose_msg->set_x(1.5);
pose_msg->set_y(2.7);
pose_msg->set_yaw(0);


ccache加速编译

ccache

先使用apt-get install ccache进行安装。

1
2
3
4
sudo vim /etc/environment
PATH='/usr/lib/ccache:keep the rest'

source /etc/environment

输入which gcc,应该得到/usr/lib/ccache/gcc

修改CMakeLists.txt

1
2
set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
set(CMAKE_C_COMPILER_LAUNCHER ccache)

但是在ROS环境下,没发现起作用

distcc

Setting up distcc (server)

So, go ahead and install distcc on the server computer (in my case, the desktop)

And then spin up the server. (The following assumes I’m on the 10.8.33.0 subnet, and I’m allowing all hosts on that subnet to send jobs)

1
sudo distccd --log-file=/tmp/distccd.log --daemon -a 10.8.33.0/24

Setting up distcc (client) So, now you have to tell ccache to use the distcc distributed compilation servers. To do this, just add the following line to your ~/.bashrc file.
1
export CCACHE_PREFIX=distcc

Next, we have to tell distcc where to go to find the server. This also, just add to the bottom of your ~/.bashrc (my desktop is at 10.8.33.182 and it has 8 cores, my laptop is at localhost and has 4)
1
export DISTCC_HOSTS='localhost/4 10.8.33.182/8'


在另一个终端,使用下面命令检验ccache的运行效果

1
watch -n1 'ccache -s -v'

或者watch -n1 'ccache --show-stats -v'

编译时,使用top看 distcc process starts spooling up its threads.


Linux程序运行时被killed

linux程序运行时突然出现Killed,可以使用dmesg | egrep -i -B100 'killed process 查看原因,输出最近killed的信息。

参考: linux 程序被Killed,查看原因


有用的程序 1
  • 不断判断随机数的大小,如果它能大于某个值维持一段时间,输出时间,否则重新计时。
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
int main()
{
int num = 0;
int count = 0;
std::chrono::steady_clock::time_point start, end;
double duration;

while(1)
{
std::srand(time(0));
count = rand() % 1000000;
if(count > 680000)
{
if(num==0)
start = std::chrono::steady_clock::now();

num++;
end = std::chrono::steady_clock::now();
duration = std::chrono::duration<double>(end - start).count();
cout << "count: " << count << endl;
cout << "time elapsed " << static_cast<int>(duration) << endl;
}
else
{
duration = 0;
num = 0;
cout << endl;
}
}
return 0;
}
  • 判断一个点是否在多边形中

opencv函数: double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

measureDist设置为true时,返回实际距离值。若返回值为正,表示点在多边形内部,返回值为负,表示在多边形外部,返回值为0,表示在多边形上。
当measureDist设置为false时,返回 -1、0、1三个固定值。若返回值为+1,表示点在多边形内部,返回值为-1,表示在多边形外部,返回值为0,表示在多边形上。

  • 计算点到直线的距离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* @tparam T point type
* @param first Given the starting point of a line segment
* @param last The endpoint of a given line segment
* @param third Given point
* @return T Distance from point to line
*/
template <typename T>
static T PointToLineDistance(const Point<T>& first, const Point<T>& last, const Point<T>& third) {
float dis_suqare =
((first.y - last.y) * third.x + (last.x - first.x) * third.y +
(first.x * last.y - last.x * first.y)) *
((first.y - last.y) * third.x + (last.x - first.x) * third.y +
(first.x * last.y - last.x * first.y)) /
((last.x - first.x) * (last.x - first.x) + (last.y - first.y) * (last.y - first.y));
return std::sqrt(dis_suqare);
}
  • 求图形的外接圆

OpenCV绘制最小外接矩形、最小外接圆
pointPolygonTest函数


Gazebo问题累计

当不正确关闭Gazebo时,再次启动Gazebo会遇到sever无法启动的问题。

1
Exception [Master.cc:50] Unable to start server[bind: Address already in use]. There is probably another Gazebo process running.

解决方法:用ps命令查找gzserver进程,将其kill。或是干脆重启系统


报错: Unable to convert from SDF version 1.7 to 1.6
把world文件的第一行改成 <sdf version = '1.6'>



文件使用了OBJ meshes,这是从Gazebo 7.4.0才可支持,使用gazebo -v发现我的gazebo版本是 7.0


报错 [joint_state_publisher-3] process has died 或者 UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position xxx ordinal

去掉urdf、xacro、launch文件中的中文注释,或者改为英文,而且第一行不能有中文。


1
2
3
4
<plugin name='front_right_middle_sonar_sensor' filename='libgazebo_ros_range.so'>
<topicName>/prius/front_sonar/right_middle_range</topicName>
<frameName>front_right_middle_sonar_link</frameName>
</plugin>

.urdf.xacro文件中的颜色不识别。需要在文件开头定义颜色值,否则默认不识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<material name="blue">
<color rgba="0.0 0.0 0.8 1.0"/>
</material>

<material name="red">
<color rgba="0.8 0.0 0.0 1.0"/>
</material>

<material name="white">
<color rgba="1.0 1.0 1.0 1.0"/>
</material>

<material name="yellow">
<color rgba="0.8 0.8 0.0 1.0"/>
</material>


运行Gazebo竟然出现错误,Gazebo页面卡住黑屏,在终端出现 Gazebo [Err] [REST.cc:205] Error in REST request

解决方案sudo gedit ~/.ignition/fuel/config.yaml,然后将 url : https://api.ignitionfuel.org注释掉,添加 url: https://api.ignitionrobotics.org


在Gazebo中进行小车仿真的过程中会出现如下警告 The root link base_link has an inertia specified in the URDF, but KDL does not support a root link with an inertia. As a workaround, you can add an extra dummy link to your URDF 该警告的意思是根关节的base_link在urdf中具有惯性参数,但是KDL不支持,建议的解决办法是,增加一个额外的dummy link

这个问题看似很麻烦,实际解决起来是非常简单的,只需添加一个link和一个joint即可:

1
2
3
4
5
6
<link name="dummy">
</link>
<joint name="dummy_joint" type="fixed">
<parent link="dummy"/>
<child link="base_link"/>
</joint>

当然link的名称是可以自己改的


gazebo版本升级以及环境太暗的解决方法


ubuntu18.04 装的Gazebo版本是9,升级方式,但是能不升级就不升级。


使用Gazebo时出现下面报错:

1
2
[spawn_entity.py-5] [ERROR] [1715655287.539237215] [spawn_entity]: Service %s/spawn_entity unavailable. Was Gazebo started with GazeboRosFactory?
[spawn_entity.py-5] [ERROR] [1715655287.540067060] [spawn_entity]: Spawn service failed. Exiting.

1
[ERROR] [gzserver-1]: process has died [pid 239512, exit code 255, cmd 'gzserver -slibgazebo_ros_init.so -slibgazebo_ros_factory.so -slibgazebo_ros_force_system.so'].

这是因为关闭的过程中有些Gazebo的程序没有完全关闭,导致再次启动时,无法正常开启。所以,只要把gazebo的相关程序关闭即可: killall gzserver。 此时,再次启动gazebo,就能正常打开了。

如果担心是自己程序的问题,使用ros2 launch gazebo_ros spawn_entity_demo.launch.py验证是不是Gazebo自身的问题


joint_state_publisher报错 UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 30-36: ordinal not in range(128)

  1. 可能是urdf文件里有中文注释,将其去掉
  2. 如果没有注释,修改两个文件 /opt/ros/melodic/lib/joint_state_publisher_gui/joint_state_publisher/opt/ros/melodic/lib/joint_state_publisher,在开头import sys之后增加
    1
    2
    reload(sys)  
    sys.setdefaultencoding('utf8')

或者修改文件/opt/ros/melodic/lib/python2.7/dist-packages/joint_state_publisher/__init__.py,也是添加

1
2
reload(sys) 
sys.setdefaultencoding("utf-8")

运行spawn_model也可能会同样的报错,到/opt/ros/melodic/lib/python2.7/dist-packages文件夹,然后新建一个sitecustomize.py文件,内容为

1
2
3
4
#coding=utf8
import sys
reload(sys)
sys.setdefaultencoding('utf8')

保存后给文件权限。


Git分支相关的命令

我使用Git几年了,至今还会碰上各种各样的新问题,实在不胜其烦

工具 gitk 可以查看和管理分支

  • origin 远程服务器
  • origin/master 远程分支
  • master 本地分支

origin并不特别,就像分支名 master 在git中没有任何特殊意义一样.当执行git init时,master会作为初始分支的默认名字,因此使得master分支名被广泛使用。而origin是执行git clone时的默认服务器名称,当然可以通过指令git clone -o cat,使得默认服务器名称为cat,而默认远程分支为cat/master

从远程分支check out一个本地分支,该本地分支被称为追踪分支(tracking branch),被追踪的分支被称为上游分支(upstream branch),追踪分支可以理解为是和远程分支有直接关联的本地分支. 如果我们在追踪分支时执行git pull,git会自动知道需要获取和merge的分支的服务器.

feat 分支

在Git 中,feat 分支指的是 ==feature 分支,也叫做功能分支==。它主要用于开发新的功能或特性,确保开发过程不会影响到主干代码的稳定性。

feat 分支是从主开发分支(如 developmain)派生出来的,用于隔离新功能的开发工作。这样做的好处是,多个开发者可以并行地在不同的 feat 分支上开发不同的功能,而互不干扰,提高了开发效率。

工作流程:

  1. 当需要开发一个新功能时,从主分支(比如 develop)创建一个新的 feat 分支。
  2. feat 分支上进行代码开发和修改。
  3. 开发完成后,测试无误,将 feat 分支合并回主分支。
  4. 合并后,feat 分支可以被删除,以保持仓库的整洁。

进一步的解释

拉取远程指定分支

1
2
git remote add origin project_url
git pull origin branch_name

在本地创建分支develop并切换到该分支

1
git checkout -b develop(本地分支名称)  origin/develop (远程分支名称)

远程分支上的内容都拉取到本地

1
2
# develop 为远程分支名称
git pull origin develop (远程分支名称)

回到本地文件夹查看,已完成拉取远程某个分支到本地。拉去更新也是这个命令,如果是git pull,可能会把所有分支的内容都拉下来,用时较长,没有必要。

提交到某分支

1
git push origin 本地分支名:远程分支名

查看分支和合并

git branch -a list both remote-tracking and local branches

git branch -vv 会列出所有的分支信息,包含追踪分支的关联关系

常见的情况是,我自己有一个分支me,还要拉取另一个人的分支,两人共用部分文件,合并到分支master。在自己分支修改文件结束后,提交到自己的分支。然后切换到master分支,git checkout master。由于不知道另一个人是否更新了代码,所以先执行git pull更新一下,然后git merge me,这就合并了我的分支,再git push origin master

有时会出现冲突,这是由于两人修改了同一个文件,冲突的提示:

1
2
3
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

git status 也可以告诉我们冲突的文件. 在冲突的文件里,Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,只能自己手动修改,然后再提交。解决冲突的过程一直是在master分支。 用带参数的git log也可以看到分支的合并情况

将user分支合并到master分支

1
2
3
4
5
6
7
8
9
# 当前是user分支
git add test.yaml
git commit -m ":wrench:"
git push origin user
# 切换到 master 分支
git checkout master
git merge user
git push origin master
git checkout user

删除本地分支

git branch -d user

1
2
3
4
5
6
user@user:~/dev_ws/$ git branch -d user
error: The branch 'user' is not fully merged.
If you are sure you want to delete it, run 'git branch -D user'.

user@user:~/dev_ws/$ git branch -D user
Deleted branch user (was cf64937b)

submodule

一个工程包含一个module,两部分的分支可能是不同的。先把工程拉取下来后,看看隐藏文件.gitmodules中的url和分支是否正确,此时的module部分应该还没有更新,使用下面的命令更新

1
2
git submodule init
git submodule update

本地文件修改后,执行git pull出现冲突

执行git pull出现下面提示

1
2
Please commit your changes or stash them before you merge.
Aborting

这是因为本地修改了一些文件,但远程仓库已经有别人提交更新了,所以有冲突。一般不会直接commit本地的修改。

  1. 放弃本地修改的改法 (这种方法会丢弃本地修改的代码,而且不可找回!)
1
2
git reset --hard
git pull

将最新的主分支更新到个人分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 当前在个人分支
git add . # 将本地修改文件加入暂存区
git commit -m "修改内容" # 提交日志

git checkout master # 切换到主分支(建议操作到这里的时候利用git branch
git pull # 将本地主分支代码更新
git checkout self-branch # 切换到自分支

git merge master # 将主分支代码合并更新到自己分支

git push # 提交到自分支远程端

git checkout master # 切换到主分支

git merge self-branch # 将自己代码合并更新到本地主分支master

git push # 将本地代码推到远程主分支master上