【Final Project】Kitti的双目视觉里程计(1)

2023-05-16

1.从CMake文件了解整体结构

(1)前置工作

​ 0)文件结构

.
├── app
│   ├── CMakeLists.txt
│   └── run_kitti_stereo.cpp
├── CMakeLists.txt
├── cmake_modules
│   ├── FindCSparse.cmake
│   ├── FindG2O.cmake
│   └── FindGlog.cmake
├── config
│   └── default.yaml
├── include
│   └── myslam
│       ├── algorithm.h
│       ├── backend.h
│       ├── camera.h
│       ├── common_include.h
│       ├── config.h
│       ├── dataset.h
│       ├── feature.h
│       ├── frame.h
│       ├── frontend.h
│       ├── g2o_types.h
│       ├── map.h
│       ├── mappoint.h
│       ├── viewer.h
│       └── visual_odometry.h
├── src
│   ├── backend.cpp
│   ├── camera.cpp
│   ├── CMakeLists.txt
│   ├── config.cpp
│   ├── dataset.cpp
│   ├── feature.cpp
│   ├── frame.cpp
│   ├── frontend.cpp
│   ├── map.cpp
│   ├── mappoint.cpp
│   ├── viewer.cpp
│   └── visual_odometry.cpp
└── test
    ├── CMakeLists.txt
    └── test_triangulation.cpp

​ 1)系统变量

​ PROJECT_BINARY_DIR:编译发生的当前目录

​ PROJECT_SOURCE_DIR:工程所在目录

​ 2)SET指令

​ 显式定义变量

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

​ 3)ADD_EXECUTABLE指令

​ 4)ADD_SUBDIRECTORY指令

​ 1 ADD_SUBDIRECTOR(src bin):源文件夹src编译的文件将保存在/build/bin下

​ 2 ADD_SUBDIRECTOR(src):源文件夹src编译的文件将保存在/build/src下

​ 5)LIST指令

​ 告诉cmake在哪儿去找的.cmake文件

​ 6)INCLUDE_DIRECTORIES指令

​ 寻找库的头文件

​ 指定路径:

include_directories("/usr/local/include/eigen3")

​ 若使用FIND_PACKAGE:

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

​ 7)TARGET_LINK_LIBRARIES指令

​ 添加需要链接的共享库

​ 第一个参数:源文件

​ 第二个参数:所依赖的库

target_link_libraries(myslam ${THIRD_PARTY_LIBS})

​ 8)ADD_LIBRARY指令

​ 添加名为…的库,库的源文件可指定

​ 第一个参数:库的名字

​ 第二个参数:库的源文件

add_library(myslam 
        frame.cpp
        mappoint.cpp
		)

​ 9)ADD_TEST指令

​ 需要与ENABLE_TESTRING()配合使用

​ 10)FOREACH——ENDFOREACH

FOREACH (test_src ${TEST_SOURCES})
    ADD_EXECUTABLE(${test_src} ${test_src}.cpp)
    TARGET_LINK_LIBRARIES(${test_src} ${THIRD_PARTY_LIBS} myslam)
    ADD_TEST(${test_src} ${test_src})
ENDFOREACH (test_src)

(2)分析

​ 1)最外层CMakeLists.txt

​ 1 把所需要的库添加进来

​ 2 自己编写程序的头文件添加进来

​ 3 编译的中间位文件做好分组

​ 4 将添加进来的库打个包,为内层调用作准备

​ 2)/src/CMakeLists.txt

​ 1 src文件夹下存放源代码文件

​ 2 将源码文件与库链接起来

​ 3 这些文件本身也可以看成库

​ 3)/test/CMakeLists.txt

​ 测试文件

​ 4)app/CMakeLists.txt

​ 1 主程序

​ 2 调用编写的文件以及库文件

2.前置知识

(1)智能指针

shared_ptr

​ 基于引用计数的共享内存解决方案

1)基本用法

#include <iostream>
#include <new>
#include <memory>

int main()
{
    //int * x = new int(3);    
    std::shared_ptr<int> x(new int(3));
    std::cout << x.use_count() << std::endl;    //引用计数
    std::shared_ptr<int> y = x;
    std::cout << y.use_count() << std::endl;
    std::cout << x.use_count() << std::endl;
}
#include <iostream>
#include <new>
#include <memory>

std::shared_ptr<int> fun()
{
    std::shared_ptr<int> res(new int(100));
    return res;
}

int main()
{
    auto y = fun();
}
weak_ptr

​ 防止循环引用而引入的智能指针

循环引用

#include <iostream>
#include <new>
#include <memory>

struct Str
{
    std::shared_ptr<Str> m_nei;

    ~Str()
    {
        std::cout << "~Str() is called!\n";
    }
};

int main()
{
    std::shared_ptr<Str> x(new Str{});    //[x] = 1
    std::shared_ptr<Str> y(new Str{});    //[y] = 1
    x->m_nei = y;    //[y] = 2
    y->m_nei = x;    //[x] = 2
}

(1)基于 shared_ptr 构造

#include <iostream>
#include <new>
#include <memory>

struct Str
{
    std::weak_ptr<Str> m_nei;

    ~Str()
    {
        std::cout << "~Str() is called!\n";
    }
};

int main()
{
    //[]表示引用计数
    std::shared_ptr<Str> x(new Str{});    //[x] = 1
    std::shared_ptr<Str> y(new Str{});    //[y] = 1
    x->m_nei = y;    //[y] = 1
    y->m_nei = x;    //[x] = 1
}

(2)lock方法

#include <iostream>
#include <new>
#include <memory>

struct Str
{
    std::weak_ptr<Str> m_nei;

    ~Str()
    {
        std::cout << "~Str() is called!\n";
    }
};

int main()
{
    //[]表示引用计数
    std::shared_ptr<Str> x(new Str{});    //[x] = 1
    std::shared_ptr<Str> y(new Str{});    //[y] = 1
    x->m_nei = y;    //[y] = 1
    y->m_nei = x;    //[x] = 1

    if (auto ptr = x->m_nei.lock(); ptr)
    {
        std::cout << "Can access pointer\n";
    }
    else
    {
        std::cout << "Cannot access pointer\n";
    }
}
unordered_map

​ 与map类似,与 set / map 相比查找性能更好,但插入操作一些情况下会慢

1树中的每个结点是一个 std::pair

#include <iostream>
#include <map>

int main()
{
    std::map<char, int> m{{'a', 3}, {'b', 4}};
    for (auto ptr = m.begin();ptr != m.end(); ptr++)
    {
        auto p = *ptr;    //std::pair<const char, int>
        std::cout << p.first << ' ' << p.second << std::endl;
    }
    //基于range-based-for
    for (auto p : m)
    {
        std::cout << p.first << ' ' << p.second << std::endl;
    }
    //或者
    for (auto [k, v] : m)
    {
        std::cout << k << ' ' << v << std::endl;
    }
}

2键 (pair.first) 需要支持使用 < 比较大小,或者采用自定义的比较函数来引入大小关系

3访问元素: find / contains / [] / at

#include <iostream>
#include <map>

int main()
{
    std::map<int, bool> m;
    m.insert(std::pair<const int, bool>(3, true));
    auto ptr = m.find(3);
    std::cout << ptr->first << '' << ptr->second;
    std::cout << m[3] << std::endl;
}

(2)多线程及线程锁

1.多线程

(1)构造方式

​ 假如直接执行run()函数,那么由于run函数内部是一个无限循环(A),则主函数中的无限循环(B)永远无法被执行;通过新开一个线程,A与 B同时执行了。

#include <iostream>
#include  <thread>
class A
{
public:
    void run()
    {
        while (true)
        {
            std::cout << "AAAA" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }

    }

};
int main()
{
    A aa;
    //aa.run();
    std::thread thr(&A::run, &aa);
    while (true)
    {
        std::cout << "BBBB" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
    return 0;
}

(2)join()方法

​ 阻塞当前线程直至 执行完毕再执行下面代码。

#include <iostream>
#include  <thread>
class A
{
public:
    void run()
    {
        while (true)
        {
            std::cout << "AAAA" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }

    }

};
int main()
{
    A * aa = new A;
    //aa.run();
    std::thread thr(&A::run, aa);
    thr.join();
    /*
    while (true)
    {
        std::cout << "BBBB" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
     */
    return 0;
}
2.互斥

(1)unique_lock

3.从相机数据开始

(1)KITTI Odometry数据集

传感器安装位置

​ Kitti传感器主要包含相机(Cam0-4)、GPS/IMU、激光雷达(Velodyne Laserscanner)。本课题中只用到了相机,所以只讨论相机。

​ 在calib.txt文件中P0到P4分别对应四个相机数据,其中,P0的原点为车辆坐标系原点,P0, P1, P2, P3 分别代表对应的相机内参矩阵, 大小为 3x4:
P r e c t i = [ f u i 0 c u i − f u i b x i 0 f v i c v i 0 0 0 1 0 ] (3) P^{i}_{rect}= \left[ \begin{matrix} f^i_u & 0 & c^i_u & -f^i_ub^i_x \\ 0 & f^i_v & c^i_v & 0 \\ 0 & 0 & 1 & 0 \end{matrix} \right] \tag{3} Precti= fui000fvi0cuicvi1fuibxi00 (3)
​ 第四列数据 − f u i b x i -f^i_ub^i_x fuibxi 中的 b x i b^i_x bxi 表示此相机相对与车辆坐标系原点的距离,只有平移没有旋转。

​ 通过 b ,将四个相机的相对关系描述出来。

(2)相机模型

​ 此部分为复习,复习课本知识并思考如何融入项目之中。

1 相机内参数

​ 由KITTI数据集给出

2 相机外参数

​ 对于现实世界的空间点P,我们需要将其转换到相机坐标系:
P c = T c w P w P_c = T_{cw}P_w Pc=TcwPw
Tips:世界坐标系不变

3 具体方法

​ 1)世界坐标系中的点P转换到相机坐标系

​ 2)相机坐标系中的点 P c P_c Pc 转换到像素坐标系

4 Camera类的构造

​ 相机内参数以及双目相机基线长度由数据集给出,唯一一个不确定的参数便是相机的位姿。

​ 通过数据集对相机位姿初始化:由数据集可以得到,Cam0 为世界坐标系的原点, b x i b_x^i bxi 为 第 i 个相机与Cam0的平移关系,旋转关系由于安装原因没有旋转。

4.基本数据结构

需要处理的数据:

​ 1 图像:双目视觉中的一对图像,可以称为一帧。

​ 2 提取特征

​ 3 通过特征计算3D位置,即路标。

(1)图像(帧)

1 构造函数

​ 通过构造函数,我认为能够迅速了解一个类。

​ 1 对于一系列的图像,可以为其编号

​ 2 该帧出现的时间

​ 3 关键帧

​ 4 相机的位姿

​ 5 读入的左右图像

Frame::Frame(long id, double time_stamp, const SE3 &pose, const Mat &left, const Mat &right)
        : id_(id), time_stamp_(time_stamp), pose_(pose), left_img_(left), right_img_(right) {}
	typedef std::shared_ptr<Frame> Ptr;

    unsigned long id_ = 0;           // id of this frame
    unsigned long keyframe_id_ = 0;  // id of key frame
    bool is_keyframe_ = false;       // 是否为关键帧
    double time_stamp_;              // 时间戳,暂不使用
    SE3 pose_;                       // Tcw 形式Pose
    std::mutex pose_mutex_;          // Pose数据锁
    cv::Mat left_img_, right_img_;   // stereo images
2 创建帧

​ 分配帧id以及关键帧id

Frame::Ptr Frame::CreateFrame() {
    static long factory_id = 0;
    Frame::Ptr new_frame(new Frame);
    new_frame->id_ = factory_id++;
    return new_frame;
}

void Frame::SetKeyFrame() {
    static long keyframe_factory_id = 0;
    is_keyframe_ = true;
    keyframe_id_ = keyframe_factory_id++;
}
3 工厂模式
Frame::Ptr Frame::CreateFrame() {
    static long factory_id = 0;
    Frame::Ptr new_frame(new Frame);
    new_frame->id_ = factory_id++;
    return new_frame;
}

(2)从帧中提取特征

​ 特征充当了一个中介,连接起3维世界中的点与图像中的点。

​ 那么最主要的信息便是从帧图像中提取到的2D位置,哪一个帧有此特征以及对应的3D路标也是需要考虑的。

    typedef std::shared_ptr<Feature> Ptr;

    std::weak_ptr<Frame> frame_;         // 持有该feature的frame
    cv::KeyPoint position_;              // 2D提取位置
    std::weak_ptr<MapPoint> map_point_;  // 关联地图点

    bool is_outlier_ = false;       // 是否为异常点
    bool is_on_left_image_ = true;  // 标识是否提在左图,false为右图
    Feature(std::shared_ptr<Frame> frame, const cv::KeyPoint &kp)
        : frame_(frame), position_(kp) {}

(3)路标点

​ 路标点最重要的信息是它的3D位置,其次是被哪些特征所观察。

1 构造函数

​ ID,3D位置

    typedef std::shared_ptr<MapPoint> Ptr;
    unsigned long id_ = 0;  // ID
    bool is_outlier_ = false;
    Vec3 pos_ = Vec3::Zero();  // Position in world
    std::mutex data_mutex_;
    int observed_times_ = 0;  // being observed by feature matching algo.
    std::list<std::weak_ptr<Feature>> observations_;
MapPoint::MapPoint(long id, Vec3 position) : id_(id), pos_(position) {}
2 创建路标点

​ 工厂模式创建路标点,可以看出与创建帧有相似性

MapPoint::Ptr MapPoint::CreateNewMappoint() {
    static long factory_id = 0;
    MapPoint::Ptr new_mappoint(new MapPoint);
    new_mappoint->id_ = factory_id++;
    return new_mappoint;
}
    Vec3 Pos() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return pos_;
    }

    void SetPos(const Vec3 &pos) {
        std::unique_lock<std::mutex> lck(data_mutex_);
        pos_ = pos;
    };
3 观察点
	void AddObservation(std::shared_ptr<Feature> feature) {
        std::unique_lock<std::mutex> lck(data_mutex_);
        observations_.push_back(feature);
        observed_times_++;
    }
void MapPoint::RemoveObservation(std::shared_ptr<Feature> feat) {
    std::unique_lock<std::mutex> lck(data_mutex_);
    for (auto iter = observations_.begin(); iter != observations_.end();
         iter++) {
        if (iter->lock() == feat) {
            observations_.erase(iter);      //erase() 删除
            feat->map_point_.reset();
            observed_times_--;
            break;
        }
    }
}

(4)地图类

​ 为了实际持有Frame和MapPoint对象,定义地图类。

1 成员变量
	typedef std::shared_ptr<Map> Ptr;
    typedef std::unordered_map<unsigned long, MapPoint::Ptr> LandmarksType;
    typedef std::unordered_map<unsigned long, Frame::Ptr> KeyframesType;
    std::mutex data_mutex_;

    LandmarksType landmarks_;         // all landmarks
    LandmarksType active_landmarks_;  // active landmarks
    KeyframesType keyframes_;         // all key-frames
    KeyframesType active_keyframes_;  // all key-frames

    Frame::Ptr current_frame_ = nullptr;

    // settings
    int num_active_keyframes_ = 7;  // 激活的关键帧数量
2 实现功能
    /// 增加一个关键帧
    void InsertKeyFrame(Frame::Ptr frame);
    /// 增加一个地图顶点
    void InsertMapPoint(MapPoint::Ptr map_point);

    /// 获取所有地图点
    LandmarksType GetAllMapPoints() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return landmarks_;
    }
    /// 获取所有关键帧
    KeyframesType GetAllKeyFrames() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return keyframes_;
    }

    /// 获取激活地图点
    LandmarksType GetActiveMapPoints() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return active_landmarks_;
    }

    /// 获取激活关键帧
    KeyframesType GetActiveKeyFrames() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return active_keyframes_;
    }
void Map::InsertKeyFrame(Frame::Ptr frame) {
    current_frame_ = frame;
    if (keyframes_.find(frame->keyframe_id_) == keyframes_.end()) {
        keyframes_.insert(make_pair(frame->keyframe_id_, frame));
        active_keyframes_.insert(make_pair(frame->keyframe_id_, frame));
    } else {
        keyframes_[frame->keyframe_id_] = frame;
        active_keyframes_[frame->keyframe_id_] = frame;
    }

    if (active_keyframes_.size() > num_active_keyframes_) {
        RemoveOldKeyframe();
    }
}
/*
	keyframes_.end():指向map容器最后一个的下一个
	keyframes_.find():通过键查找对象是否在容器里,若不存在,就指向end
	上述if语句判断传入帧的id是否在容器中,若在,就用新的把旧的替换掉;若不存在,就添加进去。
*/

void Map::InsertMapPoint(MapPoint::Ptr map_point) 	//与添加关键帧一样
{
    if (landmarks_.find(map_point->id_) == landmarks_.end()) {
        landmarks_.insert(make_pair(map_point->id_, map_point));
        active_landmarks_.insert(make_pair(map_point->id_, map_point));
    } else {
        landmarks_[map_point->id_] = map_point;
        active_landmarks_[map_point->id_] = map_point;
    }
}


void Map::RemoveOldKeyframe() {
    if (current_frame_ == nullptr) return;
    // 寻找与当前帧最近与最远的两个关键帧
    double max_dis = 0, min_dis = 9999;
    double max_kf_id = 0, min_kf_id = 0;
    auto Twc = current_frame_->Pose().inverse();
    for (auto& kf : active_keyframes_) {
        if (kf.second == current_frame_) continue;
        auto dis = (kf.second->Pose() * Twc).log().norm();
        if (dis > max_dis) {
            max_dis = dis;
            max_kf_id = kf.first;
        }
        if (dis < min_dis) {
            min_dis = dis;
            min_kf_id = kf.first;
        }
    }

    const double min_dis_th = 0.2;  // 最近阈值
    Frame::Ptr frame_to_remove = nullptr;
    if (min_dis < min_dis_th) {
        // 如果存在很近的帧,优先删掉最近的
        frame_to_remove = keyframes_.at(min_kf_id);
    } else {
        // 删掉最远的
        frame_to_remove = keyframes_.at(max_kf_id);
    }

    LOG(INFO) << "remove keyframe " << frame_to_remove->keyframe_id_;
    // remove keyframe and landmark observation
    active_keyframes_.erase(frame_to_remove->keyframe_id_);
    for (auto feat : frame_to_remove->features_left_) {
        auto mp = feat->map_point_.lock();
        if (mp) {
            mp->RemoveObservation(feat);
        }
    }
    for (auto feat : frame_to_remove->features_right_) {
        if (feat == nullptr) continue;
        auto mp = feat->map_point_.lock();
        if (mp) {
            mp->RemoveObservation(feat);
        }
    }

    CleanMap();
}

void Map::CleanMap() {
    int cnt_landmark_removed = 0;
    for (auto iter = active_landmarks_.begin();
         iter != active_landmarks_.end();) {
        if (iter->second->observed_times_ == 0) {
            iter = active_landmarks_.erase(iter);
            cnt_landmark_removed++;
        } else {
            ++iter;
        }
    }
    LOG(INFO) << "Removed " << cnt_landmark_removed << " active landmarks";
}

感受:代码读下来只能说需要时间去理解,脑子里隐约有了个关于框架,基本的参数以及参数的功能有了一定的了解,但是对于一些辅助参数比如各种 ID,时间戳等在自己编写程序的时候能够思考加上,以及选用什么样的容器也是需要思考的地方。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Final Project】Kitti的双目视觉里程计(1) 的相关文章

随机推荐

  • db2取前n条记录

    select a from table a where id 61 370 fetch first n rows only
  • 批量执行某个文件夹下所有的 .sql脚本

    copy sql all ren all all sql sqlplus aa bb 64 all 在windows下我用dir b sqlfile gt sql txt 然后用UE的列编辑模式 xff0c 给行头都加上 64 xff0c
  • FreeRTOS-Task

    Task FreeRTOS中Task为调度单位 xff0c 是独立的运行实例 xff0c 具有自己的堆栈空 间 Task通常是无限循环执行 xff0c 不允许以任何方式退出实现函数 xff08 return 语句或者运行结束 xff09 如
  • 面试必看!一线互联网公司技术面试的流程以及注意事项

    企业一般通过几轮技术面试来考察大家的各项能力 xff0c 一般流程如下 一面机试 xff1a 一般会考选择题和编程题二面基础算法面 xff1a 就是基础的算法都是该专栏要讲的三面综合技术面 xff1a 会考察编程语言 xff0c 计算机基础
  • 去哪儿2017校园招聘笔试题

    span class hljs keyword import span java util Scanner span class hljs javadoc filename extension 时间限制 xff1a C C 43 43 语言
  • 日志文件xml

    lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt ConsoleAppender 控制台输出日志 gt lt appender name 61 34 STDOUT 34 cl
  • STM32输出PWM波形错误解析

    一 背景 项目中需要用STM32F407输出4路PWM波形控制两个A4950模块 xff0c 从而驱动2个直流电机 使用TIM1的在PE9 PE11 PE13 PE14上分别产生4路PWM波形 xff0c 前两路 xff08 记作pwm1
  • Kubernetes 1.20:最优秀、美妙、酷的版本

    你填了吗 xff1f 2020年CNCF中国云原生问卷 问卷链接 xff08 https www wjx cn jq 97146486 aspx xff09 作者 xff1a Kubernetes 1 20发布团队 我们很高兴地宣布Kube
  • C++常见问题总结

    C 43 43 问题总结模块 编程之路总是路漫漫其修远兮 xff0c 吾将上下而求索 1 no matching function for call to 借用CSDN某位的文章 xff0c 成功修改错误 大概截图如下 源代码 xff1a
  • 字符串函数strchr 、 strrchr 、strrstr的实现

    include lt stdio h gt include lt stdlib h gt include lt assert h gt char my strchr const char dst char c 由于我们只是查找 xff0c
  • cadence常见问题一

    1 在画元件库时 xff0c 双击编辑一个引脚 xff0c 编辑好了点了OK xff0c 引脚就从左边跑到了右边 xff1f xff1f xff1f 居然不是固定的 xff1f 我在user properties设置下引脚名字可视化 xff
  • keil,stm32,watch窗口,正确的串口数据后面还出现ASCII字符?

    这个问题不知道如何解决 xff0c 串口调试助手数据显示都是准确的 xff0c watch窗口看就不正确 不知道正确数据后面的是什么 xff1f
  • MS5611气压计数据测试报告

    气压计测得气压和温度值为模拟量 xff0c ms5611气压计会自动将模拟量转换成数字量 xff0c 对于不同的精度 xff0c 转换时间也不相同 本测试选用的精度为最高的OSR 61 4096 xff0c 如下表所示 xff0c 转换时间
  • Fatfs文件系统,f_open函数返回值为FR_DISK_ERR解决方法

    最近在操作TF卡 xff0c 芯片stm32f103c8t6 xff0c 编译环境KEIL xff0c 金士顿32G卡 xff0c 用Fatfs文件系统向卡中写入数据 出现的问题 xff1a f open函数返回值为FR DISK ERR
  • Fatfs文件系统向文件写内容出现f_write返回值为1的问题

    f write返回值为1 xff0c 则就是FR DISK ERR 1 A hard error occurred in the low level disk I O layer 低级磁盘I O层中发生硬错误 问题解决方式 xff1a 1
  • vl53l1x激光测距讲解

    使用模块 ATK VL53L0X激光测距模块或者淘宝其他模块 通信方式 xff1a IIC xff0c 接口SHUT用于开机启动时序中 xff0c int是中断模式中的引脚 xff08 触发中断 xff09 参考资料 xff1a https
  • 如何完成一篇发明专利

    专利的组成部分 xff1a 说明书摘要摘要附图权利要求书说明书说明书附图 参考的文献有 专利法 专利审查指南 xff0c 大致写完一篇发明专利需要半个月的时间 xff1b 参考网址 xff0c http www soopat com htt
  • cmd python 缩进 3个点

    问题描述 xff1a indentationerror expected an indented block for 语句和if语句都会遇到 xff0c 解决方法是for 语句和if语句冒号后 xff0c 按enter切换下一行 xff0c
  • 陀螺仪和加速度计MPU6050的单位换算方法

    对于四轴的初学者 xff0c 可能无法理解四轴源代码里面陀螺仪和加速度数据的那些数学转换方法 下面我们来具体描述下这些转换方法 我们首先来看陀螺仪数据 在MPU6050的手册里面 xff0c 提供了一个陀螺仪数据表如下 xff1a 在表格里
  • 【Final Project】Kitti的双目视觉里程计(1)

    1 从CMake文件了解整体结构 xff08 1 xff09 前置工作 0 xff09 文件结构 app CMakeLists txt run kitti stereo cpp CMakeLists txt cmake modules Fi