锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

OpenCV Eigen Sophus PCL G2O

时间:2023-11-28 05:37:02 0931s3nc接近传感器

外用库学习笔记

    • 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


头文件

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/


数据类型

从组织结构来看:


基础类型概述
固定向量类
模板类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 );

cv::Point
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)

cv::Scalar
本质上是一个思维Point类。cv::Scalar一般是双精度四元素向量的别名,是通过整数下标来访问的,这与cv::Vec< >相同。是因为cv::Scalar直接继承cv::Vec

操作 示例
默认构造函数 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顺序。

Size
在实际操作中与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模板
首先,需要为想要封装的对象定义一个指针模板的实例。可以通过调用类似cv::Ptr p(new cv::Matx33f)cv::Ptr p = makePtr ()的形式实现。


示例

opencv头文件关系
#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;

imread 从磁盘读取各种图像,返回一个 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_ANYCOLORcv::IMREAD_ANYDEPTH组合了起来,
但也不完全是

imwrite保存图像 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是为图像文件定制的。

namedWindow 赋一个名字给窗口,未来 Highgui 和这个窗口交互通过赋予的名字。第二个参数书名了 Window 的特性,可以全部设置为0(默认情况),也可以设置为cv::WINDOW_AUTOSIZE

imshow 将创建一个窗口,如果窗口不存在,它会自动调用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.

waitkey cv::waitKey(0);函数告诉系统暂停并等待键盘事件。如果传入一个大于零的参数,它将会等待等同于该参数的毫秒时间,然后执行程序;如果参数被设置为0 或者一个负数,程序将会一直等到有键被按下。

因为有cv::Mat,图像将会在生命周期结束自动释放,其行为类似 STL 中的容器类。这种自动内存释放由内部的引用指针所控制。

destoryWindow将会关闭窗口并释放掉相关联的内存空间。

在更长、更复杂的代码中,程序员应该在窗口的生命周期自然结束之前自主销毁窗口以防内存泄露。


视频

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的构造函数。

VideoCapture可以打开和关闭许多类型的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 & 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
source 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.).
flags Mode of operation. See FileStorage::Mode
encoding 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 ¶ms = std::vector() );


图像的像素

最简单的图像——灰度图。在一张灰度图中,每个像素位置 ( 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):R2R
其中, ( 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 0281
在RGB-D相机的深度图中,记录了各个像素与相机之间的距离,这个距离通常以毫米为单位,而RGB-D相机的量程在十几米左右,超过了255。通常会采用16位整数,unsigned short来记录深度图的信息, 0 ∼ 2 16 − 1 0\sim 2^{16}-1 02161。彩色图像的表示则需要通道(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++)。因为是先行循环,后列循环。注意对应rowscols
循环内的程序,注意nm对应的与图片像素的关系。[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));

随机数发生器 cv::RNG
一个随机数对象RNG用来产生随机数的伪随机序列。这样做的好处是你可以方便的得到多重伪随机数流。在写大型系统时,在不同的代码模块中使用不同的随机数流是个好习惯。这样的话,当你移除一个模块时,不会改变其他模块中的随机数流。

cv::RNG()

cv::RNG::RNG ( void );
cv::RNG::RNG ( uint64 state);  //create using the seed 'state'

可以使用默认构造函数来创建一个RNG对象,或者传递一个64位的无符号整形数,这个数将用来作为随机数序列的种子。如果调用默认的构造函数(或者第二个参数为0),这个生成器将使用一个定制来初始化。

cv::theRNG()

cv::RNG& theRNG (void); //return a random number generator

该函数为了调用它的线程返回一个默认的随机数生成器。OpenCV自动为每一个执行中的线程创建一个cv::RNG的实例。如果你只想要一个数或者只初始化一个数组,这些函数就很方便。然后,如果你有一个循环需要产生大量随机数,最好使用一个随机数生成器。通过使用RNG::operator T()来得到自己的随机数。

cv::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():返回不小于参数的最小整数值,即向上取整

关键点和描述子

关键点和描述子的分析

  • 搜索图像中所有关键点
  • 为每个关键点创造描述子
  • 将描述子和先有的描述集比较,查找匹配项

cv::KeyPoint对象

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::FeatureDetectorcv::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)。然后搜索图像中的关键点,并找到的任何内容放置在你提供的向量中。第二个方法完全做相同的事情,不同的是它合计一个图像的向量、一个掩码的向量(或者根本没有)以及关键点向量

相关文章