├── .github └── FUNDING.yml ├── LICENSE ├── cyber ├── design │ └── readme.md ├── distributed │ └── readme.md ├── img │ ├── arch.jpg │ ├── center.jpg │ ├── classloader.jpg │ ├── data_progress.jpg │ ├── design.jpg │ ├── heap.jpg │ ├── linklist.jpg │ ├── master_node.jpg │ ├── node.jpg │ ├── requirements.jpg │ ├── schedule_main.jpg │ ├── schedule_timeline.jpg │ ├── scheduler_chor.jpg │ ├── scheduler_classic.jpg │ ├── scheduler_data.jpg │ ├── timer_task.jpg │ ├── timing_wheel.jpg │ ├── timing_wheel_multi.jpg │ └── timing_wheel_progress.jpg ├── readme.md └── source │ ├── classloader.md │ ├── croutine.md │ ├── dds.md │ ├── io.md │ ├── readme.md │ ├── record.md │ ├── service.md │ ├── tools.md │ └── transport.md ├── docker └── readme.md ├── docs ├── Makefile ├── make.bat └── source │ ├── conf.py │ └── index.rst ├── how_to_build └── readme.md ├── library └── readme.md ├── modules ├── audio │ ├── img │ │ └── main.jpg │ └── readme.md ├── bridge │ └── readme.md ├── canbus │ ├── img │ │ ├── canbus.jpg │ │ ├── canclient.jpg │ │ ├── chassis.jpg │ │ ├── factory.jpg │ │ ├── input.jpg │ │ ├── main.jpg │ │ └── process.jpg │ └── readme.md ├── control │ ├── img │ │ ├── PID.jpg │ │ ├── PID_varyingP.jpg │ │ ├── agent.jpg │ │ ├── estop.jpg │ │ ├── input.jpg │ │ ├── lon.jpg │ │ └── table.png │ ├── readme.md │ └── todo.md ├── data │ └── readme.md ├── dreamview │ ├── readme.md │ └── todo.md ├── drivers │ ├── camera │ │ └── readme.md │ ├── canbus │ │ └── readme.md │ ├── gnss │ │ └── readme.md │ ├── hesai │ │ └── readme.md │ ├── img │ │ └── drivers.jpg │ ├── microphone │ │ └── readme.md │ ├── radar │ │ └── readme.md │ ├── readme.md │ ├── todo.md │ ├── velodyne │ │ └── readme.md │ └── video │ │ └── readme.md ├── guardian │ └── readme.md ├── localization │ ├── img │ │ └── location_rtk.jpg │ ├── msf │ │ └── readme.md │ ├── ndt │ │ └── readme.md │ ├── readme.md │ └── todo.md ├── map │ ├── img │ │ ├── ClearArea.jpg │ │ ├── ParkingSpace.jpg │ │ ├── Road.jpg │ │ ├── Sidewalk.jpg │ │ ├── Signal.jpg │ │ ├── SpeedBump.jpg │ │ ├── StopSign.jpg │ │ ├── YieldSign.jpg │ │ ├── crosswalk.jpg │ │ ├── hardware.jpg │ │ ├── junction.jpg │ │ ├── lane.jpg │ │ └── register.jpg │ ├── readme.md │ └── todo.md ├── monitor │ └── readme.md ├── perception │ ├── caffe2 │ │ └── readme.md │ ├── camera │ │ └── readme.md │ ├── cnn │ │ └── readme.md │ ├── detection_component.md │ ├── fusion │ │ └── readme.md │ ├── img │ │ ├── Convolution_of_spiky_function_with_box2.gif │ │ ├── attention.png │ │ ├── cnn.png │ │ ├── cnn_qa.jpg │ │ ├── conv_1.png │ │ ├── convolution.gif │ │ ├── cuda_version.png │ │ ├── cuda_version2.png │ │ ├── edge_detection.PNG │ │ ├── fully_connect.png │ │ ├── imageNet.PNG │ │ ├── image_caption.PNG │ │ ├── imgsee.jpg │ │ ├── maxpool.jpeg │ │ ├── nvidia_drivers.png │ │ ├── object_detection.PNG │ │ ├── pascal.png │ │ ├── perception_process.jpg │ │ ├── question.PNG │ │ ├── radar_process.jpg │ │ ├── semantic.PNG │ │ ├── sensor.jpg │ │ ├── understanding.PNG │ │ └── weights.jpeg │ ├── inference │ │ └── readme.md │ ├── lidar │ │ └── readme.md │ ├── radar │ │ └── readme.md │ ├── readme.md │ └── todo.md ├── planning │ ├── img │ │ ├── Planner.png │ │ ├── create_ref_line.jpg │ │ ├── dataflow.png │ │ ├── flowchart.png │ │ ├── planning_base.png │ │ ├── planning_component.png │ │ ├── planning_flow.png │ │ ├── reference_line.jpg │ │ ├── rf_line_point.jpg │ │ └── task.png │ ├── readme.md │ ├── reference_line │ │ └── readme.md │ └── todo.md ├── prediction │ ├── img │ │ ├── container.jpg │ │ ├── obs_process.jpg │ │ └── process.jpg │ ├── readme.md │ └── todo.md ├── routing │ ├── img │ │ ├── 30px-Osm_element_area.svg.png │ │ ├── 30px-Osm_element_closedway.svg.png │ │ ├── 30px-Osm_element_node.svg.png │ │ ├── 30px-Osm_element_relation.svg.png │ │ ├── 30px-Osm_element_tag.svg.png │ │ ├── 30px-Osm_element_way.svg.png │ │ ├── 375px-Shortest_path_with_direct_weights.svg.png │ │ ├── black_map.jpg │ │ ├── edge_cost.png │ │ ├── graph.jpg │ │ ├── introduction.png │ │ ├── lane.png │ │ ├── main.jpg │ │ ├── range.jpg │ │ └── range_rank.jpg │ ├── readme.md │ └── todo.md ├── tools │ ├── readme.md │ └── todo.md ├── transform │ ├── img │ │ ├── all.jpg │ │ ├── frames2.png │ │ └── transform.jpg │ ├── readme.md │ └── todo.md └── v2x │ ├── img │ └── v2x_proccess.jpg │ └── readme.md ├── papers └── readme.md ├── performance ├── Gregg4.svg └── readme.md ├── questions ├── readme.md └── todo.md ├── readme.md ├── simulation ├── img │ ├── Robot.png │ ├── carsh.png │ ├── docker.jpg │ ├── how.jpg │ ├── map.jpg │ ├── map_edit.jpg │ ├── sence.jpg │ ├── sences.png │ ├── simulation.jpg │ ├── simulator.jpg │ ├── summary.jpg │ ├── test_case.jpg │ ├── traffic.png │ └── train.jpeg ├── readme.md └── todo.md └── what_is_apollo └── readme.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: Dig-into-Apollo # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 daohu527 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cyber/distributed/readme.md: -------------------------------------------------------------------------------- 1 | 参考 https://github.com/lgsvl/apollo-3.5 2 | 3 | ## 容器内部python运行 4 | 1. 在apollo目录下 5 | ``` 6 | source cyber/setup.bash 7 | ``` 8 | 9 | 2. 执行 10 | ``` 11 | python cyber/python/examples/listener.py 12 | python cyber/python/examples/talker.py 13 | ``` 14 | 15 | 16 | 17 | ## 容器外部执行 18 | 1. python 环境变量 19 | 20 | ``` 21 | export PYTHONPATH=$PYTHONPATH:/media/data/k8s/apollo 22 | export PYTHONPATH=$PYTHONPATH:/media/data/k8s/apollo/bazel-bin/cyber/py_wrapper 23 | ``` 24 | 25 | 3. 环境变量 26 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 27 | 28 | 2. 安装Fast-RTPS 29 | 直接拷贝so也可以 30 | /usr/local/fast-rtps/lib/libfastcdr.so.1 31 | /usr/local/fast-rtps/lib/libfastrtps.so.1 32 | 33 | 4. 拷贝so 34 | 在容器里面 35 | cp /usr/local/lib/libglog.so.0 . 36 | cp /usr/local/lib/libgflags.so.2.2 . 37 | cp /usr/lib/libprotobuf.so.17 . 38 | cp /usr/lib/libPocoFoundation.so.9 . 39 | 40 | 41 | 42 | ## 拷贝到另外的机器 43 | 1. 设置机器IP 44 | A机器 在setup.bash中设置 export CYBER_IP=192.168.1.101 45 | B机器 在setup.bash中设置 export CYBER_IP=192.168.1.102 46 | 47 | 2. 拷贝文件和依赖库 48 | 192.168.1.101 49 | 在目录 /home/k8s/apollo/PythonClient 50 | 51 | 3. 设置环境变量 52 | source setup.bash 53 | 54 | export PYTHONPATH=$PYTHONPATH:/home/k8s/apollo/bazel-bin/cyber/py_wrapper 55 | export PYTHONPATH=$PYTHONPATH:/home/k8s/apollo/py_proto 56 | 57 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 58 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/k8s/apollo/bazel-bin 59 | 60 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/k8s/apollo/third_party/tf2/lib/ 61 | 62 | 63 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/k8s/apollo/bazel-bin/cyber/py_wrapper/_cyber_init.so.runfiles/apollo/_solib_k8/ 64 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/k8s/apollo/bazel-bin/cyber/py_wrapper/_cyber_node.so.runfiles/apollo/_solib_k8 65 | 66 | 67 | export PYTHONPATH=$PYTHONPATH:/home/k8s/apollo/third_party 68 | 69 | sudo pip install protobuf 70 | 71 | ## 运行并且测试 72 | #### A机器 73 | python client_example.py --host 192.168.1.102 74 | #### B机器 75 | 1. 启动carla ./CarlaUE4.sh -carla-server 76 | 2. 容器内部执行 python listener.py 77 | 78 | 79 | ## TODO 80 | 适配carla和apollo的参数,并且对接发布出去。 81 | 82 | 83 | # dreamview 84 | 85 | 1. bag转record 86 | ``` 87 | source /your-path-to-apollo-install-dir/cyber/setup.bash 88 | rosbag_to_record input.bag output.record 89 | ``` 90 | 91 | [参考](https://github.com/ApolloAuto/apollo/blob/master/docs/cyber/CyberRT_Developer_Tools.md) 92 | 93 | 2. record播放 94 | ``` 95 | ./bazel-bin/modules/dreamview/backend/simulation_command/sim_cmd 96 | cyber_recorder play -f out.record -s 10 -c /apollo/canbus/chassis /apollo/localization/pose /apollo/sensor/gnss/odometry 97 | ``` 98 | 99 | 3. HMI status 100 | HMI status一直保存在内存中? 101 | -------------------------------------------------------------------------------- /cyber/img/arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/arch.jpg -------------------------------------------------------------------------------- /cyber/img/center.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/center.jpg -------------------------------------------------------------------------------- /cyber/img/classloader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/classloader.jpg -------------------------------------------------------------------------------- /cyber/img/data_progress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/data_progress.jpg -------------------------------------------------------------------------------- /cyber/img/design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/design.jpg -------------------------------------------------------------------------------- /cyber/img/heap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/heap.jpg -------------------------------------------------------------------------------- /cyber/img/linklist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/linklist.jpg -------------------------------------------------------------------------------- /cyber/img/master_node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/master_node.jpg -------------------------------------------------------------------------------- /cyber/img/node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/node.jpg -------------------------------------------------------------------------------- /cyber/img/requirements.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/requirements.jpg -------------------------------------------------------------------------------- /cyber/img/schedule_main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/schedule_main.jpg -------------------------------------------------------------------------------- /cyber/img/schedule_timeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/schedule_timeline.jpg -------------------------------------------------------------------------------- /cyber/img/scheduler_chor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/scheduler_chor.jpg -------------------------------------------------------------------------------- /cyber/img/scheduler_classic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/scheduler_classic.jpg -------------------------------------------------------------------------------- /cyber/img/scheduler_data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/scheduler_data.jpg -------------------------------------------------------------------------------- /cyber/img/timer_task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/timer_task.jpg -------------------------------------------------------------------------------- /cyber/img/timing_wheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/timing_wheel.jpg -------------------------------------------------------------------------------- /cyber/img/timing_wheel_multi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/timing_wheel_multi.jpg -------------------------------------------------------------------------------- /cyber/img/timing_wheel_progress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/cyber/img/timing_wheel_progress.jpg -------------------------------------------------------------------------------- /cyber/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Cyber ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 凡学之不勤,必其志之未笃也。 4 | 5 | Cyber是百度Apollo推出的替代Ros的消息中间件,自动驾驶中的各个模块通过Cyber进行消息订阅和发布,同时Cyber还提供了任务调度、录制Bag包等功能。通过Cyber实现了自动驾驶中间层,这里分为几小结分别对Cyber进行详细的分析。 6 | 7 | ## Table of Contents 8 | - [Cyber源码分析](source) 9 | - [Cyber设计思想](design) 10 | - [Cyber分布式部署](distributed) 11 | -------------------------------------------------------------------------------- /cyber/source/classloader.md: -------------------------------------------------------------------------------- 1 | ## ClassLoader 2 | 每个ClassLoader加载一个路径,每个路径代表一个so库?load_ref_count_代表加载library引用计数。 3 | 4 | plugin_ref_count_代表创建类的引用计数? 5 | 6 | ## loadLibrary 7 | ```c++ 8 | void ClassLoader::loadLibrary() 9 | { 10 | boost::recursive_mutex::scoped_lock lock(load_ref_count_mutex_); 11 | // 每次引用计数加1 12 | load_ref_count_ = load_ref_count_ + 1; 13 | // 加载库 14 | class_loader::impl::loadLibrary(getLibraryPath(), this); 15 | } 16 | ``` 17 | 18 | ## isLibraryLoaded 19 | 判断Library是否加载 20 | 21 | ```c++ 22 | bool ClassLoader::isLibraryLoaded() 23 | { 24 | return class_loader::impl::isLibraryLoaded(getLibraryPath(), this); 25 | } 26 | ``` 27 | 28 | ## isLibraryLoadedByAnyClassloader 29 | 判断Library是否被其它的classloader加载 30 | 31 | ```c++ 32 | bool ClassLoader::isLibraryLoadedByAnyClassloader() 33 | { 34 | return class_loader::impl::isLibraryLoadedByAnybody(getLibraryPath()); 35 | } 36 | ``` 37 | 38 | ## systemLibraryFormat 39 | 获取库的名称? 40 | 41 | ```c++ 42 | std::string systemLibraryFormat(const std::string & library_name) 43 | { 44 | return systemLibraryPrefix() + library_name + systemLibrarySuffix(); 45 | } 46 | ``` 47 | 48 | ## unloadLibrary 49 | 50 | 卸载Library,先判断是否plugin_ref_count_,即是否有创建的类没有销毁,如果没有销毁则不能卸载,如果类已经销毁了,需要加载次数清零,保证加载和卸载次数相等。 51 | 52 | 53 | ## 创建类 54 | ```c++ 55 | template 56 | std::shared_ptr createSharedInstance(const std::string & derived_class_name) 57 | { 58 | return std::shared_ptr( 59 | createRawInstance(derived_class_name, true), 60 | boost::bind(&ClassLoader::onPluginDeletion, this, _1)); 61 | } 62 | ``` 63 | 64 | 65 | ## registerPlugin 66 | 申明静态变量`g_register_plugin_UniqueID` 67 | 68 | 注册类,这里是注册派生类到factoryMap,通过派生类可以找到对应的MetaObject,创建的时候创建派生类。 69 | ```c++ 70 | // Create factory 71 | impl::AbstractMetaObject * new_factory = 72 | new impl::MetaObject(class_name, base_class_name); 73 | new_factory->addOwningClassLoader(getCurrentlyActiveClassLoader()); 74 | new_factory->setAssociatedLibraryPath(getCurrentlyLoadingLibraryName()); 75 | 76 | FactoryMap & factoryMap = getFactoryMapForBaseClass(); 77 | factoryMap[class_name] = new_factory; 78 | ``` 79 | 80 | 81 | ## 总结 82 | 1. 首先通过宏定义注册派生类和基类 83 | ```c++ 84 | CLASS_LOADER_REGISTER_CLASS(Derived, Base) 85 | ``` 86 | 2. 注册的派生类和基类会注册到哈希表,key为派生类的名称,value为MetaObject,创建的时候会创建派生类。 87 | 88 | AbstractMetaObjectBase 对象有一个ClassLoaderVector,是一个加载器数组。 AbstractMetaObjectBase抽象类可以对应多个classloader??? 89 | 90 | 91 | 一个ClassLoader对应一个library_path,同时还有引用计数和实例化计数。创建了多少个对象则有多少个plugin_ref_count_,每个对象析构的时候会引用计数plugin_ref_count_减去1。 92 | 同一个类可能被不同的ClassLoader加载,加载的路径当然也不一样,然后把classloader放到一个数组中。 93 | 94 | 95 | ## 程序链接几种方式 96 | 1. 静态链接 97 | 2. 动态链接 98 | 3. 动态加载 99 | 100 | 前2种在编译阶段就要指定,后1种是程序运行过程中动态加载到内存。有插件的系统都采用动态加载的方式来设计。 101 | 102 | 103 | ## 工作原理 104 | 105 | #### linux 106 | dlopen dlsysm dlclose 107 | 108 | #### ELF文件 109 | 文件格式 110 | INIT FIN 111 | ctor 112 | 符号表 113 | 114 | 通过nm命令查看 115 | readelf 116 | 117 | ## 加载原理 118 | 119 | #### extern c 120 | 这样符号表的函数名称不会变化。 121 | 122 | #### 静态变量 123 | 静态变量加载的时候会动态初始化 124 | 1. elf文件中的init和ctor段,也可以用`__attribute__ `宏来实现启动的时候就执行,然后再把控制权交给main函数 125 | 126 | 全部类继承一个基类,实现了new object方法,然后再注册工厂类到全局变量,这样就可以创建变量了。 127 | 128 | 129 | #### 注册 130 | 1. 注册通过宏定义实现 131 | 2. 加载的时候动态构造,并且注册到全局变量 132 | 3. 类实现了创建方法 133 | 134 | #### 创建 135 | 1. 找到对应的类,然后调用创建方法 136 | 2. 每次销毁的时候引用计数减1 137 | 138 | #### 加载 139 | 采用dlopen加载,可以反复加载,但是返回同一个指针,卸载的时候次数相等?classloader对原生重复加载做了拦截? 140 | 141 | #### 卸载 142 | 1. 判断是否有引用计数,有对象存活则不能卸载 143 | 2. 没有对象存活,则卸载库,这里的引用计数看起来没什么用(采用bool型就可以解决)? 144 | 145 | 146 | 总之上述2种方法都是找一个绝对的物理地址去调用,函数在代码段,通过名称去调用,静态变量在数据段,然后去调用。 147 | 148 | 149 | ## 静态变量作用域 150 | 1. file scope只作用在单个文件,全局变量的作用域,需要加上extern 151 | 2. 函数中的static变量作用域只在函数可见,并且在函数调用的时候初始化 152 | 3. static变量在动态库卸载的时候会清零,再次加载的时候会重新初始化 (apollo中和ros中的区别,需要做一些实验去验证是否正确)为什么函数中的全局变量没有被卸载?? 153 | 4. static变量的生命周期,创建一直保存到程序结束,对动态加载程序创建的变量好像是卸载的时候删除? 154 | 5. static变量线程安全,c++11之后是线程安全的 155 | 156 | -------------------------------------------------------------------------------- /cyber/source/croutine.md: -------------------------------------------------------------------------------- 1 | ## croutine 2 | 3 | 4 | # libco 5 | libco协程库分析 6 | 7 | ## 目录 8 | co_closure.h 定义了一些函数宏,具体的作用是什么? 9 | co_comm.h 可重入锁,lockguard 10 | co_epoll.h 网络接口 11 | co_hook_sys_call.cpp hook系统调用 12 | co_routine_inner.h 13 | co_routine_specific.h 14 | 15 | 16 | coctx_swap.S 栈切换 17 | co_routine.h 18 | coctx.h 19 | 20 | 21 | ## co_create 22 | 创建协程 23 | ```c++ 24 | int co_create( stCoRoutine_t **ppco,const stCoRoutineAttr_t *attr,pfn_co_routine_t pfn,void *arg ) 25 | { 26 | if( !co_get_curr_thread_env() ) 27 | { 28 | co_init_curr_thread_env(); 29 | } 30 | stCoRoutine_t *co = co_create_env( co_get_curr_thread_env(), attr, pfn,arg ); 31 | *ppco = co; 32 | return 0; 33 | } 34 | ``` 35 | 36 | ## co_resume 37 | ```c++ 38 | void co_resume( stCoRoutine_t *co ) 39 | { 40 | stCoRoutineEnv_t *env = co->env; 41 | stCoRoutine_t *lpCurrRoutine = env->pCallStack[ env->iCallStackSize - 1 ]; 42 | if( !co->cStart ) 43 | { 44 | coctx_make( &co->ctx,(coctx_pfn_t)CoRoutineFunc,co,0 ); 45 | co->cStart = 1; 46 | } 47 | env->pCallStack[ env->iCallStackSize++ ] = co; 48 | co_swap( lpCurrRoutine, co ); 49 | 50 | 51 | } 52 | ``` 53 | 54 | ## co_yield 55 | ```c++ 56 | void co_yield_env( stCoRoutineEnv_t *env ) 57 | { 58 | 59 | stCoRoutine_t *last = env->pCallStack[ env->iCallStackSize - 2 ]; 60 | stCoRoutine_t *curr = env->pCallStack[ env->iCallStackSize - 1 ]; 61 | 62 | env->iCallStackSize--; 63 | 64 | co_swap( curr, last); 65 | } 66 | ``` 67 | 68 | ## co_release 69 | ```c++ 70 | void co_release( stCoRoutine_t *co ) 71 | { 72 | co_free( co ); 73 | } 74 | ``` -------------------------------------------------------------------------------- /cyber/source/dds.md: -------------------------------------------------------------------------------- 1 | ## DDS协议介绍 -------------------------------------------------------------------------------- /cyber/source/io.md: -------------------------------------------------------------------------------- 1 | 主要是理解epoll实现,以及为什么epoll会快???顺便了解下select的实现? 2 | 3 | ## Session 4 | 5 | 6 | ## Poller 7 | 8 | 9 | ## PollHandler 10 | 11 | -------------------------------------------------------------------------------- /cyber/source/record.md: -------------------------------------------------------------------------------- 1 | record主要用来持久化topic数据,下面我们主要分析下如何保存record数据,在分析之前心中有如下几个问题。 2 | 1. record的文件格式是什么样?是否可以压缩成更小的格式 3 | 2. 订阅多个通道时候,如何找到多个通道,如何保证时间顺序? 4 | 3. 持久化数据的时候,数据带宽不够的时候,如何保证写入磁盘呢,采用增加缓存的方式? 5 | 4. 如何设置数据的缓冲区大小?? -------------------------------------------------------------------------------- /cyber/source/service.md: -------------------------------------------------------------------------------- 1 | ## service介绍 2 | -------------------------------------------------------------------------------- /cyber/source/tools.md: -------------------------------------------------------------------------------- 1 | ## cyber组件启动 2 | cyber_launch主要用来启动cyber模块,其中一个launch文件可以有一个或者多个module,每个module 包含一个dag文件,而一个dag文件则对应一个或者多个components。 3 | launch文件中有几个module则会启动几个*进程*,每个进程有单独的内存空间,比如静态变量等都不会共享。 4 | 一个dag文件中可以有一个或者多个components,一个components对应一个协程。协程中的静态变量是共享的,并且全局唯一。 5 | 6 | 理解了上述原理,我们再详细分析以下2种启动方式。 7 | 1. cyber_launch 8 | 2. dreamview 9 | 实际上上述2种启动方式没有太大区别,都是通过`mainboard`来启动程序,而一个dag文件对应一个进程,接着根据dag文件中有几个components,生成几个协程,每个components对应一个协程。 10 | 11 | 12 | 下面介绍下cyber_launch的文件结构。 13 | ## cyber_launch 14 | ``` 15 | 16 | 17 | planning \\ module名称 18 | /apollo/modules/planning/dag/planning.dag \\ module的dag文件 19 | planning \\ 指定调度文件 20 | 21 | 22 | ``` 23 | * module 用于区分模块 24 | * name 模块名称,主要用来cyber_launch启动的时候显示名称 25 | * dag_conf module模块对应的dag文件 26 | * process_name 指定module的调度文件,如果找不到则会提示 27 | 28 | 29 | ## dag 30 | 下面以`velodyne.dag`文件为例来进行说明。 31 | ``` 32 | module_config { 33 | module_library : "/apollo/bazel-bin/modules/drivers/lidar/velodyne/driver/libvelodyne_driver_component.so" 34 | // 模块的so文件,用于加载到内存 35 | # 128 36 | components { // 第1个组件 37 | class_name : "VelodyneDriverComponent" 38 | config { 39 | name : "velodyne_128_driver" // 名称必须不一样 40 | // 配置文件 41 | config_file_path : "/apollo/modules/drivers/lidar/velodyne/conf/velodyne128_conf.pb.txt" 42 | } 43 | } 44 | # 16_front_up 45 | components { // 第2个组件 46 | class_name : "VelodyneDriverComponent" 47 | config { 48 | name : "velodyne_16_front_up_driver" 49 | config_file_path : "/apollo/modules/drivers/lidar/velodyne/conf/velodyne16_front_up_conf.pb.txt" 50 | } 51 | } 52 | } 53 | 54 | module_config { 55 | module_library : "/apollo/bazel-bin/modules/drivers/lidar/velodyne/parser/libvelodyne_convert_component.so" 56 | // 模块的so文件,用于加载到内存 57 | # 128 58 | components { 59 | class_name : "VelodyneConvertComponent" 60 | config { 61 | name : "velodyne_128_convert" 62 | config_file_path : "/apollo/modules/drivers/lidar/velodyne/conf/velodyne128_conf.pb.txt" 63 | readers { 64 | channel: "/apollo/sensor/lidar128/Scan" 65 | } 66 | } 67 | } 68 | # 16_front_up_center 69 | components { 70 | class_name : "VelodyneConvertComponent" 71 | config { 72 | name : "velodyne_16_front_up_convert" 73 | config_file_path : "/apollo/modules/drivers/lidar/velodyne/conf/velodyne16_front_up_conf.pb.txt" 74 | readers { 75 | channel: "/apollo/sensor/lidar16/front/up/Scan" 76 | } 77 | } 78 | } 79 | } 80 | ``` 81 | dag文件有一个或者多个module_config,而每个module_config中对应一个或者多个components。 82 | 83 | 84 | ## cyber_launch.py分析 85 | 86 | #### start 87 | 在start函数中会遍历module,然后通过 88 | ```python 89 | for module in root.findall('module'): 90 | if process_name not in process_list: 91 | if process_type == 'binary': 92 | if len(process_name) == 0: 93 | logger.error( 94 | 'Start binary failed. Binary process_name is null.') 95 | continue 96 | pw = ProcessWrapper( 97 | process_name.split()[0], 0, [ 98 | ""], process_name, process_type, 99 | exception_handler) 100 | # Default is library 101 | else: 102 | pw = ProcessWrapper( 103 | g_binary_name, 0, dag_dict[ 104 | str(process_name)], process_name, 105 | process_type, sched_name, exception_handler) 106 | result = pw.start() 107 | if result != 0: 108 | logger.error( 109 | 'Start manager [%s] failed. Stop all!' % process_name) 110 | stop() 111 | pmon.register(pw) 112 | process_list.append(process_name) 113 | ``` 114 | 而ProcessWrapper中通过`subprocess.Popen`启动新的进程。 115 | ```python 116 | self.popen = subprocess.Popen(args_list, stdout=subprocess.PIPE, 117 | stderr=subprocess.STDOUT) 118 | ``` 119 | 120 | 121 | #### stop 122 | stop实际上是通过找到对应的launch文件,并且杀掉对应的进程来实现。(kill对应的是PID,pkill对应的是command) 123 | ```python 124 | def stop_launch(launch_file): 125 | """ 126 | Stop the launch file 127 | """ 128 | if not launch_file: 129 | cmd = 'pkill -INT cyber_launch' # 杀掉对应的进程 130 | else: 131 | cmd = 'pkill -INT -f ' + launch_file # 杀掉对应的进程 132 | 133 | os.system(cmd) 134 | time.sleep(3) 135 | logger.info('Stop cyber launch finished.') 136 | sys.exit(0) 137 | ``` 138 | 139 | 140 | ## mainboard 141 | mainboard通过LoadModule来加载模块,而`module_config`来指定加载的so文件,然后启动一个或者多个components,每个components的配置文件可以不一样。 142 | ```c++ 143 | bool ModuleController::LoadModule(const DagConfig& dag_config) { 144 | const std::string work_root = common::WorkRoot(); 145 | 146 | for (auto module_config : dag_config.module_config()) { 147 | std::string load_path; 148 | if (module_config.module_library().front() == '/') { 149 | load_path = module_config.module_library(); 150 | } else { 151 | load_path = 152 | common::GetAbsolutePath(work_root, module_config.module_library()); 153 | } 154 | 155 | if (!common::PathExists(load_path)) { 156 | AERROR << "Path does not exist: " << load_path; 157 | return false; 158 | } 159 | // 1. 加载模块的so文件 160 | class_loader_manager_.LoadLibrary(load_path); 161 | 162 | // 2.1 加载触发模块 163 | for (auto& component : module_config.components()) { 164 | const std::string& class_name = component.class_name(); 165 | std::shared_ptr base = 166 | class_loader_manager_.CreateClassObj(class_name); 167 | if (base == nullptr || !base->Initialize(component.config())) { 168 | return false; 169 | } 170 | component_list_.emplace_back(std::move(base)); 171 | } 172 | 173 | // 2.2 加载定时模块 174 | for (auto& component : module_config.timer_components()) { 175 | const std::string& class_name = component.class_name(); 176 | std::shared_ptr base = 177 | class_loader_manager_.CreateClassObj(class_name); 178 | if (base == nullptr || !base->Initialize(component.config())) { 179 | return false; 180 | } 181 | component_list_.emplace_back(std::move(base)); 182 | } 183 | } 184 | return true; 185 | } 186 | ``` 187 | 188 | 189 | ## dreamview 190 | dreamview通过界面上的滑动按钮打开和关闭模块,大概的流程是前端通过websocket发送消息到后端,后端通过调用命令行启动模块,后端主要的实现在"hmi_worker.cc"中。 191 | 192 | dreamview中的modules通过指定"start_command"和"stop_command"来启动和关闭。 193 | 194 | #### LoadMode 195 | 对于so文件,会通过LoadMode来进行生成命令,下面介绍下生成"start_command"和"stop_command"的过程。生成的命令和cyber_launch中调用的命令一致,只是前面增加了`nohup`。 196 | ```c++ 197 | HMIMode HMIWorker::LoadMode(const std::string& mode_config_path) { 198 | HMIMode mode; 199 | ACHECK(cyber::common::GetProtoFromFile(mode_config_path, &mode)) 200 | << "Unable to parse HMIMode from file " << mode_config_path; 201 | // Translate cyber_modules to regular modules. 202 | for (const auto& iter : mode.cyber_modules()) { 203 | const std::string& module_name = iter.first; 204 | const CyberModule& cyber_module = iter.second; 205 | // Each cyber module should have at least one dag file. 206 | ACHECK(!cyber_module.dag_files().empty()) 207 | << "None dag file is provided for " << module_name << " module in " 208 | << mode_config_path; 209 | 210 | Module& module = LookupOrInsert(mode.mutable_modules(), module_name, {}); 211 | module.set_required_for_safety(cyber_module.required_for_safety()); 212 | 213 | // 1. Construct start_command: 214 | // nohup mainboard -p -d ... & 215 | module.set_start_command("nohup mainboard"); 216 | const auto& process_group = cyber_module.process_group(); 217 | if (!process_group.empty()) { 218 | absl::StrAppend(module.mutable_start_command(), " -p ", process_group); 219 | } 220 | for (const std::string& dag : cyber_module.dag_files()) { 221 | absl::StrAppend(module.mutable_start_command(), " -d ", dag); 222 | } 223 | absl::StrAppend(module.mutable_start_command(), " &"); 224 | 225 | // 2. Construct stop_command: pkill -f '' 226 | const std::string& first_dag = cyber_module.dag_files(0); 227 | module.set_stop_command(absl::StrCat("pkill -f \"", first_dag, "\"")); 228 | // Construct process_monitor_config. 229 | module.mutable_process_monitor_config()->add_command_keywords("mainboard"); 230 | module.mutable_process_monitor_config()->add_command_keywords(first_dag); 231 | } 232 | mode.clear_cyber_modules(); 233 | return mode; 234 | } 235 | ``` 236 | 接着调用`void HMIWorker::StartModule(const std::string& module) const`来启动模块,启动调用的`std::system()`来启动命令行,也是一个dag文件对应一个进程。dag中的模块都对应协程。 237 | 238 | 239 | ## 用途 240 | 如何启动2个一模一样的模块 241 | 1. 如果你需要启动2个相同的模块,可以在launch文件中增加2个module,并且修改dag文件中模块的名称,这样就可以启动2个一模一样的模块了。上述这种情况在模块中有静态变量,并且你希望静态变量不相互影响的情况下可以采用。 242 | 2. 如果需要在同一个进程中启动2个模块,可以在dag中增加components,然后修改config中的名称和配置文件,这样就可以启动2个一模一样的模块了。上述这种情况静态变量会相互影响,A模块改了,B模块的静态变量值也改变了。 243 | 244 | perception中的inner消息订阅不到? 245 | perception模块中的inner消息只有在同一个进程中才会收到,也就是在同一个dag文件中启动的模块才会接收到,因为reader读取的时候会判断2个节点是否是在同一个进程,如果是同一个进程就直接传递对象,不进行序列化,如果不是同一个进程则先进行序列化,然后再通过共享内存通信。 246 | -------------------------------------------------------------------------------- /cyber/source/transport.md: -------------------------------------------------------------------------------- 1 | ## transport传输 2 | 分为Transmitter和Receiver 3 | 4 | ## Transmitter 5 | Transmitter派生了4种类型"ShmTransmitter","RtpsTransmitter","IntraTransmitter","HybridTransmitter"。 6 | 7 | 8 | 9 | ## SegmentFactory::CreateSegment 10 | 创建通道的内存分区? 11 | 12 | 13 | ## transceiver 14 | 接收发送测试程序。 15 | -------------------------------------------------------------------------------- /docker/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Docker ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 橘生淮南则为橘,生于淮北则为枳。 4 | 5 | ## Table of Contents 6 | - [docker编译](#docker_build) 7 | - [docker脚本](#docker_script) 8 | - [设置主机](#setup_host) 9 | 10 | 11 | docker的主要的好处是开箱即用,在编译docker的时候安装好需要的环境,在使用的时候就无需担心环境问题带来的影响了。下面我们主要分析下docker文件夹中的脚本,主要涉及docker的编译、启动、以及host相关的内容。 12 | 13 | 14 | 15 | ## docker编译 16 | 编译docker在目录中"apollo\docker\build"中,执行的命令为 17 | ``` 18 | ./build_dev.sh ./dev.x86_64.dockerfile 19 | ``` 20 | 在"build_dev.sh"脚本中会执行编译docker的工作,下面我们分析下docker的编译过程。 21 | 22 | #### build_dev.sh 23 | ``` 24 | # Usage: 25 | # ./build_dev.sh ./dev.x86_64.dockerfile 26 | # 1. 获取dockerfile名称,这里为第一个参数 27 | DOCKERFILE=$1 28 | 29 | # 2. 获取build_dev.sh的目录路径 30 | CONTEXT="$(dirname "${BASH_SOURCE[0]}")" 31 | 32 | REPO=apolloauto/apollo 33 | ARCH=$(uname -m) 34 | TIME=$(date +%Y%m%d_%H%M) 35 | 36 | TAG="${REPO}:dev-18.04-${ARCH}-${TIME}" 37 | 38 | # Fail on first error. 39 | set -e 40 | # 3. 编译docker 41 | docker build -t ${TAG} -f ${DOCKERFILE} ${CONTEXT} 42 | echo "Built new image ${TAG}" 43 | ``` 44 | 其中解释下几个参数: 45 | -t : 设置docker的tag名称 46 | -f : 默认不需要设置这个参数,docker会从当前目录中找dockerfile编译,当有多个dockerfile的时候就需要通过"-f"来指定编译的dockerfile 47 | CONTEXT : docker编译的资源目录 48 | 49 | [参考](https://docs.docker.com/engine/reference/commandline/build/) 50 | 51 | **疑问** 52 | 1. ARM架构和X86_64架构的docker编译有什么区别? 53 | 54 | 55 | 接着我们来看dockerfile 56 | #### dev.x86_64.dockerfile 57 | ``` 58 | # 1. 以nvidia/cuda:10.0做为基础镜像 59 | FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu18.04 60 | 61 | ENV DEBIAN_FRONTEND=noninteractive 62 | 63 | # 2. 安装软件 64 | RUN apt-get update -y && \ 65 | apt-get install -y \ 66 | apt-transport-https \ 67 | autotools-dev \ 68 | automake \ 69 | bc \ 70 | build-essential \ 71 | cmake \ 72 | cppcheck \ 73 | curl \ 74 | curlftpfs \ 75 | debconf-utils \ 76 | doxygen \ 77 | gdb \ 78 | git \ 79 | google-perftools \ 80 | graphviz \ 81 | iproute2 \ 82 | iputils-ping \ 83 | lcov \ 84 | libblas-dev \ 85 | libssl-dev \ 86 | libboost-all-dev \ 87 | libcurl4-openssl-dev \ 88 | libfreetype6-dev \ 89 | liblapack-dev \ 90 | libpcap-dev \ 91 | libsqlite3-dev \ 92 | libgtest-dev \ 93 | locate \ 94 | lsof \ 95 | nfs-common \ 96 | python-autopep8 \ 97 | shellcheck \ 98 | software-properties-common \ 99 | sshfs \ 100 | subversion \ 101 | unzip \ 102 | uuid-dev \ 103 | v4l-utils \ 104 | vim \ 105 | wget \ 106 | libasound2-dev \ 107 | zip && \ 108 | apt-get clean && rm -rf /var/lib/apt/lists/* && \ 109 | echo '\n\n\n' | ssh-keygen -t rsa 110 | 111 | # Run installers. 112 | # 3. 拷贝installers中的脚本,并且执行 113 | COPY installers /tmp/installers 114 | # 4. 安装adv,作用??? 115 | RUN bash /tmp/installers/install_adv_plat.sh 116 | # 5. 安装bazel 117 | RUN bash /tmp/installers/install_bazel.sh 118 | # 6. 安装bazel依赖的包 119 | RUN bash /tmp/installers/install_bazel_packages.sh 120 | # 7. 网络文件系统,百度bosfs基于libfuse 121 | RUN bash /tmp/installers/install_bosfs.sh 122 | # 8. 安装conda,包管理软件 123 | RUN bash /tmp/installers/install_conda.sh 124 | # 9. 安装ffmpeg,用于视频处理 125 | RUN bash /tmp/installers/install_ffmpeg.sh 126 | # 10. 安装gflag 127 | RUN bash /tmp/installers/install_gflags_glog.sh 128 | # 11. openGL扩展 129 | RUN bash /tmp/installers/install_glew.sh 130 | # 12. google代码规范 131 | RUN bash /tmp/installers/install_google_styleguide.sh 132 | # 13. 安装caffe 133 | RUN bash /tmp/installers/install_gpu_caffe.sh 134 | # 14. 连续系统的大规模非线性优化的软件库 135 | RUN bash /tmp/installers/install_ipopt.sh 136 | # 15. osqp求解器 137 | RUN bash /tmp/installers/install_osqp.sh 138 | # 16. json rpc调用,是哪个开源库没有备注 139 | RUN bash /tmp/installers/install_libjsonrpc-cpp.sh 140 | # 17. nonlinear optimization非线性优化 141 | RUN bash /tmp/installers/install_nlopt.sh 142 | # 18. node.js版本管理 143 | RUN bash /tmp/installers/install_node.sh 144 | # 19. 视频编解码库 145 | RUN bash /tmp/installers/install_openh264.sh 146 | # 20. ota安全包,具体是??? 147 | RUN bash /tmp/installers/install_ota.sh 148 | # 21. 安装pcl点云库 149 | RUN bash /tmp/installers/install_pcl.sh 150 | # 22. poco提供快速的可移植的网络开发 151 | RUN bash /tmp/installers/install_poco.sh 152 | # 23. 安装protobuf 153 | RUN bash /tmp/installers/install_protobuf.sh 154 | # 24. 安装python模块 155 | RUN bash /tmp/installers/install_python_modules.sh 156 | # 25. qp库 157 | RUN bash /tmp/installers/install_qp_oases.sh 158 | # 26. 安装QT 159 | RUN bash /tmp/installers/install_qt.sh 160 | # 27. 161 | RUN bash /tmp/installers/install_supervisor.sh 162 | # 28. 2D图像的变换 163 | RUN bash /tmp/installers/install_undistort.sh 164 | # 29. 增加用户,并且添加初始化脚本 165 | RUN bash /tmp/installers/install_user.sh 166 | # 30. 安装yarn,nodejs管理工具??? 167 | RUN bash /tmp/installers/install_yarn.sh 168 | # 31. 性能调试库 169 | RUN bash /tmp/installers/post_install.sh 170 | # 32. 音频编解码 171 | RUN bash /tmp/installers/install_opuslib.sh 172 | 173 | # 33. 设置工作路径和用户为apollo 174 | WORKDIR /apollo 175 | USER apollo 176 | 177 | ``` 178 | 179 | 还有一些没有用到的脚本 180 | ``` 181 | 1. 182 | install_adolc.sh 183 | 184 | 2. 安装fast-rtps,一个网络发现协议 185 | install_fast-rtps.sh 186 | 187 | 3. 安装pytorch 188 | install_libtorch.sh 189 | ``` 190 | 191 | 192 | 193 | ## docker脚本 194 | 容器相关的脚本在"scripts"中,下面我逐步分析下这些脚本做了哪些工作。 195 | 196 | #### dev_start.sh 197 | 启动容器的脚本 198 | 199 | #### dev_into.sh 200 | 进入容器的脚本 201 | 202 | 203 | 204 | 205 | ## 设置主机 206 | 设置host,提供了一些在主机上使用的脚本 207 | 208 | #### cleanup_resources.sh 209 | 清楚宿主机上的docker镜像,卷 210 | 211 | #### install_docker.sh & install_nvidia_docker.sh 212 | 安装docker 213 | 214 | #### setup_host.sh 215 | ``` 216 | APOLLO_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )" 217 | 218 | # Setup core dump format. 219 | if [ -e /proc/sys/kernel ]; then 220 | echo "${APOLLO_ROOT_DIR}/data/core/core_%e.%p" | \ 221 | sudo tee /proc/sys/kernel/core_pattern 222 | fi 223 | 224 | # 设置每1分钟进行NTP时间同步 225 | # Setup ntpdate to run once per minute. Log at /var/log/syslog. 226 | grep -q ntpdate /etc/crontab 227 | if [ $? -ne 0 ]; then 228 | echo "*/1 * * * * root ntpdate -v -u us.pool.ntp.org" | \ 229 | sudo tee -a /etc/crontab 230 | fi 231 | 232 | # 使用udev管理设备文件 233 | # Add udev rules. 234 | sudo cp -r ${APOLLO_ROOT_DIR}/docker/setup_host/etc/* /etc/ 235 | 236 | # 237 | # Add uvcvideo clock config. 238 | grep -q uvcvideo /etc/modules 239 | if [ $? -ne 0 ]; then 240 | echo "uvcvideo clock=realtime" | sudo tee -a /etc/modules 241 | fi 242 | ``` 243 | 244 | 245 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'dig-into-apollo' 21 | copyright = '2022, daohu527' 22 | author = 'daohu527' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | 'breathe', 32 | 'myst_parser', 33 | ] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The language for content autogenerated by Sphinx. Refer to documentation 39 | # for a list of supported languages. 40 | # 41 | # This is also used if you do content translation via gettext catalogs. 42 | # Usually you set "language" from the command line for these cases. 43 | language = 'zh' 44 | 45 | # List of patterns, relative to source directory, that match files and 46 | # directories to ignore when looking for source files. 47 | # This pattern also affects html_static_path and html_extra_path. 48 | exclude_patterns = [] 49 | 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | # The theme to use for HTML and HTML Help pages. See the documentation for 54 | # a list of builtin themes. 55 | # 56 | html_theme = 'alabaster' 57 | 58 | # Add any paths that contain custom static files (such as style sheets) here, 59 | # relative to this directory. They are copied after the builtin static files, 60 | # so a file named "default.css" will overwrite the builtin "default.css". 61 | html_static_path = ['_static'] -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. dig-into-apollo documentation master file, created by 2 | sphinx-quickstart on Thu May 26 11:44:46 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to dig-into-apollo's documentation! 7 | =========================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /how_to_build/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Build ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 高行微言,所以修身。 4 | 5 | ## Table of Contents 6 | - [编译](#build) 7 | - [常见问题](#question) 8 | 9 | 10 | 11 | 12 | ## 编译 13 | 编译脚本在"apollo.sh"中实现,通过shell脚本设置一些参数和环境变量,最后通过bazel编译。下面我们分析下apollo.sh的具体实现。 14 | 15 | 16 | "apollo.sh"中实现了一些函数,我们先介绍下build函数 17 | #### build 18 | ``` 19 | function build() { 20 | if [ "${USE_GPU}" = "1" ] ; then 21 | echo -e "${YELLOW}Running build under GPU mode. GPU is required to run the build.${NO_COLOR}" 22 | else 23 | echo -e "${YELLOW}Running build under CPU mode. No GPU is required to run the build.${NO_COLOR}" 24 | fi 25 | info "Start building, please wait ..." 26 | 27 | generate_build_targets 28 | info "Building on $MACHINE_ARCH..." 29 | 30 | MACHINE_ARCH=$(uname -m) 31 | JOB_ARG="--jobs=$(nproc) --ram_utilization_factor 80" 32 | if [ "$MACHINE_ARCH" == 'aarch64' ]; then 33 | JOB_ARG="--jobs=3" 34 | fi 35 | info "Building with $JOB_ARG for $MACHINE_ARCH" 36 | 37 | bazel build $JOB_ARG $DEFINES -c $@ $BUILD_TARGETS 38 | if [ ${PIPESTATUS[0]} -ne 0 ]; then 39 | fail 'Build failed!' 40 | fi 41 | 42 | # Build python proto 43 | build_py_proto 44 | 45 | # Clear KV DB and update commit_id after compiling. 46 | if [ "$BUILD_FILTER" == 'cyber' ] || [ "$BUILD_FILTER" == 'drivers' ]; then 47 | info "Skipping revision recording" 48 | else 49 | bazel build $JOB_ARG $DEFINES -c $@ $BUILD_TARGETS 50 | if [ ${PIPESTATUS[0]} -ne 0 ]; then 51 | fail 'Build failed!' 52 | fi 53 | rm -fr data/kv_db* 54 | REVISION=$(get_revision) 55 | ./bazel-bin/modules/common/kv_db/kv_db_tool --op=put \ 56 | --key="apollo:data:commit_id" --value="$REVISION" 57 | fi 58 | 59 | if [ -d /apollo-simulator ] && [ -e /apollo-simulator/build.sh ]; then 60 | cd /apollo-simulator && bash build.sh build 61 | if [ $? -ne 0 ]; then 62 | fail 'Build failed!' 63 | fi 64 | fi 65 | if [ $? -eq 0 ]; then 66 | success 'Build passed!' 67 | else 68 | fail 'Build failed' 69 | fi 70 | } 71 | ``` 72 | 73 | 74 | 75 | ## 常见问题 76 | 1. 如果机器内存小于8G,会出现编译错误的情况,单独编译又没有问题,问题的原因是内存缓冲不足,导致报错。调大内存后解决,错误信息如下。 77 | ``` 78 | gcc: internal compiler error: Killed 79 | ``` 80 | 81 | 2. 因为一些文件需要下载之后才能编译,如果启动离线模式,或者想下载文件之后再进行编译,可以参考。 82 | [bazel](https://docs.bazel.build/versions/0.18.1/external.html) -------------------------------------------------------------------------------- /library/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Library ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 读书患不多,思义患不明。 4 | 5 | ## Table of Contents 6 | 7 | * [Abseil](https://github.com/abseil/abseil-cpp) 8 | * [ADOL-C](https://github.com/coin-or/ADOL-C) 9 | * [ad-rss-lib](https://github.com/intel/ad-rss-lib) 10 | * [Benchmark](https://github.com/google/benchmark) 11 | * [Bazel](https://github.com/bazelbuild/bazel) 12 | * [Boost](http://www.boost.org) 13 | * [Bootstrap](https://github.com/twbs/bootstrap) 14 | * [Buildifier](https://github.com/bazelbuild/buildtools) 15 | * [ColPack](https://github.com/CSCsw/ColPack) 16 | * [CivetWeb](https://github.com/civetweb/civetweb) 17 | * [Cpplint](https://github.com/cpplint/cpplint) 18 | * [cuteci](https://github.com/hasboeuf/cuteci) 19 | * [Docker](https://www.docker.com) 20 | * [Eigen](http://eigen.tuxfamily.org/index.php) 21 | * [FastCDR](https://github.com/eProsima/Fast-CDR) 22 | * [Fast RTPS](https://github.com/eProsima/Fast-RTPS) 23 | * [FFmpeg](https://github.com/FFmpeg/FFmpeg) 24 | * [FFTW](http://www.fftw.org/fftw3_doc/License-and-Copyright.html) 25 | * [Ipopt](https://github.com/coin-or/Ipopt) 26 | * [GFlags](https://github.com/gflags/gflags) 27 | * [GLog](https://github.com/google/glog) 28 | * [gRPC](https://github.com/grpc/grpc) 29 | * [GoogleTest](https://github.com/google/googletest) 30 | * [jQuery](https://jquery.org/) 31 | * [JSON for Modern C++](https://github.com/nlohmann/json) 32 | * [OpenCV](https://opencv.org) 33 | * [OSQP](https://github.com/oxfordcontrol/osqp) 34 | * [PCL](https://github.com/PointCloudLibrary/pcl) 35 | * [PyTorch](https://github.com/pytorch/pytorch) 36 | * [POCO C++ Libraries](https://github.com/pocoproject/poco) 37 | * [PROJ](https://github.com/OSGeo/PROJ) 38 | * [Prettier](https://github.com/prettier/prettier) 39 | * [Protocol Buffer](https://github.com/protocolbuffers/protobuf) 40 | * [python-requests](http://docs.python-requests.org) 41 | * [Qt5](https://doc.qt.io/qt-5) 42 | * [shfmt](https://github.com/mvdan/sh) 43 | * [socketio](https://github.com/socketio/socket.io-client) 44 | * [VTK](https://github.com/Kitware/VTK) 45 | * [YAML-cpp](https://github.com/jbeder/yaml-cpp) 46 | * [three.js](https://threejs.org/) -------------------------------------------------------------------------------- /modules/audio/img/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/audio/img/main.jpg -------------------------------------------------------------------------------- /modules/audio/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Audio ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 勤学如春起之苗,不见其增,日有所长。 4 | 5 | 6 | ## Table of Contents 7 | - [Audio模块介绍](#introduction) 8 | - [输入输出](#input_output) 9 | - [目录结构](#content) 10 | - [模块入口](#main_process) 11 | - [消息处理(MessageProcess)](#message_process) 12 | - [声音识别(SirenDetection)](#siren_detection) 13 | - [移动检测(MovingDetection)](#moving_detection) 14 | - [方向检测(DirectionDetection)](#direction_detection) 15 | - [工具(tools)](#tools) 16 | 17 | 18 | 19 | 20 | ## Audio模块介绍 21 | audio模块是Apollo 6.0新增加的模块,主要的用途是通过声音来识别紧急车辆(警车,救护车,消防车)。目前的功能还相对比较简单,只能识别单个紧急车辆,同时需要环境风速低于20mph。下面我们会详细分析这个模块的原理以及实现。 22 | 23 | 24 | 25 | #### 输入输出 26 | audio模块的输入是`/apollo/sensor/microphone`,输入的消息来源于"drivers/microphone",用到的硬件模块是"RESPEAKER",目前有双通道和四通道,Apollo用到的硬件是四通道,关于硬件的相关介绍,会在"drivers/microphone"中进行说明。 27 | 28 | audio模块的输出是`/apollo/audio_detection`,输出的消息包括:是否检测到紧急车辆,检测到紧急车辆的移动类型(接近还是远离),位置以及角度。 29 | 30 | 也就是说audio模块单纯的通过声音来识别有没有紧急车辆,以及紧急车辆的位置,为无人驾驶车的感知添加了新的维度。语音交互在车机智能里是非常好的交互方式,目前面临的主要难点是汽车里噪音的影响,这在通过声音进行感知的时候尤其重要。 31 | 32 | 33 | 34 | ## 目录结构 35 | audio模块的目录结构如下,整体来说并不复杂,主要的逻辑在"inference"中。 36 | ``` 37 | ├── audio_component.cc 38 | ├── audio_component.h // 模块入口 39 | ├── BUILD 40 | ├── common 41 | ├── conf 42 | ├── dag 43 | ├── data // 模型文件 44 | ├── inference // 推理 45 | ├── launch 46 | ├── proto // 消息格式 47 | ├── README.md 48 | └── tools // 工具 49 | ``` 50 | 51 | Audio模块为事件驱动,当接受到驱动模块的声音输入的时候,就开始解析声音,并且输出结果,下面我们来详细分析具体的处理过程。 52 | audio模块的整体调用流程如图: 53 | ![调用流程](img/main.jpg) 54 | 55 | 56 | 57 | #### 模块入口 58 | audio模块通过"Init()"进行初始化,主要是读取录音机的外参,并且创建"audio_writer_"用于发布消息。初始化好之后,接着通过"Proc"来处理消息。处理的过程通过"OnMicrophone"来完成。 59 | ```c++ 60 | bool AudioComponent::Proc(const std::shared_ptr& audio_data) { 61 | // TODO(all) remove GetSignals() multiple calls 62 | AudioDetection audio_detection; 63 | MessageProcess::OnMicrophone(*audio_data, respeaker_extrinsics_file_, 64 | &audio_info_, &direction_detection_, &moving_detection_, 65 | &siren_detection_, &audio_detection); 66 | 67 | FillHeader(node_->Name(), &audio_detection); 68 | audio_writer_->Write(audio_detection); 69 | return true; 70 | } 71 | ``` 72 | 73 | 74 | 75 | #### 消息处理(MessageProcess) 76 | MessageProcess类实际上是推理模块的一个集合,具体的任务实际上还是在"inference"目录中完成的,主要有3个类,分别为 77 | * **SirenDetection** - 通过深度学习的方法,判断是否是紧急车辆声音。 78 | * **MovingDetection** - 通过声音强度信息和多普勒效应,来判断车辆是靠近还是远离。 79 | * **DirectionDetection** - 通过多个通道之间的差异,计算声音的方向。 80 | 81 | 下面我们开始分别介绍这3个功能。 82 | 83 | 84 | 85 | ## 声音识别(SirenDetection) 86 | 声音识别SirenDetection类中只有2个函数, LoadModel进行模型的加载,模型文件在"audio/data"目录中。 87 | ```c++ 88 | class SirenDetection { 89 | public: 90 | bool Evaluate(const std::vector>& signals); 91 | 92 | private: 93 | void LoadModel(); 94 | ``` 95 | 96 | Evaluate函数对多个通道声音进行处理,然后输入到模型,并且得出正向和反向的结果,当正向的分数大于反向的时候,则表示检测到了特殊车辆。 97 | ```c++ 98 | bool SirenDetection::Evaluate(const std::vector>& signals) { 99 | // 1. 读取4个通道的数据,装入audio_tensor中 100 | torch::Tensor audio_tensor = torch::empty(4 * 1 * 72000); 101 | float* data = audio_tensor.data_ptr(); 102 | 103 | for (const auto& channel : signals) { 104 | for (const auto& i : channel) { 105 | *data++ = static_cast(i) / 32767.0; 106 | } 107 | } 108 | 109 | // 2. 转换声音为张量 110 | torch::Tensor torch_input = torch::from_blob(audio_tensor.data_ptr(), 111 | {4, 1, 72000}); 112 | std::vector torch_inputs; 113 | torch_inputs.push_back(torch_input.to(device_)); 114 | 115 | // 3. 模型推理 116 | at::Tensor torch_output_tensor = torch_model_.forward(torch_inputs).toTensor() 117 | .to(torch::kCPU); 118 | auto torch_output = torch_output_tensor.accessor(); 119 | 120 | // 4. 投票表决 121 | float neg_score = torch_output[0][0] + torch_output[1][0] + 122 | torch_output[2][0] + torch_output[3][0]; 123 | float pos_score = torch_output[0][1] + torch_output[1][1] + 124 | torch_output[2][1] + torch_output[3][1]; 125 | 126 | if (neg_score < pos_score) { 127 | return true; 128 | } else { 129 | return false; 130 | } 131 | } 132 | ``` 133 | 134 | 135 | 136 | ## 移动检测(MovingDetection) 137 | 移动检测在"MovingDetection"类中实现,通过检测最近3帧的声音强度和声音频率,来判断紧急车辆是接近还是远离。MovingDetection中主要有3个函数。 138 | ```c++ 139 | // 1. 快速傅里叶变换 140 | std::vector> fft1d(const std::vector& signals); 141 | // 2. 移动检测 142 | MovingResult Detect(const std::vector>& signals); 143 | // 3. 检测单个通道 144 | MovingResult DetectSingleChannel( 145 | const std::size_t channel_index, const std::vector& signal); 146 | ``` 147 | 148 | 快速傅里叶变换在fft1d中完成,主要是把时域的东西转换到频域,在这里主要是为了得到声音的频率。 149 | 150 | 接下来我们看"DetectSingleChannel"的实现。 151 | ```c++ 152 | MovingResult MovingDetection::DetectSingleChannel( 153 | const std::size_t channel_index, const std::vector& signals) { 154 | static constexpr int kStartFrequency = 3; 155 | static constexpr int kFrameNumStored = 10; 156 | std::vector> fft_results = fft1d(signals); 157 | // 1. 获取声音信息 158 | SignalStat signal_stat = GetSignalStat(fft_results, kStartFrequency); 159 | signal_stats_[channel_index].push_back(signal_stat); 160 | while (static_cast(signal_stats_[channel_index].size()) > 161 | kFrameNumStored) { 162 | signal_stats_[channel_index].pop_front(); 163 | } 164 | // 2. 分析声音强度 165 | MovingResult power_result = AnalyzePower(signal_stats_[channel_index]); 166 | if (power_result != UNKNOWN) { 167 | return power_result; 168 | } 169 | // 3. 分析声音频率 170 | MovingResult top_frequency_result = 171 | AnalyzeTopFrequence(signal_stats_[channel_index]); 172 | return top_frequency_result; 173 | } 174 | ``` 175 | 可以看到单个通道先通过"GetSignalStat"获取声音信息,并且优先采用声音强度信息,然后采用声音频率。 176 | 177 | 我们接着上一步骤看如何获取声音强度和声音频率。 178 | ```c++ 179 | MovingDetection::SignalStat MovingDetection::GetSignalStat( 180 | const std::vector>& fft_results, 181 | const int start_frequency) { 182 | double total_power = 0.0; 183 | int top_frequency = -1; 184 | double max_power = -1.0; 185 | for (int i = start_frequency; i < static_cast(fft_results.size()); ++i) { 186 | double power = std::abs(fft_results[i]); 187 | // 1. 对一段时间的声音强度做累加 188 | total_power += power; 189 | // 2. 找出一段时间内声音强度最大的作为当时的频率 190 | if (power > max_power) { 191 | max_power = power; 192 | top_frequency = i; 193 | } 194 | } 195 | return {total_power, top_frequency}; 196 | } 197 | ``` 198 | 199 | "AnalyzePower"和"AnalyzeTopFrequence"的逻辑相对简单,就是判断过去的三帧,声音强度是否一直减少、增大,频率是否一直减少、增大。以此来判断汽车是远离还是靠近。 200 | 201 | 最后"Detect"函数会综合4个通道的数据来进行投票表决,然后得到汽车是远离还是靠近,最后输出结果。 202 | 203 | 204 | 205 | 206 | ## 方向检测(DirectionDetection) 207 | 在"DirectionDetection"类中对紧急车辆的方向进行估计,通过2个通道的差异和声音的速度,就可以得到车辆的大概位置。 208 | DirectionDetection类的主要实现在"EstimateSoundSource"函数中,下面我们来分析下具体的实现。 209 | ```c++ 210 | std::pair DirectionDetection::EstimateSoundSource( 211 | std::vector>&& channels_vec, 212 | const std::string& respeaker_extrinsic_file, const int sample_rate, 213 | const double mic_distance) { 214 | // 1. 加载外参 215 | if (!respeaker2imu_ptr_.get()) { 216 | respeaker2imu_ptr_.reset(new Eigen::Matrix4d); 217 | LoadExtrinsics(respeaker_extrinsic_file, respeaker2imu_ptr_.get()); 218 | } 219 | // 2. 计算方向角度 220 | double degree = 221 | EstimateDirection(move(channels_vec), sample_rate, mic_distance); 222 | // 3. 计算距离 223 | Eigen::Vector4d source_position(kDistance * sin(degree), 224 | kDistance * cos(degree), 0, 1); 225 | source_position = (*respeaker2imu_ptr_) * source_position; 226 | 227 | Point3D source_position_p3d; 228 | source_position_p3d.set_x(source_position[0]); 229 | source_position_p3d.set_y(source_position[1]); 230 | source_position_p3d.set_z(source_position[2]); 231 | degree = NormalizeAngle(degree); 232 | return {source_position_p3d, degree}; 233 | } 234 | ``` 235 | 236 | 那么如何计算方向角度呢? 下面我们看下"EstimateDirection"的实现。 237 | ```c++ 238 | double DirectionDetection::EstimateDirection( 239 | std::vector>&& channels_vec, const int sample_rate, 240 | const double mic_distance) { 241 | // 1. 把声音数据放入channels_ts 242 | std::vector channels_ts; 243 | auto options = torch::TensorOptions().dtype(torch::kFloat64); 244 | int size = static_cast(channels_vec[0].size()); 245 | for (auto& signal : channels_vec) { 246 | channels_ts.push_back(torch::from_blob(signal.data(), {size}, options)); 247 | } 248 | 249 | double tau0, tau1; 250 | double theta0, theta1; 251 | const double max_tau = mic_distance / kSoundSpeed; 252 | // 2. 分别计算通道0和2,1和3的组合来得出角度 253 | tau0 = GccPhat(channels_ts[0], channels_ts[2], sample_rate, max_tau, 1); 254 | theta0 = asin(tau0 / max_tau) * 180 / M_PI; 255 | tau1 = GccPhat(channels_ts[1], channels_ts[3], sample_rate, max_tau, 1); 256 | theta1 = asin(tau1 / max_tau) * 180 / M_PI; 257 | 258 | int best_guess = 0; 259 | // 3. 得到最优解 260 | if (fabs(theta0) < fabs(theta1)) { 261 | best_guess = theta1 > 0 ? std::fmod(theta0 + 360, 360) : (180 - theta0); 262 | } else { 263 | best_guess = theta0 < 0 ? std::fmod(theta1 + 360, 360) : (180 - theta1); 264 | best_guess = (best_guess + 90 + 180) % 360; 265 | } 266 | best_guess = (-best_guess + 480) % 360; 267 | 268 | return static_cast(best_guess) / 180 * M_PI; 269 | } 270 | ``` 271 | 272 | 计算2个通道,从而得到角度信息的实现如下。 273 | ```c++ 274 | double DirectionDetection::GccPhat(const torch::Tensor& sig, 275 | const torch::Tensor& refsig, int fs, 276 | double max_tau, int interp) { 277 | const int n_sig = sig.size(0), n_refsig = refsig.size(0), 278 | n = n_sig + n_refsig; 279 | torch::Tensor psig = at::constant_pad_nd(sig, {0, n_refsig}, 0); 280 | torch::Tensor prefsig = at::constant_pad_nd(refsig, {0, n_sig}, 0); 281 | psig = at::rfft(psig, 1, false, true); 282 | prefsig = at::rfft(prefsig, 1, false, true); 283 | 284 | ConjugateTensor(&prefsig); 285 | // 1. 复数相乘 286 | torch::Tensor r = ComplexMultiply(psig, prefsig); 287 | // 2. 复数取绝对值 288 | torch::Tensor cc = 289 | at::irfft(r / ComplexAbsolute(r), 1, false, true, {interp * n}); 290 | int max_shift = static_cast(interp * n / 2); 291 | if (max_tau != 0) 292 | max_shift = std::min(static_cast(interp * fs * max_tau), max_shift); 293 | 294 | auto begin = cc.index({Slice(cc.size(0) - max_shift, None)}); 295 | auto end = cc.index({Slice(None, max_shift + 1)}); 296 | cc = at::cat({begin, end}); 297 | // find max cross correlation index 298 | const int shift = at::argmax(at::abs(cc), 0).item() - max_shift; 299 | const double tau = shift / static_cast(interp * fs); 300 | 301 | return tau; 302 | } 303 | ``` 304 | 305 | Todo: 306 | 关于上述过程的详细计算原理,之后需要做进一步的补充??? 307 | 308 | 309 | 310 | ## 工具(tools) 311 | tools目录提供了一些录制和调试工具。 312 | 313 | * **audiosaver.py** - 录制声音并且保存。 314 | * **audio_offline_processing.cc** - 离线测试工具,提供audio模块的离线功能。 315 | 316 | 317 | 至此,我们就得到了是否有紧急车辆,以及车辆的移动方式和方向。实际上audio模块的代码中还遗留有位置信息、感知的结果,估计后面会增加一些新的融合功能。 318 | 319 | 整体上audio模块还是挺有意思的,当然声音的识别,以及在嘈杂环境如何获取到比较关注的声音,都是业界研究的热点方向,但主要还是集中在室内对人的声音的追踪,室外以及对车或者后续增加到人的场景,还有待发掘。 320 | -------------------------------------------------------------------------------- /modules/bridge/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Bridge ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 上穷碧落下黄泉,两处茫茫皆不见。 4 | 5 | 6 | ## Table of Contents 7 | -------------------------------------------------------------------------------- /modules/canbus/img/canbus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/canbus.jpg -------------------------------------------------------------------------------- /modules/canbus/img/canclient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/canclient.jpg -------------------------------------------------------------------------------- /modules/canbus/img/chassis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/chassis.jpg -------------------------------------------------------------------------------- /modules/canbus/img/factory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/factory.jpg -------------------------------------------------------------------------------- /modules/canbus/img/input.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/input.jpg -------------------------------------------------------------------------------- /modules/canbus/img/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/main.jpg -------------------------------------------------------------------------------- /modules/canbus/img/process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/canbus/img/process.jpg -------------------------------------------------------------------------------- /modules/canbus/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Canbus ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 黑发不知勤学早,白首方悔读书迟。 4 | 5 | 6 | ## Table of Contents 7 | - [Canbus模块介绍](#introduction) 8 | - [Canbus模块主流程](#main) 9 | - [车辆工厂模式(VehicleFactory)](#vehicle_factory) 10 | - [车辆控制器(LincolnController)](#lincoln_controller) 11 | - [Canbus(驱动程序)](#canbus_driver) 12 | - [消息管理器(MessageManager)](#message_manager) 13 | - [消息接收(CanReceiver)](#can_receiver) 14 | - [消息发送(CanSender)](#can_sender) 15 | - [canbus客户端(CanClient)](#can_client) 16 | - [Reference](#reference) 17 | 18 | 19 | 20 | ## Canbus模块介绍 21 | 我们先看下什么是Canbus: 控制器局域网 (Controller Area Network,简称CAN或者CAN bus) 是一种车用总线标准。被设计用于在不需要主机(Host)的情况下,允许网络上的节点相互通信。采用广播机制,并利用标识符来定义内容和消息的优先顺序,使得canbus的扩展性良好,同时不基于特殊类型(Host)的节点,增加了升级网络的便利性。 22 | 这里的**Canbus模块其实可以称为Chassis模块**,主要的作用是反馈车当前的状态(航向,角度,速度等信息),并且发送控制命令到车线控底盘,**可以说Canbus模块是车和自动驾驶软件之间的桥梁**。由于这个模块和"drivers/canbus"的联系紧密,因此也一起在这里介绍。 23 | Canbus模块是车和自动驾驶软件之间的桥梁,通过canbus驱动(drivers/canbus)来实现将车身信息发送给apollo上层软件,同时接收控制命令,发送给汽车线控底盘实现对汽车的控制。 24 | 25 | 那么canbus模块的输入是什么?输出是什么呢? 26 | ![input](img/input.jpg) 27 | 可以看到canbus模块: 28 | * **输入** - 1. ControlCommand(控制命令) 29 | * **输出** - 1. Chassis(汽车底盘信息), 2. ChassisDetail(汽车底盘信息详细信息) 30 | 31 | Canbus模块的输入是control模块发送的控制命令,输出汽车底盘信息,这里apollo的上层模块被当做一个can_client来处理,实现接收和发送canbus上的消息。 32 | 33 | Canbus模块的目录结构如下: 34 | ``` 35 | ├── BUILD // bazel编译文件 36 | ├── canbus_component.cc // canbus主入口 37 | ├── canbus_component.h 38 | ├── canbus_test.cc // canbus测试 39 | ├── common // gflag配置 40 | ├── conf // 配置文件 41 | ├── dag // dag依赖 42 | ├── launch // launch加载 43 | ├── proto // protobuf文件 44 | ├── testdata // 测试数据 45 | ├── tools // 遥控汽车和测试canbus总线工具 46 | └── vehicle // 47 | ``` 48 | 接着我们来分析下Canbus模块的执行流程。 49 | 50 | 51 | 52 | 53 | ## Canbus模块主流程 54 | Canbus模块的主流程在文件"canbus_component.cc"中,canbus模块为定时触发,每10ms执行一次,发布chassis信息,而ControlCommand则是每次读取到之后触发回调"OnControlCommand",发送"control_command"到线控底盘。 55 | ```c++ 56 | bool CanbusComponent::Proc() { 57 | PublishChassis(); 58 | if (FLAGS_enable_chassis_detail_pub) { 59 | PublishChassisDetail(); 60 | } 61 | return true; 62 | } 63 | ``` 64 | 由于不同型号的车辆的canbus命令不一样,在"/vehicle"中适配了不同型号车辆的canbus消息格式,所有的车都继承自Vehicle_controller基类,通过对Vehicle_controller的抽象来发送和读取canbus信息。 65 | ![main](img/main.jpg) 66 | 67 | 68 | 69 | 70 | #### 车辆工厂模式(VehicleFactory) 71 | 在vehicle中可以适配不同的车型,而每种车型都对应一个vehicle_controller,创建每种车辆的控制器(VehicleController)和消息管理(MessageManager)流程如下: 72 | ![factory](img/factory.jpg) 73 | VehicleFactory类通过创建不同的类型AbstractVehicleFactory,每个车型自己的Factory在创建出对应的VehicleController和MessageManager,用林肯来举例子就是: VehicleFactory创建LincolnVehicleFactory,之后通过CreateMessageManager和CreateVehicleController创建对应的控制器(LincolnController)和消息管理器(LincolnMessageManager)。 74 | 上述代码流程用到了设计模式的工厂模式,通过车辆工厂创造不同的车辆类型。 75 | 76 | 77 | 78 | 79 | #### 车辆控制器(LincolnController) 80 | 下面以林肯来介绍LincolnController,以及如何接收chassis信息,其它的车型可以以此类推,下面主要分为2部分介绍,第一部分为controller的init->start->stop流程,第二部分为chassis信息获取: 81 | ![process](img/process.jpg) 82 | 可以看到control模块初始化(init)的过程获取了发送的消息的格式,通过can_sender应该发送那些消息,而启动(start)之后启动一个看门狗,检查canbus消息格式是否正确,最后关闭(stop)模块则是结束看门狗进程。 83 | 84 | ![chassis](img/chassis.jpg) 85 | 而chassis的获取则是通过message_manager_获取chassis_detail,之后对chassis进行赋值。 86 | 87 | 88 | 89 | 90 | ## Canbus(驱动程序) 91 | 上层的canbus就介绍完成了,而canbus的发送(CanSender)和接收(CanReceiver),还有消息管理(MessageManager)都是在"drivers/canbus"中实现的。 92 | 93 | 94 | 95 | 96 | #### 消息管理器(MessageManager) 97 | MessageManager是如何获取消息的呢? 98 | 99 | **MessageManager主要作用是解析和保存canbus数据,而具体的接收和发送则是在"CanReceiver"和"CanSender"中**,拿接收消息举例子,也就是说CanReceiver收到消息后,会调用MessageManager中的parse去解析消息,消息的解析协议在"modules/canbus/vehicle/lincoln/protocol"中,每个消息把自己对应的信息塞到"chassis_detail"中完成了消息的接收。 100 | ![canbus](img/canbus.jpg) 101 | 102 | 103 | 104 | 105 | #### 消息接收(CanReceiver) 106 | canbus消息的接收在上面有介绍,在CanReceiver中的"Start"调用"RecvThreadFunc"实现消息的接收,这里会启动一个异步进程去完成接收。 107 | ```c++ 108 | template 109 | ::apollo::common::ErrorCode CanReceiver::Start() { 110 | if (is_init_ == false) { 111 | return ::apollo::common::ErrorCode::CANBUS_ERROR; 112 | } 113 | is_running_.exchange(true); 114 | 115 | // 启动异步接收消息 116 | async_result_ = cyber::Async(&CanReceiver::RecvThreadFunc, this); 117 | return ::apollo::common::ErrorCode::OK; 118 | } 119 | ``` 120 | RecvThreadFunc通过"can_client_"接收消息,然后通过"MessageManager"去解析消息,在MessageManager中有讲到。 121 | ```c++ 122 | template 123 | void CanReceiver::RecvThreadFunc() { 124 | 125 | ... 126 | while (IsRunning()) { 127 | std::vector buf; 128 | int32_t frame_num = MAX_CAN_RECV_FRAME_LEN; 129 | 130 | // 1. can_client_接收canbus数据 131 | if (can_client_->Receive(&buf, &frame_num) != 132 | ::apollo::common::ErrorCode::OK) { 133 | 134 | cyber::USleep(default_period); 135 | continue; 136 | } 137 | ... 138 | 139 | for (const auto &frame : buf) { 140 | uint8_t len = frame.len; 141 | uint32_t uid = frame.id; 142 | const uint8_t *data = frame.data; 143 | 144 | // 2. MessageManager解析canbus数据 145 | pt_manager_->Parse(uid, data, len); 146 | if (enable_log_) { 147 | ADEBUG << "recv_can_frame#" << frame.CanFrameString(); 148 | } 149 | } 150 | cyber::Yield(); 151 | } 152 | AINFO << "Can client receiver thread stopped."; 153 | } 154 | ``` 155 | 156 | 157 | 158 | #### 消息发送(CanSender) 159 | 消息发送对应的是在CanSender中的"Start"调用"PowerSendThreadFunc",我们可以看具体实现: 160 | ```c++ 161 | template 162 | common::ErrorCode CanSender::Start() { 163 | if (is_running_) { 164 | AERROR << "Cansender has already started."; 165 | return common::ErrorCode::CANBUS_ERROR; 166 | } 167 | is_running_ = true; 168 | 169 | // 启动线程发送消息 170 | thread_.reset(new std::thread([this] { PowerSendThreadFunc(); })); 171 | 172 | return common::ErrorCode::OK; 173 | } 174 | ``` 175 | PowerSendThreadFunc再通过"can_client"发送消息: 176 | ```c++ 177 | std::vector can_frames; 178 | CanFrame can_frame = message.CanFrame(); 179 | can_frames.push_back(can_frame); 180 | 181 | // 通过can_client发送消息 182 | if (can_client_->SendSingleFrame(can_frames) != common::ErrorCode::OK) { 183 | AERROR << "Send msg failed:" << can_frame.CanFrameString(); 184 | } 185 | ``` 186 | 187 | 188 | 189 | 190 | #### canbus客户端(CanClient) 191 | CanClient是canbus客户端,同时也是canbus的驱动程序,针对不同的canbus卡,对发送和接收进行封装,并且提供给消息发送和接收控制器使用。 192 | ![canclient](img/canclient.jpg) 193 | 拿"EsdCanClient"来举例子,发送在"Send"函数中,调用的是第三方的硬件驱动,目录在"third_party/can_card_library/esd_can",实现can消息的发送: 194 | ```c++ 195 | ErrorCode EsdCanClient::Send(const std::vector &frames, 196 | int32_t *const frame_num) { 197 | ... 198 | 199 | // canWrite为第三方库的硬件驱动,third_party/can_card_library/esd_can 200 | // Synchronous transmission of CAN messages 201 | int32_t ret = canWrite(dev_handler_, send_frames_, frame_num, nullptr); 202 | if (ret != NTCAN_SUCCESS) { 203 | AERROR << "send message failed, error code: " << ret << ", " 204 | << GetErrorString(ret); 205 | return ErrorCode::CAN_CLIENT_ERROR_BASE; 206 | } 207 | return ErrorCode::OK; 208 | } 209 | ``` 210 | 其他的can卡可以参考上述的流程,至此整个canbus驱动就分析完成了。 211 | 212 | 213 | 214 | ## Reference 215 | [Controller Area Network (CAN BUS) 通訊​協定​原理​概述](https://www.ni.com/zh-tw/innovations/white-papers/06/controller-area-network--can--overview.html) 216 | -------------------------------------------------------------------------------- /modules/control/img/PID.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/PID.jpg -------------------------------------------------------------------------------- /modules/control/img/PID_varyingP.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/PID_varyingP.jpg -------------------------------------------------------------------------------- /modules/control/img/agent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/agent.jpg -------------------------------------------------------------------------------- /modules/control/img/estop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/estop.jpg -------------------------------------------------------------------------------- /modules/control/img/input.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/input.jpg -------------------------------------------------------------------------------- /modules/control/img/lon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/lon.jpg -------------------------------------------------------------------------------- /modules/control/img/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/control/img/table.png -------------------------------------------------------------------------------- /modules/control/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Control ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 岁不寒,无以知松柏;事不难,无以知君子。 4 | 5 | ## Table of Contents 6 | - [Control模块简介](#introduction) 7 | - [Control模块主流程](#main) 8 | - [控制器注册](#register) 9 | - [Pad消息](#pad) 10 | - [estop_标志位](#estop) 11 | - [控制器](#controller) 12 | - [LonController控制器](#lon_controller) 13 | - [校准表](#calibration_table) 14 | - [PID参数调节](#pid) 15 | - [LatController控制器](#lat_controller) 16 | - [MPCController控制器](#mpc_controller) 17 | - [StanleyLatController控制器](#stanleylat_controller) 18 | - [Reference](#reference) 19 | 20 | 21 | 22 | 23 | ## Control模块简介 24 | Apollo控制模块的逻辑相对比较简单,控制模块的作用是**根据规划(planning模块)生成的轨迹,计算出汽车的油门,刹车和方向盘信号,控制汽车按照规定的轨迹行驶**。采用的方法是根据汽车动力和运动学的知识,对汽车进行建模,实现对汽车的控制。目前apollo主要用到了2种控制方式:PID控制和模型控制。 25 | 26 | 首先我们需要搞清楚control模块的输入是什么,输出是什么? 27 | ![input.jpg](img/input.jpg) 28 | 可以看到control模块: 29 | * **输入** - Chassis(车辆状态信息), LocalizationEstimate(位置信息), ADCTrajectory(planning模块规划的轨迹) 30 | * **输出** - ControlCommand(油门,刹车,方向盘) 31 | 32 | Control模块的目录结构如下: 33 | ``` 34 | ├── BUILD // bazel编译文件 35 | ├── common // PID和控制器的具体实现 --- 算法具体实现 36 | ├── conf // 配置文件 --- 配置文件 37 | ├── control_component.cc // 模块入口 38 | ├── control_component.h 39 | ├── control_component_test.cc 40 | ├── controller // 控制器 --- 具体的控制器实现 41 | ├── dag // dag依赖 42 | ├── integration_tests // 测试 43 | ├── launch // launch加载 44 | ├── proto // protobuf文件,主要是各个控制器的配置数据结构 45 | ├── testdata // 测试数据 46 | └── tools // 工具类 47 | ``` 48 | 49 | 下面我们接着分析下control模块的执行流程。 50 | 51 | 52 | 53 | ## Control模块主流程 54 | Control模块的入口在"control_component.cc"中,和其它的模块一样,Control模块注册为cyber的一个模块,其中control模块为定时模块,也就是每隔10ms执行一次命令。这里需要注意了planning模块的输出是100ms一次,也就是说100ms才给出一条曲线,而control模块会根据这条曲线,每10ms处理一次,控制汽车按照指定的速度到达指定的位置。 55 | Control模块执行的主函数是"ControlComponent::Proc()",即每10ms调用一次该函数,处理的流程在该函数中: 56 | ```c++ 57 | bool ControlComponent::Proc() { 58 | // 1. 读取输入数据,通过拷贝读取输入信息 59 | chassis_reader_->Observe(); 60 | const auto &chassis_msg = chassis_reader_->GetLatestObserved(); 61 | if (chassis_msg == nullptr) { 62 | AERROR << "Chassis msg is not ready!"; 63 | return false; 64 | } 65 | 66 | OnChassis(chassis_msg); 67 | 68 | ... 69 | const auto &trajectory_msg = trajectory_reader_->GetLatestObserved(); 70 | 71 | ... 72 | const auto &localization_msg = localization_reader_->GetLatestObserved(); 73 | 74 | 75 | ControlCommand control_command; 76 | // 2. 生成控制命令 77 | Status status = ProduceControlCommand(&control_command); 78 | 79 | ... 80 | 81 | common::util::FillHeader(node_->Name(), &control_command); 82 | // 3. 发送控制命令 83 | control_cmd_writer_->Write(std::make_shared(control_command)); 84 | 85 | return true; 86 | } 87 | ``` 88 | 上述的流程大概分为3个阶段: 89 | 1. **读取输入数据** 90 | 2. **生成控制命令** 91 | 3. **发送控制命令** 92 | 93 | 生成控制命令的核心函数在"ProduceControlCommand"中,其实这个函数还包含了参数检查和_estop(紧急情况)的处理,真正生成命令的函数只有一行: 94 | ```c++ 95 | Status status_compute = controller_agent_.ComputeControlCommand( 96 | &local_view_.localization, &local_view_.chassis, 97 | &local_view_.trajectory, control_command); 98 | ``` 99 | 100 | 101 | 102 | #### 控制器注册 103 | 上述过程是通过"controller_agent_"产生控制命令,而"controller_agent_"是一个代理,其它的控制器通过注册到代理控制器,从而实现多个控制器的访问(是否用到了设计模式的代理模式?): 104 | ![agent.jpg](img/agent.jpg) 105 | 其中控制器包括: 106 | * **纵向控制** 107 | * **横向控制** 108 | * **模型预测控制** 109 | * **Stanley横向控制** 110 | > Stanley横向控制可以[参考](https://www.ri.cmu.edu/pub_files/2009/2/Automatic_Steering_Methods_for_Autonomous_Automobile_Path_Tracking.pdf) 111 | 112 | 113 | 那么这些控制器是如何注册到"controller_agent_"的呢? 114 | ```c++ 115 | // 1. 在ControlComponent::Init()中调用初始化 116 | bool ControlComponent::Init() { 117 | // 注册控制器 118 | if (!controller_agent_.Init(&control_conf_).ok()) { 119 | monitor_logger_buffer_.ERROR("Control init controller failed! Stopping..."); 120 | return false; 121 | } 122 | } 123 | 124 | // 2. 在ControllerAgent类中注册和初始化 125 | Status ControllerAgent::Init(const ControlConf *control_conf) { 126 | // 注册控制器 127 | RegisterControllers(control_conf); 128 | // 实例化控制器 129 | CHECK(InitializeConf(control_conf).ok()) << "Fail to initialize config."; 130 | // 控制器初始化 131 | for (auto &controller : controller_list_) { 132 | if (controller == NULL || !controller->Init(control_conf_).ok()) { 133 | if (controller != NULL) { 134 | AERROR << "Controller <" << controller->Name() << "> init failed!"; 135 | return Status(ErrorCode::CONTROL_INIT_ERROR, 136 | "Failed to init Controller:" + controller->Name()); 137 | } else { 138 | return Status(ErrorCode::CONTROL_INIT_ERROR, 139 | "Failed to init Controller"); 140 | } 141 | } 142 | AINFO << "Controller <" << controller->Name() << "> init done!"; 143 | } 144 | return Status::OK(); 145 | } 146 | ``` 147 | 上面就是control模块的主流程,我们接下来先介绍下control模块中的"pad message"和"estop_",然后再逐个介绍各个控制器。 148 | 149 | 150 | 151 | 152 | #### Pad消息 153 | Pad消息通过发送状态来控制汽车的模式(自动驾驶还是人工驾驶),其中"DrivingAction"一共有3种状态,在"pad_msg.proto"中: 154 | ``` 155 | enum DrivingAction { 156 | STOP = 0; 157 | START = 1; 158 | RESET = 2; 159 | }; 160 | ``` 161 | Control模块中只能使用pad消息"RESET"来清空"estop_"的状态。实际上control模块会判断"driving_mode"来决定是否启动自动驾驶,而"driving_mode"是通过发送pad消息状态给"canbus"模块来控制的,这会在canbus模块中详细介绍。 162 | 总之pad消息的2个作用是: 163 | 1. **发送消息给canbus模块,来控制driving_mode,control模块判断当前driving_mode的状态来决定是否启动自动驾驶** 164 | 2. **通过reset来清空estop_的状态** 165 | 166 | 167 | 168 | 169 | #### estop_标志位 170 | 那么"estop_"有什么作用呢? estop_标志位的作用是判断control模块是否处于紧急状态,而触发紧急停车。那么在哪些状态下,"estop_"为真,汽车进入紧急停车状态呢? 171 | 1. 输入错误(CheckInput返回false) 172 | 2. 控制命令计算失败(ComputeControlCommand失败) 173 | 3. planning模块直接给出紧急停车 174 | 175 | 下面是"estop_"的状态变迁图和紧急停车流程: 176 | ![estop](img/estop.jpg) 177 | 178 | 紧急停车的流程很好理解,我们学驾照的时候都知道,紧急停车的时候不能够狂打方向盘,特别是高速的时候,容易侧翻,因此这里的紧急停车流程也是保持方向盘,然后紧急制动。 179 | 180 | 181 | 182 | ## 控制器 183 | 首先我们把汽车的控制分为横向控制和纵向控制2部分,其中纵向控制通过油门和刹车控制车纵向的加减速,而横向控制则通过控制方向盘的转动来控制前轮的方向,从而控制汽车的行驶方向。 184 | 185 | 186 | 187 | #### LonController控制器 188 | 纵向控制器,主要是通过油门和刹车控制车的纵向速度。汽车的纵向模型比较简单,我们可以通过经典力学对汽车进行建模,汽车的纵向受力如果是前轮驱动,那么前轮提供一个向前的滚动摩擦,后轮有一个滚动摩擦阻力再加上风阻,如果有坡度则再加上重力分量,这就构成了汽车的纵向受力模型: 189 | ![lon](img/lon.jpg) 190 | 纵向控制要实现的目标是让汽车在指定的时间内到达指定的地点,首先是保证位置准确,如果能够直接通过油门去控制汽车的位置,那么采用单一的PID环就够了。重点是位置是和速度与时间相关的,因此先通过位置PID得到需要的速度,然后再通过速度PID得到需要多大油门,这样的方式叫做串级PID控制器。通过2级PID来控制汽车的速度,从而确定需要踩多少的油门。 191 | 现在自动驾驶普遍是电动汽车的情况下,汽车的动力实际上由内燃机换成了电动马达,这种控制方式完全可以由伺服控制器来解决,传统的伺服控制器实现了位置控制,速度控制,可以把汽车的控制模块直接由伺服控制器来替代(也许可以解放汽车的控制,如果是线性的电机,那么其实很好办,油门可以直接对应为电机的电流,而电流对应电机输出的扭矩大小),但汽车有个最大的不同点是后退需要换倒车档。 192 | 下面是串级PID的示意图,其中先根据位置误差得到速度,然后根据速度误差得到油门和刹车,同时在汽车有俯仰角度(汽车行驶在有坡度的路面上)这时候会对PID提供重力分量的补偿: 193 | ![PID](img/PID.jpg) 194 | 195 | 196 | 197 | #### 校准表 198 | 知道速度之后我们可以根据当前速度的误差,来确定是增大油门或者是刹车,而油门和速度的关系是如何对应的呢,举个例子:当你发现车的速度和预期相差为3km/h,你应该踩多大油门呢?当然你可以先假设一个值,比如油门增加2,如果速度低了你再增加到3,总之根据一个经验值,然后再通过PID算法去调节,那怎么去拟合速度和油门的关系曲线呢? 199 | 我们需要控制汽车到达某个速度,根据牛顿经典力学,只需要知道汽车的初速度和加速度,就可以知道物体一段时候后的速度。因此我们只要找到速度,加速度和油门的关系,就可以通过控制汽车的加速度来让汽车达到某个速度。也就是对速度加速度和油门的关系进行建模,得到它们之间的关系。Apollo采用的是在实际的汽车行驶过程中记录不同速度下,不同的油门值对汽车的加速度的影响,从而得到一张表格,最后通过查表的方式来得到具体的油门和刹车值大小,得到的配置最后保存在conf文件夹中。 200 | ![table](img/table.png) 201 | 这个表的生成需要测试每种油门以及每种速度下的表现,但是表不可能列出无限连续的数据,因此最后还是需要通过插值的方式来得到结果。 202 | 203 | 204 | 205 | #### PID参数调节 206 | 什么样的PID参数比较合适?这一部分可以参考WIKI百科的PID参数动画。调试参数的方法是先调节速度环,再调节位置环;先调节比例系数,再调节积分系数。 207 | ![PID_varyingP](img/PID_varyingP.jpg) 208 | 另外关于PID的控制,正常情况下没有问题,如果遇到路面不平的情况,相当于一张巨大的手在拨弄小车,这样就对PID引入了一个震荡反馈,震荡的引入之后系统是否能够保持稳态呢,这就是PID控制需要研究的范围??? 209 | 210 | 211 | 212 | ## LatController控制器 213 | 纵向控制主要是控制速度,而横向控制主要是控制方向。方向盘的角度不一样,车的行驶路径不一样,因此汽车的横向控制主要是通过控制方向盘的转角来控制汽车行驶的角度。 214 | 纵向控制采用的是**LQR控制器**,为什么不继续采用PID控制器呢?因为每种控制器都有它的适合情况和不适合情况(有一篇文章专门介绍了不同的控制器的优劣,PID主要的缺点是不适合泊车和曲率比较大的情况)。关于LQR控制器主要是一些公式的推导,后面再详细介绍下推导过程。 215 | 216 | 217 | 218 | ## MPCController控制器 219 | 模型预测控制(MPC)是一类特殊的控制。它的当前控制动作是在每一个采样瞬间通过求解一个有限时域开环最优控制问题而获得。过程的当前状态作为最优控制问题的初始状态,解得的最优控制序列只实施第一个控制作用。这是它与那些使用预先计算控制律的算法的最大不同。本质上模型预测控制求解一个开环最优控制问题。它的思想与具体的模型无关,但是实现则与模型有关。 220 | 221 | 222 | 223 | ## StanleyLatController控制器 224 | 225 | 226 | ## 问题 227 | 1. 遇到上坡\下坡的情况,原来的系数表就只能提供一个参考,如何提供补偿,PID的每次输入都是从表中查询还是根据调节的结果来的? 228 | 2. 遇到下雨的时候,路面的摩擦系数改变和上述问题一样 229 | 3. 遇到长下坡,PID调节是否会和人一样“不能长时间踩脚踏板” 230 | 4. 转弯的场景是否适合这个参数? 231 | 5. 是否所有的场景都适合这个参数?比如自主泊车的情况? 232 | 6. 提供的是TrajectoryPoint,里面包含距离,速度,加速度和时间,这3者只要知道2者即可以了。控制的时候如何选择的颗粒度?比如提供的轨迹的时间间隔是否固定,是否每次都需要control模块自己调节(在时间间隔比较长的情况下),PID每次只能参考一个标准,这里是以距离为准,还是已速度为准?如果是,速度还有何意义?planning生成的TrajectoryPoint是否符合物理学规律? 233 | 234 | 235 | 236 | ## Reference 237 | [Apollo代码学习(一)—控制模块概述](https://blog.csdn.net/u013914471/article/details/82775091) 238 | [百度Apollo 2.0 车辆控制算法之LQR控制算法解读](https://blog.csdn.net/weijimin1/article/details/85794084) 239 | [Apollo代码学习(五)—横纵向控制](https://blog.csdn.net/u013914471/article/details/83748571) 240 | [Apollo自动驾驶入门课程第⑩讲 — 控制(下)](https://blog.csdn.net/cg129054036/article/details/83413482) 241 | [how_to_tune_control_parameters](https://github.com/ApolloAuto/apollo/blob/master/docs/howto/how_to_tune_control_parameters.md) 242 | [throttle-affect-the-rpm-of-an-engine](https://www.physicsforums.com/threads/how-does-the-throttle-affect-the-rpm-of-an-engine.832029/) 243 | [PID_controller](https://en.wikipedia.org/wiki/PID_controller) 244 | [Stanley横向控制](https://www.ri.cmu.edu/pub_files/2009/2/Automatic_Steering_Methods_for_Autonomous_Automobile_Path_Tracking.pdf) 245 | [Linear–quadratic regulator](https://en.wikipedia.org/wiki/Linear%E2%80%93quadratic_regulator) 246 | [模型预测控制](https://baike.baidu.com/item/%E6%A8%A1%E5%9E%8B%E9%A2%84%E6%B5%8B%E6%8E%A7%E5%88%B6) 247 | 248 | -------------------------------------------------------------------------------- /modules/control/todo.md: -------------------------------------------------------------------------------- 1 | 1. 在紧急情况下,control模块应该有自己处理汽车当前状况的能力。而什么是紧急状况,紧急状况的定义是什么呢?首先的一点就是规划命令有3秒没有下发(是否一定要触发停车?),或者一些紧急情况,需要立刻停车的场景? 2 | 3 | ## todo 4 | 1. 各个控制器的理解和推导 5 | 6 | 7 | ## Reference 8 | https://www.mathworks.com/help/mpc/ug/adaptive-cruise-control-using-model-predictive-controller.html 9 | http://ctms.engin.umich.edu/CTMS/index.php?example=CruiseControl§ion=SystemModeling 10 | https://www.mathworks.com/help/physmod/sdl/ug/about-the-complete-vehicle-model.html 11 | https://www.mathworks.com/help/physmod/sdl/ug/control-vehicle-throttle-input-using-a-powertrain-blockset-driver.html 12 | -------------------------------------------------------------------------------- /modules/data/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Data ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 业精于勤,荒于嬉;行成于思,毁于随。 4 | 5 | ## Table of Contents 6 | - [data目录结构](#introduction) 7 | 8 | 9 | 10 | 11 | 12 | ## data目录结构 13 | data目录的结构如下, 14 | ``` 15 | . 16 | ├── BUILD 17 | ├── README.md 18 | ├── conf 19 | ├── proto 20 | ├── channel_pool.cc 21 | ├── channel_pool.h // 设置small_channels_ 22 | ├── drive_event_trigger.cc 23 | ├── drive_event_trigger.h 24 | ├── emergency_mode_trigger.cc 25 | ├── emergency_mode_trigger.h 26 | ├── hard_brake_trigger.cc 27 | ├── hard_brake_trigger.h 28 | ├── interval_pool.cc 29 | ├── interval_pool.h 30 | ├── post_record_processor.cc 31 | ├── post_record_processor.h 32 | ├── realtime_record_processor.cc 33 | ├── realtime_record_processor.h 34 | ├── record_processor.cc 35 | ├── record_processor.h 36 | ├── regular_interval_trigger.cc 37 | ├── regular_interval_trigger.h 38 | ├── small_topics_trigger.cc 39 | ├── small_topics_trigger.h 40 | ├── smart_recorder.cc 41 | ├── smart_recorder_gflags.cc 42 | ├── smart_recorder_gflags.h 43 | ├── swerve_trigger.cc 44 | ├── swerve_trigger.h 45 | ├── trigger_base.cc 46 | └── trigger_base.h 47 | ``` 48 | 49 | 主要的实现分为2部分,一部分为trigger,一部分为record。 50 | trigger主要的作用是生成对应的时间段,并且放到pool中。 51 | record主要用来录制数据,分为在线和离线录制。主要是调用RecordWriter和RecordReader,这2个类实现实时读取。 52 | 53 | 54 | ## RecordViewer 55 | RecordViewer实现了迭代器,并且可以读取文件和实时的流式数据?? 56 | RecordViewer中包含RecordReader 57 | 58 | ```c++ 59 | reader->GetHeader() 60 | ``` 61 | 以上的原理是什么??如果是文件则从文件读取,如果不是文件应该如何?? 62 | 63 | RecordViewer通过`Iterator`读取msg_buffer_中的消息,通过`FillBuffer`往msg_buffer_中写入消息,注意这里的"std::multimap"为什么要用map因为每次读取的时候是读取的1s内的reader消息,遍历多个reader的时候,那么时间顺序可能会打乱,因此这里采用红黑树的方式按照key进行排序,所以采用了multimap的结构。 64 | 65 | 每次在Update中更新消息, 66 | ```c++ 67 | bool RecordViewer::Update(RecordMessage* message) { 68 | bool find = false; 69 | do { 70 | // 1. 如果msg_buffer_为空,则填充buffer,填充1s内的消息 71 | if (msg_buffer_.empty() && !FillBuffer()) { 72 | break; 73 | } 74 | // 2. 找到buffer中的第一条消息,并且退出 75 | auto& msg = msg_buffer_.begin()->second; 76 | if (channels_.empty() || channels_.count(msg->channel_name) == 1) { 77 | *message = *msg; 78 | find = true; 79 | } 80 | msg_buffer_.erase(msg_buffer_.begin()); 81 | } while (!find); 82 | 83 | return find; 84 | } 85 | ``` 86 | 87 | 下面我们看下Process的过程,也就是说实时过程先启动recorder_,然后再从文件中读取消息,然后再重新保存??? 88 | ```c++ 89 | bool RealtimeRecordProcessor::Process() { 90 | // 保存到文件 91 | recorder_->Start(); 92 | 93 | // Now fast reader follows and reacts for any events 94 | std::string record_path; 95 | do { 96 | if (!GetNextValidRecord(&record_path)) { 97 | break; 98 | } 99 | auto reader = std::make_shared(record_path); 100 | RecordViewer viewer(reader, 0, std::numeric_limits::max(), 101 | ChannelPool::Instance()->GetAllChannels()); 102 | 103 | if (restore_reader_time_ == 0) { 104 | restore_reader_time_ = viewer.begin_time(); 105 | GetNextValidRecord(&restore_path_); 106 | } 107 | // 迭代器++调用RecordViewer::Update读取消息 108 | for (const auto& msg : viewer) { 109 | for (const auto& trigger : triggers_) { 110 | trigger->Pull(msg); 111 | } 112 | // 保存消息 113 | RestoreMessage(msg.time); 114 | } 115 | } while (!is_terminating_); // 循环上述步骤 116 | // Try restore the rest of messages one last time 117 | RestoreMessage(std::numeric_limits::max()); 118 | if (monitor_thread && monitor_thread->joinable()) { 119 | monitor_thread->join(); 120 | monitor_thread = nullptr; 121 | } 122 | return true; 123 | } 124 | ``` 125 | 126 | Recorder开始Start 127 | ```c++ 128 | bool Recorder::Start() { 129 | // 1. 创建文件 130 | writer_.reset(new RecordWriter(header_)); 131 | if (!writer_->Open(output_)) { 132 | return false; 133 | } 134 | std::string node_name = "cyber_recorder_record_" + std::to_string(getpid()); 135 | node_ = ::apollo::cyber::CreateNode(node_name); 136 | // 2. 初始化reader 137 | if (!InitReadersImpl()) { 138 | return false; 139 | } 140 | message_count_ = 0; 141 | message_time_ = 0; 142 | is_started_ = true; 143 | 144 | // 3. 显示进度 145 | display_thread_ = 146 | std::make_shared([this]() { this->ShowProgress(); }); 147 | 148 | return true; 149 | } 150 | ``` 151 | 152 | 153 | InitReadersImpl 154 | ```c++ 155 | bool Recorder::InitReadersImpl() { 156 | std::shared_ptr channel_manager = 157 | TopologyManager::Instance()->channel_manager(); 158 | 159 | // get historical writers 160 | std::vector role_attr_vec; 161 | channel_manager->GetWriters(&role_attr_vec); 162 | for (auto role_attr : role_attr_vec) { 163 | FindNewChannel(role_attr); 164 | } 165 | 166 | // listen new writers in future 167 | change_conn_ = channel_manager->AddChangeListener( 168 | std::bind(&Recorder::TopologyCallback, this, std::placeholders::_1)); 169 | if (!change_conn_.IsConnected()) { 170 | AERROR << "change connection is not connected"; 171 | return false; 172 | } 173 | return true; 174 | } 175 | ``` 176 | 177 | InitReaderImpl 178 | 注册callback给reader,然后写入文件 179 | ```c++ 180 | bool Recorder::InitReaderImpl(const std::string& channel_name, 181 | const std::string& message_type) { 182 | try { 183 | std::weak_ptr weak_this = shared_from_this(); 184 | std::shared_ptr reader = nullptr; 185 | auto callback = [weak_this, channel_name]( 186 | const std::shared_ptr& raw_message) { 187 | auto share_this = weak_this.lock(); 188 | if (!share_this) { 189 | return; 190 | } 191 | share_this->ReaderCallback(raw_message, channel_name); 192 | }; 193 | ReaderConfig config; 194 | config.channel_name = channel_name; 195 | config.pending_queue_size = 196 | gflags::Int32FromEnv("CYBER_PENDING_QUEUE_SIZE", 50); 197 | reader = node_->CreateReader(config, callback); 198 | if (reader == nullptr) { 199 | AERROR << "Create reader failed."; 200 | return false; 201 | } 202 | channel_reader_map_[channel_name] = reader; 203 | return true; 204 | } catch (const std::bad_weak_ptr& e) { 205 | AERROR << e.what(); 206 | return false; 207 | } 208 | } 209 | ``` 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /modules/dreamview/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Dreamview ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 草木有本心,何求美人折。 4 | 5 | ## Table of Contents 6 | 7 | dreamview中enable模块是在 backend的hmi接口中实现,调用std::cmd()执行启动命令。 8 | -------------------------------------------------------------------------------- /modules/dreamview/todo.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/dreamview/todo.md -------------------------------------------------------------------------------- /modules/drivers/camera/readme.md: -------------------------------------------------------------------------------- 1 | ## camera摄像头 2 | Camera模块支持USB UVC类型的摄像头,也就是说只有符合UVC规范的摄像头才支持,Camera的实现参考了linux驱动中V4L2的内核代码。 3 | -------------------------------------------------------------------------------- /modules/drivers/canbus/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Drivers/canbus ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 一叶遮目,不见泰山 4 | 5 | 6 | ## Table of Contents 7 | - [Canbus](#canbus) 8 | - [Linux Canbus](#linux_canbus) 9 | - [Reference](#reference) 10 | 11 | 12 | 13 | 14 | ## Canbus 15 | 由于汽车采用的总线是canbus总线,因此汽车采用大部分的器件都是canbus,**其中Radar,线控系统采用的就是canbus总线,当然canbus总线也有缺点,就是带宽不够,所以Camera采用的就是USB总线**,个人认为后面汽车可能会有新的总线出现,一满足目前的高带宽,二满足可扩展;另外的一个发展方向就是各个模块的集成化发展,比如以后摄像头和Radar一样都有单独的硬件处理数据之后上报。 16 | 那么我们再看下radar的驱动实际上依赖canbus,只有装好canbus的驱动,radar才可以收发数据。 17 | ![drivers](img/drivers.jpg) 18 | apollo的canbus驱动在"third_party/can_card_library"目录中,主要是引用linux的动态链接库"lib/libntcan.so.4",我们从linux操作系统开始,把整个canbus的调用流程分析一遍。 19 | 20 | 21 | 22 | #### Linux Canbus 23 | 首先下载linux kernel(这里以linux-3.16.64举例),Canbus的驱动主要在"drivers/net/can"目录中,编译内核的时候可以打开对应的开关来指定内核是否编译这些模块。Canbus驱动的目录结构如下: 24 | ``` 25 | . 26 | ├── at91_can.c 27 | ├── bfin_can.c 28 | ├── cc770 29 | ├── c_can 30 | ├── dev.c 31 | ├── flexcan.c 32 | ├── grcan.c 33 | ├── janz-ican3.c 34 | ├── Kconfig 35 | ├── led.c 36 | ├── Makefile 37 | ├── mscan 38 | ├── pch_can.c 39 | ├── rcar_can.c 40 | ├── sja1000 41 | ├── slcan.c 42 | ├── softing 43 | ├── spi 44 | ├── ti_hecc.c 45 | ├── usb 46 | ├── vcan.c 47 | └── xilinx_can.c 48 | ``` 49 | 50 | 51 | 52 | 53 | ## Reference 54 | [linux](https://www.kernel.org/) 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /modules/drivers/gnss/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Drivers/Gnss ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 一叶遮目,不见泰山 4 | 5 | 6 | ## Table of Contents 7 | - [Gnss](#gnss) 8 | - [Reference](#reference) 9 | 10 | 11 | 12 | 13 | ## Gnss 14 | 这里的坐标转换采用的是"proj.4"库来实现。从WGS84坐标转换为UTM坐标。 proj.4的参数找了下关于参数的介绍,发现都没有介绍的非常详细。这篇文章翻译的还比较详细,但是英文原版已经找不到了。 15 | [参考](https://www.cnblogs.com/jiangleads/articles/10803464.html) 16 | 17 | 18 | 19 | 20 | ## Reference 21 | [linux](https://www.kernel.org/) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /modules/drivers/hesai/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/drivers/hesai/readme.md -------------------------------------------------------------------------------- /modules/drivers/img/drivers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/drivers/img/drivers.jpg -------------------------------------------------------------------------------- /modules/drivers/microphone/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Microphone ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 遥知兄弟登高处,遍插茱萸少一人。 4 | 5 | 6 | ## Table of Contents 7 | [Canbus驱动子模块介绍](canbus#canbus_module) 8 | 9 | 10 | ## 目录结构 11 | Microphone的主要用途是捕获车当前的声音信息。 12 | -------------------------------------------------------------------------------- /modules/drivers/radar/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Drivers ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 一叶遮目,不见泰山 4 | 5 | 6 | ## Table of Contents 7 | - [Radar](#radar) 8 | - [Reference](#reference) 9 | 10 | 11 | 12 | 13 | ## Radar 14 | Radar驱动包含了2种类型的雷达:毫米波雷达和超声波雷达。其中毫米波雷达有博世和理工雷科2种类型,我尝试着在网上找相关资料,暂时没有找到相关的驱动程序,只找到了博世的说明文档,下面根据Apollo中的代码做一个介绍。 15 | ``` 16 | . 17 | ├── conti_radar 18 | ├── racobit_radar 19 | └── ultrasonic_radar 20 | ``` 21 | 22 | 23 | 24 | 25 | ## Reference 26 | [linux](https://www.kernel.org/) 27 | -------------------------------------------------------------------------------- /modules/drivers/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Drivers ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 一叶遮目,不见泰山 4 | 5 | 6 | ## Table of Contents 7 | [Canbus驱动子模块介绍](canbus#canbus_module) 8 | [Radar驱动子模块介绍](radar#radar_module) 9 | [Camera驱动子模块介绍](camera#camera_module) 10 | [velodyne驱动子模块介绍](velodyne#velodyne_module) 11 | [hesai驱动子模块介绍](hesai#hesai_module) 12 | [Gnss驱动子模块介绍](gnss#gnss_module) 13 | 14 | 15 | 16 | 17 | 18 | ## Reference 19 | [linux](https://www.kernel.org/) 20 | -------------------------------------------------------------------------------- /modules/drivers/todo.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/drivers/todo.md -------------------------------------------------------------------------------- /modules/drivers/velodyne/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/drivers/velodyne/readme.md -------------------------------------------------------------------------------- /modules/drivers/video/readme.md: -------------------------------------------------------------------------------- 1 | ## video 2 | video是通过网络UDP来获取录像并发布,看起来是支持网络摄像头,即接口是网口的摄像头。 -------------------------------------------------------------------------------- /modules/guardian/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Guardian ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 人不知而不愠,不亦君子乎。 4 | 5 | ## Table of Contents 6 | 7 | 8 | 9 | 10 | ## 简介 11 | Guardian模块的主要作用是监控自动驾驶系统状态,当出现模块为失败状态的时候,会主动切断控制命令输出,并且刹车。 12 | 13 | ## 触发 14 | guardian模块的触发条件主要有2个。 15 | 1. 上报模块状态的消息间隔超过kSecondsTillTimeout(2.5秒) 16 | 2. 上报的状态消息中有safety_mode_trigger_time字段 17 | 这时候就会触发进入接管。 18 | 19 | ## TriggerSafetyMode 20 | 安全模式的步骤分为2步骤,第一步状态消息中需要紧急刹车或者超声波检测到障碍物,如果检测到障碍物则说明车已经非常接近障碍物了,该检测被称为硬件触发的检测,因为已经发现模块故障,也非常接近障碍物所以刹车会加急。第二步是普通刹车,刹车没有那么急。 guardian为定时模块,所以该过程中会一直发送消息,直到车辆停车。 21 | 当前版本代码屏蔽了上述超声波检测。 22 | 23 | ## 问题 24 | guardian模块的频率是10ms,因此最大会增加control命令的延时10ms。 25 | -------------------------------------------------------------------------------- /modules/localization/img/location_rtk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/localization/img/location_rtk.jpg -------------------------------------------------------------------------------- /modules/localization/msf/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - MSF Localization ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | ## 目录 4 | 由于依赖单一的定位都会出现失效的情况,因此MSF融合了GPS、IMU和激光雷达3者输出定位信息,因此结果会比较鲁棒,相关的论文可以参考《Robust and Precise Vehicle Localization Based on Multi-Sensor Fusion in Diverse City Scenes》。 单个的定位信息的获取我们已经了解过了,例如GPS和IMU的RTK定位、激光雷达和IMU的NDT定位,而MSF则是融合(一般采用卡尔曼滤波)了2者的结果。最后定位都会面临现有地图还是先定位的问题,因此如何验证地图的准确性是值得研究和讨论的方向。 5 | 6 | MSF模块的整体目录结构如下。主要分为地图、工具和定位器。其中MSF的部分代码是采用so文件来提供的,不过好在我们可以看到接口定义,这部分头文件在`third_party/localization_msf/x86_64/include`中。 7 | 8 | ``` 9 | . 10 | ├── BUILD 11 | ├── common 12 | ├── local_integ 13 | ├── local_map 14 | ├── local_pyramid_map 15 | ├── local_tool 16 | ├── msf_localization.cc 17 | ├── msf_localization_component.cc 18 | ├── msf_localization_component.h 19 | ├── msf_localization.h 20 | ├── msf_localization_test.cc 21 | ├── params 22 | └── README.md 23 | ``` 24 | 25 | ## local_integ 26 | MSF定位的功能主要在以下模块中实现,它的*入口函数*在`localization_integ.h`和`localization_integ.cc`中,*输入*为激光雷达消息、IMU消息、GPS消息和汽车heading消息,*输出*为融合后的定位信息。 27 | ``` 28 | . 29 | ├── BUILD 30 | ├── gnss_msg_transfer.cc 31 | ├── gnss_msg_transfer.h 32 | ├── lidar_msg_transfer.cc 33 | ├── lidar_msg_transfer.h 34 | ├── localization_gnss_process.cc 35 | ├── localization_gnss_process.h 36 | ├── localization_integ.cc 37 | ├── localization_integ.h 38 | ├── localization_integ_impl.cc 39 | ├── localization_integ_impl.h 40 | ├── localization_integ_process.cc 41 | ├── localization_integ_process.h 42 | ├── localization_lidar.cc 43 | ├── localization_lidar.h 44 | ├── localization_lidar_process.cc 45 | ├── localization_lidar_process.h 46 | ├── localization_params.h 47 | ├── measure_republish_process.cc 48 | ├── measure_republish_process.h 49 | ├── online_localization_expert.cc 50 | └── online_localization_expert.h 51 | ``` 52 | 53 | 接下来我们开始按照执行过程分析代码。 54 | 55 | #### localization_integ 56 | `LocalizationInteg`的函数接口简单明了,分别对应了激光雷达消息、IMU消息、GPS消息和汽车heading消息的处理。这里唯一需要注意的时IMU和GPS有2个接口。 57 | 58 | 我们先看IMU的接口。这2个接口分别代表了FLU(前-左-天空)坐标系和RFU(右-前-天空)坐标系,需要根据坐标系调用不同的接口。 59 | ```c++ 60 | void RawImuProcessFlu(const drivers::gnss::Imu &imu_msg); 61 | void RawImuProcessRfu(const drivers::gnss::Imu &imu_msg); 62 | ``` 63 | 在看GPS的接口,其中`RawObservationProcess`和`RawEphemerisProcess`组合使用,效果类似于`GnssBestPoseProcess`。由于组合导航的差异选择不同的接口,这里默认采用`GnssBestPoseProcess`。 64 | ```c++ 65 | // Gnss Info process. 66 | void RawObservationProcess( 67 | const drivers::gnss::EpochObservation &raw_obs_msg); 68 | void RawEphemerisProcess(const drivers::gnss::GnssEphemeris &gnss_orbit_msg); 69 | // gnss best pose process 70 | void GnssBestPoseProcess(const drivers::gnss::GnssBestPose &bestgnsspos_msg); 71 | ``` 72 | 73 | localization_integ调用的是`localization_integ_impl`接口,也就是说具体的功能实现在`localization_integ_impl`中。 74 | 75 | #### localization_integ_impl 76 | `localization_integ_impl`分为3个部分: 77 | 1. 处理消息(LocalizationGnssProcess, LocalizationLidarProcess) 78 | 2. 重新发布(MeasureRepublishProcess) 79 | 3. 融合过程(LocalizationIntegProcess) 80 | 4. 完善状态(OnlineLocalizationExpert) 81 | 也就是说先分别处理激光雷达、GPS、IMU等的消息,然后重新发布,转换为一种类型的消息(MeasureData),然后交给`LocalizationIntegProcess`做融合,最后完善融合结果的状态。 82 | 83 | 84 | #### localization_gnss_process 85 | 求解GNSS的结果,求解器在`GnssSolver`中实现,头文件在`localization_msf/gnss_solver.h`中,以库文件的方式提供。 86 | 87 | #### localization_lidar_process 88 | 求解Lidar的定位结果,和NDT定位类似,加入了反射率信息,实现在`localization_lidar_process`和`localization_lidar`中,也用到了Lidar求解器`LidarLocator`,同样头文件在`localization_msf/lidar_locator.h`中,以库文件的方式提供。 89 | 90 | #### localization_integ_process 91 | 最后,我们最关注的融合过程,求解过程在`localization_msf/sins.h`中。 92 | 93 | 1. 在`StartThreadLoop`中处理之前放入`measure_data_queue_`的消息。 94 | 2. RawImuProcess中接收IMU的消息,并且计算融合之后的Pose。 95 | ```c++ 96 | // add imu msg and get current predict pose 97 | sins_->AddImu(imu_msg); 98 | sins_->GetPose(&ins_pva_, pva_covariance_); 99 | sins_->GetRemoveBiasImu(&corrected_imu_); 100 | sins_->GetEarthParameter(&earth_param_); 101 | ``` 102 | 103 | #### measure_republish_process 104 | 重新发布消息,实际上是转换不同的消息到`MeasureData`。 105 | 106 | 107 | #### online_localization_expert 108 | 主要完善各个定位的状态信息 109 | 110 | 111 | 112 | ## local_map 113 | ``` 114 | . 115 | ├── base_map 116 | ├── lossless_map 117 | ├── lossy_map 118 | ├── ndt_map 119 | └── test_data 120 | ``` 121 | 122 | ## local_pyramid_map 123 | ``` 124 | . 125 | ├── base_map 126 | ├── ndt_map 127 | └── pyramid_map 128 | ``` 129 | 130 | ## local_tool 131 | `local_tool`包含了3个工具:解压数据、可视化定位结果和创建地图。 132 | ``` 133 | . 134 | ├── data_extraction // 解压数据 135 | ├── local_visualization // 本地可视化 136 | └── map_creation // 创建地图 137 | ``` 138 | 139 | #### local_visualization 140 | 可视化定位结果分为在线工具和离线工具,实际上只是消息的获取方式不一样,实现的原理都是一样,采用opencv绘制不同的定位结果和历史轨迹,方便进行分析。个人认为这一部分还可以优化,例如记录历史轨迹的方差大小,以及点云匹配差异的可视化展示等。 141 | 142 | 143 | ## params 144 | `params`目录主要是存放一些参数,用于坐标转换,例如激光雷达到IMU的转换矩阵(激光雷达外参,通过标定获取),世界坐标到IMU的转换关系等。由于比较简单,这里就不赘述了。 145 | ``` 146 | . 147 | ├── BUILD 148 | ├── gnss_params 149 | ├── novatel_localization_extrinsics.yaml 150 | ├── vehicle_params 151 | └── velodyne_params 152 | ``` 153 | 154 | -------------------------------------------------------------------------------- /modules/localization/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Localization ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 虽千万人,吾往矣。 4 | 5 | ## Table of Contents 6 | - [Localization模块简介](#introduction) 7 | - [代码目录](#content) 8 | - [RTK定位流程](#rtk) 9 | - [NDT定位流程](ndt) 10 | - [MSF定位流程](msf) 11 | - [Reference](#reference) 12 | 13 | 14 | 15 | 16 | ## Localization模块简介 17 | localization模块主要实现了以下2个功能: 18 | 1. 输出车辆的位置信息(planning模块使用) 19 | 2. 输出车辆的姿态,速度信息(control模块使用) 20 | 21 | 其中apollo代码中分别实现了3种定位方法: 22 | 1. GNSS + IMU定位 23 | 2. NDT定位(点云定位) 24 | 3. MSF(融合定位) 25 | 26 | > MSF方法参考论文"Robust and Precise Vehicle Localization Based on Multi-Sensor Fusion in Diverse City Scenes" 27 | 28 | 29 | 30 | 31 | ## 代码目录 32 | 下面是localization的目录结构,在查看具体的代码之前最好看下定位模块的readme文件: 33 | ``` 34 | ├── common // 声明配置(flags),从conf目录中读取相应的值 35 | ├── conf // 配置文件存放目录 36 | ├── dag // cyber DAG流 37 | ├── launch // cyber的配置文件,依赖DAG图(这2个和cyber有关的后面再分析) 38 | ├── msf // 融合定位(gnss,点云,IMU融合定位) 39 | │ ├── common 40 | │ │ ├── io 41 | │ │ ├── test_data 42 | │ │ └── util 43 | │ ├── local_integ 44 | │ ├── local_map 45 | │ │ ├── base_map 46 | │ │ ├── lossless_map 47 | │ │ ├── lossy_map 48 | │ │ ├── ndt_map 49 | │ │ └── test_data 50 | │ ├── local_tool 51 | │ │ ├── data_extraction 52 | │ │ ├── local_visualization 53 | │ │ └── map_creation 54 | │ └── params 55 | │ ├── gnss_params 56 | │ ├── vehicle_params 57 | │ └── velodyne_params 58 | ├── ndt // ndt定位 59 | │ ├── map_creation 60 | │ ├── ndt_locator 61 | │ └── test_data 62 | │ ├── ndt_map 63 | │ └── pcds 64 | ├── proto // 消息格式 65 | ├── rtk // rtk定位 66 | └── testdata // imu和gps的测试数据 67 | ``` 68 | 通过上述目录可以知道,定位模块主要实现了rtk,ndt,msf这3个定位方法,分别对应不同的目录。proto文件夹定义了消息的格式,common和conf主要是存放一些配置和消息TOPIC。下面我们逐个分析RTK定位、NDT定位和MSF定位。 69 | 70 | 71 | 72 | 73 | ## RTK定位流程 74 | RTK定位是通过GPS和IMU的信息做融合然后输出车辆所在的位置。RTK通过基准站的获取当前GPS信号的误差,用来校正无人车当前的位置,可以得到厘米级别的精度。IMU的输出频率高,可以在GPS没有刷新的情况下(通常是1s刷新一次)用IMU获取车辆的位置。下面是RTK模块的目录结构。 75 | ``` 76 | ├── BUILD // bazel编译文件 77 | ├── rtk_localization.cc // rtk定位功能实现模块 78 | ├── rtk_localization_component.cc // rtk消息发布模块 79 | ├── rtk_localization_component.h 80 | ├── rtk_localization.h 81 | └── rtk_localization_test.cc // 测试 82 | ``` 83 | 其中"rtk_localization_component.cc"注册为标准的cyber模块,RTK定位模块在"Init"中初始化,每当接收到"localization::Gps"消息就触发执行"Proc"函数。 84 | ```c++ 85 | class RTKLocalizationComponent final 86 | : public cyber::Component { 87 | public: 88 | RTKLocalizationComponent(); 89 | ~RTKLocalizationComponent() = default; 90 | 91 | bool Init() override; 92 | 93 | bool Proc(const std::shared_ptr &gps_msg) override; 94 | ``` 95 | 下面我们分别查看这2个函数。 96 | 1. Init函数 97 | Init函数实现比较简单,一是初始化配置信息,二是初始化IO。初始化配置信息主要是读取一些配置,例如一些topic信息等。下面主要看下初始化IO。 98 | ```c++ 99 | bool RTKLocalizationComponent::InitIO() { 100 | // 1.读取IMU信息,每次接收到localization::CorrectedImu消息,则回调执行“RTKLocalization::ImuCallback” 101 | corrected_imu_listener_ = node_->CreateReader( 102 | imu_topic_, std::bind(&RTKLocalization::ImuCallback, localization_.get(), 103 | std::placeholders::_1)); 104 | CHECK(corrected_imu_listener_); 105 | // 2.读取GPS状态信息,每次接收到GPS状态消息,则回调执行"RTKLocalization::GpsStatusCallback" 106 | gps_status_listener_ = node_->CreateReader( 107 | gps_status_topic_, std::bind(&RTKLocalization::GpsStatusCallback, 108 | localization_.get(), std::placeholders::_1)); 109 | CHECK(gps_status_listener_); 110 | 111 | // 3.发布位置信息和位置状态信息 112 | localization_talker_ = 113 | node_->CreateWriter(localization_topic_); 114 | CHECK(localization_talker_); 115 | 116 | localization_status_talker_ = 117 | node_->CreateWriter(localization_status_topic_); 118 | CHECK(localization_status_talker_); 119 | return true; 120 | } 121 | ``` 122 | 也就是说,RTK模块同时还接收IMU和GPS的状态信息,然后触发对应的回调函数。具体的实现在"RTKLocalization"类中,我们先看下回调的具体实现。以"GpsStatusCallback"为例,每次读取到gps状态信息之后,会把信息保存到"gps_status_list_"列表中。"ImuCallback"类似,也是接收到IMU消息后,保存到"imu_list_"列表中。 123 | ```c++ 124 | void RTKLocalization::GpsStatusCallback( 125 | const std::shared_ptr &status_msg) { 126 | std::unique_lock lock(gps_status_list_mutex_); 127 | if (gps_status_list_.size() < gps_status_list_max_size_) { 128 | gps_status_list_.push_back(*status_msg); 129 | } else { 130 | gps_status_list_.pop_front(); 131 | gps_status_list_.push_back(*status_msg); 132 | } 133 | } 134 | ``` 135 | 136 | 2. Proc 137 | 在每次接收到"localization::Gps"消息后,触发执行"Proc"函数。这里注意如果需要接收多个消息,这里是3个消息,则选择最慢的消息作为触发,否则,如果选择比较快的消息作为触发,这样会导致作为触发的消息刷新了,而其它的消息还没有刷新。所以这里采用的是GPS消息作为触发消息,IMU的消息刷新快。下面我们看具体的实现。 138 | ```c++ 139 | bool RTKLocalizationComponent::Proc( 140 | const std::shared_ptr& gps_msg) { 141 | // 1. 通过RTKLocalization处理GPS消息回调 142 | localization_->GpsCallback(gps_msg); 143 | 144 | if (localization_->IsServiceStarted()) { 145 | LocalizationEstimate localization; 146 | // 2. 获取定位消息 147 | localization_->GetLocalization(&localization); 148 | LocalizationStatus localization_status; 149 | // 3. 获取定位状态 150 | localization_->GetLocalizationStatus(&localization_status); 151 | 152 | // publish localization messages 153 | // 4. 发布位置信息 154 | PublishPoseBroadcastTopic(localization); 155 | // 5. 发布位置转换信息 156 | PublishPoseBroadcastTF(localization); 157 | // 6. 发布位置状态信息 158 | PublishLocalizationStatus(localization_status); 159 | ADEBUG << "[OnTimer]: Localization message publish success!"; 160 | } 161 | 162 | return true; 163 | } 164 | ``` 165 | 具体的执行过程如下图所示。 166 | ![rtk](img/location_rtk.jpg) 167 | 主要的执行过程在"GpsCallback"中,然后通过"GetLocalization"和"GetLocalizationStatus"获取结果,最后发布对应的位置信息、位置转换信息和位置状态信息。 168 | 由于"GpsCallback"主要执行过程在"PrepareLocalizationMsg"中,因此我们主要分析"PrepareLocalizationMsg"的实现。 169 | 170 | #### 获取定位信息 171 | PrepareLocalizationMsg函数的具体实现如下。 172 | ```c++ 173 | void RTKLocalization::PrepareLocalizationMsg( 174 | const localization::Gps &gps_msg, LocalizationEstimate *localization, 175 | LocalizationStatus *localization_status) { 176 | // find the matching gps and imu message 177 | double gps_time_stamp = gps_msg.header().timestamp_sec(); 178 | CorrectedImu imu_msg; 179 | // 1.寻找最匹配的IMU信息 180 | FindMatchingIMU(gps_time_stamp, &imu_msg); 181 | // 2.根据GPS和IMU信息,给位置信息赋值 182 | ComposeLocalizationMsg(gps_msg, imu_msg, localization); 183 | 184 | drivers::gnss::InsStat gps_status; 185 | // 3.查找最近的GPS状态信息 186 | FindNearestGpsStatus(gps_time_stamp, &gps_status); 187 | // 4.根据GPS状态信息,给位置状态信息赋值 188 | FillLocalizationStatusMsg(gps_status, localization_status); 189 | } 190 | ``` 191 | 下面我们逐个分析上述4个过程。 192 | 193 | #### FindMatchingIMU 194 | 在队列中找到最匹配的IMU消息,其中区分了队列的第一个,最后一个,以及如果在中间位置则进行插值。插值的时候根据距离最近的原则进行反比例插值。 195 | 196 | ```c++ 197 | bool RTKLocalization::FindMatchingIMU(const double gps_timestamp_sec, 198 | CorrectedImu *imu_msg) { 199 | 200 | // 加锁,这里有疑问,为什么换个变量就没有锁了呢? 201 | std::unique_lock lock(imu_list_mutex_); 202 | auto imu_list = imu_list_; 203 | lock.unlock(); 204 | 205 | // 在IMU队列中找到最新的IMU消息 206 | // scan imu buffer, find first imu message that is newer than the given 207 | // timestamp 208 | auto imu_it = imu_list.begin(); 209 | for (; imu_it != imu_list.end(); ++imu_it) { 210 | if ((*imu_it).header().timestamp_sec() - gps_timestamp_sec > 211 | std::numeric_limits::min()) { 212 | break; 213 | } 214 | } 215 | 216 | if (imu_it != imu_list.end()) { // found one 217 | if (imu_it == imu_list.begin()) { 218 | AERROR << "IMU queue too short or request too old. " 219 | << "Oldest timestamp[" << imu_list.front().header().timestamp_sec() 220 | << "], Newest timestamp[" 221 | << imu_list.back().header().timestamp_sec() << "], GPS timestamp[" 222 | << gps_timestamp_sec << "]"; 223 | *imu_msg = imu_list.front(); // the oldest imu 224 | } else { 225 | // here is the normal case 226 | auto imu_it_1 = imu_it; 227 | imu_it_1--; 228 | if (!(*imu_it).has_header() || !(*imu_it_1).has_header()) { 229 | AERROR << "imu1 and imu_it_1 must both have header."; 230 | return false; 231 | } 232 | // 根据最新的IMU消息和它之前的消息做插值。 233 | if (!InterpolateIMU(*imu_it_1, *imu_it, gps_timestamp_sec, imu_msg)) { 234 | AERROR << "failed to interpolate IMU"; 235 | return false; 236 | } 237 | } 238 | } else { 239 | // 如果没有找到,则取最新的20ms以内的消息,如果超过20ms则报错。 240 | // give the newest imu, without extrapolation 241 | *imu_msg = imu_list.back(); 242 | if (imu_msg == nullptr) { 243 | AERROR << "Fail to get latest observed imu_msg."; 244 | return false; 245 | } 246 | 247 | if (!imu_msg->has_header()) { 248 | AERROR << "imu_msg must have header."; 249 | return false; 250 | } 251 | 252 | if (std::fabs(imu_msg->header().timestamp_sec() - gps_timestamp_sec) > 253 | gps_imu_time_diff_threshold_) { 254 | // 20ms threshold to report error 255 | AERROR << "Cannot find Matching IMU. IMU messages too old. " 256 | << "Newest timestamp[" << imu_list.back().header().timestamp_sec() 257 | << "], GPS timestamp[" << gps_timestamp_sec << "]"; 258 | } 259 | } 260 | 261 | return true; 262 | } 263 | ``` 264 | 接下来我们看线性插值 265 | 1. InterpolateIMU 266 | 根据上述函数得到2个IMU消息分别对角速度、线性加速度、欧拉角进行插值。原则是根据比例,反比例进行插值。 267 | ```c++ 268 | bool RTKLocalization::InterpolateIMU(const CorrectedImu &imu1, 269 | const CorrectedImu &imu2, 270 | const double timestamp_sec, 271 | CorrectedImu *imu_msg) { 272 | 273 | if (timestamp_sec - imu1.header().timestamp_sec() < 274 | std::numeric_limits::min()) { 275 | AERROR << "[InterpolateIMU1]: the given time stamp[" << timestamp_sec 276 | << "] is older than the 1st message[" 277 | << imu1.header().timestamp_sec() << "]"; 278 | *imu_msg = imu1; 279 | } else if (timestamp_sec - imu2.header().timestamp_sec() > 280 | std::numeric_limits::min()) { 281 | AERROR << "[InterpolateIMU2]: the given time stamp[" << timestamp_sec 282 | << "] is newer than the 2nd message[" 283 | << imu2.header().timestamp_sec() << "]"; 284 | *imu_msg = imu1; 285 | } else { 286 | // 线性插值 287 | *imu_msg = imu1; 288 | imu_msg->mutable_header()->set_timestamp_sec(timestamp_sec); 289 | 290 | double time_diff = 291 | imu2.header().timestamp_sec() - imu1.header().timestamp_sec(); 292 | if (fabs(time_diff) >= 0.001) { 293 | double frac1 = 294 | (timestamp_sec - imu1.header().timestamp_sec()) / time_diff; 295 | // 1. 分别对角速度、线性加速度、欧拉角进行插值 296 | if (imu1.imu().has_angular_velocity() && 297 | imu2.imu().has_angular_velocity()) { 298 | auto val = InterpolateXYZ(imu1.imu().angular_velocity(), 299 | imu2.imu().angular_velocity(), frac1); 300 | imu_msg->mutable_imu()->mutable_angular_velocity()->CopyFrom(val); 301 | } 302 | 303 | ... 304 | } 305 | } 306 | return true; 307 | } 308 | ``` 309 | 2. InterpolateXYZ 310 | 根据距离插值,反比例,即frac1越小,则越靠近p1,frac1越大,则越靠近p2 311 | ```c++ 312 | template 313 | T RTKLocalization::InterpolateXYZ(const T &p1, const T &p2, 314 | const double frac1) { 315 | T p; 316 | double frac2 = 1.0 - frac1; 317 | if (p1.has_x() && !std::isnan(p1.x()) && p2.has_x() && !std::isnan(p2.x())) { 318 | p.set_x(p1.x() * frac2 + p2.x() * frac1); 319 | } 320 | if (p1.has_y() && !std::isnan(p1.y()) && p2.has_y() && !std::isnan(p2.y())) { 321 | p.set_y(p1.y() * frac2 + p2.y() * frac1); 322 | } 323 | if (p1.has_z() && !std::isnan(p1.z()) && p2.has_z() && !std::isnan(p2.z())) { 324 | p.set_z(p1.z() * frac2 + p2.z() * frac1); 325 | } 326 | return p; 327 | } 328 | ``` 329 | 330 | #### ComposeLocalizationMsg 331 | 填充位置信息,这里实际上涉及到姿态解算,具体是根据GPS和IMU消息对位置信息进行赋值。需要注意需要根据航向对IMU的信息进行转换。 332 | ```c++ 333 | void RTKLocalization::ComposeLocalizationMsg( 334 | const localization::Gps &gps_msg, const localization::CorrectedImu &imu_msg, 335 | LocalizationEstimate *localization) { 336 | localization->Clear(); 337 | 338 | FillLocalizationMsgHeader(localization); 339 | 340 | localization->set_measurement_time(gps_msg.header().timestamp_sec()); 341 | 342 | // combine gps and imu 343 | auto mutable_pose = localization->mutable_pose(); 344 | // GPS消息包含位置信息 345 | if (gps_msg.has_localization()) { 346 | const auto &pose = gps_msg.localization(); 347 | // 1. 获取位置 348 | if (pose.has_position()) { 349 | // position 350 | // world frame -> map frame 351 | mutable_pose->mutable_position()->set_x(pose.position().x() - 352 | map_offset_[0]); 353 | mutable_pose->mutable_position()->set_y(pose.position().y() - 354 | map_offset_[1]); 355 | mutable_pose->mutable_position()->set_z(pose.position().z() - 356 | map_offset_[2]); 357 | } 358 | // 2. 获取方向 359 | // orientation 360 | if (pose.has_orientation()) { 361 | mutable_pose->mutable_orientation()->CopyFrom(pose.orientation()); 362 | double heading = common::math::QuaternionToHeading( 363 | pose.orientation().qw(), pose.orientation().qx(), 364 | pose.orientation().qy(), pose.orientation().qz()); 365 | mutable_pose->set_heading(heading); 366 | } 367 | // linear velocity 368 | // 3. 获取速度 369 | if (pose.has_linear_velocity()) { 370 | mutable_pose->mutable_linear_velocity()->CopyFrom(pose.linear_velocity()); 371 | } 372 | } 373 | 374 | if (imu_msg.has_imu()) { 375 | const auto &imu = imu_msg.imu(); 376 | // linear acceleration 377 | // 4. 获取imu的线性加速度 378 | if (imu.has_linear_acceleration()) { 379 | if (localization->pose().has_orientation()) { 380 | // linear_acceleration: 381 | // convert from vehicle reference to map reference 382 | // 为什么需要做旋转???转换为车当前方向的速度??? 383 | Vector3d orig(imu.linear_acceleration().x(), 384 | imu.linear_acceleration().y(), 385 | imu.linear_acceleration().z()); 386 | Vector3d vec = common::math::QuaternionRotate( 387 | localization->pose().orientation(), orig); 388 | mutable_pose->mutable_linear_acceleration()->set_x(vec[0]); 389 | mutable_pose->mutable_linear_acceleration()->set_y(vec[1]); 390 | mutable_pose->mutable_linear_acceleration()->set_z(vec[2]); 391 | 392 | // linear_acceleration_vfr 393 | // 设置线性加速度 394 | mutable_pose->mutable_linear_acceleration_vrf()->CopyFrom( 395 | imu.linear_acceleration()); 396 | } else { 397 | AERROR << "[PrepareLocalizationMsg]: " 398 | << "fail to convert linear_acceleration"; 399 | } 400 | } 401 | 402 | // 5. 设置角速度,也需要根据航向转换 403 | // angular velocity 404 | ... 405 | 406 | // 6. 设置欧拉角 407 | // euler angle 408 | if (imu.has_euler_angles()) { 409 | mutable_pose->mutable_euler_angles()->CopyFrom(imu.euler_angles()); 410 | } 411 | } 412 | } 413 | ``` 414 | 415 | #### FindNearestGpsStatus 416 | 获取最近的Gps状态信息,这里实现的算法是遍历查找离"gps_time_stamp"最近的状态,GPS状态信息不是按照时间顺序排列的??? 417 | ```c++ 418 | bool RTKLocalization::FindNearestGpsStatus(const double gps_timestamp_sec, 419 | drivers::gnss::InsStat *status) { 420 | ... 421 | // 1. 遍历查找最近的GPS状态信息 422 | double timestamp_diff_sec = 1e8; 423 | auto nearest_itr = gps_status_list.end(); 424 | for (auto itr = gps_status_list.begin(); itr != gps_status_list.end(); 425 | ++itr) { 426 | double diff = std::abs(itr->header().timestamp_sec() - gps_timestamp_sec); 427 | if (diff < timestamp_diff_sec) { 428 | timestamp_diff_sec = diff; 429 | nearest_itr = itr; 430 | } 431 | } 432 | ... 433 | } 434 | ``` 435 | #### FillLocalizationStatusMsg 436 | 获取位置状态,一共有3种状态:稳定状态(INS_RTKFIXED)、浮动状态(INS_RTKFLOAT)、错误状态(ERROR)。由于代码比较简单,这里就不分析了。 437 | 438 | #### 发布消息 439 | 最后通过以下几个函数发布消息。 440 | 1. PublishPoseBroadcastTopic // 发布位置信息 441 | 2. PublishPoseBroadcastTF // 发布位置转换transform信息 442 | 3. PublishLocalizationStatus // 发布位置状态信息 443 | 444 | 以上就是整个RTK的定位流程,主要的思路是通过接收GPS和IMU信息结合输出无人车的位置信息,这里还有一个疑问是为什么最后输出的定位信息的位置是直接采用的GPS的位置信息,没有通过IMU信息对位置信息做解算,还是说在其它模块中实现的??? 445 | 446 | 447 | 448 | 449 | ## Reference 450 | [Robust and Precise Vehicle Localization Based on Multi-Sensor Fusion in Diverse City Scenes](https://ieeexplore.ieee.org/document/8461224) 451 | 452 | 453 | -------------------------------------------------------------------------------- /modules/map/img/ClearArea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/ClearArea.jpg -------------------------------------------------------------------------------- /modules/map/img/ParkingSpace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/ParkingSpace.jpg -------------------------------------------------------------------------------- /modules/map/img/Road.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/Road.jpg -------------------------------------------------------------------------------- /modules/map/img/Sidewalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/Sidewalk.jpg -------------------------------------------------------------------------------- /modules/map/img/Signal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/Signal.jpg -------------------------------------------------------------------------------- /modules/map/img/SpeedBump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/SpeedBump.jpg -------------------------------------------------------------------------------- /modules/map/img/StopSign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/StopSign.jpg -------------------------------------------------------------------------------- /modules/map/img/YieldSign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/YieldSign.jpg -------------------------------------------------------------------------------- /modules/map/img/crosswalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/crosswalk.jpg -------------------------------------------------------------------------------- /modules/map/img/hardware.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/hardware.jpg -------------------------------------------------------------------------------- /modules/map/img/junction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/junction.jpg -------------------------------------------------------------------------------- /modules/map/img/lane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/lane.jpg -------------------------------------------------------------------------------- /modules/map/img/register.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/img/register.jpg -------------------------------------------------------------------------------- /modules/map/todo.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/map/todo.md -------------------------------------------------------------------------------- /modules/monitor/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Monitor ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 人不知而不愠,不亦君子乎。 4 | 5 | ## Table of Contents 6 | 7 | 8 | 9 | 10 | ## 简介 11 | monitor模块主要是监控硬件和软件状态,当出现故障的时候,显示故障原因,并且输出状态给guardian模块进行紧急处理。 12 | 13 | ## MonitorManager 14 | 15 | #### 初始化Init 16 | 17 | 18 | #### StartFrame 19 | 20 | 21 | #### EndFrame 22 | 23 | 24 | ## hardware 25 | 26 | 27 | ## software 28 | 软件主要打开功能安全functional_safety_monitor, 29 | -------------------------------------------------------------------------------- /modules/perception/caffe2/readme.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | - [Caffe2环境准备](#env) 3 | - [安装显卡驱动](#drivers) 4 | - [安装CUDA](#cuda) 5 | - [选择CUDA版本](#cuda_version) 6 | - [安装CUDA](#cuda_install) 7 | - [设置环境变量](#cuda_env) 8 | - [检验安装](#cuda_check) 9 | - [安装cuDNN](#cudnn) 10 | - [安装Caffe2](#caffe2) 11 | - [参考](#reference) 12 | 13 | 14 | 15 | 因为Apollo中的深度学习框架采用的是Caffe2框架,我们需要安装好Caffe2的环境才能进一步学习,下面主要介绍了如何安装Caffe2。安装完成之后,就可以训练深度学习模型了。 16 | 17 | ## Caffe2环境准备 18 | 我们以有显卡的情况为例,来安装caffe2环境,安装caffe2之前需要先安装英伟达的显卡驱动,之后还要安装cuda toolkit和cuDNN,最后安装Caffe2。 19 | 我们先把**需要安装的软件**列出来,再告诉如何选择对应的版本: 20 | * 操作系统: Ubuntu 16.04.5 LTS 21 | * 显卡驱动版本: 384.130 22 | * CUDA Toolkit版本: CUDA Toolkit 9.0 23 | * cuDNN 版本: cuDNN v7.6.0 24 | * pytorch 版本: pytorch-nightly-1.2.0 25 | 26 | 27 | 28 | ## 安装显卡驱动 29 | ubuntu 16.04可以在设置"System Settings - Software & Updates"中选择Using NVIDIA驱动。 30 | ![nvidia_drivers](../img/nvidia_drivers.png) 31 | 安装好驱动后可以通过下面的命令来验证驱动是否安装成功: 32 | ``` 33 | root@root:~/cuda/$ nvidia-smi 34 | Wed May 29 12:05:01 2019 35 | +-----------------------------------------------------------------------------+ 36 | | NVIDIA-SMI 384.130 Driver Version: 384.130 | 37 | |-------------------------------+----------------------+----------------------+ 38 | | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | 39 | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | 40 | |===============================+======================+======================| 41 | | 0 GeForce MX130 Off | 00000000:02:00.0 Off | N/A | 42 | | N/A 73C P0 N/A / N/A | 1240MiB / 2002MiB | 67% Default | 43 | +-------------------------------+----------------------+----------------------+ 44 | 45 | +-----------------------------------------------------------------------------+ 46 | | Processes: GPU Memory | 47 | | GPU PID Type Process name Usage | 48 | |=============================================================================| 49 | | 0 1682 G /usr/lib/xorg/Xorg 445MiB | 50 | | 0 2744 G compiz 192MiB | 51 | | 0 3101 G ...quest-channel-token=7501548652516259525 599MiB | 52 | +-----------------------------------------------------------------------------+ 53 | ``` 54 | 55 | 56 | 57 | ## 安装CUDA 58 | 59 | 60 | 61 | #### 选择CUDA版本 62 | 安装好显卡驱动后,就可以安装CUDA了,那么我们如何选择CUDA版本呢?首先查看显卡驱动版本,就是上面"nvidia-smi"显示的"Driver Version: 384.130",然后查看下表,[官网地址](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html): 63 | ![cuda_version](../img/cuda_version.png) 64 | 可以看到驱动版本">= 390.46"选择CUDA 9.0,当然还需要查看下内核,GCC,GLIBC版本是否支持,参照下表,[官网地址](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html): 65 | ![cuda_version2](../img/cuda_version2.png) 66 | 67 | 查看linux内核版本: 68 | ``` 69 | root:~/cuda$ cat /proc/version 70 | Linux version 4.15.0-50-generic (buildd@lgw01-amd64-029) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)) #54~16.04.1-Ubuntu SMP Wed May 8 15:55:19 UTC 2019 71 | 72 | ``` 73 | 74 | 这里提供的是支持CUDA10.1需要的配置,我目前的ubuntu 16.04已经都支持了,所以9.0肯定没有问题。 75 | 76 | 77 | 78 | #### 安装CUDA 79 | 选择好版本后,就可以下载安装CUDA了,CUDA官方的[下载地址](https://developer.nvidia.com/cuda-toolkit-archive),选择对应的版本,下载之后运行: 80 | ``` 81 | sudo sh cuda_9.0.176_384.81_linux.run 82 | ``` 83 | **记住前面已经安装了驱动,所以安装CUDA的时候第一步需要跳过安装驱动,只安装CUDA Toolkit**,接着按照提示安装就可以了。 84 | 85 | 86 | 87 | 88 | #### 设置环境变量 89 | 安装完成之后,我们需要设置CUDA环境变量才能运行: 90 | ``` 91 | sudo vi ~/.bashrc 92 | ``` 93 | 在文件最后增加2行: 94 | ``` 95 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/cuda-9.0/lib64:/usr/local/cuda-9.0/extras/CUPTI/lib64" 96 | export CUDA_HOME=/usr/local/cuda-9.0 97 | ``` 98 | 将环境变量生效: 99 | ``` 100 | source ~/.bashrc 101 | ``` 102 | 103 | 104 | 105 | #### 检验安装 106 | 我们可以通过samples的deviceQuery来检验CUDA是否安装成功。 107 | ``` 108 | cd /usr/local/cuda/samples/1_Utilities/deviceQuery 109 | sudo make 110 | ./deviceQuery 111 | ``` 112 | 如果提示"Result = PASS",则表示安装成功,如果提示"Result = FAIL",则表示安装失败: 113 | ``` 114 | $ ./deviceQuery 115 | ./deviceQuery Starting... 116 | 117 | CUDA Device Query (Runtime API) version (CUDART static linking) 118 | 119 | Detected 1 CUDA Capable device(s) 120 | 121 | Device 0: "GeForce MX130" 122 | CUDA Driver Version / Runtime Version 9.0 / 9.0 123 | CUDA Capability Major/Minor version number: 5.0 124 | Total amount of global memory: 2003 MBytes (2100232192 bytes) 125 | ( 3) Multiprocessors, (128) CUDA Cores/MP: 384 CUDA Cores 126 | GPU Max Clock rate: 1189 MHz (1.19 GHz) 127 | Memory Clock rate: 2505 Mhz 128 | Memory Bus Width: 64-bit 129 | L2 Cache Size: 1048576 bytes 130 | Maximum Texture Dimension Size (x,y,z) 1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096) 131 | Maximum Layered 1D Texture Size, (num) layers 1D=(16384), 2048 layers 132 | Maximum Layered 2D Texture Size, (num) layers 2D=(16384, 16384), 2048 layers 133 | Total amount of constant memory: 65536 bytes 134 | Total amount of shared memory per block: 49152 bytes 135 | Total number of registers available per block: 65536 136 | Warp size: 32 137 | Maximum number of threads per multiprocessor: 2048 138 | Maximum number of threads per block: 1024 139 | Max dimension size of a thread block (x,y,z): (1024, 1024, 64) 140 | Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535) 141 | Maximum memory pitch: 2147483647 bytes 142 | Texture alignment: 512 bytes 143 | Concurrent copy and kernel execution: Yes with 1 copy engine(s) 144 | Run time limit on kernels: Yes 145 | Integrated GPU sharing Host Memory: No 146 | Support host page-locked memory mapping: Yes 147 | Alignment requirement for Surfaces: Yes 148 | Device has ECC support: Disabled 149 | Device supports Unified Addressing (UVA): Yes 150 | Supports Cooperative Kernel Launch: No 151 | Supports MultiDevice Co-op Kernel Launch: No 152 | Device PCI Domain ID / Bus ID / location ID: 0 / 2 / 0 153 | Compute Mode: 154 | < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) > 155 | 156 | deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.0, CUDA Runtime Version = 9.0, NumDevs = 1 157 | Result = PASS 158 | ``` 159 | 160 | 161 | 162 | ## 安装cuDNN 163 | 我们安装好了CUDA之后,如果希望深度学习训练的时候能够加速,则需要安装"cuDNN",这一步很简单,只需要根据CUDA的版本选择对应的cuDNN版本就可以了。 164 | 可以在[官网](https://developer.nvidia.com/rdp/cudnn-download)下载,CUDA 9.0对应的cuDNN版本为v7.6.0,下载选择linux版本。 165 | 下载完成之后解压文件: 166 | ``` 167 | tar -xzvf cudnn-9.0-linux-x64-v7.6.0.64.tgz 168 | ``` 169 | 解压之后的文件如下: 170 | ``` 171 | ├──cuda 172 | │   ├── include 173 | │   │   └── cudnn.h 174 | │   ├── lib64 175 | │   │   ├── libcudnn.so -> libcudnn.so.7 176 | │   │   ├── libcudnn.so.7 -> libcudnn.so.7.6.0 177 | │   │   ├── libcudnn.so.7.6.0 178 | │   │   └── libcudnn_static.a 179 | │   └── NVIDIA_SLA_cuDNN_Support.txt 180 | ``` 181 | 然后把文件拷贝到CUDA的目录下就可以了: 182 | ``` 183 | sudo cp cuda/include/cudnn.h /usr/local/cuda-9.0/include 184 | sudo cp cuda/lib64/libcudnn* /usr/local/cuda-9.0/lib64 185 | ``` 186 | 187 | 经过上面的步骤,我们就安装好了整个CUDA环境,主要是确认好CUDA版本,如果版本不对,对应的检测就不通过。需要仔细确认系统支持的CUDA版本。 188 | 189 | 190 | 191 | 192 | ## 安装Caffe2 193 | #### 直接安装(二进制文件安装) 194 | caffe2选择直接用anaconda安装,因为anaconda集成了大部分的工具,安装起来也很简单: 195 | ``` 196 | conda install pytorch-nightly -c pytorch 197 | ``` 198 | 这是官网的[安装说明](https://caffe2.ai/docs/getting-started.html?platform=ubuntu&configuration=prebuilt) 199 | 200 | 201 | 至此所有的安装就已经完成了,可以开始深度学习的尝试了。 202 | 203 | #### 源码安装 204 | 上面的方法是直接安装编译好的caffe2,而有些选项默认为关闭,想要打开这些选项(例如USE_LMDB),就需要从源码安装caffe2。 205 | 206 | 从源码安装caffe2可以参考[官网教程](https://github.com/pytorch/pytorch#from-source)。 207 | 1. 安装依赖 208 | ``` 209 | conda install numpy ninja pyyaml mkl mkl-include setuptools cmake cffi typing 210 | ``` 211 | 2. 安装magma 212 | ``` 213 | # Add LAPACK support for the GPU if needed 214 | conda install -c pytorch magma-cuda90 # or [magma-cuda92 | magma-cuda100 ] depending on your cuda version 215 | ``` 216 | 3. 下载PyTorch 217 | ``` 218 | git clone --recursive https://github.com/pytorch/pytorch 219 | cd pytorch 220 | # if you are updating an existing checkout 221 | git submodule sync 222 | git submodule update --init --recursive 223 | ``` 224 | 4. 安装PyTorch 225 | ``` 226 | export CMAKE_PREFIX_PATH=${CONDA_PREFIX:-"$(dirname $(which conda))/../"} 227 | python setup.py install 228 | ``` 229 | 230 | 如果需要运行MNIST.ipynb的例子,需要同时安装LMDB,参考[issue](https://github.com/pytorch/pytorch/issues/21117) 231 | ``` 232 | 1. install lmdb 233 | conda install lmdb leveldb 234 | 235 | 2. rebuild caffe2 from source 236 | USE_LMDB=ON python setup.py install --cmake 237 | ``` 238 | 239 | 这之后整个安装过程就结束了,现在你可以试一试在jupyter-notebook中运行MNIST.ipynb,开始学习caffe2! 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | ## 参考 248 | [Ubuntu16.04+Ananconda3+CUDA+CUDNN+Tensorflow-gpu配置教程](https://zhuanlan.zhihu.com/p/37569310) 249 | [ubuntu16.04下安装CUDA,cuDNN及tensorflow-gpu版本过程](https://blog.csdn.net/u014595019/article/details/53732015) 250 | -------------------------------------------------------------------------------- /modules/perception/fusion/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## fusion 4 | fusion传感器数据融合,整体的融合有硬件融合和数据融合,主要的工作是时间同步和数据融合。 5 | 6 | ## app 7 | 8 | ## lib 9 | -------------------------------------------------------------------------------- /modules/perception/img/Convolution_of_spiky_function_with_box2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/Convolution_of_spiky_function_with_box2.gif -------------------------------------------------------------------------------- /modules/perception/img/attention.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/attention.png -------------------------------------------------------------------------------- /modules/perception/img/cnn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/cnn.png -------------------------------------------------------------------------------- /modules/perception/img/cnn_qa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/cnn_qa.jpg -------------------------------------------------------------------------------- /modules/perception/img/conv_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/conv_1.png -------------------------------------------------------------------------------- /modules/perception/img/convolution.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/convolution.gif -------------------------------------------------------------------------------- /modules/perception/img/cuda_version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/cuda_version.png -------------------------------------------------------------------------------- /modules/perception/img/cuda_version2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/cuda_version2.png -------------------------------------------------------------------------------- /modules/perception/img/edge_detection.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/edge_detection.PNG -------------------------------------------------------------------------------- /modules/perception/img/fully_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/fully_connect.png -------------------------------------------------------------------------------- /modules/perception/img/imageNet.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/imageNet.PNG -------------------------------------------------------------------------------- /modules/perception/img/image_caption.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/image_caption.PNG -------------------------------------------------------------------------------- /modules/perception/img/imgsee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/imgsee.jpg -------------------------------------------------------------------------------- /modules/perception/img/maxpool.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/maxpool.jpeg -------------------------------------------------------------------------------- /modules/perception/img/nvidia_drivers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/nvidia_drivers.png -------------------------------------------------------------------------------- /modules/perception/img/object_detection.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/object_detection.PNG -------------------------------------------------------------------------------- /modules/perception/img/pascal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/pascal.png -------------------------------------------------------------------------------- /modules/perception/img/perception_process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/perception_process.jpg -------------------------------------------------------------------------------- /modules/perception/img/question.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/question.PNG -------------------------------------------------------------------------------- /modules/perception/img/radar_process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/radar_process.jpg -------------------------------------------------------------------------------- /modules/perception/img/semantic.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/semantic.PNG -------------------------------------------------------------------------------- /modules/perception/img/sensor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/sensor.jpg -------------------------------------------------------------------------------- /modules/perception/img/understanding.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/understanding.PNG -------------------------------------------------------------------------------- /modules/perception/img/weights.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/perception/img/weights.jpeg -------------------------------------------------------------------------------- /modules/perception/inference/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## inference推理 5 | 深度学习模型包括训练和推理2个过程,训练模型的过程是通过设计神经网络,通过数据训练出模型的参数。而推理则是部署深度学习的过程,实际上目前训练深度学习主要用的是python语言,而部署的时候大部分会采用c++,并且通过gpu进行加速,而inference模块则实现了上述功能。 6 | 7 | inference主要实现了tensorflow, caffe, paddlepaddle3种框架的实现,并且通过cuda进行计算加速。 8 | 9 | #### caffe 10 | 11 | 12 | #### paddlepaddle 13 | 14 | 15 | #### tensorrt 16 | 17 | 18 | #### 目录介绍 19 | ``` 20 | ├── BUILD 21 | ├── caffe // caffe框架 22 | ├── inference.cc // 定义了推理接口 23 | ├── inference_factory.cc // 推理工厂,用来创建推理器 24 | ├── inference_factory.h 25 | ├── inference_factory_test.cc 26 | ├── inference.h 27 | ├── inference_test.cc 28 | ├── inference_test_data // 推理测试数据 29 | ├── layer.cc // 定义了layer接口 30 | ├── layer.h 31 | ├── layer_test.cc 32 | ├── operators // 算子??? 33 | ├── paddlepaddle // paddlepaddle框架 34 | ├── tensorrt // tensorrt框架 35 | ├── test 36 | ├── tools // yolo,lane等的例子 37 | └── utils // cuda加速工具 38 | ``` 39 | 40 | 41 | #### CreateInferenceByName 42 | CreateInferenceByName可以创建3种形式的推理器caffe, paddlepaddle, tensorrt。这里的tensorrt就是英伟达的加速库吗?也就是说如果是其它模型则采用tensorrt部署,caffe和paddlepaddle则采用这2种高级别的api部署? 43 | ```c++ 44 | Inference *CreateInferenceByName(const std::string &name, 45 | const std::string &proto_file, 46 | const std::string &weight_file, 47 | const std::vector &outputs, 48 | const std::vector &inputs, 49 | const std::string &model_root) { 50 | if (name == "CaffeNet") { 51 | return new CaffeNet(proto_file, weight_file, outputs, inputs); 52 | } else if (name == "RTNet") { 53 | return new RTNet(proto_file, weight_file, outputs, inputs); 54 | } else if (name == "RTNetInt8") { 55 | return new RTNet(proto_file, weight_file, outputs, inputs, model_root); 56 | } else if (name == "PaddleNet") { 57 | return new PaddleNet(proto_file, weight_file, outputs, inputs); 58 | } 59 | return nullptr; 60 | } 61 | ``` 62 | 63 | 3种推理模型分别在caffe, paddlepaddle, tensorrt目录中,其中有用到cuda进行加速,其中".cu"是cuda对C++的扩展。 64 | 65 | -------------------------------------------------------------------------------- /modules/perception/lidar/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## lidar 4 | 5 | ## app 6 | app目录主要实现3个功能lidar_obstacle_detection,lidar_obstacle_segmentation,lidar_obstacle_tracking三个功能。 7 | 8 | 9 | 10 | ## lib目录 11 | 整个激光雷达的处理流程是什么??? 先分割,找地面,然后找障碍物??? 12 | 13 | #### classifier 14 | 15 | #### ground_detector 16 | 17 | 18 | #### map_manager 19 | 20 | 21 | #### object_builder 22 | 23 | 24 | #### object_filter_bank 25 | 26 | 27 | #### pointcloud_preprocessor 28 | 29 | 30 | #### roi_filter 31 | 感兴趣区域过滤 32 | 33 | #### scene_manager 34 | 场景管理??? 35 | 36 | #### segmentation 37 | 分割 38 | 39 | #### tracker 40 | 追踪 41 | 42 | ## tools 43 | tools目录主要有2个工具,一个是OfflineLidarObstaclePerception,另一个是msg_exporter_main,下面分别介绍这2个工具的作用。 44 | 45 | -------------------------------------------------------------------------------- /modules/perception/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Perception ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 温故而知新,可以为师矣 4 | 5 | 6 | ## Table of Contents 7 | - [CNN](cnn) 8 | - [什么是CNN?](cnn#what_is_cnn) 9 | - [CNN的原理](cnn#cnn_principle) 10 | - [卷积层(Convolutional Layer)](cnn#convolutional) 11 | - [池化层(Max Pooling Layer)](cnn#max_pool) 12 | - [全连接层(Fully Connected Layer)](cnn#fully_connect) 13 | - [如何构建CNN](cnn#how_to) 14 | - [基本概念](cnn#base_concept) 15 | - [引用](cnn#reference) 16 | - [Caffe2](caffe2) 17 | - [Caffe2环境准备](caffe2#env) 18 | - [安装显卡驱动](caffe2#drivers) 19 | - [安装CUDA](caffe2#cuda) 20 | - [选择CUDA版本](caffe2#cuda_version) 21 | - [安装CUDA](caffe2#cuda_install) 22 | - [设置环境变量](caffe2#cuda_env) 23 | - [检验安装](caffe2#cuda_check) 24 | - [安装cuDNN](caffe2#cudnn) 25 | - [安装Caffe2](caffe2#caffe2) 26 | - [参考](caffe2#reference) 27 | - [Perception模块简介](#introduction) 28 | - [production目录](#production) 29 | - [onboard目录](#onboard) 30 | - [radar子模块](#sub_module) 31 | - [camera子模块](#sub_module) 32 | - [lidar子模块](#sub_module) 33 | - [fusion子模块](#sub_module) 34 | - [inference推理子模块](#sub_module) 35 | - [Reference](#reference) 36 | 37 | 38 | 39 | ## Perception模块简介 40 | 41 | 首先简单看下perception的目录结构: 42 | ``` 43 | . 44 | ├── BUILD 45 | ├── Perception_README_3_5.md 46 | ├── README.md 47 | ├── base // 基础类 48 | ├── camera // 相机相关 --- 子模块流程 49 | ├── common // 公共目录 50 | ├── data // 相机的内参和外参 51 | ├── fusion // 传感器融合 52 | ├── inference // 深度学习推理模块 53 | ├── lib // 一些基础的库,包括线程、时间等 54 | ├── lidar // 激光雷达相关 --- 子模块流程 55 | ├── map // 地图 56 | ├── model // 深度学习模型 57 | ├── onboard // 各个子模块的入口 --- 子模块入口 58 | ├── production // 感知模块入口(深度学习模型也存放在这里)--- 通过cyber启动子模块 59 | ├── proto // 数据格式,protobuf 60 | ├── radar // 毫米波 --- 子模块流程 61 | ├── testdata // 上述几个模块的测试数据 62 | └── tool // 离线测试工具 63 | ``` 64 | 下面介绍几个重要的目录结构: 65 | * production目录 - **感知模块的入口在production目录,通过lanuch加载对应的dag,启动感知模块**,感知模块包括多个子模块,在onboard目录中定义。 66 | * onboard目录 - 定义了多个子模块,分别用来处理不同的传感器信息(Lidar,Radar,Camera)。各个子模块的入口在onboard目录中,每个传感器的流程大概相似,可以分为预处理,物体识别,感兴趣区域过滤以及追踪。 67 | * inference目录 - 深度学习推理模块,我们知道深度学习模型训练好了之后需要部署,而推理则是深度学习部署的过程,实际上部署的过程会对模型做加速,**主要实现了caffe,TensorRT和paddlepaddle**3种模型部署。训练好的深度模型放在"modules\perception\production\data"目录中,然后通过推理模块进行加载部署和在线计算。 68 | * camera目录 - 主要实现车道线识别,红绿灯检测,以及障碍物识别和追踪。 69 | * radar目录 - 主要实现障碍物识别和追踪(由于毫米波雷达上报的就是障碍物信息,这里主要是对障碍物做追踪)。 70 | * lidar目录 - 主要实现障碍物识别和追踪(对点云做分割,分类,识别等)。 71 | * fusion目录 - 对上述传感器的感知结果做融合。 72 | 73 | 整个模块的流程如图: 74 | ![process](img/perception_process.jpg) 75 | 可以看到感知模块由production模块开始,由fusion模块结束。 76 | 77 | 78 | 79 | 80 | ## production目录 81 | production中主要是存放: 82 | 1. 配置和lanuch和dag启动文件 83 | 2. 存放训练好的模型 84 | ``` 85 | . 86 | ├── conf // 配置文件 87 | ├── dag // dag启动文件 88 | ├── data // 训练好的模型 89 | └── launch // cyber launch加载dag 90 | ``` 91 | 该文件中有多个lanuch文件,同时一个lanuch文件中包含多个dag文件,也就是说一个lanuch文件会启动多个子模块。 92 | 93 | 94 | 95 | 96 | ## onboard目录 97 | onboard目录定义了多个子模块,每个子模块对应一个功能,包括:车道线识别,障碍物识别,红绿灯识别,传感器融合,场景分割等。 98 | ``` 99 | . 100 | ├── common_flags 101 | ├── component // 子模块入口 102 | ├── inner_component_messages 103 | ├── msg_buffer 104 | ├── msg_serializer 105 | ├── proto 106 | └── transform_wrapper 107 | ``` 108 | 109 | 实际上几个子模块可能合并为一个模块,如何确定模块是否合并呢?我们可以查看"onboard/component"目录中的BUILD文件。 110 | ``` 111 | name = "perception_component_inner_lidar", 112 | srcs = [ 113 | "fusion_component.cc", 114 | "lidar_output_component.cc", 115 | "radar_detection_component.cc", 116 | "recognition_component.cc", 117 | "segmentation_component.cc", 118 | "detection_component.cc", 119 | ], 120 | ``` 121 | 在BUILD文件中上述几个模块被编译为一个模块"libperception_component_lidar"。也就是说在dag中实际上只需要启动**libperception_component_lidar**这一个模块就相当于启动了上述几个模块。 122 | 123 | 124 | 看完了perception模块的入口,以及各个子模块的定义,那么各个子模块的功能如何实现的呢? 实际上感知各个子模块的功能是通过lidar,radar和camera3种传感器实现的,每种传感器分别都执行了目标识别和追踪的任务,最后通过fusion对传感器的数据做融合,执行代码分别在"perception/radar","perception/lidar","perception/camera"目录中。这里有2种查看代码的方式,一种是正序的方式,根据具体的功能,例如从物体识别子模块入手,分别查看lidar,radar和camera模块中的物体识别功能,另一种是倒序的方式,根据传感器划分,先查看传感器分别实现了哪些功能,然后回过头来看各个子模块是如何把上述功能整合起来的。这里我们采用第2种方式,先看各个传感器的执行流程如下图。 125 | ![sensor](img/sensor.jpg) 126 | 从图中可以看到,每个传感器都实现了物体识别的功能,而摄像头还实现了车道线识别和红绿灯检测的功能,每个传感器执行的任务流水线也大概相似,先进行预处理,然后做识别,最后过滤并且追踪目标。其中物体识别用到了推理引擎inference。 127 | 128 | 接下来来我们分别查看各个传感器的具体实现。我们先从radar开始看起,主要是radar模块相对比较简单。 129 | 130 | 131 | 132 | 133 | ## 子模块介绍 134 | [radar子模块介绍](radar#radar_module) 135 | [camera子模块介绍](camera#camera_module) 136 | [lidar子模块介绍](lidar#lidar_module) 137 | [fusion子模块](fusion#fusion_module) 138 | [inference推理子模块](inference#inference_module) 139 | 140 | 141 | 142 | 143 | ## Reference 144 | [A Beginner's Guide to Convolutional Neural Networks](https://skymind.ai/wiki/convolutional-network) 145 | [cnn](https://cs231n.github.io/convolutional-networks/) 146 | [traffic light dataset](https://hci.iwr.uni-heidelberg.de/node/6132/download/3d66608cfb112934ef40175e9a20c81f) 147 | [pytorch-tutorial](https://github.com/yunjey/pytorch-tutorial) 148 | [全连接层的作用是什么?](https://www.zhihu.com/question/41037974) 149 | [索伯算子](https://zh.wikipedia.org/wiki/%E7%B4%A2%E8%B2%9D%E7%88%BE%E7%AE%97%E5%AD%90) 150 | [卷积](https://zh.wikipedia.org/wiki/%E5%8D%B7%E7%A7%AF) 151 | [TensorRT(1)-介绍-使用-安装](https://arleyzhang.github.io/articles/7f4b25ce/) 152 | [高性能深度学习支持引擎实战——TensorRT](https://zhuanlan.zhihu.com/p/35657027) 153 | 154 | -------------------------------------------------------------------------------- /modules/perception/todo.md: -------------------------------------------------------------------------------- 1 | # URL 2 | https://blog.csdn.net/jinzhuojun/article/details/80875264 3 | https://blog.csdn.net/jinzhuojun/article/details/83038279 4 | https://zhuanlan.zhihu.com/p/33416142 5 | https://github.com/ApolloAuto/apollo/blob/master/docs/specs/3d_obstacle_perception_cn.md 6 | 7 | 8 | # 感知综述 9 | https://zhuanlan.zhihu.com/p/33416142 -------------------------------------------------------------------------------- /modules/planning/img/Planner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/Planner.png -------------------------------------------------------------------------------- /modules/planning/img/create_ref_line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/create_ref_line.jpg -------------------------------------------------------------------------------- /modules/planning/img/dataflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/dataflow.png -------------------------------------------------------------------------------- /modules/planning/img/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/flowchart.png -------------------------------------------------------------------------------- /modules/planning/img/planning_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/planning_base.png -------------------------------------------------------------------------------- /modules/planning/img/planning_component.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/planning_component.png -------------------------------------------------------------------------------- /modules/planning/img/planning_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/planning_flow.png -------------------------------------------------------------------------------- /modules/planning/img/reference_line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/reference_line.jpg -------------------------------------------------------------------------------- /modules/planning/img/rf_line_point.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/rf_line_point.jpg -------------------------------------------------------------------------------- /modules/planning/img/task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/planning/img/task.png -------------------------------------------------------------------------------- /modules/planning/todo.md: -------------------------------------------------------------------------------- 1 | ## Frame 2 | Frame代表了规划模块中的一帧,包括了多种信息。 3 | 4 | #### FindDriveReferenceLineInfo 5 | 找到代价最小并且可以行驶的参考线,然后返回结果。 6 | ```c++ 7 | const ReferenceLineInfo *Frame::FindDriveReferenceLineInfo() { 8 | double min_cost = std::numeric_limits::infinity(); 9 | drive_reference_line_info_ = nullptr; 10 | // 遍历找到最小代价,并且可以行驶的reference line 11 | for (const auto &reference_line_info : reference_line_info_) { 12 | if (reference_line_info.IsDrivable() && 13 | reference_line_info.Cost() < min_cost) { 14 | drive_reference_line_info_ = &reference_line_info; 15 | min_cost = reference_line_info.Cost(); 16 | } 17 | } 18 | return drive_reference_line_info_; 19 | } 20 | ``` 21 | 22 | #### FindTargetReferenceLineInfo 23 | 返回第一个变道类型的参考线,如果没有找到,则返回最后一个参考线。 24 | ```c++ 25 | const ReferenceLineInfo *Frame::FindTargetReferenceLineInfo() { 26 | const ReferenceLineInfo *target_reference_line_info = nullptr; 27 | for (const auto &reference_line_info : reference_line_info_) { 28 | if (reference_line_info.IsChangeLanePath()) { 29 | return &reference_line_info; 30 | } 31 | target_reference_line_info = &reference_line_info; 32 | } 33 | return target_reference_line_info; 34 | } 35 | ``` 36 | 37 | #### FindFailedReferenceLineInfo 38 | 找到是变道类型并且不能行驶的参考线,如果没有找到则返回空。 39 | ```c++ 40 | const ReferenceLineInfo *Frame::FindFailedReferenceLineInfo() { 41 | for (const auto &reference_line_info : reference_line_info_) { 42 | // Find the unsuccessful lane-change path 43 | if (!reference_line_info.IsDrivable() && 44 | reference_line_info.IsChangeLanePath()) { 45 | return &reference_line_info; 46 | } 47 | } 48 | return nullptr; 49 | } 50 | ``` 51 | 52 | #### DriveReferenceLineInfo 53 | 返回FindDriveReferenceLineInfo中找到的参考线。即代价最小并且可以行驶的参考线。 54 | ```c++ 55 | const ReferenceLineInfo *Frame::DriveReferenceLineInfo() const { 56 | return drive_reference_line_info_; 57 | } 58 | ``` 59 | 60 | #### UpdateReferenceLinePriority 61 | 更新参考线的优先级,其中key为lane的id,value为优先级。(todo)这里的lanes是`hdmap::RouteSegments`类型,为什么id只有一个? 62 | ```c++ 63 | void Frame::UpdateReferenceLinePriority( 64 | const std::map &id_to_priority) { 65 | for (const auto &pair : id_to_priority) { 66 | const auto id = pair.first; 67 | const auto priority = pair.second; 68 | auto ref_line_info_itr = 69 | std::find_if(reference_line_info_.begin(), reference_line_info_.end(), 70 | [&id](const ReferenceLineInfo &ref_line_info) { 71 | return ref_line_info.Lanes().Id() == id; 72 | }); 73 | if (ref_line_info_itr != reference_line_info_.end()) { 74 | ref_line_info_itr->SetPriority(priority); 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | #### CreateReferenceLineInfo 81 | 根据reference_lines创建reference_line_info_(也是一个数组),并且添加障碍物到数组。 82 | ```c++ 83 | bool Frame::CreateReferenceLineInfo( 84 | const std::list &reference_lines, 85 | const std::list &segments) { 86 | reference_line_info_.clear(); 87 | auto ref_line_iter = reference_lines.begin(); 88 | auto segments_iter = segments.begin(); 89 | // 1. reference_line_info_添加成员,(todo)这里ref_line_iter和segments_iter是一一对应的吗? 90 | while (ref_line_iter != reference_lines.end()) { 91 | if (segments_iter->StopForDestination()) { 92 | is_near_destination_ = true; 93 | } 94 | reference_line_info_.emplace_back(vehicle_state_, planning_start_point_, 95 | *ref_line_iter, *segments_iter); 96 | ++ref_line_iter; 97 | ++segments_iter; 98 | } 99 | 100 | // 2. 如果reference_line_info_大小为2 101 | if (reference_line_info_.size() == 2) { 102 | common::math::Vec2d xy_point(vehicle_state_.x(), vehicle_state_.y()); 103 | common::SLPoint first_sl; 104 | if (!reference_line_info_.front().reference_line().XYToSL(xy_point, 105 | &first_sl)) { 106 | return false; 107 | } 108 | common::SLPoint second_sl; 109 | if (!reference_line_info_.back().reference_line().XYToSL(xy_point, 110 | &second_sl)) { 111 | return false; 112 | } 113 | // 2.1 根据车当前的位置求出到起点的距离 114 | const double offset = first_sl.l() - second_sl.l(); 115 | reference_line_info_.front().SetOffsetToOtherReferenceLine(offset); 116 | reference_line_info_.back().SetOffsetToOtherReferenceLine(-offset); 117 | } 118 | 119 | bool has_valid_reference_line = false; 120 | // 3. 初始化障碍物,如果有一个成功则表示有合理的参考线 121 | for (auto &ref_info : reference_line_info_) { 122 | if (!ref_info.Init(obstacles())) { 123 | AERROR << "Failed to init reference line"; 124 | } else { 125 | has_valid_reference_line = true; 126 | } 127 | } 128 | return has_valid_reference_line; 129 | } 130 | ``` 131 | 132 | -------------------------------------------------------------------------------- /modules/prediction/img/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/prediction/img/container.jpg -------------------------------------------------------------------------------- /modules/prediction/img/obs_process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/prediction/img/obs_process.jpg -------------------------------------------------------------------------------- /modules/prediction/img/process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/prediction/img/process.jpg -------------------------------------------------------------------------------- /modules/prediction/todo.md: -------------------------------------------------------------------------------- 1 | ## 如何获取场景??? 2 | 如何分析并且获取场景???? 3 | ``` 4 | void ScenarioManager::Run() { 5 | auto environment_features = FeatureExtractor::ExtractEnvironmentFeatures(); 6 | 7 | auto ptr_scenario_features = ScenarioAnalyzer::Analyze(environment_features); 8 | 9 | current_scenario_ = ptr_scenario_features->scenario(); 10 | 11 | // TODO(all) other functionalities including lane, junction filters 12 | } 13 | ``` 14 | 15 | ## 如何生成LaneGraph 16 | ``` 17 | Obstacle::BuildLaneGraph() 18 | ``` 19 | 1. 障碍物如何生成Lane图,作用是什么??? 20 | 21 | 2. common目录的"RoadGraph"生成的图和上述的图有什么关系??? 22 | 23 | 24 | ## protobuf中mutable_的作用 25 | 可以看出,对于每个字段会生成一个has函数(has_number)、clear清除函数(clear_number)、set函数(set_number)、get函数(number和mutable_number)。这儿解释下get函数中的两个函数的区别,对于原型为const std::string &number() const的get函数而言,返回的是常量字段,不能对其值进行修改。但是在有一些情况下,对字段进行修改是必要的,所以提供了一个mutable版的get函数,通过获取字段变量的指针,从而达到改变其值的目的。 26 | 27 | 28 | ## 计划 29 | 1. 分析下LaneGraph和RoadGraph是如何生成的??? 30 | 2. 分析LTSM和MLP的实现??? 31 | 3. 分析预测曲线的原理??? 32 | 33 | 4. planning模块是如何利用这些预测的障碍物信息的??? 34 | -------------------------------------------------------------------------------- /modules/routing/img/30px-Osm_element_area.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/30px-Osm_element_area.svg.png -------------------------------------------------------------------------------- /modules/routing/img/30px-Osm_element_closedway.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/30px-Osm_element_closedway.svg.png -------------------------------------------------------------------------------- /modules/routing/img/30px-Osm_element_node.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/30px-Osm_element_node.svg.png -------------------------------------------------------------------------------- /modules/routing/img/30px-Osm_element_relation.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/30px-Osm_element_relation.svg.png -------------------------------------------------------------------------------- /modules/routing/img/30px-Osm_element_tag.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/30px-Osm_element_tag.svg.png -------------------------------------------------------------------------------- /modules/routing/img/30px-Osm_element_way.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/30px-Osm_element_way.svg.png -------------------------------------------------------------------------------- /modules/routing/img/375px-Shortest_path_with_direct_weights.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/375px-Shortest_path_with_direct_weights.svg.png -------------------------------------------------------------------------------- /modules/routing/img/black_map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/black_map.jpg -------------------------------------------------------------------------------- /modules/routing/img/edge_cost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/edge_cost.png -------------------------------------------------------------------------------- /modules/routing/img/graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/graph.jpg -------------------------------------------------------------------------------- /modules/routing/img/introduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/introduction.png -------------------------------------------------------------------------------- /modules/routing/img/lane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/lane.png -------------------------------------------------------------------------------- /modules/routing/img/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/main.jpg -------------------------------------------------------------------------------- /modules/routing/img/range.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/range.jpg -------------------------------------------------------------------------------- /modules/routing/img/range_rank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/routing/img/range_rank.jpg -------------------------------------------------------------------------------- /modules/routing/todo.md: -------------------------------------------------------------------------------- 1 | 1. 为什么会出现找不到节点的情况??? 2 | 节点没有连接,所以需要找到节点附近最近的路段。 3 | 4 | 2. Astar算法 5 | http://www-cs-students.stanford.edu/~amitp/gameprog.html#Paths 6 | 7 | http://theory.stanford.edu/~amitp/GameProgramming/ 8 | 9 | https://www.geeksforgeeks.org/a-search-algorithm/ 10 | 11 | http://theory.stanford.edu/~amitp/GameProgramming/AStarComparison.html 12 | 13 | 14 | 3. "RoutingComponent"类继承至"cyber::Component",并且申明为"public"方式,"cyber::Component"是一个模板类,它定义了"Initialize"和"Process"方法。 15 | ``` 16 | template 17 | class Component : public ComponentBase { 18 | public: 19 | Component() {} 20 | ~Component() override {} 21 | bool Initialize(const ComponentConfig& config) override; 22 | bool Process(const std::shared_ptr& msg); 23 | 24 | private: 25 | virtual bool Proc(const std::shared_ptr& msg) = 0; 26 | }; 27 | ``` 28 | // todo 模板方法中为虚函数,而继承类中为公有方法?为什么? 29 | 30 | 31 | ## SearchRoute 32 | 33 | ``` 34 | bool Navigator::SearchRoute(const RoutingRequest& request, 35 | RoutingResponse* const response) { 36 | ... 37 | // 初始化规划点和起点 38 | std::vector way_nodes; 39 | std::vector way_s; 40 | if (!Init(request, graph_.get(), &way_nodes, &way_s)) { 41 | return false; 42 | } 43 | // 根据节点和起点,查找返回结果,注意这里返回的是一段范围 44 | std::vector result_nodes; 45 | if (!SearchRouteByStrategy(graph_.get(), way_nodes, way_s, &result_nodes)) { 46 | return false; 47 | } 48 | if (result_nodes.empty()) { 49 | return false; 50 | } 51 | // 插入起点和终点 52 | result_nodes.front().SetStartS(request.waypoint().begin()->s()); 53 | result_nodes.back().SetEndS(request.waypoint().rbegin()->s()); 54 | // 生成通道区域 55 | if (!result_generator_->GeneratePassageRegion( 56 | graph_->MapVersion(), request, result_nodes, topo_range_manager_, 57 | response)) { 58 | return false; 59 | } 60 | ... 61 | } 62 | ``` 63 | 64 | ## FillLaneInfoIfMissing 65 | 如果routing请求中没有包含lane信息,则会自动补全这一部分信息。 66 | ``` 67 | RoutingRequest Routing::FillLaneInfoIfMissing( 68 | const RoutingRequest& routing_request) { 69 | RoutingRequest fixed_request(routing_request); 70 | // 遍历routing请求的点 71 | for (int i = 0; i < routing_request.waypoint_size(); ++i) { 72 | const auto& lane_waypoint = routing_request.waypoint(i); 73 | // routing_request请求的点有lane_id,则表示在路上,不用查找 74 | if (lane_waypoint.has_id()) { 75 | continue; 76 | } 77 | auto point = common::util::MakePointENU(lane_waypoint.pose().x(), 78 | lane_waypoint.pose().y(), 79 | lane_waypoint.pose().z()); 80 | 81 | double s = 0.0; 82 | double l = 0.0; 83 | hdmap::LaneInfoConstPtr lane; 84 | // FIXME(all): select one reasonable lane candidate for point=>lane 85 | // is one to many relationship. 86 | // 找到当前点最近的lane信息 87 | if (hdmap_->GetNearestLane(point, &lane, &s, &l) != 0) { 88 | AERROR << "Failed to find nearest lane from map at position: " 89 | << point.DebugString(); 90 | return routing_request; 91 | } 92 | auto waypoint_info = fixed_request.mutable_waypoint(i); 93 | waypoint_info->set_id(lane->id().id()); 94 | waypoint_info->set_s(s); 95 | } 96 | return fixed_request; 97 | } 98 | ``` 99 | 100 | BlackListRangeGenerator生成黑名单路段 101 | 通过RoutingRequest中的black_lane和black_road来生成黑名单路段,这里会设置一整段路都为黑名单。 102 | ``` 103 | void BlackListRangeGenerator::GenerateBlackMapFromRequest( 104 | const RoutingRequest& request, const TopoGraph* graph, 105 | TopoRangeManager* const range_manager) const { 106 | AddBlackMapFromLane(request, graph, range_manager); 107 | AddBlackMapFromRoad(request, graph, range_manager); 108 | range_manager->SortAndMerge(); 109 | } 110 | ``` 111 | 112 | 通过Terminal来设置黑名单,应用场景是设置routing的起点和终点。这里的起点和终点都是一个点,功能是把lane切分为2个subNode 113 | ``` 114 | void BlackListRangeGenerator::AddBlackMapFromTerminal( 115 | const TopoNode* src_node, const TopoNode* dest_node, double start_s, 116 | double end_s, TopoRangeManager* const range_manager) const { 117 | double start_length = src_node->Length(); 118 | double end_length = dest_node->Length(); 119 | if (start_s < 0.0 || start_s > start_length) { 120 | AERROR << "Illegal start_s: " << start_s << ", length: " << start_length; 121 | return; 122 | } 123 | if (end_s < 0.0 || end_s > end_length) { 124 | AERROR << "Illegal end_s: " << end_s << ", length: " << end_length; 125 | return; 126 | } 127 | 128 | double start_cut_s = MoveSBackward(start_s, 0.0); 129 | range_manager->Add(src_node, start_cut_s, start_cut_s); 130 | AddBlackMapFromOutParallel(src_node, start_cut_s / start_length, 131 | range_manager); 132 | 133 | double end_cut_s = MoveSForward(end_s, end_length); 134 | range_manager->Add(dest_node, end_cut_s, end_cut_s); 135 | AddBlackMapFromInParallel(dest_node, end_cut_s / end_length, range_manager); 136 | range_manager->SortAndMerge(); 137 | } 138 | ``` 139 | > TODO: 如果在dreamview里设置多个routing点的情况,那么会出现第一段的终点和第二段的起点有overlap的情况,排序之后,会出现2厘米的gap?目前看起来不会影响,因为会往前开,另外为什么要设置往后偏移1厘米,如果刚好停在边界点上,会如何处理??? 140 | 141 | 142 | TODO: 判断是否足够进行切换Lane, 143 | ``` 144 | bool TopoNode::IsOutRangeEnough(const std::vector& range_vec, 145 | double start_s, double end_s) { 146 | // 是否足够切换Lane 147 | if (!NodeSRange::IsEnoughForChangeLane(start_s, end_s)) { 148 | return false; 149 | } 150 | int start_index = BinarySearchForSLarger(range_vec, start_s); 151 | int end_index = BinarySearchForSSmaller(range_vec, end_s); 152 | 153 | int index_diff = end_index - start_index; 154 | if (start_index < 0 || end_index < 0) { 155 | return false; 156 | } 157 | if (index_diff > 1) { 158 | return true; 159 | } 160 | 161 | double pre_s_s = std::max(start_s, range_vec[start_index].StartS()); 162 | double suc_e_s = std::min(end_s, range_vec[end_index].EndS()); 163 | 164 | if (index_diff == 1) { 165 | double dlt = range_vec[start_index].EndS() - pre_s_s; 166 | dlt += suc_e_s - range_vec[end_index].StartS(); 167 | return NodeSRange::IsEnoughForChangeLane(dlt); 168 | } 169 | if (index_diff == 0) { 170 | return NodeSRange::IsEnoughForChangeLane(pre_s_s, suc_e_s); 171 | } 172 | return false; 173 | } 174 | ``` 175 | 176 | ## GeneratePassageRegion 177 | 生成passageRegion,这里需要注意每个Passage中的直行都是合并了的,也就是说passage中只有每次换向的时候才会从新启用新的passage。 178 | 179 | 180 | ## SubTopoGraph 181 | 构建subtopograph的作用就是为了方便routing切割lane,然后把lane分割成几个子节点,子节点的网络是如何建立的?如何根据这些节点来进行查找和计算代价??? 182 | 183 | ## Navigator::MergeRoute 184 | 没有看出来从哪里mergeRoute 185 | 186 | ## AStarStrategy::Search 187 | 具体的查找过程,每次还是会从子网络查找 188 | 189 | ## 190 | 191 | 192 | ## Reference 193 | 城市道路分析: 194 | https://geoffboeing.com/2016/11/osmnx-python-street-networks/ 195 | https://automating-gis-processes.github.io/2018/notebooks/L6/network-analysis.html 196 | https://socialhub.technion.ac.il/wp-content/uploads/2017/08/revise_version-final.pdf 197 | https://stackoverflow.com/questions/29639968/shortest-path-using-openstreetmap-datanodes-and-ways 198 | 199 | -------------------------------------------------------------------------------- /modules/tools/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Tools ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 君子生非异也,善假于物也。 4 | 5 | ## Table of Contents 6 | 7 | - [mapviewers](#mapviewers) 8 | 9 | 10 | 11 | ## mapviewers 12 | mapviewers用来可视化生成好的高精度地图。 13 | 14 | 1. 首先编译文件,这里可以把python文件编译为可执行文件。 15 | ``` 16 | bazel build //modules/tools/mapviewers:hdmapviewer 17 | bazel build //modules/tools/mapviewers:gmapviewer 18 | ``` 19 | 20 | 2. 编译好之后,执行命令。 21 | ``` 22 | ./bazel-bin/modules/tools/mapviewers/hdmapviewer -m modules/map/data/demo/base_map.txt 23 | ``` 24 | 25 | 3. 生成好的可视化地图文件在当前目录的base_map.html。 26 | 27 | #### todo 28 | 这里gmapviewer和hdmapviewer的区别是什么? 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /modules/tools/todo.md: -------------------------------------------------------------------------------- 1 | ## 查找依赖库 2 | 由于c++中是允许重载,而函数名要唯一,所以需要名字修饰 3 | 4 | ``` 5 | ldd -r /media/data/k8s/apollo/bazel-bin/cyber/py_wrapper/../../_solib_k8/libcyber_Sclass_Uloader_Slibclass_Uloader.so 6 | 7 | c++filt _ZN4Poco13SharedLibraryC1ERKSs 8 | ``` 9 | 10 | 也可以用"nm"命令查询: 11 | ``` 12 | nm -u /media/data/k8s/apollo/bazel-bin/cyber/py_wrapper/../../_solib_k8/libcyber_Sclass_Uloader_Slibclass_Uloader.so | grep _ZN4Poco13SharedLibraryC1ERKSs 13 | ``` 14 | 15 | [undefined symbol](https://blog.csdn.net/stpeace/article/details/76561814) 16 | [名字修饰](https://zh.wikipedia.org/wiki/%E5%90%8D%E5%AD%97%E4%BF%AE%E9%A5%B0) 17 | 18 | 19 | ## CoreDump 20 | https://www.jianshu.com/p/3dc143c53ca2 21 | 22 | 23 | ## 编译 24 | #### gdb调试 25 | https://blog.csdn.net/davidhopper/article/details/82589722 26 | 启动gdb调试 27 | ``` 28 | gdb -q bazel-bin/modules/map/relative_map/navigation_lane_test 29 | ``` 30 | 进入GDB调试界面后,使用l命令查看源代码,使用b 138在源代码第138行(可根据需要修改为自己所需的代码位置 )设置断点,使用r命令运行navigation_lane_test程序,进入断点暂停后,使用p navigation_lane_查看当前变量值(可根据需要修改为其他变量名),使用n单步调试一条语句,使用s单步调试进入函数内部,使用c继续执行后续程序。如果哪个部分测试通不过,调试信息会立刻告诉你具体原因,可使用bt查看当前调用堆栈。 31 | 32 | 33 | 34 | 35 | #### bazel编译 36 | 编译清除 37 | ``` 38 | bazel clean --expunge 39 | ``` 40 | -------------------------------------------------------------------------------- /modules/transform/img/all.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/transform/img/all.jpg -------------------------------------------------------------------------------- /modules/transform/img/frames2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/transform/img/frames2.png -------------------------------------------------------------------------------- /modules/transform/img/transform.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/transform/img/transform.jpg -------------------------------------------------------------------------------- /modules/transform/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Transform ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 三人行,必有我师。 4 | 5 | 6 | ## Table of Contents 7 | - [Transform模块简介](#introduction) 8 | - [Transform(静态变换)](#static_transform) 9 | - [transform_broadcaster(广播)](#no_static_transform) 10 | - [Buffer(接收缓存)](#buffer) 11 | - [缓存接口](#buffer_interface) 12 | - [缓存实现](#buffer_class) 13 | - [总结](#summary) 14 | - [Reference](#reference) 15 | 16 | 17 | 18 | 19 | ## Transform模块简介 20 | 关于transform模块开始一直不知道是干啥的,一直看到一个"/tf"的TOPIC,还以为是tensorflow的缩写,想着是不是和神经网络有关系,后来才知道tf是transform的缩写,主要的用途是进行坐标转换,原型即是大名鼎鼎的"ros/tf2"库。那么为什么要进行坐标转换呢? 21 | ![tf2](img/frames2.png) 22 | 在机器人系统中,经常需要用到坐标转换,比如一个机器人的手臂要去拿一个运动的物体,控制的时候我们需要手臂的当前坐标,同时还需要知道手臂和身体的坐标,这个时候就需要用到坐标转换,把手臂的坐标转换为身体的坐标系,这样控制起来就方便一点。当然这里是动态的情况,也存在静态的情况,比如机器人头顶的摄像头,转换到身体的坐标系,那么位置关系是相对固定的,所以可以一开始就写到固定的位置。这里就引入了以下几个问题: 23 | 1. 有固定转换关系的文件放在哪里?如果都是集中放在一个地方,那么这个地方损坏会导致所有的转换关系失效,一个比较好的方法是各个节点自己广播自己的转换关系。而其实静态的转换关系只需要发送一次就可以了,因为不会变化。 24 | 2. 有动态转换关系的节点,需要实时动态发布自己的转换关系,这样会涉及到时间戳,以及过时。 25 | 3. 转换关系的拓扑结构如何确定?是树型还是网络型的,这涉及到转换关系传递的问题。 26 | 27 | 28 | 29 | 30 | ## Transform(静态变换) 31 | 32 | TransformComponent模块的入口在"static_transform_component.cc"和"static_transform_component.h"中。实现了"StaticTransformComponent"类,我们接下来看下它的实现。 33 | ```c++ 34 | class StaticTransformComponent final : public apollo::cyber::Component<> { 35 | public: 36 | StaticTransformComponent() = default; // 构造函数 37 | ~StaticTransformComponent() = default; // 析构函数 38 | 39 | public: 40 | bool Init() override; // 初始化函数 41 | 42 | private: 43 | void SendTransforms(); // 发送变换 44 | void SendTransform(const std::vector& msgtf); //发送变换,参数为数组 45 | bool ParseFromYaml(const std::string& file_path, TransformStamped* transform); // 从yaml中解析 46 | 47 | apollo::static_transform::Conf conf_; // 配置文件 48 | std::shared_ptr> writer_; // cyber node写句柄 49 | TransformStampeds transform_stampeds_; // 变换关系,在proto中定义 50 | }; 51 | ``` 52 | 53 | 下面我们来分析StaticTransformComponent类具体的实现,首先是Init函数,Init函数做了2件事情,一是读取conf配置,二是发布"/tf_static"消息。 54 | ```c++ 55 | bool StaticTransformComponent::Init() { 56 | // 读取配置 57 | if (!GetProtoConfig(&conf_)) { 58 | AERROR << "Parse conf file failed, " << ConfigFilePath(); 59 | return false; 60 | } 61 | // 发布消息 62 | cyber::proto::RoleAttributes attr; 63 | attr.set_channel_name("/tf_static"); 64 | attr.mutable_qos_profile()->CopyFrom( 65 | cyber::transport::QosProfileConf::QOS_PROFILE_TF_STATIC); 66 | // 注意这里的node_继承至apollo::cyber::Component 67 | writer_ = node_->CreateWriter(attr); 68 | SendTransforms(); 69 | return true; 70 | } 71 | ``` 72 | 73 | 接着看SendTransforms()函数,主要就是遍历conf文件,判断extrinsic_file(实际上对应各种传感器的外参)是否使能,如果使能则根据提供的文件路径解析对应的转换关系"ParseFromYaml",把转换关系添加到数组"tranform_stamped_vec"中,然后发送。 74 | ```c++ 75 | void StaticTransformComponent::SendTransforms() { 76 | std::vector tranform_stamped_vec; 77 | // 遍历对应的文件,实际上对应各种传感器的外参 78 | for (auto& extrinsic_file : conf_.extrinsic_file()) { 79 | // 是否使能 80 | if (extrinsic_file.enable()) { 81 | AINFO << "Broadcast static transform, frame id [" 82 | << extrinsic_file.frame_id() << "], child frame id [" 83 | << extrinsic_file.child_frame_id() << "]"; 84 | TransformStamped transform; 85 | // 解析yaml文件,获取转换,并且添加到数组中 86 | if (ParseFromYaml(extrinsic_file.file_path(), &transform)) { 87 | tranform_stamped_vec.emplace_back(transform); 88 | } 89 | } 90 | } 91 | // 发送对应的转换 92 | SendTransform(tranform_stamped_vec); 93 | } 94 | ``` 95 | 96 | 解析yaml需要注意的地方,在conf中的frame_id和child_id实际上没有使用,最后还是以yaml文件中的为准。其中yaml文件的格式为 97 | ``` 98 | child_frame_id: novatel 99 | transform: 100 | translation: 101 | x: 0.0 102 | y: 0.0 103 | z: 0.0 104 | rotation: 105 | x: 0.0 106 | y: 0.0 107 | z: 0.0 108 | w: 1.0 109 | header: 110 | frame_id: localization 111 | ``` 112 | 我们在看下如何解析yaml文件: 113 | ```c++ 114 | bool StaticTransformComponent::ParseFromYaml( 115 | const std::string& file_path, TransformStamped* transform_stamped) { 116 | ... 117 | YAML::Node tf = YAML::LoadFile(file_path); 118 | try { 119 | // 读取yaml文件中的frame_id和child_frame_id 120 | transform_stamped->mutable_header()->set_frame_id( 121 | tf["header"]["frame_id"].as()); 122 | transform_stamped->set_child_frame_id( 123 | tf["child_frame_id"].as()); 124 | // translation 位置 125 | auto translation = 126 | transform_stamped->mutable_transform()->mutable_translation(); 127 | translation->set_x(tf["transform"]["translation"]["x"].as()); 128 | translation->set_y(tf["transform"]["translation"]["y"].as()); 129 | translation->set_z(tf["transform"]["translation"]["z"].as()); 130 | // rotation 角度 131 | auto rotation = transform_stamped->mutable_transform()->mutable_rotation(); 132 | rotation->set_qx(tf["transform"]["rotation"]["x"].as()); 133 | rotation->set_qy(tf["transform"]["rotation"]["y"].as()); 134 | rotation->set_qz(tf["transform"]["rotation"]["z"].as()); 135 | rotation->set_qw(tf["transform"]["rotation"]["w"].as()); 136 | } catch (...) { 137 | AERROR << "Extrinsic yaml file parse failed: " << file_path; 138 | return false; 139 | } 140 | return true; 141 | } 142 | ``` 143 | 最后我们再看下如何发送转换关系: 144 | ```c++ 145 | void StaticTransformComponent::SendTransform( 146 | const std::vector& msgtf) { 147 | for (auto it_in = msgtf.begin(); it_in != msgtf.end(); ++it_in) { 148 | bool match_found = false; 149 | int size = transform_stampeds_.transforms_size(); 150 | 151 | // 如果child_frame_id重复,那么则覆盖对应的配置 152 | for (int i = 0; i < size; ++i) { 153 | if (it_in->child_frame_id() == 154 | transform_stampeds_.mutable_transforms(i)->child_frame_id()) { 155 | auto it_msg = transform_stampeds_.mutable_transforms(i); 156 | *it_msg = *it_in; 157 | match_found = true; 158 | break; 159 | } 160 | } 161 | if (!match_found) { 162 | // 获取增加的指针地址,并且赋值 163 | auto ts = transform_stampeds_.add_transforms(); 164 | *ts = *it_in; 165 | } 166 | } 167 | writer_->Write(std::make_shared(transform_stampeds_)); 168 | } 169 | ``` 170 | 所以这里需要注意**child_frame_id的值一定不要一样,否则会覆盖之前的配置**。 171 | 172 | 173 | 按照流程总结一下就如下图所示,首先遍历conf,获取传感器的外参数文件路径,然后解析对应的yaml文件,并且发布到"/tf_static"。 174 | ![transform流程](img/transform.jpg) 175 | 176 | 177 | 178 | 179 | ## transform_broadcaster(广播) 180 | **各个模块通过广播的方式来发布动态变换,实际上就是各个模块通过调用transform_broadcaster的库函数来实现广播转换消息**,我们接下来看下transform_broadcaster是如何实现的,transform_broadcaster做为一个lib库,入口在"transform_broadcaster.h"和"transform_broadcaster.cc"中。 181 | ```c++ 182 | class TransformBroadcaster { 183 | public: 184 | // 这里注意构造的时候需要传入node 185 | explicit TransformBroadcaster(const std::shared_ptr& node); 186 | 187 | // 发送单个转换关系 188 | void SendTransform(const TransformStamped& transform); 189 | 190 | // 发送一组转换关系 191 | void SendTransform(const std::vector& transforms); 192 | 193 | private: 194 | std::shared_ptr node_; 195 | std::shared_ptr> writer_; 196 | }; 197 | ``` 198 | 从上面的分析可以看出,构造TransformBroadcaster的时候需要传入node。为什么需要传入node呢,因为cyber的一个module不能同时创建2个node,所以这里谁调用,就用谁的node创建reader和writer。如果是自己创建node,那么其他模块自己的node和引用该模块创建的node就打破了cyber上述的限制,这里的node是否可以理解为一个进程? 199 | 下面我们分析具体的实现,首先是TransformBroadcaster构造函数: 200 | ```c++ 201 | TransformBroadcaster::TransformBroadcaster( 202 | const std::shared_ptr& node) 203 | : node_(node) { 204 | cyber::proto::RoleAttributes attr; 205 | // 发布的topic为"/tf" 206 | attr.set_channel_name("/tf"); 207 | writer_ = node_->CreateWriter(attr); 208 | } 209 | ``` 210 | 创建writer并且往"/tf"发消息。这里可以看到,这里存在多个节点往一个topic发消息的情况。 211 | 发送消息比较简单,直接写对应的消息: 212 | ```c++ 213 | void TransformBroadcaster::SendTransform( 214 | const std::vector& transforms) { 215 | auto message = std::make_shared(); 216 | *message->mutable_transforms() = {transforms.begin(), transforms.end()}; 217 | writer_->Write(message); 218 | } 219 | ``` 220 | 221 | 222 | 223 | ## Buffer(接收缓存) 224 | Buffer实际上提供了一个工具类给其它模块,它的主要作用是接收"/tf"和"/tf_static"的消息,并且保持在buffer中,提供给其它节点进行查找和转换到对应的坐标系,我们先看BufferInterface的实现: 225 | 226 | 227 | 228 | 229 | #### 缓存接口 230 | BufferInterface类定义了缓存需要实现的接口: 231 | ```c++ 232 | class BufferInterface { 233 | public: 234 | // 根据frame_id获取2帧的转换关系 235 | virtual apollo::transform::TransformStamped lookupTransform( 236 | const std::string& target_frame, const std::string& source_frame, 237 | const cyber::Time& time, const float timeout_second = 0.01f) const = 0; 238 | 239 | // 根据frame_id获取2帧的转换关系,假设固定帧??? 240 | virtual apollo::transform::TransformStamped lookupTransform( 241 | const std::string& target_frame, const cyber::Time& target_time, 242 | const std::string& source_frame, const cyber::Time& source_time, 243 | const std::string& fixed_frame, 244 | const float timeout_second = 0.01f) const = 0; 245 | 246 | // 测试转换是否可行 247 | virtual bool canTransform(const std::string& target_frame, 248 | const std::string& source_frame, 249 | const cyber::Time& time, 250 | const float timeout_second = 0.01f, 251 | std::string* errstr = nullptr) const = 0; 252 | 253 | // 测试转换是否可行 254 | virtual bool canTransform(const std::string& target_frame, 255 | const cyber::Time& target_time, 256 | const std::string& source_frame, 257 | const cyber::Time& source_time, 258 | const std::string& fixed_frame, 259 | const float timeout_second = 0.01f, 260 | std::string* errstr = nullptr) const = 0; 261 | 262 | // Transform, simple api, with pre-allocation 263 | // 预分配内存 264 | template 265 | T& transform(const T& in, T& out, const std::string& target_frame, // NOLINT 266 | float timeout = 0.0f) const { 267 | // do the transform 268 | tf2::doTransform(in, out, lookupTransform(target_frame, tf2::getFrameId(in), 269 | tf2::getTimestamp(in), timeout)); 270 | return out; 271 | } 272 | 273 | // transform, simple api, no pre-allocation 274 | // 没有预分配内存 275 | template 276 | T transform(const T& in, const std::string& target_frame, 277 | float timeout = 0.0f) const { 278 | T out; 279 | return transform(in, out, target_frame, timeout); 280 | } 281 | 282 | // transform, simple api, different types, pre-allocation 283 | // 不同的类型 284 | template 285 | B& transform(const A& in, B& out, const std::string& target_frame, // NOLINT 286 | float timeout = 0.0f) const { 287 | A copy = transform(in, target_frame, timeout); 288 | tf2::convert(copy, out); 289 | return out; 290 | } 291 | 292 | // Transform, advanced api, with pre-allocation 293 | template 294 | T& transform(const T& in, T& out, const std::string& target_frame, // NOLINT 295 | const cyber::Time& target_time, const std::string& fixed_frame, 296 | float timeout = 0.0f) const { 297 | // do the transform 298 | tf2::doTransform( 299 | in, out, lookupTransform(target_frame, target_time, tf2::getFrameId(in), 300 | tf2::getTimestamp(in), fixed_frame, timeout)); 301 | return out; 302 | } 303 | 304 | // transform, advanced api, no pre-allocation 305 | template 306 | T transform(const T& in, const std::string& target_frame, 307 | const cyber::Time& target_time, const std::string& fixed_frame, 308 | float timeout = 0.0f) const { 309 | T out; 310 | return transform(in, out, target_frame, target_time, fixed_frame, timeout); 311 | } 312 | 313 | // Transform, advanced api, different types, with pre-allocation 314 | template 315 | B& transform(const A& in, B& out, const std::string& target_frame, // NOLINT 316 | const cyber::Time& target_time, const std::string& fixed_frame, 317 | float timeout = 0.0f) const { 318 | // do the transform 319 | A copy = transform(in, target_frame, target_time, fixed_frame, timeout); 320 | tf2::convert(copy, out); 321 | return out; 322 | } 323 | }; 324 | ``` 325 | BufferInterface实现的功能主要是查找转换关系,以及查看转换关系是否存在,以及做最后的转换。 326 | 327 | 328 | 329 | #### 缓存实现 330 | 下面我们接着看buffer类的实现,可以看到buffer类继承了"BufferInterface"和"tf2::BufferCore",其中"tf2::BufferCore"就是大名鼎鼎的ROS中的tf2库。 331 | ```c++ 332 | class Buffer : public BufferInterface, public tf2::BufferCore { 333 | public: 334 | using tf2::BufferCore::canTransform; 335 | using tf2::BufferCore::lookupTransform; 336 | 337 | // 构造buffer object 338 | int Init(); 339 | 340 | // 根据frame_id获取2帧的转换关系,继承至BufferInterface 341 | virtual apollo::transform::TransformStamped lookupTransform( 342 | const std::string& target_frame, const std::string& source_frame, 343 | const cyber::Time& time, const float timeout_second = 0.01f) const; 344 | 345 | // 继承至BufferInterface 346 | virtual apollo::transform::TransformStamped lookupTransform( 347 | const std::string& target_frame, const cyber::Time& target_time, 348 | const std::string& source_frame, const cyber::Time& source_time, 349 | const std::string& fixed_frame, const float timeout_second = 0.01f) const; 350 | 351 | // 继承至BufferInterface 352 | virtual bool canTransform(const std::string& target_frame, 353 | const std::string& source_frame, 354 | const cyber::Time& target_time, 355 | const float timeout_second = 0.01f, 356 | std::string* errstr = nullptr) const; 357 | 358 | // 继承至BufferInterface 359 | virtual bool canTransform(const std::string& target_frame, 360 | const cyber::Time& target_time, 361 | const std::string& source_frame, 362 | const cyber::Time& source_time, 363 | const std::string& fixed_frame, 364 | const float timeout_second = 0.01f, 365 | std::string* errstr = nullptr) const; 366 | 367 | private: 368 | // 转换回调??? 369 | void SubscriptionCallback( 370 | const std::shared_ptr& 371 | transform); 372 | void StaticSubscriptionCallback( 373 | const std::shared_ptr& 374 | transform); 375 | void SubscriptionCallbackImpl( 376 | const std::shared_ptr& 377 | transform, 378 | bool is_static); 379 | void AsyncSubscriptionCallbackImpl( 380 | const std::shared_ptr& 381 | transform, 382 | bool is_static); 383 | 384 | // 转换tf2消息为cyber protobuf格式 385 | void TF2MsgToCyber( 386 | const geometry_msgs::TransformStamped& tf2_trans_stamped, 387 | apollo::transform::TransformStamped& trans_stamped) const; // NOLINT 388 | 389 | std::unique_ptr node_; 390 | std::shared_ptr> 391 | message_subscriber_tf_; 392 | std::shared_ptr> 393 | message_subscriber_tf_static_; 394 | 395 | cyber::Time last_update_; 396 | std::vector static_msgs_; 397 | // 单例 398 | DECLARE_SINGLETON(Buffer) 399 | }; // class 400 | ``` 401 | 这里注意buffer为单例模式,即接收转换消息,并且放到buffer中保存。其他模块需要用到转换的时候,则从buffer中查找是否存在转换关系,并且进行对应的转换。 402 | 403 | 下面我们看buffer类的具体实现,buffer类的初始化在Init函数中: 404 | ```c++ 405 | int Buffer::Init() { 406 | std::string node_name = 407 | "transform_listener_" + std::to_string(cyber::Time::Now().ToNanosecond()); 408 | // 创建node节点 409 | node_ = cyber::CreateNode(node_name); 410 | cyber::ReaderConfig tf_reader_config; 411 | 412 | // 读取"/tf"消息 413 | tf_reader_config.channel_name = "/tf"; 414 | tf_reader_config.pending_queue_size = 5; 415 | 416 | message_subscriber_tf_ = 417 | node_->CreateReader( 418 | tf_reader_config, 419 | [&](const std::shared_ptr& 420 | msg_evt) { SubscriptionCallbackImpl(msg_evt, false); }); 421 | 422 | // 读取"/tf_static"消息 423 | apollo::cyber::proto::RoleAttributes attr_static; 424 | attr_static.set_channel_name("/tf_static"); 425 | attr_static.mutable_qos_profile()->CopyFrom( 426 | apollo::cyber::transport::QosProfileConf::QOS_PROFILE_TF_STATIC); 427 | message_subscriber_tf_static_ = 428 | node_->CreateReader( 429 | attr_static, 430 | [&](const std::shared_ptr& 431 | msg_evt) { SubscriptionCallbackImpl(msg_evt, true); }); 432 | 433 | return cyber::SUCC; 434 | } 435 | ``` 436 | 可以看到在Init函数中主要实现的功能是创建节点,并且订阅"/tf"和"/tf_static"消息,由于Buffer为单例,在cyber初始化的时候创建的node,不是在模块内部创建的node(关于这块,后面有时间在详细论述下,cyber可以存在多个node,而启动的模块则不能,是不是因为cyber做为调度器,为了方便控制)。 437 | 438 | 回调函数都是SubscriptionCallbackImpl,我们看下它是如何缓存消息的? 439 | ```c++ 440 | void Buffer::SubscriptionCallbackImpl( 441 | const std::shared_ptr& msg_evt, 442 | bool is_static) { 443 | cyber::Time now = cyber::Time::Now(); 444 | // authority的用途??? 445 | std::string authority = 446 | "cyber_tf"; // msg_evt.getPublisherName(); // lookup the authority 447 | 448 | // 看起来不可能进入这个条件,除非多线程??? 449 | if (now.ToNanosecond() < last_update_.ToNanosecond()) { 450 | AINFO << "Detected jump back in time. Clearing TF buffer."; 451 | clear(); 452 | // cache static transform stamped again. 453 | for (auto& msg : static_msgs_) { 454 | setTransform(msg, authority, true); 455 | } 456 | } 457 | last_update_ = now; 458 | 459 | for (int i = 0; i < msg_evt->transforms_size(); i++) { 460 | try { 461 | // 封装消息 462 | geometry_msgs::TransformStamped trans_stamped; 463 | 464 | // header 465 | const auto& header = msg_evt->transforms(i).header(); 466 | trans_stamped.header.stamp = 467 | static_cast(header.timestamp_sec() * kSecondToNanoFactor); 468 | trans_stamped.header.frame_id = header.frame_id(); 469 | trans_stamped.header.seq = header.sequence_num(); 470 | 471 | // child_frame_id 472 | trans_stamped.child_frame_id = msg_evt->transforms(i).child_frame_id(); 473 | 474 | // translation 475 | const auto& transform = msg_evt->transforms(i).transform(); 476 | trans_stamped.transform.translation.x = transform.translation().x(); 477 | trans_stamped.transform.translation.y = transform.translation().y(); 478 | trans_stamped.transform.translation.z = transform.translation().z(); 479 | 480 | // rotation 481 | trans_stamped.transform.rotation.x = transform.rotation().qx(); 482 | trans_stamped.transform.rotation.y = transform.rotation().qy(); 483 | trans_stamped.transform.rotation.z = transform.rotation().qz(); 484 | trans_stamped.transform.rotation.w = transform.rotation().qw(); 485 | 486 | // 保存静态转换,用于上面判断条件的时候,重新设置静态转换 487 | if (is_static) { 488 | static_msgs_.push_back(trans_stamped); 489 | } 490 | // 调用tf2的函数,保存转换到cache,区分静态和动态的转换 491 | setTransform(trans_stamped, authority, is_static); 492 | } catch (tf2::TransformException& ex) { 493 | std::string temp = ex.what(); 494 | AERROR << "Failure to set recieved transform:" << temp.c_str(); 495 | } 496 | } 497 | } 498 | ``` 499 | 接着是lookupTransform和canTransform分别调用tf2的库函数,实现查找转换和判断是否能够转换的实现,由于函数功能比较简单这里就不介绍了。 500 | 可以看到主要的缓存实现都是在tf2的库函数中,后面有时间再分析下tf2具体的实现。 501 | 502 | 503 | 504 | ## 总结 505 | 接下来我们用一张图来总结Apollo中的坐标变换关系,即静态坐标转换由"StaticTransform"模块提供,而动态转换由需要发布的模块自行发布如"NDTLocalization","RTKLocalization"和""Gnss,可以看到动态变换主要是世界坐标到本地坐标的转换,而静态转换主要是各个传感器之间的转换。最后转换关系统一由Buffer模块接收,并且提供查询。 506 | ![all](img/all.jpg) 507 | 508 | 509 | 510 | ## Reference 511 | [tf2](http://wiki.ros.org/tf2) 512 | 513 | -------------------------------------------------------------------------------- /modules/transform/todo.md: -------------------------------------------------------------------------------- 1 | ## todo 2 | 3 | tf_static的问题: 4 | 1. 为什么把obs_sensor2novatel_tf2_frame_id改为localization了??? 5 | 2. 为什么发布的时候需要传入node,即多个node往一个topic发送,还是说多个线程访问同一个node(location和perception同时通过一个node发布topic)? 6 | 3. 为什么需要根据child_frame_id做过滤,即使child_frame_id不能一样,否则会后面会覆盖前面的?(StaticTransformComponent::SendTransform) 7 | 4. 订阅的时候是每个模块都有listener吗,即遵循tf的设计原则。即代码可以复用,但是实例是每个模块自己new一个。 8 | 5. Buffer::SubscriptionCallbackImpl 中为什么时间戳比更新就清空整个List? 9 | ``` 10 | if (now.ToNanosecond() < last_update_.ToNanosecond()) { 11 | AINFO << "Detected jump back in time. Clearing TF buffer."; 12 | clear(); 13 | // cache static transform stamped again. 14 | for (auto& msg : static_msgs_) { 15 | setTransform(msg, authority, true); 16 | } 17 | } 18 | ``` 19 | 6. Buffer是一个单例,"apollo::transform::Buffer"提供了查询接口,即每个模块共享这个单例,和tf的设计原则还是不相符合? 注意线程安全? 20 | 21 | ## 坐标系 22 | 23 | #### UTM坐标系 24 | UTM坐标系的坐标原点位于本初子午线与赤道交点,以正东方向为x轴正方向(UTM Easting),正北方向为y轴正方向(UTM Northing) 25 | 26 | #### GPS坐标系 27 | 以地心为原点,连接南北两极并同纬线垂直相交的线叫做经线,垂直于经线的绕地球一圈就是纬线。 28 | 29 | 30 | 31 | #### UTM坐标转GPS坐标 32 | [参考](https://www.ibm.com/developerworks/cn/java/j-coordconvert/index.html) 33 | -------------------------------------------------------------------------------- /modules/v2x/img/v2x_proccess.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/modules/v2x/img/v2x_proccess.jpg -------------------------------------------------------------------------------- /papers/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Papers ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 我见青山多妩媚,料青山见我应如是。 4 | 5 | ## Table of Contents 6 | 7 | - [localization papers](#localization) 8 | - [perception papers](#perception) 9 | - [prediction papers](#prediction) 10 | - [planning papers](#planning) 11 | - [control papers](#control) 12 | - [simulation papers](#simulation) 13 | 14 | 15 | 16 | 17 | ## Localization papers 18 | 19 | * [Robust and Precise Vehicle Localization based on Multi-sensor Fusion in Diverse City Scenes](https://arxiv.org/abs/1711.05805) 20 | 21 | 22 | 23 | ## Perception papers 24 | 25 | [ChauffeurNet: Learning to Drive by Imitating the Best and Synthesizing the Worst](https://arxiv.org/abs/1812.03079) 26 | 27 | 28 | 29 | ## Prediction papers 30 | 31 | * [Simple Online and Realtime Tracking with a Deep Association Metric](https://arxiv.org/abs/1703.07402) - [code](https://github.com/nwojke/deep_sort) 32 | 33 | 34 | 35 | 36 | ## Planning papers 37 | 38 | * [Baidu Apollo EM Motion Planner](https://arxiv.org/abs/1807.08048) 39 | 40 | 41 | 42 | 43 | ## Control papers 44 | 45 | * [Baidu Apollo Auto-Calibration System - An Industry-Level Data-Driven and Learning based Vehicle Longitude Dynamic Calibrating Algorithm](https://arxiv.org/abs/1808.10134) 46 | 47 | 48 | 49 | 50 | 51 | ## Simulation papers 52 | 53 | * [AADS: Augmented Autonomous Driving Simulation using Data-driven Algorithms](https://arxiv.org/abs/1901.07849) 54 | * [Augmented LiDAR Simulator for Autonomous Driving](https://arxiv.org/abs/1811.07112) 55 | 56 | 57 | -------------------------------------------------------------------------------- /performance/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Performance ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 四方上下曰宇,往古来今曰宙。 4 | 5 | ## Table of Contents 6 | - [线程调度](#schedule) 7 | - [Cgroups](#cgroups) 8 | - [CPU亲和性](#cpu) 9 | - [中断绑定](#interrupt) 10 | - [linux性能优化](#linux) 11 | - [Perf安装](#perf) 12 | - [火焰图](#flame_graph) 13 | - [Reference](#reference) 14 | 15 | 我们主要从以下几个方面来优化我们的系统,使得系统更加稳定,节省资源,同时能够又能保证任务的实时性。以下几个技术都是目前Apollo Cyber中采用的技术。 16 | 17 | 18 | 19 | ## 线程调度 20 | 由于linux操作系统提供了控制线程的API接口,cyber通过系统提供的API对进程的优先级和使用的资源进行调节。首先cyber将要求比较高的进程设置为实时进程,linux操作系统中实时进程的优先级最高,实时进程可以抢占其它线程,好处是能够保证实时进程在一定的时间内返回结果,这在自动驾驶控制系统中非常关键,试想一下如果需要发送一条指令给汽车,系统没有在规定的时间内响应,或者响应有延迟,就有可能导致车祸。 21 | linux对**实时进程**的调度有2种方式: 22 | 1. SCHED_FIFO - 先到的进程优先执行,后到的进程需要等之前的进程执行完成之后再开始执行。 23 | 2. SCHED_RR - 基于时间片轮转,先到的进程执行完成之后放到队列尾部,在队列中循环执行。 24 | 25 | > 基于FIFO方式的平均等待时间和进程的顺序有关系,如果先到的进程执行时间很长,那么后到的进程等待时间就会变长;如果先到进程的执行时间很短,那么后到进程的等待时间就会变短。当然基于时间片轮转的方式就没有这个缺点,但是先到进程的执行时间会变长,因为基于轮转的,需要循环队列执行,那么先到进程需要等待其它进程的执行。所以需要根据不同的场景来选择不同的调度策略。 26 | 27 | 28 | 29 | ## Cgroups 30 | cgroups,名称源自控制组群(control groups)的简写,是Linux内核的一个功能,用来限制、控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。 31 | cgroups的一个设计目标是为不同的应用情况提供统一的接口,从控制单一进程(像nice)到操作系统层虚拟化(像OpenVZ,Linux-VServer,LXC)。cgroups提供: 32 | * 资源限制:组可以被设置不超过设定的内存限制;这也包括虚拟内存。 33 | * 优先级:一些组可能会得到大量的CPU或磁盘IO吞吐量。 34 | * 结算:用来衡量系统确实把多少资源用到适合的目的上。 35 | * 控制:冻结组或检查点和重启动。 36 | 37 | > 利用cgroups技术,我们可以设置这一组进程的优先级,并且根据重要程度和进程类型分配不同的资源。例如给重要的进程组分配更多的CPU和内存,限制其他进程组的CPU和内存防止其影响系统性能等。 38 | 39 | 40 | 41 | ## CPU亲和性 42 | CPU亲和性又叫Processor affinity或CPU pinning。现在的CPU都是多核心的,比如Apollo推荐的计算单元就是4核8线程,多核心CPU的好处是可以同时执行多个任务。现在假设多核CPU有以下场景,一个核上的任务很多,而另外的核心都是空闲状态,那么就会出现一个核累死,而其他的核都在等待的状态,这时候操作系统就想到了一种技术来解决这个问题,即CPU的负载均衡,当一个CPU核心上的任务很多,而其他CPU是空闲状态的时候,操作系统会把这个核上的任务迁移到其他核心,这样多核CPU的利用率就上来了。 43 | 对整个系统来说,CPU负载均衡是一个好技术,但是对单个线程来说,就不是那么好了。线程迁移会导致额外的开销,比如当前的CACHE需要重新刷新,而且把重要的任务绑定到单独的核心上,可以保证这个任务的高效执行而不被打断。 44 | linux操作系统中通过"sched_setaffinity" API来设置线程的CPU亲和性,通过"sched_getaffinity"来获取线程的CPU亲和性。 45 | ``` 46 | #define _GNU_SOURCE /* See feature_test_macros(7) */ 47 | #include 48 | 49 | int sched_setaffinity(pid_t pid, size_t cpusetsize, 50 | cpu_set_t *mask); 51 | 52 | int sched_getaffinity(pid_t pid, size_t cpusetsize, 53 | cpu_set_t *mask); 54 | ``` 55 | 56 | 57 | 58 | 59 | ## 中断绑定 60 | 中断绑定又叫smp_affinity,通过"cat /proc/interrupts"可以列出系统中每个 I/O 设备中每个 CPU 的中断数,处理的中断数,中断类型,以及注册为接收中断的驱动程序列表。系统通过"smp_affinity"可以指定多核CPU是否会响应这个中断,这在频繁有中断的系统中相当有用,比如CAN总线会频繁通过中断来传递传感器消息,如果没有绑定中断,那么系统中每个核心都可能被打断,如果这个核心上有任务在运行,那么CPU就会打断当前任务的执行,而去处理中断程序,从而带来中断上下文切换开销。如果我们把中断绑定到一个单独的核心上,让这个CPU核心去处理中断,而其它CPU核心则不会被频繁打断。 61 | smp_affinity 的默认值为 f,即可为系统中任意 CPU 提供 IRQ。将这个值设定为 1,如下,即表示只有 CPU 0 可以提供这个中断: 62 | ``` 63 | # echo 1 >/proc/irq/32/smp_affinity 64 | # cat /proc/irq/32/smp_affinity 65 | 1 66 | ``` 67 | 68 | 69 | 70 | 71 | 72 | ## linux性能优化 73 | linux操作系统的"perf"命令可以采样一段时间内的系统调用,保存成文件之后再结合火焰图,可以查看当前系统各个进程对cpu的使用情况,火焰图中的横轴代表了CPU占用时间的比例,宽度越宽,代表该进程越耗时。火焰图的横轴是当前进程的调用栈,可以逐级查看每个调用栈和具体的耗时。 74 | 75 | 76 | 77 | #### perf安装 78 | ubuntu下执行如下命令安装perf: 79 | ``` 80 | apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r` 81 | ``` 82 | 83 | 84 | 85 | #### 火焰图 86 | 安装成功之后可以执行"perf"命令来采样系统进程调用: 87 | ``` 88 | // 先找到需要统计的apollo进程 89 | sudo ps -ef | grep apollo 90 | 91 | // 查看到进行号之后,用进程号替换下面的PID,进行采样,采样频率为99HZ,采样时间为120秒 92 | sudo perf record -F 99 -p PID -g -- sleep 120 93 | 94 | // 输出perf文件 95 | sudo perf script > out.perf 96 | ``` 97 | 上述步骤就完成了对apollo进程的采样,并且输出了采样文件,下面我们通过生成火焰图来分析进程的调用状况。火焰图采用开源工具"FlameGraph",执行如下命令: 98 | ``` 99 | // 下载FlameGraph项目 100 | git clone --depth 1 https://github.com/brendangregg/FlameGraph.git 101 | 102 | // 折叠调用栈 103 | FlameGraph/stackcollapse-perf.pl out.perf > out.folded 104 | 105 | // 生成火焰图 106 | FlameGraph/flamegraph.pl out.folded > out.svg 107 | 108 | ``` 109 | 110 | 最后把生成的"out.svg"文件在浏览器中打开,就可以点击并且查看对应的调用时间和调用栈,来分析系统耗时。火焰图如下,源文件在github上下载: 111 | [![Gregg4](https://github.com/daohu527/Dig-into-Apollo/blob/master/performance/Gregg4.svg)](https://github.com/daohu527/Dig-into-Apollo/blob/master/performance/Gregg4.svg) 112 | > 图片引用自阮一峰《如何读懂火焰图?》 113 | 114 | 115 | 116 | 117 | 118 | ## Reference 119 | [cgroups](https://zh.wikipedia.org/wiki/Cgroups) 120 | [Processor affinity](https://en.wikipedia.org/wiki/Processor_affinity) 121 | [sched_setaffinity](https://linux.die.net/man/2/sched_setaffinity) 122 | [SMP IRQ affinity](https://www.kernel.org/doc/Documentation/IRQ-affinity.txt) 123 | [4.3. 中断和 IRQ 调节](https://access.redhat.com/documentation/zh-cn/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-cpu-irq) 124 | [perf](http://www.brendangregg.com/perf.html) 125 | [使用Perf和火焰图分析CPU性能](http://senlinzhan.github.io/2018/03/18/perf/) 126 | [如何读懂火焰图?](http://www.ruanyifeng.com/blog/2017/09/flame-graph.html) -------------------------------------------------------------------------------- /questions/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Questions ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 非淡漠无以明德,非宁静无以致远。 4 | 5 | ## 目录 6 | 7 | - [build](#build) 8 | - [map](#map) 9 | - [simulation](#simulation) 10 | - [planning](#planning) 11 | - [misc](#misc) 12 | 13 | 14 | 15 | 16 | ## build 17 | 1. 编译提示下载依赖库失败,可以把下载下来的文件放到'.cache/distdir/',重新编译, 如果是老版本那么可以在`tools/bazel.rc`中增加路径,然后放到指定目录 18 | ``` 19 | build --distdir=.cache/distdir/ 20 | ``` 21 | 22 | 23 | 24 | ## map 25 | * How to change map.bin to human-readable map.txt? 26 | In apollo docker run below cmds. 27 | ``` 28 | source scripts/apollo_base.sh 29 | 30 | protoc --decode apollo.hdmap.Map modules/map/proto/map.proto < modules/map/data/sunnyvale_loop/base_map.bin > base_map.txt 31 | ``` 32 | 33 | * How to create new map with RTK 34 | 1. 解压制作好的地图 35 | ``` 36 | python modules/tools/map_gen/extract_path.py map_file data/bag/20210406112554.record.00000 37 | ``` 38 | 查看当前轨迹 39 | ``` 40 | python modules/tools/map_gen/plot_path.py map_file 41 | ``` 42 | 43 | 2. 生成地图 44 | ``` 45 | python modules/tools/map_gen/map_gen.py map_file 46 | ``` 47 | 48 | 3. 查看地图 49 | ``` 50 | python modules/tools/mapshow/mapshow.py -m map_map_file.txt 51 | ``` 52 | 53 | 54 | 生成地图 55 | ``` 56 | python modules/tools/create_map/convert_map_txt2bin.py -i map_map_file.txt -o /apollo/modules/map/data/your_map_dir/base_map.bin 57 | ``` 58 | 59 | 生成sim_map 60 | ``` 61 | ./bazel-bin/modules/map/tools/sim_map_generator -map_dir=/apollo/modules/map/data/your_map_dir -output_dir=/apollo/modules/map/data/your_map_dir 62 | ``` 63 | 64 | 生成routing_map 65 | ``` 66 | /apollo/bazel-bin/modules/routing/topo_creator/topo_creator -map_dir=/apollo/modules/map/data/your_map_dir --flagfile=modules/routing/conf/routing.conf 67 | ``` 68 | 69 | 测试之前需要修改"vi modules/common/data/global_flagfile.txt",屏蔽选项"--log_dir/--use_navigation_mode" 70 | ``` 71 | --map_dir=/apollo/modules/map/data/your_map_dir 72 | ``` 73 | 测试生成的routing_map是否可以联通 74 | ``` 75 | python modules/tools/routing/debug_topo.py 76 | ``` 77 | 78 | 79 | 80 | 81 | ## simulation 82 | 83 | 84 | 85 | 86 | ## Planning 87 | 88 | * How to add decider or optimizer to a planning scenario ? 89 | 90 | 1. Add your own decider in "modules/planning/tasks/deciders" 91 | 2. Add config in "modules/planning/conf/scenario/lane_follow_config.pb.txt" 92 | 3. Add TaskType in "modules/planning/proto/planning_config.proto" 93 | 4. Register your task in "TaskFactory::Init" 94 | 95 | 96 | 97 | 98 | ## misc 99 | 100 | * What is the format of the config file in Apollo? 101 | The config file base by **protobuf** format, and read by `cyber::common::GetProtoFromFile()` method. 102 | 103 | * How to open the debug log? 104 | 105 | 1. modify the "apollo/cyber/setup.bash" 106 | ``` 107 | # for DEBUG log 108 | export GLOG_v=4 109 | ``` 110 | 2. Enable environment variables 111 | ``` 112 | source cyber/setup.bash 113 | ``` 114 | 115 | 3. Export bag data to lidar,camera 116 | ``` 117 | ./bazel-bin/modules/localization/msf/local_tool/data_extraction/cyber_record_parser --bag_file data/bag/20210305145950.record.00000 --out_folder data/ --cloud_topic=/apollo/sensor/lidar32/compensator/PointCloud2 118 | ``` 119 | -------------------------------------------------------------------------------- /questions/todo.md: -------------------------------------------------------------------------------- 1 | https://joancharmant.com/blog/sub-frame-accurate-synchronization-of-two-usb-cameras/ 2 | https://forums.ni.com/t5/Machine-Vision/Synchronized-capture-for-multiple-USB-cameras/td-p/1879647?profile.language=en 3 | https://raspberrypi.stackexchange.com/questions/73588/usb-camera-synchronization-with-external-trigger 4 | https://stackoverflow.com/questions/25161920/creating-synchronized-stereo-videos-using-webcams 5 | https://discourse.vvvv.org/t/200-300-cameras-synchronized-triggering-and-access/13840 6 | https://www.aliexpress.com/item/ELP-USB-Camera-Module-Micro-Mini-Industrial-USB-2-0-Synchronized-Dual-Lens-Stereo-Camera-Driverless/32866119916.html 7 | https://raspberrypi.stackexchange.com/questions/58376/multiple-frame-synced-still-cameras -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) [![Documentation Status](https://readthedocs.org/projects/dig-into-apollo/badge/?version=latest)](https://dig-into-apollo.readthedocs.io/en/latest/?badge=latest) 2 | 3 | > I'm designing a new autonomous driving system called [**WheelOS**](https://github.com/wheelos), driven entirely by user input. I am fully dedicating my efforts to this project, aiming for its growth and success. 4 | 5 | Dig into Apollo is mainly to help learning [Apollo](https://github.com/ApolloAuto/apollo) autopilot system. We first introduce the functions of each module in detail, and then analyzed the code of each module. If you like autonomous driving and want to learn it, let's start this project and discuss anything you want to know! 6 | 7 | #### readthedocs 8 | - [dig-into-apollo](https://dig-into-apollo.readthedocs.io/en/latest/index.html#) 9 | 10 | #### google groups 11 | - [disscuss-apollo](https://groups.google.com/g/d-apollo) 12 | 13 | ## Friendly tips 14 | 15 | * If "git clone" on github is too slow, pls try apollo **[mirror](https://gitee.com/baidu/apolloauto)**. 16 | * If you have any questions, please feel free to ask in **[git issue](https://github.com/daohu527/dig-into-apollo/issues)**. 17 | * If you need to add new documents or give suggestions, welcome to participate in **[git discussion](https://github.com/daohu527/dig-into-apollo/discussions)** 18 | 19 | ## Table of Contents 20 | 21 | - [What's apollo](what_is_apollo) 22 | - [How to build](how_to_build) 23 | - [Code learning](code_learning) 24 | - [cyber](cyber) 25 | - [docker](docker) 26 | - [modules](modules) 27 | - [audio](modules/audio) 28 | - [bridge](modules/bridge) 29 | - [canbus](modules/canbus) 30 | - [data](modules/data) 31 | - [drivers](modules/drivers) 32 | - [dreamview](modules/dreamview) 33 | - [map](modules/map) 34 | - [localization](modules/localization) 35 | - [perception](modules/perception) 36 | - [prediction](modules/prediction) 37 | - [routing](modules/routing) 38 | - [planning](modules/planning) 39 | - [control](modules/control) 40 | - [transform](modules/transform) 41 | - [tools](modules/tools) 42 | - [v2x](modules/v2x) 43 | - [performance](performance) 44 | - [simulation](simulation) 45 | - [library](library) 46 | - [papers](papers) 47 | - [questions](questions) 48 | 49 | 50 | ## Getting Started 51 | 52 | If you are like me before and don’t know how to start the Apollo project. Here are some suggestions. 53 | 54 | 1. First understand the basic module functions. If you are not clear about the general function of the module, it's difficult for you to understand what's the code doing. Here is an beginner level [tutorial](https://apollo.auto/devcenter/coursetable_cn.html?target=1) 55 | 2. Then you need to understand the specific methods according to the module, which will be documented in this tutorial. We will analyze the code in depth next. You can learn step by step according to our tutorial. 56 | 3. I know it will be a painful process, especially when you are first learning Apollo. If you persist in asking questions and studying, it will become easier in 1-2 months. 57 | 4. Last but not least, the method in Apollo is almost perfect, but there will be some problems. Try to implement and improve it, find papers, try the latest methods, and hone your skills. I believe you will enjoy this process. 58 | 59 | 60 | ## How to learn? 61 | 62 | **Basic learning** 63 | 64 | 1. Watching some introductory tutorials will help you understand the Apollo autopilot system more quickly. I highly recommend [tutorial](https://apollo.auto/devcenter/coursetable_cn.html?target=1) 65 | 2. Try to ask some questions, read some blogs, papers, or go to github to add some [issues](https://github.com/ApolloAuto/apollo/issues). 66 | 3. If you don’t have a self-driving car, you can try to deploy a simulation environment, I highly recommend [lgsvl](https://github.com/lgsvl/simulator). Its community is very friendly! 67 | 4. If there are any problems with the simulation environment, such as map creation, or some other problems, welcome to participate in this project [Flycars](https://github.com/Flycars). We will help as much as possible! 68 | 69 | **Code learning** 70 | 71 | 1. First of all, you must understand c++. If you are not very familiar with it, I recommend the book **"c++ primer"**. This book is very good, but a bit thick. If you just want to start quickly, then try to find some simple tutorial, here I recommend teacher [Hou Jie](https://search.bilibili.com/all?keyword=%E4%BE%AF%E6%8D%B7) 72 | 2. After understanding C++, it is best to have some basic understanding of the modules, which will help you to read the code. This has been explained many times. 73 | 3. Use code reading tools to help you read the code. I highly recommend [vscode](https://code.visualstudio.com/). It supports both Windows and Linux, and has a wealth of plug-ins, which can help you track codes, search and find call relationships. 74 | 4. Of course, there are many professional knowledge and professional libraries. I can’t repeat the best tutorials one by one here, but I can try to recommend some. [Hongyi Li's deep learning](https://www.bilibili.com/video/BV1JE411g7XF?p=1), even a math tutorial [3Blue1Brown's math](https://space.bilibili.com/88461692/) 75 | 5. Do some experiments with the simulator. We say "Make your hands dirty". You can't just watch, you need to try to modify some configurations and see if it takes effect, if possible, you can also try to answer some questions. 76 | 6. Remember that autonomous driving is still far from mature. Read some papers, I read a lot of [papers](https://github.com/daohu527/awesome-self-driving-car#papers-blogs), it help me a lot. 77 | 78 | Hope you have fun! 79 | 80 | ## Contributing 81 | This project welcomes contributions and suggestions. Please see our contribution guidelines. 82 | 83 | 84 | ## References & resources 85 | - [apollo](https://github.com/ApolloAuto/apollo) 86 | - [awesome-self-driving-car](https://github.com/daohu527/awesome-self-driving-car) 87 | -------------------------------------------------------------------------------- /simulation/img/Robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/Robot.png -------------------------------------------------------------------------------- /simulation/img/carsh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/carsh.png -------------------------------------------------------------------------------- /simulation/img/docker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/docker.jpg -------------------------------------------------------------------------------- /simulation/img/how.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/how.jpg -------------------------------------------------------------------------------- /simulation/img/map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/map.jpg -------------------------------------------------------------------------------- /simulation/img/map_edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/map_edit.jpg -------------------------------------------------------------------------------- /simulation/img/sence.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/sence.jpg -------------------------------------------------------------------------------- /simulation/img/sences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/sences.png -------------------------------------------------------------------------------- /simulation/img/simulation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/simulation.jpg -------------------------------------------------------------------------------- /simulation/img/simulator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/simulator.jpg -------------------------------------------------------------------------------- /simulation/img/summary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/summary.jpg -------------------------------------------------------------------------------- /simulation/img/test_case.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/test_case.jpg -------------------------------------------------------------------------------- /simulation/img/traffic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/traffic.png -------------------------------------------------------------------------------- /simulation/img/train.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daohu527/dig-into-apollo/2ec029853e3b396c29d8c2f9b57366ba83c84853/simulation/img/train.jpeg -------------------------------------------------------------------------------- /simulation/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Simulation ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 古之学者必有师。师者,所以传道受业解惑也。 4 | 5 | 6 | ## 目录 7 | - [为什么需要仿真](#why_simulation) 8 | - [如何仿真](#how_simulation) 9 | - [仿真软件](#simulator) 10 | - [工作方式](#simulator_work) 11 | - [工作原理](#simulator_principle) 12 | - [如何使用](#how_to) 13 | - [桥接器](#adapter) 14 | - [制作地图](#make_map) 15 | - [测试场景](#test_case) 16 | - [功能多样化](#features) 17 | - [如何构建自动驾驶仿真系统?](#how_build) 18 | - [地图](#simulation_map) 19 | - [真实场景地图生成](#map_generate) 20 | - [地图模型制作](#model_build) 21 | - [高精度地图制作](#hdmap_build) 22 | - [虚拟场景地图生成](#virtual_map_build) 23 | - [车](#car) 24 | - [行为](#behavior) 25 | - [NPC](#npc) 26 | - [天气](#weather) 27 | - [红绿灯](#traffic_light) 28 | - [API](#api) 29 | - [部署](#deploy) 30 | - [总结](#summary) 31 | - [参考](#reference) 32 | 33 | 34 | 35 | 36 | ## 为什么需要仿真 37 | ![how](img/how.jpg) 38 | 1. 想象一下当你发现了一个新的算法,但还不确认它是否有效,你是否会直接找一辆自动驾驶汽车,更新软件,并且进行测试呢?这样做可能并不安全,你必须把所有的场景测试一遍以保证它足够好,这可需要大量的时间。仿真的好处显而易见,**它通过软件模拟来发现和复现问题,而不需要真实的环境和硬件,可以极大的节省成本和时间**。 39 | 2. 随着现在深度学习的兴起,仿真在自动驾驶领域有了新的用武之地。**自动驾驶平台通过仿真采集数据,可以把训练时间大大提高,远远超出路测的时间,加快模型迭代速度**。先利用集群训练模型,然后再到实际的路测中去检验,采用数据驱动的方式来进行自动驾驶研究。 40 | 41 | 自动驾驶的仿真的论文可以参考英伟达的[End to End Learning for Self-Driving Cars](https://arxiv.org/abs/1604.07316),主要的目的是通过软件来模拟车以及车所在的环境,实现自动驾驶的集成测试,训练模型,模拟事发现场等功能。那么我们是如何模拟车所在的环境的呢? 42 | 43 | 44 | 45 | 46 | ## 如何仿真 47 | **要模拟车所在的环境,就得把真实世界投影到虚拟世界,并且需要构造真实世界的物理规律**。例如需要模拟真实世界的房子,车,树木,道路,红绿灯,不仅需要大小一致,还需要能够模拟真实世界的物理规律,比如树和云层会遮挡住阳光,房子或者障碍物会阻挡你的前进,车启动和停止的时候会有加减速曲线。 48 | **总之,这个虚拟世界得满足真实世界的物理规律才足够真实,模拟才足够好**。而这些场景恰恰和游戏很像,游戏就是模拟真实世界,并且展示出来,游戏做的越好,模拟的也就越真实。实现这一切的就是游戏引擎,通过游戏引擎模拟自然界的各种物理规律,可以让游戏世界和真实世界差不多。这也是越来越多的人沉迷游戏的原因,因为有的时候根本分不清是真实世界还是游戏世界。 49 | 现在我们找到了一条捷径,用游戏来模拟自动驾驶,这看起来是一条可行的路,我们把自动驾驶中的场景复制到游戏世界,然后模拟自动驾驶中各种传感器采集游戏世界中的数据,看起来我们就像是在真实世界中开着自动驾驶汽车在测试了。 50 | 51 | 52 | 53 | 54 | #### 仿真软件 55 | 我们已经知道可以用游戏来模拟自动驾驶,而现在大家也都是这么做的,目前主流的仿真软件都是根据游戏引擎来开发,下面是主要的几个仿真软件: 56 | 57 | | 仿真软件 | 引擎 | 介绍 | 58 | |------------------------------------------------------------|---------|-----------------------------------| 59 | | [Udacity](https://github.com/udacity/self-driving-car-sim) | Unity | 优达学城的自动驾驶仿真平台 | 60 | | [Carla](https://github.com/carla-simulator/carla) | Unreal4 | Intel和丰田合作的自动驾驶仿真平台 | 61 | | [AirSim](https://github.com/Microsoft/AirSim) | Unreal4 | 微软的仿真平台,还可以用于无人机 | 62 | | [lgsvl](https://github.com/lgsvl/simulator) | Unity | LG的自动驾驶仿真平台 | 63 | | [Apollo](https://github.com/ApolloAuto/apollo) | | Dreamview百度的自动驾驶仿真平台 | 64 | 65 | 66 | * **Unreal4** - 主要的编程方式是c++,源码完全开源,还可以通过蓝图来编程。比较著名的游戏有:《鬼泣5》《绝地求生:刺激战场》 67 | * **Unity** - 主要的编程方式是c#和脚本,源码不开放,超过盈利上限收费。比较著名的游戏有:《王者荣耀》《炉石传说》 68 | 69 | 70 | 71 | 72 | #### 工作方式 73 | 那么仿真软件是如何工作的呢?大部分的仿真软件分为2部分:server端和client端。 74 | * server端主要就是游戏引擎,提供模拟真实世界的传感器数据,并且提供控制车辆,红绿灯以及行人的接口,还提供一些辅助接口,例如改变天气状况,检测车辆是否有碰撞等。 75 | * client端则根据server端返回的传感器数据进行具体的控制,调整参数等。 76 | 77 | 可以认为server就是游戏机,而client则是游戏手柄,根据游戏中的情况,选择适当的控制方式,直到游戏通关。 78 | 79 | 80 | 81 | 82 | #### 工作原理 83 | 我们知道游戏引擎模拟了传感器的数据,那么游戏引擎是如何实现模拟真实世界中的传感器数据的呢? 84 | * 摄像头深度信息 85 | * 摄像头场景分割 86 | * 摄像头长短焦 87 | * Lidar点云 88 | * radar毫米波 89 | * Gps信息 90 | 91 | 除了传感器数据,还需要模拟真实世界的物理规律: 92 | * 碰撞检测 93 | * 光线和天气变化 94 | * 汽车动力学模型 95 | 96 | > TODO: 补充原理 97 | 98 | 下面分析下carla中如何实现上述的模拟,其实也可以看做Unreal4中如何实现上述功能,carla传感器的实现在"carla/Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Sensor"中。 99 | 1. 其中摄像头深度信息是通过投影"carla/Unreal/CarlaUE4/Plugins/Carla/Content/PostProcessingMaterials"中的材质实现的,这里有点疑惑就是难道深度信息是实现就生成的,还是说材质类似做一层滤镜的操作? 100 | 2. 而Lidar是通过Raycast来实现的,即发送射线检测距离。主要的疑问是如何模拟点云的角度,参数等信息? 101 | 3. 天气的变化直接是通过"蓝图"实现的,没有找到具体的地方? 102 | 4. 汽车动力学模型暂时也没有找到地方? 103 | 104 | 105 | 106 | 107 | 108 | ## 如何使用 109 | 110 | 111 | 112 | #### 桥接器 113 | 如果是单独实现或者测试一个算法,直接拿写好的算法在仿真软件上进行测试就可以了,但是如果是需要测试已经开发好的软件,比如apollo和autoware系统,则需要实现仿真软件和自动驾驶系统的对接。一个简单的想法就是增加一个桥接器,就像手机充电器的转换头一样,通过桥接器来连接仿真软件和自动驾驶系统。目前carla和lgsvl都实现了通过桥接器和自动驾驶系统的对接,可以直接通过仿真软件来测试自动驾驶系统。 114 | 115 | > 目前carla和lgsvl都是单独把apollo和autoware拉了一个分支,然后在其中集成一个适配器(ROS桥接),来实现仿真软件和自动驾驶系统的对接。当然apollo3.5切换到cyber框架之后,可以通过cyber桥接来实现。 116 | 117 | 118 | 119 | 120 | #### 制作地图 121 | 仿真中另外一个问题经常遇到的问题就是制作地图,以上的仿真软件都提供了地图编辑器来构建自己想要测试的地图。目前地图格式主要采用的是OpenDrive格式的地图,如果是和Apollo集成的化,需要把OpenDrive格式的地图转换为Apollo中能够使用的地图格式。现在的主要问题是地图编辑器不是那么好用,大部分好用的地图编辑软件都需要收费。 122 | 123 | 124 | 125 | 126 | #### 测试场景 127 | 根据我们的测试需求,我们可以构建以下几种测试场景: 128 | ![test_case](img/test_case.jpg) 129 | * **场景复现** - 假如自动驾驶过程中出现了一次接管(自动驾驶遇到突发状况解决不了,被人类驾驶员接管),首先我们需要复现当时的场景,这时候不可能再重新回去构建相同的场景,这时候就需要仿真去模拟当时的场景,找到问题之后,我们也可以通过仿真来看针对上述接管的情况是否解决。仿真通过模拟当时接管的场景,可以复现当时出现的问题,同时判断修改软件之后是否对当时的场景有所改善。 130 | * **集成测试** - 每次开发一个新的功能和迭代之后,通过仿真构造全部场景的测试用例,例如:红绿灯,超车,停车,左拐弯,右拐弯,掉头,十字路口等情况来测试所有场景是否都没有问题,可以在软件真正上车测试之前保证软件的可靠性,检验新开发的功能,提高软件质量,减少测试成本。 131 | * **训练模型** - 通过仿真软件来生成数据训练模型,真实场景的数据采集需要大量的车和时间,而软件可以通过分布式部署就可以实现模拟真实场景的大量数据,特别是针对目前感知的深度学习算法需要大量数据训练的情况,所以通过仿真可以加快模型训练和部署的速度。另外斯坦福大学还通过仿真来模拟汽车失控的情况下,尽量避免碰撞的场景,做一些新的研究和尝试。 132 | 133 | 134 | 135 | 136 | #### 功能多样化 137 | 我们需要仿真软件能够适应不同的测试场景,就必须要求仿真软件能够提供灵活和多样化的功能,我们要提供哪些功能呢? 138 | * **多机控制** - 不仅可以控制自己,还可以控制游戏中的其他角色。控制多辆车在一个地图里面跑,好处是可以多辆车竞争,有点类似遗传算法,把一些车放到里面跑,然后其中选出最好的,如此往复,得到最好的模型。同时多机控制还可以帮助我们控制游戏中的其他车辆,构建不同的测试场景,比如:行人横穿马路,超车等情况。 139 | * **传感器参数调整** - 通过调整传感器参数实现不同硬件配置下的自动驾驶模拟,例如调整摄像头的参数,调整激光雷达的位置等,增加了传感器的灵活性。 140 | * **汽车模型** - 根据需要导入不同的汽车模型,包括卡车,三轮车,小汽车的3D模型和动力学模型。 141 | * **地图模型** - 如果纯手工制作模型太难了,是否可以根据3D点云的数据,然后根据软件来虚拟生成道路模型。 142 | 143 | 144 | 145 | ## 如何构建自动驾驶仿真系统? 146 | 仿真最主要的目的是:**通过模拟真实环境和构建汽车模型,找出自动驾驶过程中可能出现的问题**。那么如何构建自动驾驶仿真系统呢?目前主流的实现方式是**通过游戏引擎来模拟真实环境,通过CarSim等软件构建汽车的动力学模型来实现自动驾驶仿真**。下面我们先看下自动驾驶仿真系统的整体结构。 147 | ![simulator](img/simulator.jpg) 148 | 我们需要自动驾驶仿真系统满足: 149 | 1. 场景丰富 150 | 2. 接口灵活 151 | 3. 恢复快速 152 | 4. 部署方便 153 | 154 | 首先我们关注仿真器本身,仿真器无非是模拟支持各种场景,其中场景分为:可以定义的场景和随机场景。可以定义的场景又分为:单元场景和真实场景。下面我们分别介绍下这几种场景: 155 | * **可定义的场景** - 主要是针对驾驶过程中遇到的不同情况,比如会车,超车,红绿灯,变道等,这些场景一般都比较简单,类似于单元测试,主要是测试单个场景是否能够满足要求,这一部分业界已经有规范,可以参考[openscenario](http://www.openscenario.org/)。拿超车的场景举例子,可以创建一辆NPC车辆在本车的前面,在不同的速度和距离条件下,测试本车超车是否成功。 156 | * **真实场景** - 复现真实场景中遇到的问题,比如真实路测过程中遇到问题,需要复现当时的情况,并且验证问题是否已经解决,可以回放真实场景的数据来进行测试。 157 | * **随机场景** - 这种场景类似于路测,模拟真实环境中的地图,并且随机生成NPC,天气,交通情况等,模拟汽车在虚拟的环境中进行路测,由于可以大规模部署,可以快速的发现问题。 158 | 159 | 我们可以看到不管是哪个场景,都是"地图+车+行为"的模式,场景的需求复杂多变,因此能够灵活的加载地图,车和行为就成为仿真器易用的关键。 160 | ![scence](img/sence.jpg) 161 | 我们的需求是能够根据不同的要求创建不同的场景,动态的添加地图,车和行为。场景生成器是一个框架,支持通过不同的配置,动态创建不同的场景,来满足我们的要求。除了场景生成器,我们还需要仿真器具备以下几个基本功能: 162 | * **复位** - 在故障发生之后,我们能够复位环境和车辆到初始状态,同时也要求我们能够复位对应的自动驾驶系统。这样再每次故障后,可以不用人工操作,而自动恢复测试。 163 | * **快照** - 能够生成对应帧的信息,保存快照的好处是能够恢复事故现场,同时也可以用于自动驾驶数据集的建设。保存的点云和图片由于有groundtruth,可以作为机器学习的输入来训练模型。 164 | * **回放** - 回放功能主要是用于故障定位,在发生碰撞之后,回放信息用于定位问题。 165 | * **统计** - 统计主要是用于作为benchmark,来衡量系统的稳定性。 166 | 167 | 有了这些基础功能还不够,我们还需要关心具体的场景,下面我们分别对地图、车以及行为来详细描述需要实现的具体功能: 168 | 169 | 170 | 171 | ## 地图 172 | 地图是场景中第一个需要考虑的,地图包括2部分,其中一部分是游戏中的模型,另外一部分是这些模型的高精度地图。换一种说法就是,首先我们需要在游戏中构建一个1:1的虚拟世界,然后再绘制出这个世界的高精度地图。其实游戏中的模型是游戏引擎的需求,游戏引擎是根据模型来渲染游戏画面的,没有模型也就渲染不出地图。而高精度地图是自动驾驶系统所需要的,高精度地图可以采用根据现场绘制的地图,也可以先得到游戏模型,然后在模型中绘制。下面是游戏中的地图和高精度地图的对应关系。 173 | ![map](img/map.jpg) 174 | 175 | 176 | 177 | ## 真实场景地图生成 178 | 179 | 180 | 181 | #### 地图模型制作 182 | 游戏中地图模型的制作相对来说是工作量比较大的工作,涉及到以下2点: 183 | * **单个模型制作** - 单个模型包括地图中的建筑物、道路、树木、信号灯、交通牌、以及其他的信息。这些信息如果是要完全模拟真实环境,需要大量的材质和贴图,一般是在maya和3d-max等软件中建模,然后再导入模型到游戏引擎中使用。 184 | * **地图布局** - 有了单个模型,当需要把单个模型组合成地图的时候,首先需要解决的是道路的位置信息,比如这个道路有多长,道路的曲率是多少?比较简单点的方法是直接导入2维地图(百度,高德,OSM),然后对照着2维地图放模型,最后生成整个地图的布局。而实际的问题是2维地图的精度往往达不到要求,国内的地图还加入了GPS偏置,所以生成的地图布局必定会不太准确。 185 | 186 | 187 | 188 | #### 高精度地图制作 189 | * **根据模型生成地图** - 接着上面的地图布局来讲,虽然得到的地图布局不准确,但是我们再根据游戏中的模型布局,绘制出高精度地图,然后把这个高精度地图给自动驾驶系统使用,基本上也能满足我们的要求。 190 | * **根据地图生成模型** - 上述的问题就是游戏中的真实位置和实际道路的位置有轻微的误差。要解决上面的问题,我们可以反其道而行之,先生成高精度地图,即根据真实环境先绘制出高精度地图,然后再把高精度地图导入游戏引擎,动态的生成模型,这个方案的好处是地图100%是真实场景,而且不需要在游戏引擎中重新绘制高精度地图,坏处是建筑的模型无法生成。 191 | 192 | 关于真实场景的地图生成,目前还没有一个比较完美的解决方案,都需要大量的工作。下面我们再看下虚拟场景的地图生成。 193 | 194 | 195 | 196 | ## 虚拟场景地图生成 197 | 虚拟场景的道路生成就比较简单,主要的应用场景是一些园区,或者一些测试场景。这一部分完全可以制作一个地图编辑器,类似游戏中的地图编辑器,玩家可以根据自己的需求创建游戏中的地图,然后再由脚本动态的生成高精度地图。**这部分的功能主要是对标Carsim等仿真软件的地图编辑功能**。 198 | ![map_edit](img/map_edit.jpg) 199 | 200 | 说完了地图,接下来看下车 201 | 202 | 203 | 204 | ## 车 205 | 车主要分为2部分:车的动力学模型,以及传感器。接下来我们详细分析下这2部分: 206 | * **车的动力学模型** - 这一部分是传统仿真软件的强项,由于应用已经非常成熟,游戏中的汽车动力学模型都比较简单,由于CarSim等软件没有开源,所以目前短期内一个比较好的解决方案是,仿真器提供API接口,调用CarSim和Simulink等软件的动力学模型,实现对汽车的模拟。 207 | * **传感器** - 传感器主要是GPS、IMU、LIDAR、RADAR、CAMERA等,涉及到传感器的位置,校准参数等。当然这一部分也可以仿真传感器视野范围(FOV),也可以仿真传感器的校准算法。 208 | 209 | 210 | 211 | ## 行为 212 | 现在我们加载了地图,车辆,接着我们需要定义一些行为来模拟真实世界。 213 | 214 | 215 | 216 | #### NPC 217 | npc包括行人和车辆。 218 | * **行人** - 目前主要是模拟行人过马路,以及在路边行走,以及更加复杂的场景,例如下雨天打伞的行人,对于这些异常场景,感知模块不一定能够正常识别。 219 | * **车辆** - 车辆的行为可以由一些简单的行为来模拟复杂的行为,例如停车,变道,加速,减速,来组合出超车,会车等复杂行为。也可以通过模拟真实情况的交通流数据,来模拟整个行为。前一种测试的行为比较成熟,后一种需要根据实际的情况提取出行为,再加入补全信息,才能够正常工作。 220 | 221 | 222 | 223 | #### 天气 224 | 天气主要是影响传感器的感知,最主要的就是摄像头。对LIDAR的影响由于目前没有阅读相关平台是否有加入噪声,这里就先不展开了。 225 | * **天气** - 雨、雪、雾、云层 调整不同的比率来模拟不同的天气情况对传感器的影响,云层主要是会影响光照变化,多云投射的阴影对车道线识别等会有影响。 226 | * **时间** - 白天和夜晚不同光照场景下对传感器的影响。 227 | 228 | 229 | 230 | #### 红绿灯 231 | 这一部分可以归纳为交通信号的行为,其中分为: 232 | * **有保护的红绿灯** - 各大城市是最普遍的,即有箭头的红绿灯,根据对应车道的红绿灯直行或者拐弯。 233 | * **无保护的红绿灯** - 即圆形的红绿灯,对面可以直线的同时,你可以拐弯,需要注意对面直行的车辆,选择让车之后再拐弯。 234 | * **无红绿灯** - 这种常见于郊区路口,需要判断有没有车辆经过而让行或者停止,然后再通过路口。 235 | 236 | 关于仿真器就介绍完毕了,那么我们如何控制仿真器来实现这些呢? 237 | 238 | 239 | 240 | ## API 241 | 目前主要是通过python API的方式来控制仿真器加载模型,控制仿真器的行为。好处是不用图形界面手工操作,可以实现自动化部署。API的主要是根据上述所说仿真器的功能实现统一的接口,实现交互。 242 | 243 | 244 | 245 | ## 部署 246 | 为了提高测试效率,我们还需要大规模部署,一个比较好的方式是通过容器化的方式部署。针对于多台机器,一个显而易见的需求就是创建一个管理平台来实现对仿真器的管理。容器部署平台可以监控对应仿真器的状态,并且提供可视化的配置界面,生成和部署不同的场景。 247 | * **监控** - 可以监控仿真器的监控状态,显示正常和有问题的集群,保存日志,维护集群的稳定。 248 | * **可视化** - 首先是配置可视化,可以方便的选择不同的配置(不同的地图,车,行为)来生成不同的场景,其次是通过可视化反馈仿真结果,屏蔽仿真集群的细节,使用起来更加直观方便。 249 | ![docker](img/docker.jpg) 250 | 251 | 252 | 253 | ## 总结 254 | 最后根据功能划分,我们可以单独仿真自动驾驶系统的规划控制模块,也可以单独仿真感知模块,可以仿真传感器校准,也可以端到端的仿真所有模块。可以仿真单个受限的场景,也可以仿真整个地图。总之,仿真系统需要提供灵活的场景生成框架,统一的API接口,以及大规模部署的能力。 255 | ![summary](img/summary.jpg) 256 | 257 | 258 | 259 | 260 | ## 参考 261 | [虚幻引擎游戏列表](https://zh.wikipedia.org/wiki/%E8%99%9A%E5%B9%BB%E5%BC%95%E6%93%8E%E6%B8%B8%E6%88%8F%E5%88%97%E8%A1%A8) 262 | [Unity3D](https://baike.baidu.com/item/Unity3D) 263 | [Udacity](https://github.com/udacity/self-driving-car-sim) 264 | [Carla](https://github.com/carla-simulator/carla) 265 | [AirSim](https://github.com/Microsoft/AirSim) 266 | [lgsvl](https://github.com/lgsvl/simulator) 267 | [Apollo](https://github.com/ApolloAuto/apollo) 268 | [EventSystem](https://docs.unity3d.com/ScriptReference/EventSystems.EventSystem.html) 269 | [openscenario](http://www.openscenario.org/) 270 | -------------------------------------------------------------------------------- /simulation/todo.md: -------------------------------------------------------------------------------- 1 | # lgsvl介绍 2 | ## apollo桥接 3 | lgsvl通过socket实现了apollo和lgsvl的桥接,主要的原理如下: 4 | ![]() 5 | 6 | #### 数据格式 7 | 需要重点关注的是数据格式的转换: 8 | ![]() 9 | 10 | 11 | 12 | ## 地图制作 13 | lgsvl默认支持的地图为"San Francisco",目前还不支持导入地图,如果需要自己制作地图可以按照下面的流程,在cityengine中制作城市的3维地图,然后导出模型到unity,用unity的地图编辑器编辑并且导出hd_map到apollo,之后在unity中创建场景,并且导入车的模型,整个地图的制作就完成了。 14 | 15 | ## lgsvl场景介绍 16 | 我们以"SimpleMap"场景来举例子,介绍场景是如何组成的,SimpleMap由以下几个场景组成: 17 | ``` 18 | Directional light // 平行光 19 | EventSystem // 事件系统 20 | XE_Rigged-apollo // 车模型 21 | ProceduralRoad // 地图中的房屋以及模型 22 | MapSimple // 道路,交通灯模型 23 | HDMapTool // apollo高精度地图制作工具 24 | VectorMapTool // autoware地图制作工具 25 | PointCloudTool // ?? 26 | PointCloudBounds // ?? 27 | MapOrigin // 地图起点,用来确定gps坐标?? 28 | spawn_transform // 29 | spawn_transform(1) 30 | ``` 31 | 32 | EventSystem 事件系统检测游戏的事件,并且提供调用接口。例如键盘鼠标回调,射线检测。 33 | Spawn GameObject 特殊的创建游戏对象事件。[](https://docs.unity3d.com/Manual/UNetSpawning.html) 34 | 35 | sanfrancisco 36 | ``` 37 | Terrains 地形 38 | ``` 39 | 40 | 碰撞采用的是[网格碰撞](http://docs.manew.com/Components/class-MeshCollider.html) 41 | 42 | 43 | 44 | 45 | ## Unity帮助文档 46 | http://docs.manew.com/Manual/71.html 47 | 48 | #### 回放 49 | https://gamedev.stackexchange.com/questions/141149/action-replay-in-unity-3d 50 | https://www.gamasutra.com/blogs/AustonMontville/20141105/229437/Implementing_a_replay_system_in_Unity_and_how_Id_do_it_differently_next_time.php 51 | https://forum.unity.com/threads/best-approach-to-replay-system.624517/ 52 | -------------------------------------------------------------------------------- /what_is_apollo/readme.md: -------------------------------------------------------------------------------- 1 | # Dig into Apollo - Introduction ![GitHub](https://img.shields.io/github/license/daohu527/Dig-into-Apollo.svg?style=popout) 2 | 3 | > 衣带渐宽终不悔,为伊消得人憔悴。 4 | 5 | ## Table of Contents 6 | - [简介](#introduction) 7 | - [目录结构](#content) 8 | - [编译](#compile) 9 | 10 | 11 | 12 | ## 简介 13 | apollo是百度的自动驾驶开源框架,根据自动驾驶的功能划分为不同的模块,下面会根据目录结构和功能模块分别介绍和学习"apollo模块"。下面简单介绍下各个模块的作用: 14 | * **定位** - 知道汽车在哪里,这里的定位可能涉及方方面面,比如GPS,但是GPS的精度只有米级别,有下面几个场景会不太合适,比如你在桥洞里,GPS信号不好的情况,另外还有一种情况是,停车的时候,你要知道前后车的距离,另外比如在雪天或者路况复杂的情况,这些情况下,仅有的GPS信号可能不能满足我们的要求,因此无人车又增加了激光雷达来测量周围环境的距离,而且可以精确到厘米。 15 | 因此产生了高精地图的需求,高精地图可以把周围的3D环境都记录下来,这样我们就可以通过3D图像来匹配周围的场景,来找到自己的位置,而且高精地图还可以记录比地图多的多的东西,比如红绿灯的位置,交通标志,左转还是右转道.通过高精地图我们不仅可以知道道路情况,还可以知道车辆需要获取的一些其他信息,让车辆知道自己的实时位置。 16 | * **感知** - 我们总是希望车辆行驶在马路中间,这样更加安全,这就需要追踪到道路的路牙线,而道路随时会出现拐弯,那么追踪路牙线就用到了图像处理技术,另外还要感知到什么是车辆,什么是行人,主要涉及到图像的语义分割,感知是自动驾驶中最难,而且最具有挑战性的一块,因为只有感知到周围的行人,车辆,以及突发状况,才能为后面规划线路。 17 | * **规划** - 目前已经知道当前道路情况,而且也已经感知到前面的车辆或者行人,如何去规划我们的行驶线路呢,这里需要解决的就是2个点之间的线路,而且行驶中途可能会出现新的情况,又需要重新规划线路,还有一种情况是,通过高精地图,我们已经知道前面需要转弯了,我们可以提前调整线路来适应这种需求。 18 | * **控制** - 现在已经规划出了一条线路,剩下的就是控制汽车,按照已经规划好的线路行驶,而且如果遇到突发状况,需要立即停车,而且控制汽车能够按照预定的线路不会出现很大的偏离,这就是控制要做的事情。 19 | 20 | 21 | 22 | ## 目录结构 23 | apollo是一个全栈的自动驾驶框架,下面是整个apollo代码的目录结构,主要是按照功能模块划分: 24 | ``` 25 | |-cyber 消息中间件,替换ros作为消息层 26 | |-docker 容器相关 27 | |-docs 文档相关 28 | |-modules 自动驾驶模块,主要的定位,预测,感知,规划都在这里 29 | |-calibration 校准,主要用于传感器坐标的校准,用于感知模块做传感器融合 30 | |-canbus 通讯总线,工业领域的标准总线,鉴于工业界的保守,我估计后面会有新的总线来取代 31 | |-common 32 | |-contrib 33 | |-control 控制模块,根据planning生成的路径对车辆轨迹进行控制,再底层就是发送命令到can总线,实现车辆的控制。 34 | |-data 地图等生成好的数据放在这里(其他数据待补充) 35 | |-dreamview 仿真,能够对自动驾驶过程中的数据进行回放,其他厂家也有推出一些仿真平台,后面有机会再介绍下 36 | |-drivers 雷达,lidar,GPS, canbus,camera等驱动 37 | |-guardian 监护程序? 38 | |-localization 定位,获取汽车的当前位置 39 | |-map 地图模块 40 | |-monitor 监控模块,主要是监控汽车状态,并且记录,用于故障定位,健康检查等 41 | |-perception 感知,获取汽车当前的环境,行人,车辆,红绿灯等,给planning模块规划线路 42 | |-planning 规划,针对感知到的情况,对路径做规划,短期规划,只规划100-200M的距离,生成好的路径给control模块 43 | |-prediction 预测,属于感知模块,对运动物体的轨迹做预测 44 | |-routing 导航线路,就是百度地图上查询2点之间的线路,生成的线路短期规划还是planning模块 45 | |-third_party_perception 第三方感知模块 46 | |-tools 工具,这里面的工具倒是很多,后面再详细介绍下 47 | |-transform 转换,主要是? 48 | |-v2x 顾名思义就vehicle-to-everything,其希望实现车辆与一切可能影响车辆的实体实现信息交互, 49 | 目的是减少事故发生,减缓交通拥堵,降低环境污染以及提供其他信息服务. 50 | |-scripts 脚本 51 | |-third_party 第三方库 52 | |-tools 工具目录,基本就是个空目录 53 | ``` 54 | 55 | 56 | 57 | ## 编译 58 | apollo采用的是bazel来进行编译,因为我对JAVA的maven比较熟,因此对maven的包管理的功能觉得特别好用,第一能解决包自动下载,第二解决依赖传递的问题,第三解决了编译的问题。也就是说你只要引用对应的包,就可以把包的依赖全部解决。 59 | 而bazel就是c++对应的编译管理,bazel主要是通过WORKSPACE和BUILD文件来进行编译。 60 | 61 | --------------------------------------------------------------------------------