Linux平台C++程序调试

[TOC]

Overview

定位函数名

建立main.c文件,内容如下:

#include <stdio.h>
void main() {
    int a = 5/0;
}

编译 main.c

gcc -g main.c

生成a.out文件

执行 .\a.out, 出现如下错误信息:

Floating point exception (core dumped)

使用 dmesg | tail 命令查看:

[   26.511616] VBoxPciLinuxInit
[   26.701426] vboxpci: IOMMU not found (not registered)
[ 4526.498595] usb 1-5: USB disconnect, device number 2
[ 4526.895918] usb 1-5: new high-speed USB device number 3 using ehci-pci
[ 4527.045378] usb 1-5: New USB device found, idVendor=2717, idProduct=ff48
[ 4527.045381] usb 1-5: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 4527.045383] usb 1-5: Product: MI MAX 2
[ 4527.045385] usb 1-5: Manufacturer: Xiaomi
[ 4527.045387] usb 1-5: SerialNumber: 8fc7deed
[31563.822978] traps: a.out[19804] trap divide error ip:4004e5 sp:7ffdd3cf89c0 error:0 in a.out[400000+1000]

根据错误地址(ip后面的地址),使用如下命令,可以定位(输出)函数名:

addr2line -e a.out 4004e5 -f

如下所示:

main
/home/gordon/main.c:5

建立main.cc文件,内容如下:

#include <iostream>
using namespace std;
void foo() {
    cout << "the address of foo() is " << (void*)foo << endl;
}
int main() {
    foo();
    return 0;
}

编译 main.cc

g++ -g main.cc

生成a.out文件

执行 .\a.out, 输出如下信息:

the address of foo() is 0x400896

根据输出的函数地址,使用如下命令可以查看函数名:

addr2line -e a.out 0x400896 -f

如下所示:

_Z3foov
/home/gordon/main.cc:5

可以看出C++编译器对函数名进行了编码,我们可以在上一步命令的最后加上 --demangle=gnu-v3 选项,输出函数名,如下所示:

addr2line -e a.out 0x400896 -f --demangle=gnu-v3

输出的函数名如下所示:

foo()
/home/gordon/main.cc:5

Others

  • 用gcc编绎该文件
    gcc -c hello.c -o hello.o
    
  • 生成静态库 用ar工具(ar是archive的意思),同时可以查看静态库中包含那些.o文件
    ar cqs libhello.a hello.o
    
    ar -t libname.a # 查看一个静态库由那些.o文件
    
  • 生成动态库 用gcc来完成,由于可能存在多个版本,因此通常指定版本号
    gcc -shared -o libhello.so.1.0 hello.o
    
  • 在Linux下,动态库和静态库同事存在时,gcc/g++的链接程序,默认链接的动态库
  • To link your program with lib1, lib3 dynamically and lib2 statically, use such gcc call:
    gcc program.o -llib1 -Wl,-Bstatic -llib2 -Wl,-Bdynamic -llib3
    
  • 使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库
    ldd /bin/lnlibc.so.6
    
  • 使用nm工具,查看静态库和动态库中有那些函数名
    nm libhello.so
    
  • 如何查看动态库和静态库是32位,还是64位下的库
    file *.so  # 动态库
    
    objdump -x *.a  # 静态
    
  • LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
  • LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

  • 比如我们有一个基础库libbase.a,还有一个依赖libbase.a编译的库,叫做libchild.a;在我们编译程序时,一定要先-lchild再-lbase。 如果使用 -lbase -lchild,在编译时将出现一些函数undefined,而这些函数实际上已经在base中已经定义;
Read More

SLAM之PTAM学习笔记

[TOC]

概述

PTAM,全称 Parallel Tracking And Mapping,是最早提出将Track和Map分开作为两个线程的一种SLAM算法,是一种 基于关键帧单目视觉SLAM算法。PTAM主要分为这几部分:

1) Track线程

  • FAST特征提取
  • 地图初始化
  • 跟踪定位
  • 选取添加关键帧到缓存队列
  • 重定位

2) Map线程

  • 局部BundleAdjustment
  • 全局BundleAdjustment
  • 从缓存队列取出关键帧到地图
  • 极线搜索加点到地图

另一方面,按照一般的视觉SLAM框架,PTAM也可分为

  • 传感器数据获取(摄像头输入图像数据)
  • 前端视觉里程计(跟踪定位、重定位)
  • 后端优化(Bundle Adjustment)
  • 建图(极线搜索加点)
  • 没有回环检测

代码:cggos/ptam_cg,对原始PTAM代码进行了部分改动(将原来的makefile工程改成了cmake工程,改变了部分代码结构,添加带有公式的Doxygen文档注释,某些本文档提及的算法公式可能会出现代码注释中),核心算法没变。

FAST特征提取

  • 为了SLAM的实时性,选择 FAST 作为特征提取的方法;
  • 对于从数据流中输入的 每一帧图像 先进行金字塔分层(4层均值金字塔),每一层都要进行FAST特征提取;

地图初始化

对于每帧图像提取的FAST特征点,因经常出现“扎堆”现象,再进行 非极大值抑制,选出较好的特征点,然后对每个特征点计算 Shi-Tomas得分,选出得分较高的特征点(不超过1000个,设置数量阈值),作为特征匹配的 候选特征点

先选择一帧图像,再通过基于 SSD的块匹配 选出第二帧图像,作为两帧关键帧;根据两帧图像间的匹配特征点,计算出两帧间的 单应性矩阵 ,然后分解出对应的 旋转平移矩阵,作为相机的初始位姿。

单目的尺度不确定问题,根据经验设定一个 尺度,作用于初始两帧间的旋转平移矩阵,并作为全局的尺度。

根据初始两帧间的旋转平移矩阵和特征点像素坐标,利用 线性三角法深度估计算法 估算出第一帧坐标系下的世界点三维坐标,再通过 BundleAdjustment 方法对世界点和相机初始位姿进行优化;因先前计算出的世界点数量可能不够,再通过 极线搜索 添加世界点,再通过 BundleAdjustment 方法对世界点和相机初始位姿进行优化。

根据现在的世界点,通过 RANSAC 找出主平面,作为系统的世界坐标系,同时计算出质心 $C$;计算出内点和主平面质心的 协方差矩阵,通过 PCA主成分分析 得出主平面的 法向量 $N$,然后通过 Gram-Schmidt正交化 计算出第一帧坐标系和主平面坐标系旋转矩阵 $R$,再根据质心 $C$ 和公式 $P_w=R(P_c-C)=RP_c-RC$ 计算出平移向量 $t=-RC$。

根据主平面计算出的旋转平移矩阵,将第一帧坐标系下的世界点和两帧的旋转平移矩阵变换到主平面对应的世界坐标系下,第二帧对应的旋转平移矩阵作为当前相机的位姿。

跟踪定位

(1)根据上一帧的相机位姿(旋转平移矩阵),通过作用 运动模型基于ESM的视觉跟踪算法 对当前帧的相机位姿进行预测。

(2)根据预测的相机位姿,将当前所有世界点根据 小孔成像原理 进行投影,投影后的像素点记为 $p_i$,并计算出对应的金字塔层级。

(3)根据金字塔高层优先原则,选取一定数量世界点(通常,粗搜索选取30~60个,细搜索选取1000个左右)。

(4)遍历选取的世界点,对于每一个 世界点对应源帧图像中已经进行Warp变换的8x8的模板块,和以 当前帧图像点 $p_i$ 一定范围内的每一个FAST特征点为中心选取的8x8像素块,进行 基于SSD的相似度 计算,选择具有最小SSD值的FAST特征点,并记录查找到的特征点数量,用于后期跟踪质量评估;出于精确考虑,可通过 反向合成图像对齐算法 求取该特征点的 亚像素坐标,记为 $p_i’$,这样选取的每个世界点都对应 $p_i$ 和 $p_i’$,重投影误差 即为 $p_i’ - p_i$。

(5)根据重投影误差建立起误差函数,以预测的相机位姿作为初始值,通过 高斯-牛顿非线性优化方法 计算出当前帧的相机位姿,其中每次迭代的位姿增量为 李代数 形式。

实际上,PTAM中位姿计算分 粗跟踪细跟踪 两个阶段,每个阶段均进行上述的(3)~(5)过程,主要差别在于选取世界点进行计算的点数。

关键帧选取

关键帧选取的指标主要有:

  • 跟踪质量(主要根据跟踪过程中搜索到的点数和搜索的点数比例)
  • 距离最近关键帧的距离是否足够远(空间)
  • 距离上一关键帧的帧数是否足够多(时间)
  • 关键帧缓存队列是否已满

重定位

构建关键帧时,每一帧都会生成一个 高斯模糊小图(大小为顶层金字塔尺寸的一半,并进行了0.75的高斯卷积,以及去中心化)

重定位时,基于 SSD算法 计算 当前帧的高斯模糊小图 和 地图中所有的关键帧的高斯模糊小图 的 相似度,选择相似度最高的关键帧的相机位姿,根据 基于ESM的视觉跟踪算法 计算出相机位姿作为当前帧的相机位姿。

Bundle Adjustment

Bundle Adjustment(以下简称BA),中文翻译“光束法平差”,本质是一个优化模型,目的是最小化重投影误差,用于最后一步优化,优化相机位姿和世界点。PTAM中BA主要在Map线程中,分为 局部BA全局BA,是其中比较耗时的操作。

局部BA用于优化局部的相机位姿,提高跟踪的精确度;全局BA用于全局过程中的相机位姿,使相机经过长时间、长距离的移动之后,相机位姿还比较准确。

BA是一个图优化模型,一般选择 LM(Levenberg-Marquardt)算法 并在此基础上利用 BA模型的稀疏性 进行计算;可以直接计算,也可以使用 g2o或者Ceres等优化库 进行计算。

极线搜索

选择关键帧容器中最后一帧作为 源帧,然后在所有关键帧中找到距离其最近的一帧作为 目标帧

通过 源帧 中像素特征点、场景平均深度场景深度方差,根据 对极几何 原理,找出 源帧 中平均深度附近一定范围光束,并将其投影到目标帧成像平面,为一段 极线

遍历该段 极线附近所有的候选特征点,通过 基于SSD的块匹配方法 查找出与源帧图像匹配的特征点,再通过 反向合成算法求取其亚像素坐标,然后 三角法计算世界点

分析

优化算法:基本是基于最小二乘的非线性优化算法,跟踪部分使用G-N求解 基于权重的最小二乘,BA使用L-M;

矩阵求逆:Cholesky分解

线性方程组求解:SVD分解

初始化相机位姿求解,用的是 基于2D-2D的对极几何 计算 单应性矩阵,适用于 共面特征点;后面跟踪部分相机位姿求解,用的是 基于2D-3D的PnP算法

块匹配:基于SSD的相似度计算;

Read More

OpenCV编译安装配置总结

[TOC]

Overview

  • check version from source
    modules/core/include/opencv2/core/version.hpp
    
  • check informations after installation
    # 查看opencv版本
    pkg-config --modversion opencv
    
    # 查看opencv包含目录
    pkg-config --cflags opencv
    
    # 查看opencv库目录
    pkg-config --libs opencv
    

Build & Installation

Dependencies

sudo apt install build-essential  
sudo apt install  libgtk2.0-dev libavcodec-dev libavformat-dev  libtiff4-dev  libswscale-dev libjasper-dev
sudo apt install cmake pkg-config

build with CMake

#!/usr/bin/env bash
cmake \
  -D CMAKE_BUILD_TYPE=Release \
  -D CMAKE_INSTALL_PREFIX=/usr/local/opencv_249 \
  -D WITH_VTK=OFF \
  -D WITH_MATLAB=OFF \
  -D WITH_TBB=ON \
  -D WITH_IPP=OFF \
  -D WITH_FFMPEG=OFF \
  -D WITH_V4L=ON \
  -D WITH_CUDA=OFF \
  -D CUDA_GENERATION=Kepler \
  -D ENABLE_PRECOMPILED_HEADERS=OFF \
  -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
  ..

# or by the CMake curses interface `ccmake`

time make -j4
sudo make install  

Uninstall

sudo make uninstall

# or

# install-mainfest.txt包含了安装文件的路径
sudo cat install-manifest.txt | sudo xargs rm

Errors

  • /usr/local/include/c++/6.2.0/cstdlib:75:25: fatal error: stdlib.h: No such file or directory
      -D ENABLE_PRECOMPILED_HEADERS=OFF
    
  • nvcc fatal : Unsupported gpu architecture ‘compute_11’ CMake Error at cuda_compile_generated_matrix_operations.cu.o.cmake:206
      -D CUDA_GENERATION=Kepler
      # When using cmake to do configurations,
      # set the option CUDA_GENERATION to specific your GPU architecture
    
  • opencv/modules/videoio/src/ffmpeg_codecs.hpp:111:7: error: ‘CODEC_ID_H263P’ was not declared in this scope
      -D WITH_FFMPEG=OFF
    

Usage

using g++

g++ `pkg-config opencv --cflags` test.cpp -o test `pkg-config opencv --libs`

using CMake

set(OpenCV_DIR "/home/ubuntu/src/opencv-3.1.0/build")
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )

using Makefile

将opencv-3.1.0.pc和opencv-2.4.12.pc拷贝到/usr/lib/pkgconfig目录(可设置PKG_CONFIG_PATH)下, 使用opencv-3.1.0时,Makefile中为:

COMMON  += -DOPENCV
CFLAGS  += -DOPENCV
LDFLAGS += `pkg-config --libs opencv-3.1.0`
COMMON  += `pkg-config --cflags opencv-3.1.0`

使用opencv-2.4.12时,Makefile中为:

COMMON  += -DOPENCV
CFLAGS  += -DOPENCV
LDFLAGS += `pkg-config --libs opencv-2.4.12`
COMMON  += `pkg-config --cflags opencv-2.4.12`

Cross-Compilation

for ARM

for Android

  • platforms/android 目录执行以下代码
    #!/usr/bin/env bash
    
    # cd `dirname $0`/..   # platforms/
    
    mkdir -p build_android
    
    cd build_android
    
    cmake \
      -DCMAKE_MAKE_PROGRAM="/usr/bin/make" \
      -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
      -DCMAKE_INSTALL_PREFIX="/opt/opencv_android_sdk" \
      -DCMAKE_TOOLCHAIN_FILE="../android.toolchain.cmake" \
      -DCMAKE_BUILD_TYPE=Release \
      -DBUILD_SHARED_LIBS=ON \
      -DBUILD_DOCS=OFF \
      -DBUILD_EXAMPLES=OFF \
      -DBUILD_PERF_TESTS=OFF \
      -DBUILD_TESTS=OFF \
      -DBUILD_opencv_java=OFF \
      -DBUILD_opencv_face=OFF \
      -DBUILD_opencv_face=OFF \
      -DWITH_IPP=OFF \
      -DWITH_TBB=OFF \
      -DWITH_TIFF=OFF \
      -DWITH_OPENEXR=OFF \
      -DWITH_OPENCL=ON \
      -DWITH_CUDA=OFF \
      -DWITH_MATLAB=OFF \
      -DOPENCV_EXTRA_MODULES_PATH="../../../../opencv_contrib-3.3.1/modules/"  \
      -DBUILD_ANDROID_EXAMPLES=OFF \
      -DANDROID_NDK=${ANDROID_NDK} \
      -DANDROID_ABI="armeabi-v7a" \
      -DANDROID_ARM_NEON=TRUE \
      -DANDROID_STL=gnustl_static \
      -DANDROID_CPP_FEATURES="rtti exceptions" \
      -DANDROID_NATIVE_API_LEVEL=23 \
      -DANDROID_PLATFORM=android-23 \
      -DANDROID_SDK_TARGET=23 \
      -DANDROID_DISABLE_FORMAT_STRING_CHECKS=TRUE \
      $@ ../../..
    
    # -DANDROID_OPENCL_SDK="./OpenCL-SDK" \
    
  • to compile for Android with ANDROID_STL=c++_static(shared), use Android NDK own CMake toolchain file and clang(ref: https://github.com/opencv/opencv/issues/8742#issuecomment-385553011)
    -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK}/build/cmake/android.toolchain.cmake" \
    -DANDROID_TOOLCHAIN=clang \
    -DANDROID_STL=c++_static \
    
  • with platforms/android/build_sdk.py
    python build_sdk.py \
      --ndk_path $ANDROID_NDK \
      --sdk_path $ANDROID_SDK \
      --extra_modules_path ../../../opencv_contrib-3.3.1/modules \
      build_cg ../..
    
Read More

世界上最伟大的十个公式

英国科学期刊《物理世界》曾让读者投票评选了“最伟大的公式”,最终榜上有名的十个公式既有无人不知的1+1=2,又有著名的E=mc2;既有简单的圆周公式,又有复杂的欧拉公式……

从什么时候起我们开始厌恶数学?这些东西原本如此美丽,如此精妙。这个地球上有多少伟大的智慧曾耗尽一生,才最终写下一个等号。每当你解不开方程的时候,不妨换一个角度想,暂且放下对理科的厌恶和对考试的痛恨。因为你正在见证的,是科学的美丽与人类的尊严。

No.1 麦克斯韦方程组(The Maxwell’s Equations)

积分形式:

微分形式:

任何一个能把这几个公式看懂的人,一定会感到背后有凉风——如果没有上帝,怎么解释如此完美的方程?这组公式融合了电的高斯定律、磁的高斯定律、法拉第定律以及安培定律。比较谦虚的评价是:“一般地,宇宙间任何的电磁现象,皆可由此方程组解释。”到后来麦克斯韦仅靠纸笔演算,就从这组公式预言了电磁波的存在。我们不是总喜欢编一些故事,比如爱因斯坦小时候因为某一刺激从而走上了发奋学习、报效祖国的道路么?事实上,这个刺激就是你看到的这个方程组。也正是因为这个方程组完美统一了整个电磁场,让爱因斯坦始终想要以同样的方式统一引力场,并将宏观与微观的两种力放在同一组式子中:即著名的“大一统理论”。爱因斯坦直到去世都没有走出这个隧道,而如果一旦走出去,我们将会在隧道另一头看到上帝本人。

No.2 欧拉公式(Euler’s Identity)

这个公式是上帝写的么?到了最后几名,创造者个个神人。欧拉是历史上最多产的数学家,也是各领域(包含数学的所有分支及力学、光学、音响学、水利、天文、化学、医药等)最多著作的学者。数学史上称十八世纪为“欧拉时代”。欧拉出生于瑞士,31岁丧失了右眼的视力,59岁双眼失明,但他性格乐观,有惊人的记忆力及集中力。他一生谦逊,很少用自己的名字给他发现的东西命名。不过还是命名了一个最重要的一个常数——e。

这个公式的巧妙之处在于,它没有任何多余的内容,将数学中最基本的e、i、pie放在了同一个式子中,同时加入了数学也是哲学中最重要的0和1,再以简单的加号相连。高斯曾经说:“一个人第一次看到这个公式而不感到它的魅力,他不可能成为数学家。”

No.3 牛顿第二定律(Newton’s Second Law of Motion)

有史以来最伟大的没有之一的科学家在有史以来最伟大没有之一的科学巨作《自然哲学的数学原理》当中的被认为是经典物理学中最伟大的没有之一的核心定律。动力的所有基本方程都可由它通过微积分推导出来。对于学过高中物理的人,没什么好多讲了。

No.4 勾股定理/毕达哥拉斯定理(Pythagorean Theorem)

做数学不可能没用到过吧,不多讲了。

No.5 质能方程(Mass–energy Equivalence)

好像从来没有一个科学界的公式有如此广泛的意义。在物理学“奇迹年”1905年,由一个叫爱因斯坦的年轻人提出。同年他还发表了《论动体的电动力学》——俗称狭义相对论。这个公式告诉我们,爱因斯坦是挺NB的,能量和质量是可以互换的。副产品:原子弹。

No.6 薛定谔方程(The Schrödinger Equation)

也是一般人完全不明白的。因此我摘录官方评价:“薛定谔方程是世界原子物理学文献中应用最广泛、影响最大的公式。”由于对量子力学的杰出贡献,薛定谔获得1933年诺贝尔物理奖。另外薛定谔虽然姓薛,但是奥地利人。

No.7 1+1=2

这个公式不需要名称,不需要翻译,不需要解释。

No.8 德布罗意方程组(The de Broglie Relations)

高中物理学到光学的话很多概念跟它是远亲。简要地说德布罗意这人觉得电子不仅是一个粒子,也是一种波,它还有 “波长”。于是搞啊搞就有了这个物质波方程,表达了波长、能量等等之间的关系。同时他获得了1929年诺贝尔物理学奖。

No.9 傅立叶变换(The Fourier Transform)

这个挺专业的,一般人完全不明白。不多作解释。简要地说没有这个式子没有今天的电子计算机,所以你能在这里上网除了感谢党感谢政府还要感谢这个完全看不懂的式子。另外傅立叶虽然姓傅,但是法国人。

No.10 圆的周长公式(The Length of the Circumference of a Circle)

目前,人类已经能得到圆周率的2061亿位精度。现代科技领域使用的圆周率值,有十几位已经足够了。如果用35位精度的圆周率值,来计算一个能把太阳系包起来的一个圆的周长,误差还不到质子直径的百万分之一。现在的人计算圆周率,多数是为了验证计算机的计算能力,还有就是为了兴趣。

Read More

Computer Vision Libraries

libCVD

libCVD is a very portable and high performance C++ library for computer vision, image, and video processing.

# libCVD
link_libraries( cvd )

OpenGL Suits

# OpenGL
find_package(OpenGL REQUIRED)
if(OPENGL_FOUND)
    link_libraries( ${OPENGL_LIBRARY} )
endif()
# GLUT
find_package(GLUT REQUIRED)
if(GLUT_FOUND)
    link_libraries( ${GLUT_LIBRARY} )
endif()
# GLEW
find_package(GLEW REQUIRED)
if (GLEW_FOUND)
    include_directories(${GLEW_INCLUDE_DIRS})
    link_libraries(${GLEW_LIBRARIES})
endif()

Pangolin

Pangolin is a lightweight portable rapid development library for managing OpenGL display / interaction and abstracting video input.Pangolin also provides a mechanism for manipulating program variables through config files and ui integration, and has a flexible real-time plotter for visualising graphical data.

# Pangolin
find_package( Pangolin )
if(Pangolin_FOUND)
    include_directories( ${Pangolin_INCLUDE_DIRS} )
    link_directories( ${Pangolin_LIBRARIES} )
endif()

OpenCV

# OpenCV
find_package( OpenCV 3.1 REQUIRED )
if(OpenCV_FOUND)
    include_directories( ${OpenCV_INCLUDE_DIRS} )
    link_libraries( ${OpenCV_LIBS} )
endif()

PCL

# pcl
set( PCL_DIR "/usr/local/share/pcl-1.7/" )
find_package( PCL REQUIRED COMPONENTS common io )
if(PCL_FOUND)
    include_directories( ${PCL_INCLUDE_DIRS} )
    add_definitions( ${PCL_DEFINITIONS} )
    link_libraries( ${PCL_LIBRARIES} )
endif()
Read More

^