外用库学习笔记
-
- OpenCV
-
- 需知
- OpenCV的结构
- OpenCV modules
- 头文件
- 数据类型
- 示例
-
- 显示图片
- 视频
- 读取摄像头
- 阅读并存储文件
- 读取配置文件
- 将图片保存到文件中
- 图像的像素
- 绘图
- 随机选择颜色
- 取整函数
- 关键点和描述子
- 对极几何
- 计算极线
- 单应函数
- 透视变换
- 填充多边形
- Eigen
-
- 头文件
- 示例
- Eigen中旋转的相互转换
- 注意
- Sophus
-
- 需知
- 示例
- Pangolin
-
- 需知
- PCL - Point Cloud Library
-
- 需知
- 说明
- 数据类型
- 示例
-
- 点云的创建
- 点云的转换
- 下采样
- 去除离群点
- 离群点的显示
- 移动最小二乘
- 法线的估计
- 贪心投影三角化
- 输出点云文件
- 图优化 g2o
-
- g2o基本框架结构
- 示例
-
- 求解器
- 设置顶点
- 设置边
OpenCV
https://docs.opencv.org/3.1.0/
需知
查看OpenCV安装版本pkg-config --modversion opencv
当系统已安装时opencv,不同版本的重新安装opencv需要修改安装路径,以防覆盖以前的版本。 安装路径的修改在cmake -DCMAKE_INSTALL_PREFIX=/...
,与默认修改路径不同/usr/local
。或者sudo make install DESTDIR=/...
中修改。 切换OpenCV不同版本:在~/.bashrc
中加入
export PKG_CONFIG_PATH={
opencv安装路径/.../lib/pkgconfig} export LD_LIBRARY_PATH={
opencv安装路径/.../lib}
source ~/.bashrc
也可以修改CMakeLists文件
set(OpenCV_DIR ".../build") find_package(OpenCV REQUIRED)
OpenCV的结构
层次结构组织:最上层 OpenCV 与操作系统的交互,接下来是语言绑定和示例应用,下一层是 opencv_contrib
模块包含 OpenCV 其他开发人员贡献的代码,包括大多数高级函数。
OpenCV modules
core
包含 OpenCV 库的基本结构和基本操作improc
图像处理模块包括基本图像转换,包括滤波器和类似的卷积操作imcodecs
videoio
highgui
包含用户交互函数,可用于显示图像或简单输入。它可以看作是一个非常轻的级别 Windows UI 工具包video
函数包括读写视频流calib3d
实现单个校准和双目记忆多个相机的算法- 多个
cude*
模块 主要是函数 CUDA GPU 此外,还有一些只用于优化实现。 GPU 功能。其中一些函数可以使狗返回良好的结果,但如果硬件没有,需要足够好的计算资源 GPU,没有进步。
头文件
CMakelists
find_package (OpenCV 3 REQURIED) include_directories(${OpenCV_INCLUDE_DIR}) ... target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})
opencv2/opencv.hpp
由于包含了各个模块的头文件,如opencv2/core.hpp
等。因此,可以使用opencv2/opencv.hpp
来包含说有可能在 OpenCV 函数中用到的头文件,但会减慢编译速度。
通过指令locate opencv | less
,/ modules
可找到头文件所在的 .../modules
文件夹,其对应的源文件都在modules/xxxx/src/
中
数据类型
从组织结构来看:
- 直接从C++言语中继承的基础数据类型:int float等。这些类型包括简单的数组和矩阵,也代表一些简单的几何概念,比如点、矩阵、大小等
- 辅助对象。垃圾收集指针类、用于数据切片的范围对象以及抽象的终止条件类等。
- 大型数组类型。
cv::Mat
cv::SparseMat
。
- 还是用了很多标准模板库STL,尤其依赖
vector
类。
: 模板类cv::Vec< >
,即使cv::Vec< >
是模板,但是通常不会使用这个形式,而是使用别名typedef
,如cv::Vec2i
cv::Vec3i
等。用于已知维度的小型变量。在编译前必须已知维度。
cv::Vec{
2,3,4,6}{
b,w,s,i,f,d};//相互组合
b = unsigned char; w = unsigned short; s = short;
i = int; f = float; d = double;
与模板类cv::Mat< >
相关联。适用于特定的小型矩阵操作,在编译前必须已知维度。区别于cv::Mat
cv::Matx{
1,2,3,4,6}{
1,2,3,4,6}{
f,d}
cv::Mat K = ( cv::Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
Point
类不是从固定向量类继承下来的,而是由自己的模板派生的,但是同时它们可以从固定向量类转换得到。Point
类与固定向量类之间最大的不同是它们的成员函数是通过名称变量访问的mypoint.x
,而不是通过访问下标myvec[0]
。和cv::Vec< >
一样,是通过别名调用作为一个正确模板的实例。这些别名可以是cv::Point2i
cv::Point2f
等。
操作
示例
默认构造函数
cv::Point2i p
cv::Point3i
复制构造函数
cv::Point3d p2(p1)
值构造函数
cv::Point2i (x0,x1)
cv::Point3d p(x0,x1,x2)
构造固定向量类
(cv::Vec3f) p;
成员访问
p.x,p.y
点乘
float x = p1.dot(p2)
叉乘
double x = p1.cross(p2)
本质上是一个思维Point
类。cv::Scalar
一般是双精度四元素向量的别名,是通过整数下标来访问的,这与cv::Vec< >
相同。是因为cv::Scalar
直接继承cv::Vec<double, 4>
。
操作
示例
默认构造函数
cv::Scalar s
复制构造函数
cv::Scalar s2(s1)
值构造函数
cv::Scalar s(x0)
cv::Scalar s(x0,x1,x2,x3)
元素相乘
s1.mul(s2)
四元数共轭
s.conj();//return cv::Scalar(s0, -s1, -s2,-s3);
四元数真值测试
s.isReal();//return true, if s1==s2==s3==0;
const Scalar& color;
Scalar color = Scalar(* ,* ,*);//* 表示0-255
Scalar color = Scalar(255);//表示B=255,GR为0
颜色都是下限0指的是黑色,上限255或者1指的是白色。光的成色原理是加法,也就是从黑色到白色,从0到1,在黑暗的情况下,加上三原色变亮。颜料的成色原理是减法,也就是从白色到黑色,从1到0,白纸上使用颜料变暗。 (0,0,0)
为RGB。RGB为加法。BGR顺序。
在实际操作中与Point
相似,且可以与Point
相互转换。主要区别是成员函数的不同。 Size
类的三个别名为cv::Size
cv::Size2i
cv::Size2f
。前两个为等价的,表示整数大小,而最后一个是表示32位浮点大小
操作
示例
默认构造函数
cv::Size sz;
cv::Size2i sz;
cv::Size2f sz;
复制构造函数
cv::Size sz2(sz1)
值构造函数
cv::Size2f sz(w, h)
成员访问
sz.width(); sz.height()
计算面积
sz.area();
首先,需要为想要封装的对象定义一个指针模板的实例。可以通过调用类似cv::Ptr<Matx33f> p(new cv::Matx33f)
或cv::Ptr<Matx33f> p = makePtr<cv::Matx33f> ()
的形式实现。
示例
#include "opencv2/"
下的
头文件
函数
imagcodecs.hpp
imread
imwrite
highgui.hpp
namedWindow
imshow
waitkey
destoryWindow
videoio.hpp
VideoCapture
utility.hpp
glob
features2d/features2d.hpp
FeatureDetector
calib3d/calib3d.hpp
findFundamentalMat()
computeCorrespondEpilines()
显示图片
cv::Mat img = cv::imread(argv[1], -1);
if (img.empty())
return -1;
cv::namedWindow("Example1", cv::WINDOW_AUTOSIZE);
cv::imshow("Example1", img);
cv::waitKey(0);
cv::destroyWindow("Example1");
return 0;
从磁盘读取各种图像,返回一个 Mat 结构。 Loads an image from a file.
cv::Mat cv::imread(
const String& filename, //Input filename
int flags = cv::IMREAD_COLOR //Flags set how to interpret file
);
FLAGS
可以设置为以下任意一个值
标志
含义
默认值
cv::IMREAD_COLOR
总是读取三通道图像
是
cv::IMREAD_GRAYSCALE
总是读取单通道图像
否
cv::IMREAD_ANYCOLOR
通道数由文件实际通道数决定(不超过3)
否
cv::IMREAD_ANYDEPTH
允许加载超过8bit深度
否
cv::IMREAD_UNCHANGED
等于将cv::IMREAD_ANYCOLOR
和cv::IMREAD_ANYDEPTH
组合了起来,但也不完全是
否
保存图像 Save an image to a specified file.
bool cv::imwrite(
const String& filename, //Input filename
cv::InputArray image, //Image to write to file
const vector<int>& params = vector<int>() //(Optional) for parameterized fmts
);
第一个参数给定了文件名,文件名的拓展名部分用来决定以何种格式保存图像,以下是对应的扩展名
拓展名
保存格式
通道
*.jpg/*.jpeg
以baseline JPEG
格式保存
8位数据,单通道或三通道输入
*.jp2
JPEG2000
8位或16位数据,单通道或三通道输入
*.tif/*.tiff
TIFF
8位或16位数据,单通道、三通道或四通道输入
*.png
PNG
8位或16位数据,单通道、三通道或四通道输入
*.bmp
BMP
单通道、三通道或四通道输入
*.ppm/*.pgm
NetPBM
8位数据,单通道(PGM)或三通道(PPM)
第二个参数是待存储的输入图像。 第三个参数是被作用特殊类型文件的写入操作时所需的数据。 cv::imwrite
是为图像文件定制的。
赋一个名字给窗口,未来 Highgui 和这个窗口交互通过赋予的名字。第二个参数书名了 Window 的特性,可以全部设置为0(默认情况),也可以设置为cv::WINDOW_AUTOSIZE
。
将创建一个窗口,如果窗口不存在,它会自动调用cv::namedWindow()
建立一个窗口。无论何时,只要在cv::Mat
中拥有一个图像结构,都可以通过cv::imshow()
显示。This function should be followed by cv::waitKey
function which displays the image for specified milliseconds. Otherwise, it won’t display the image.
cv::waitKey(0);
函数告诉系统暂停并等待键盘事件。如果传入一个大于零的参数,它将会等待等同于该参数的毫秒时间,然后执行程序;如果参数被设置为0 或者一个负数,程序将会一直等到有键被按下。
因为有cv::Mat
,图像将会在生命周期结束自动释放,其行为类似 STL 中的容器类。这种自动内存释放由内部的引用指针所控制。
将会关闭窗口并释放掉相关联的内存空间。
在更长、更复杂的代码中,程序员应该在窗口的生命周期自然结束之前自主销毁窗口以防内存泄露。
视频
OpenCV 播放视频与图像的唯一区别就是需要用循环读取视频序列中的每一帧,还需要来跳出循环。
cv::namedWindow("Example3", cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap;
cap.open( string(argv[1]));//读取视频
cv::Mat frame;//声明了一个可以保存视频帧的结构
for (;;){
cap >> frame;
if (frame.empty()) break;
cv::imshow("Example3", frame);
if (cv::waitKey(33) >= 0) break;
}
string(argv[1])
是string的构造函数。
可以打开和关闭许多类型的ffmpeg
支持的视频文件。 cv::watikey(33)
表示每一帧图片显示后都会等待 33 毫秒。为什么是33毫秒,是因为这能让视频以30FPS的速率播放,并且能够允许用户在播放的时候打断。根据以往的经验,最好去检查VideoCapture
结构来确定视频真正的帧率。
从摄像头中读取
cv::VideoCapture cap;
if(argc == 1){
cap.open(0);//open the first camera
} else {
cap.open(argv[1]);
}
if ( !cap.isOpened()){
//check if we succeeded
std::cerr << "Couldn't open capture." << std::endl;
return -1;
}
通过cv::VideoCapture
对硬盘上的文件和摄像头是有一致的接口的。对于前者来说,需要给它一个指示读取文件名的路径;对于后者来说,需要给它一个相机的 ID 号,(如果只有一个摄像头链接,这个 ID 号通常为0)。ID 的默认值是-1,这意味着随意选择一个。
读取文件并存放
utility.hpp
void cv::glob( String pattern, std::vector<String> & result, bool recursive = false);
读取路径pattern
中的文件,并存入result
中
cv::String filepath = " ...";
std::vector<cv::String> result;
cv::glob ( filepath, result);
读取配置文件
XML/YAML file storage class that encapsulates all the information necessary for writing or reading data to/from a file.
cv::FileStorage::FileStorage ( const String & source,
int flags,
const String & encoding = String()
)
Parameter Name of the file to open or the text string to read the data from. Extension of the file (.xml or .yml/.yaml) determines its format (XML or YAML respectively). Also you can append .gz to work with compressed files, for example myHugeMatrix.xml.gz. If both FileStorage::WRITE and FileStorage::MEMORY flags are specified, source is used just to specify the output file format (e.g. mydata.xml, .yml etc.). Mode of operation. See FileStorage::Mode Encoding of the file. Note that UTF-16 XML encoding is not supported currently and you should use 8-bit encoding instead of it.
保存图片到文件
bool cv::imwrite(const String & filename, InputArray img, const std::vector<int> ¶ms = std::vector<int>() );
图像的像素
最简单的图像——灰度图。在一张灰度图中,每个像素位置 ( x , y ) (x,y) (x,y)对应一个灰度值 I I I,所以一张宽度为 w w w、高度为 h h h 的图像,数学上可以记为一个函数: I ( x , y ) : R 2 → R I(x,y):\mathbb{R}^2\to \mathbb{R} I(x,y):R2→R 其中, ( x , y ) (x,y) (x,y) 是像素的坐标。像素是位置坐标,每个像素中的存储值为与图片相关的值。 如灰度图存储的为灰度值,灰度值一般用0-255的整数表示,255为纯白色。一张宽度为640像素、高度为480像素分辨率的灰度图可以表示为 uchar image[480][640]
。分辨率中的宽度代表列数col
,高度代表行数row
。
uchar pixel = image[y][x]
uchar pixel = image.at<uchar>(y,x)
uchar pixel = *image.ptr<uchar>(y.x);
读取[x][y]
的像素。一个像素的灰度可以用8位整数记录,也就是一个 0 ∼ 2 8 − 1 0\sim 2^8-1 0∼28−1 在RGB-D相机的深度图中,记录了各个像素与相机之间的距离,这个距离通常以毫米为单位,而RGB-D相机的量程在十几米左右,超过了255。通常会采用16位整数,unsigned short
来记录深度图的信息, 0 ∼ 2 16 − 1 0\sim 2^{16}-1 0∼216−1。彩色图像的表示则需要通道(channel)的概念。在计算机中,使用红绿蓝三色来组合任意色彩。对于每一个像素,都要记录其R、G、B三个数值,每个数值就成为一个通道。如最常见的彩色图像有三个通道,每个通道都由8位整数表示。在这种规定下,一个像素占24位空间。也就是得到一个24位的像素,每8位代表一个颜色的色值,如果还想表达图像的透明度,就使用RGBA。在OpenCV中默认顺序为BGR。
用指针遍历像素,速度优于逐个遍历像素
for ( size_t y = 0; y < image.rows; y++ ) {
uchar *row_ptr = image.ptr<uchar>(y);// 图像的行指针 row_ptr是第
// 行的头指针
for ( size_t x = 0; x < image.cols; x++) {
uchar *data_ptr = &row_ptr[x * image.channels()];//data_ptr 指向待访问的像素数据
}
}
遍历像素: 外层循环是for (int n; n < rows; n++)
,内层循环是for (int n; n < rcols; n++)
。因为是先行循环,后列循环。注意对应rows
和 cols
。 循环内的程序,注意n
和m
对应的与像素的关系。[m][n]
是与有关,但是行列元素还是正常表示。
绘图
cv::circle()
void circle(
cv::Mat& img, //Image to be drawn on
cv::Point center,//Location of circle center
int radius, //Radius of circle
const cv::Scalar& color, //Color, RGB form
int thickness = 1,//Thickness of line]
int linetype = 8,//Connectedness, 4 or 8
int shift = 0//Bits of radius to treat as fraction
);
第二个参数是圆心的二维坐标点,然后是radius color thickness 等应用在圆心和半径上。
cv::line()
void line (
cv::Mat& img, //Image to be drawn on
cv::Point pt1,//First endpoint of line
cv::Point pt2,//Secoind endpoint of line
const cv::Scalar& color,//Color,BGR form
int linetype = 8,//Connectedness, 4 or 8
int shift = 0 //Bits of radius to treat as fraction
);
在图像img上绘制一条从pt1到pt2的直线。直线自动被图像边缘截断。
随机选择颜色
RNG rng;
Scalar color = Scalar( rng(255), rng(255), rng(255));
一个随机数对象RNG
用来产生随机数的伪随机序列。这样做的好处是你可以方便的得到多重伪随机数流。在写大型系统时,在不同的代码模块中使用不同的随机数流是个好习惯。这样的话,当你移除一个模块时,不会改变其他模块中的随机数流。
cv::RNG::RNG ( void );
cv::RNG::RNG ( uint64 state); //create using the seed 'state'
可以使用默认构造函数来创建一个RNG对象,或者传递一个64位的无符号整形数,这个数将用来作为随机数序列的种子。如果调用默认的构造函数(或者第二个参数为0),这个生成器将使用一个定制来初始化。
cv::RNG& theRNG (void); //return a random number generator
该函数为了调用它的线程返回一个默认的随机数生成器。OpenCV自动为每一个执行中的线程创建一个cv::RNG
的实例。如果你只想要一个数或者只初始化一个数组,这些函数就很方便。然后,如果你有一个循环需要产生大量随机数,最好使用一个随机数生成器。通过使用RNG::operator T()
来得到自己的随机数。
T
是数据类型
cv::RNG::operator uchar();
cv::RNG::operator schar();
cv::RNG::operator ushort();
cv::RNG::operator short int();
cv::RNG::operator int();
cv::RNG::operator unsigned();
cv::RNG::operator float();
cv::RNG::operator double();
上述都是强制类型转化,重载的类型转换操作符()
cv::RNG rng = cv::theRNG();
cout << "An integer: " << (int)rng << endl;
cout << "Another integer: " << int(rng) << endl;
cout << "A float: " << (float)rng << endl;
cout << "Another float: " << float(rng) << endl;
产生整数的死后,将覆盖整个取值范围;当产生浮点数时,它们的范围始终是 [ 0.0 , 1.0 ) [0.0, 1.0) [0.0,1.0)。
取整函数
函数cvRound()
,cvFloor()
,cvCeil()
- cvRound():返回跟参数最接近的整数值,即四舍五入
- cvFloor():返回不大于参数的最大整数值,即向下取整
- cvCeil():返回不小于参数的最小整数值,即向上取整
关键点和描述子
关键点和描述子的分析
- 搜索图像中所有关键点
- 为每个关键点创造描述子
- 将描述子和先有的描述集比较,查找匹配项
class cv::KeyPoint {
public:
cv::Point2f pt; //coodinates of the keypoint
float size://diameter of the meaningful keypoint neighborhood
float angle;//computed orientation of the keypoints (-1 if none)
float response;/response for which the keypoints was selected
...
}
每个关键点都有一个cv::Point2f
成员,这告诉我们它位于哪里。size
是关于关键点周围区域的某些信息,或者在某种程度上包含关键点存在于第一位的判定,或者将在该关键点的描述子中发挥的作用。关键点的angle
只对某些关键点有意义。response
用于能够对一个关键点比另一个关键点更强做出响应的检测器。 cv::KeyPoint
对象有两个构造函数,基本相同,区别在于使用两个浮点数还是单个cv::Point2f
对象来设置关键点的位置。
为了找到关键点且/或计算描述子,有cv::Feature2D
类,有一些叫做cv::FeatureDetector
和cv::DescriptorExtractor
的类,适用于单纯的特征检测或描述子提取算法的单独的类。在OpenCV 3.x
开始,是通过typedef
定义的cv::Feature2D
的同义词,typedef Feature2D FeatureDetector
等
using namespace cv;
Ptr<FeatureDetector> detector;
Ptr<DescriptorExtractor> descriptor;
如果是单纯的关键点检测算法,如FAST,实际的实现可能只是执行cv::Feature2D::detect()
;如果它是一个纯特征描述算法,如FREAK,实际的实现可能只是cv::Feature2D::compute()
;在要求完全解决的算法的情况下,如SIFT、SURF、ORB等,实际的实现就会是cv::Feature2D::detectAndCompute()
,在这种情况下,detect()
和compute()
会被隐式调用
detect(image, keypoints, mask) ~ detectAndCompute(image, mask, keypoints, noArray(), false)
cv::Feature2D::detect()
方法直接或通过调用detectAndCompute()
进行关键点的基本工作。第一个方法需要输入图像、关键点向量和可选掩码(掩码就是屏蔽图片,位图掩码,就是将图片的每个像素和掩码中对应的像素进行与运算, 1&23=23 ,0&89= 0)。然后搜索图像中的关键点,并找到的任何内容放置在你提供的向量中。第二个方法完全做相同的事情,不同的是它合计一个图像的向量、一个掩码的向量(或者根本没有)以及关键点向量