【Opencv】【OpenCV实践】【OpenCV的使用学习记录】【fmt学习记录】
【Opencv】【OpenCV实践】【OpenCV的使用学习记录】【fmt学习记录】
- 0 前言
- 1 opencv使用说明
- 1.1 头文件的使用
- 1.2 CMakeLists.txt的使用
- 1.3 代码的基础使用
- 1.3.1 定义图像格式
- 1.3.2 读取指定路径下的图像|并判断是否读取
- 1.3.2.1 读取方式
- 1.3.2.2 读取方式的含义
- 1.3.3 图像的高、宽、通道数、灰度值
- 1.3.4 显示图像
- 1.3.5 判断图像类型
- 1.3.6 遍历图像的一种方式
- 1.3.7 图像复制的两种方式
- 1.3.7.1 直接=赋值
- 1.3.7.2 使用clone赋值
- 1.3.8 关闭所有图像窗口
- 1.4 额外的功能代码
- 1.4.1 随机数产生
- 1.4.1.1 生成高斯随机数gaussion(double sigmma)
- 1.4.1.2 在设定阈值内生成随机数
- 1.4.2 保存图片
- 1.4.2.1 cv::Mat的填充
- 1.4.2.2 进行保存
- 1.4.3 Mat当作矩阵使用
- 1.4.3.1 矩阵定义和赋值`cv::Mat<double>(3,3)`
- 1.4.3.2 cout输出矩阵
- 1.4.4 二维点`cv::Point2f`
- 1.4.4.1 定义
- 1.4.4.2 赋值
- 1.4.5 cv中的旋转矩阵R转换
- 1.4.5.1 旋转向量->旋转矩阵
- 1.4.6 画图
- 1.4.6.1 画圈(点)
- 1.4.6.2 画线
- 1.4.6.3 画矩形
- 1.4.6.3 转换颜色空间
- 1.4.7 从参考图像中获取灰度值(双线性插值)
- 1.4.8 图片重新赋予尺寸
- 1.4.9 裁取指定区域
- 1.4.9.1 矩阵形状
- 1.4.10 对mat矩阵的一些操作
- 1.4.10.1 求特征值和特征向量`cv::eigen()`
- 1.4.10.2 对矩阵求转置` cv::transpose()`
- 1.4.10.3 矩阵的复制`mat.copy()`
- 1.4.11 cv对数据的转换
- 1.4.11.1 eigen矩阵转换为cv::Mat矩阵`cv::eigen2cv()`
- 1.4.11.2 cv::Mat矩阵转换为eigen矩阵`cv::cv2eigen()`
- 1.4.11.3 cv::Mat的旋转矩阵转换为cv::Mat格式的旋转向量`cv::Rodrigues()`
- 1.4.11.4 cv::Mat的旋转向量转换为cv::Mat格式的旋转矩阵`cv::Rodrigues()`
- 1.5 进阶使用
- 1.5.1 OpenCV的ORB特征
- 1.5.1.1 头文件
- 1.5.1.2 初始化
- 1.5.1.2.1 定义特征点和描述子
- 1.5.1.2.1.1 遍历descriptors_1
- 1.5.1.2.1.2 获取匹配对matches里的某一对的点
- 1.5.1.2.2 定义ORB提取器
- 1.5.1.2.2.1 补充:定义FAST提取器
- 1.5.1.2.2.2 补充:定义GFTT角点提取器
- 1.5.1.2.2.3 补充:提取器的使用的三个参数
- 1.5.1.2.3 定义匹配器
- 1.5.1.3 检测Oriented FAST角点位置
- 1.5.1.4 根据角点位置计算BRIFE描述子
- 1.5.1.4.1 同时检测ORB特征点和计算描述子
- 1.5.1.5 将图片和特征点绘制在一起
- 1.5.1.6 对两张图像的BRIFE描述子进行匹配`cv::DMatch`
- 1.5.1.6.1 定义用于匹配关键点描述子的类
- 1.5.1.6.1.1 输出匹配对的距离
- 1.5.1.6.1.2 输出匹配对的大小
- 1.5.1.6.1.3 输出其匹配点对在各自点云的下标
- 1.5.1.6.2 进行匹配
- 1.5.1.7 匹配点筛选
- 1.5.1.7.1 计算最小距离和最大距离
- 1.5.1.7.2 筛选匹配失误的匹配
- 1.5.1.7.3 将两张图像与它们的特征点和匹配对绘制在一起
- 1.5.2 手写ORB特征
- 1.5.2.1 头文件
- 1.5.2.2 利用FAST从图中提取关键点keypoints
- 1.5.3 对极约束求解相机运动
- 1.5.3.1 计算基础矩阵`cv::findFundamentalMat()`
- 1.5.3.2 计算本质矩阵`cv::findEssentialMat()`
- 1.5.3.3 计算单应矩阵`cv::findHomography()`
- 1.5.3.4 从本质矩阵中恢复旋转和平移信息`cv::recoverPose()`
- 1.5.3.5 把像素坐标转换为相机归一化坐标函数`pixel2cam ()`
- 1.5.3.6 验证对级约束
- 1.5.4 三角测量
- 1.5.4.1 实现三角测量的函数`triangulation()`
- 1.5.4.2 验证三角化点与特征点的重投影关系
- 1.5.5 求解PnP
- 1.5.5.1 cv的PnP计算函数
- 1.5.5.1.1 直接线性变换(默认)`cv::SOLVEPNP_ITERATIVE`.
- 1.5.5.1.1 EPNP方法`cv::SOLVEPNP_EPNP`
- 1.5.5.1.2 UPNP方法`cv::SOLVEPNP_UPNP`
- 1.5.5.1.3 DLS方法`cv::SOLVEPNP_DLS`
- 1.5.5.1.4 P3P方法`cv::SOLVEPNP_P3P`
- 1.5.5.2 利用RGB图和深度图还原三维点
- 1.5.6 光流追踪
- 1.5.6.1 头文件的使用
- 1.5.6.2 光流追踪函数`cv::calcOpticalFlowPyrLK()`
- 1.5.6.1.1 声明函数
- 1.5.6.1.2 应用实例一
- 1.5.6.1.3 应用实例二
0 前言
- 全文代码参考【slam十四讲第二版】【课本例题代码向】【第五讲~相机与图像】【opencv3.4.1安装】【OpenCV、图像去畸变、双目和RGB-D、遍历图像像素14种】的3 OpenCV基本使用方法
1 opencv使用说明
1.1 头文件的使用
- 核心头文件
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
- 2d特征点(ORB特征点)
#include <opencv2/features2d/features2d.hpp>#include <opencv2/calib3d/calib3d.hpp>
- 好像使用这一个就行
#include <opencv2/opencv.hpp>
1.2 CMakeLists.txt的使用
find_package( OpenCV 3 REQUIRED )include_directories(${OpenCV_INCLUDE_DIR})#或者 include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(xxx src/xxx.cpp)target_link_libraries( xxx ${OpenCV_LIBS} )
#或者
#target_link_libraries(xxx ${OpenCV_LIBRARIES})
1.3 代码的基础使用
- 头文件
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
1.3.1 定义图像格式
// 读取argv[1]指定的图像cv::Mat image;
1.3.2 读取指定路径下的图像|并判断是否读取
1.3.2.1 读取方式
- 读取指定路径下的图像
1.1 读取指定文件
image = cv::imread("./src/ubuntu.png",CV_LOAD_IMAGE_COLOR);
1.2 定义指定文件路径串
string first_file = "../src/1.png";cv::Mat first_image = cv::imread(first_file, 0);
1.3 读取输入变量
image = cv::imread(argv[1]); //cv::imread函数读取指定路径下的图像
1.4 读取组装路径
string path_to_dataset= "../data";string associate_file = path_to_dataset + "/associate.txt";color = cv::imread(path_to_dataset + "/" + rgb_file);//默认是IMREAD_COLOR = 1,彩色图
1.5 读取文件夹下所有文件并存储在vector里
vector<Mat> images; //图像
for ( int i=0; i<10; i++ )//遍历读取十张图像
{string path = "./data/"+to_string(i+1)+".png";images.push_back( imread(path) );
}
1.6 读取自定义变量组装的路径
for (int i = 0; i < 5; i++) {boost::format fmt("../data/%s/%d.%s"); //图像文件格式colorImgs.push_back(cv::imread((fmt % "color" % (i + 1) % "png").str()));depthImgs.push_back(cv::imread((fmt % "depth" % (i + 1) % "png").str(), -1)); // 使用-1读取原始图像}
1.7 给出图片所在文件夹,获取该文件夹下所有的图片路径
- 参考:【slam十四讲第二版】【课本例题代码向】【第十一讲~回环检测】【DBoW3的安装】【创建字典】【相似度检测】【增加字典规模】的4 增加字典规模
String directoryPath = "/home/bupo/my_study/slam14/slam14_my/cap11/gen_vocab_large/rgbd_dataset_freiburg1_desk2/rgb";//图像路径vector<String> imagesPath;cv::glob(directoryPath, imagesPath);for ( String path : imagesPath ){}
- 判断是否读取
2.1 方式一
// 判断图像文件是否正确读取if (image.data == nullptr) { //数据不存在,可能是文件不存在cerr << "文件" << argv[1] << "不存在." << endl;return 0;}
2.2 方式二
//检查图片指针是否为空assert(first_image.data != nullptr && second_image.data != nullptr);
- 判断输入变量个数是否满足要求
if (argc != 2) {cout << "Usage: pose_graph_g2o_SE3 sphere.g2o" << endl;return 1;}
- 判断文件是否读取成功
ifstream fin(argv[1]);if (!fin) {cout << "file " << argv[1] << " does not exist." << endl;return 1;}
- 读取文件并判断文件是否读取结束
- 参考:【slam十四讲第二版】【课本例题代码向】【第十二讲~建图】【实践:单目稠密重建】
ifstream fin(path + "/first_200_frames_traj_over_table_input_sequence.txt");if (!fin) return false;while (!fin.eof()) {// 数据格式:图像文件名 tx, ty, tz, qx, qy, qz, qw ,注意是 TWC 而非 TCWstring image;fin >> image;double data[7];for (double &d:data) fin >> d;color_image_files.push_back(path + string("/images/") + image);poses.push_back(SE3d(Quaterniond(data[6], data[3], data[4], data[5]),Vector3d(data[0], data[1], data[2])));if (!fin.good()) break;}fin.close();
- 读取图像深度(包含使用Mat初始化其长、宽、数据格式)
// load reference depthfin.open(path + "/depthmaps/scene_000.depth");ref_depth = cv::Mat(height, width, CV_64F);if (!fin) return false;for (int y = 0; y < height; y++)for (int x = 0; x < width; x++) {double depth = 0;fin >> depth;ref_depth.ptr<double>(y)[x] = depth / 100.0;}
1.3.2.2 读取方式的含义
CV_LOAD_IMAGE_UNCHANGED
:数值为-1;可用于读取深度图,深度图为16位无符号数,单通道图像IMREAD_GRAYSCALE
:数值为0,用于读取单通道灰度图像CV_LOAD_IMAGE_COLOR
:默认值,数值为1;读取彩色图,RGB图像
1.3.3 图像的高、宽、通道数、灰度值
// 文件顺利读取, 首先输出一些基本信息cout << "图像宽为" << image.cols << ",高为" << image.rows << ",通道数为" << image.channels() << endl;
输出:
图像宽为1200,高为674,通道数为3
- 灰度值的使用是图像保存为0,如1.3.2
2.1 使用at
//计算区域内的像素坐标,关键点坐标(x,y)+偏移坐标(dx,dy)uchar pixel = img.at<uchar>(kp.pt.y+dy,kp.pt.x+dx);
2.2 使用ptr
- keypoints_1参考本文的1.5.1.2.1
- m就是
for(cv::DMatch m : matches)
,参考本文的1.5.3.6
d1 = cv::imread(argv[3], -1);// 深度图为16位无符号数,单通道图像ushort d = d1.ptr<unsigned short> (int (keypoints_1[m.queryIdx].pt.y))[int (keypoints_1[m.queryIdx].pt.x)];
2.3 使用img.data[(x,y)]
const Mat &img
const Vector2d &pt
uchar *d = &img.data[int(pt(1, 0)) * img.step + int(pt(0, 0))];
- 获取行数和通道数
color.step
是指每一行有多少个字节color.channels()
是说每一组信息有多少个通道,一般是RGB图像中,是3
cv::Mat color = colorImgs[i];p.b = color.data[v * color.step + u * color.channels()];p.g = color.data[v * color.step + u * color.channels() + 1];p.r = color.data[v * color.step + u * color.channels() + 2];
1.3.4 显示图像
cv::imshow("image", image); // 用cv::imshow显示图像//如果值为1,表示等待1mscv::waitKey(0); // 暂停程序,等待一个按键输入
1.3.5 判断图像类型
// 判断image的类型if (image.type() != CV_8UC1 && image.type() != CV_8UC3) {// 图像类型不符合要求cout << "请输入一张彩色图或灰度图." << endl;return 0;}
1.3.6 遍历图像的一种方式
// 遍历图像, 请注意以下遍历方式亦可使用于随机像素访问// 使用 std::chrono 来给算法计时chrono::steady_clock::time_point t1 = chrono::steady_clock::now();for (size_t y = 0; y < image.rows; y++) {// 用cv::Mat::ptr获得图像的行指针unsigned char *row_ptr = image.ptr<unsigned char>(y); // row_ptr是第y行的头指针for (size_t x = 0; x < image.cols; x++) {// 访问位于 x,y 处的像素unsigned char *data_ptr = &row_ptr[x * image.channels()]; // data_ptr 指向待访问的像素数据// 输出该像素的每个通道,如果是灰度图就只有一个通道for (int c = 0; c != image.channels(); c++) {unsigned char data = data_ptr[c]; // data为I(x,y)第c个通道的值}}}chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast < chrono::duration < double >> (t2 - t1);cout << "遍历图像用时:" << time_used.count() << " 秒。" << endl;
输出:
遍历图像用时:0.00867939 秒。
1.3.7 图像复制的两种方式
1.3.7.1 直接=赋值
- 这种方式修改 image_another 会导致 image 发生变化
- 这种方式类似于创建快捷方式
// 关于 cv::Mat 的拷贝// 直接赋值并不会拷贝数据cv::Mat image_another = image;// 修改 image_another 会导致 image 发生变化image_another(cv::Rect(0, 0, 100, 100)).setTo(0); // 将左上角100*100的块置零cv::imshow("image", image);cv::imshow("image_another", image_another);cv::waitKey(0);
1.3.7.2 使用clone赋值
- 这种方式类似于拷贝
// 使用clone函数来拷贝数据cv::Mat image_clone = image.clone();image_clone(cv::Rect(0, 0, 100, 100)).setTo(255);cv::imshow("image", image);cv::imshow("image_clone", image_clone);cv::waitKey(0);
1.3.8 关闭所有图像窗口
cv::destroyAllWindows();
1.4 额外的功能代码
1.4.1 随机数产生
- 定义随机数产生器
cv::RNG rng; // OpenCV随机数产生器 OpenCV random number generator
1.4.1.1 生成高斯随机数gaussion(double sigmma)
- 如果要产生均值为λ,标准差为σ的随机数,可以λ+ RNG::gaussian( σ)
double x;
x = exp(λ) + rng.gaussian(σ)
1.4.1.2 在设定阈值内生成随机数
int x = rng.uniform(10, 100);
1.4.2 保存图片
1.4.2.1 cv::Mat的填充
- 代码含义参考本文的1.5.1.5
cv::Mat outimg1;cv::drawKeypoints(img_1,keypoints_1,outimg1,cv::Scalar::all(-1),cv::DrawMatchesFlags::DEFAULT);
1.4.2.2 进行保存
cv::Mat outimg1;...if(cv::imwrite("../src/ORB_features.png",outimg1) == false){cout << "Failed to save the image" << endl;}
1.4.3 Mat当作矩阵使用
1.4.3.1 矩阵定义和赋值cv::Mat<double>(3,3)
// 相机内参,TUM Freiburg2cv::Mat K = (cv::Mat_<double>(3,3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
1.4.3.2 cout输出矩阵
cout << K << endl;
1.4.4 二维点cv::Point2f
1.4.4.1 定义
vector<cv::Point2f> points1;
1.4.4.2 赋值
- matches是1.5.1.6介绍
- keypoints_1是1.5.1.2.1 介绍
points1.push_back(keypoints_1[matches[i].queryIdx].pt);//匹配点对中第一张图片上的点points2.push_back(keypoints_2[matches[i].trainIdx].pt);//匹配点对中第二张图片上的点
1.4.5 cv中的旋转矩阵R转换
1.4.5.1 旋转向量->旋转矩阵
cv::Mat r;cv::Mat R;cv::Rodrigues(r, R);//r为旋转向量形式,利用cv的Rodrigues()函数将旋转向量转换为旋转矩阵
1.4.6 画图
1.4.6.1 画圈(点)
cv::circle(img_show, kp, 10, cv::Scalar(0, 240, 0), 1);
参数1 img_show
:cv::Mat格式,表示圈要画在该图像中参数2 kp
:cv::Point2d格式,表示圈的圆心参数3 10
:表示圈的半径参数4 cv::Scalar(0, 240, 0)
:表示圈的颜色参数5 1
: 表示圈的边的粗细
1.4.6.2 画线
cv::line(img2_show, cv::Point2f(p_ref[0], p_ref[1]), cv::Point2f(p_cur[0], p_cur[1]), cv::Scalar(0, 250, 0));
参数1 img2_show
:cv::Mat格式,表示圈要画在该图像中参数2 cv::Point2f(p_ref[0], p_ref[1])
:cv::Point2f格式,表示线段的起点参数3 cv::Point2f(p_cur[0], p_cur[1])
:cv::Point2f格式,表示线段的终点参数4 cv::Scalar(0, 240, 0)
:表示圈的颜色参数5 1
: 表示圈的边的粗细
1.4.6.3 画矩形
- 参考 1.4.9.1 矩阵形状
1.4.6.3 转换颜色空间
将图像从一个颜色空间转换为另一个颜色空间。
该函数将输入图像从一个颜色空间转换为另一个颜色空间。在从RGB颜色空间转换的情况下,应该显式指定通道的顺序(RGB或BGR)。注意,OpenCV中的默认颜色格式通常被称为RGB,但它实际上是BGR(字节颠倒)。因此,一个标准(24位)彩色图像的第一个字节将是一个8位的蓝色组件,第二个字节将是绿色的,第三个字节将是红色的。第四个、第五个和第六个字节将是第二个像素(然后是蓝色、绿色、红色),依此类推。
常规的R, G和B通道值范围是:
0~255为CV_8U图象
0 ~ 65535为CV_16U图象
0~1 CV_32F图像对于线性变换,值域无关紧要。但是在非线性转换的情况下,一个输入的RGB图像应该被规范化到适当的值范围,以得到正确的结果。
例如,如果你有一个32位浮点图像直接从一个8位图像转换而来,没有任何缩放,那么它将有0…255的取值范围,而不是0…函数假定的1。因此,在调用
cvtColor
之前,你需要先将图像缩小:
img *= 1./255;
cvtColor (img, img, COLOR_BGR2Luv);
- 如果你使用
cvtColor
与8位图像,转换会有一些信息丢失。对于许多应用程序,这将不明显,但建议使用32位图像的应用程序,需要全范围的颜色或在操作前转换图像,然后转换回来。 - 如果转换增加了alpha通道,其值将设置为相应通道范围的最大值:CV_8U的值为255,CV_16U的值为65535,CV_32F的值为1。
- 参数
@param
src
输入图像:8位无符号,16位无符号(CV_16UC…),或者单精度浮点。@param
DST
输出与src相同大小和深度的图像。@param
code
颜色空间转换代码(参见# colorconverversioncodes)。
可取值如下
COLOR_GRAY2BGR = 8,
- @param dstCn目标映像中的通道数;如果参数为0,则表示通道是自动从SRC和代码派生的。
CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );
例如:
cv::cvtColor(ref, ref_show, CV_GRAY2BGR);//表示将当前灰度图像转变为RGB图像
1.4.7 从参考图像中获取灰度值(双线性插值)
- 函数声明和函数实现方法一
/*** get a gray scale value from reference image (bi-linear interpolated)* 从参考图像中获取灰度值(双线性插值)* @param img* @param x* @param y* @return the interpolated value of this pixel*/
inline float GetPixelValue(const cv::Mat &img, float x, float y) {// boundary checkif (x < 0) x = 0;if (y < 0) y = 0;if (x >= img.cols) x = img.cols - 1;if (y >= img.rows) y = img.rows - 1;uchar *data = &img.data[int(y) * img.step + int(x)];float xx = x - floor(x);float yy = y - floor(y);return float((1 - xx) * (1 - yy) * data[0] +xx * (1 - yy) * data[1] +(1 - xx) * yy * data[img.step] +xx * yy * data[img.step + 1]);
}
- 方法二
// 双线性灰度插值
inline double getBilinearInterpolatedValue(const Mat &img, const Vector2d &pt) {uchar *d = &img.data[int(pt(1, 0)) * img.step + int(pt(0, 0))];//读取double xx = pt(0, 0) - floor(pt(0, 0));double yy = pt(1, 0) - floor(pt(1, 0));return ((1 - xx) * (1 - yy) * double(d[0]) +xx * (1 - yy) * double(d[1]) +(1 - xx) * yy * double(d[img.step]) +xx * yy * double(d[img.step + 1])) / 255.0;
}
1.4.8 图片重新赋予尺寸
- 函数声明
void resize( InputArray src, OutputArray dst,Size dsize, double fx = 0, double fy = 0,int interpolation = INTER_LINEAR );
- 使用案例
摘自:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】的3.2.4.3 NextFrame()
cv::resize(image_left, image_left_resized, cv::Size(), 0.5, 0.5,cv::INTER_NEAREST);//cv::INTER_NEAREST = 最近邻插值
- 参数
image_left
: 格式cv::Mat
,输入的原图像 - 参数
image_left_resized
:格式cv::Mat
,输出的改变尺寸后的图像 - 参数
cv::Size()
:也就是dsize
,输出图像大小;如果它等于零,则计算为dsize=Size((fx*src.cols),(fy*src.rows)))
- 参数
0.5
:也就是fx - 参数
0.5
:也就是fy - 参数
cv::INTER_NEAREST
:对于插值方法,
- 插值方法
INTER_NEAREST
:也就是0,即最近邻插值INTER_LINEAR
:也就是1,即线性插值
- dsize或fx和fy都必须为非零。
1.4.9 裁取指定区域
1.4.9.1 矩阵形状
- 摘自与:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】的3.2.6.14 DetectFeatures()
- 如下:
cv::rectangle(mask, feat->position_.pt - cv::Point2f(10, 10),feat->position_.pt + cv::Point2f(10, 10), 0, CV_FILLED);
- 函数声明:
函数cv::rectangle
绘制一个矩形轮廓或一个填充矩形,其两个相对的角是pt1和pt2。
CV_EXPORTS_W void rectangle(InputOutputArray img, Point pt1, Point pt2,const Scalar& color, int thickness = 1,int lineType = LINE_8, int shift = 0);
- 参数
img
:输入图像也是输出图像 - 参数
pt1
:相对的角之一 - 参数
pt2
:相对的角之二 - 参数
color
:矩形颜色或亮度(灰度图像) - 参数
thickness
:默认是1,构成矩形的线的厚度。负值,如CV_FILLED
,意味着函数必须绘制一个填充矩形。 - 参数
lineType
:默认是LINE_8
,线条类型,还有抗锯齿线LINE_AA
- 参数
shift
:默认是0,点坐标中的小数位数。
1.4.10 对mat矩阵的一些操作
1.4.10.1 求特征值和特征向量cv::eigen()
- 参考:【OpenCV4】计算对称矩阵特征值和特征向量 cv::eigen() 用法详解和代码示例(c++)
- 函数原型
bool cv::eigen ( InputArray src,OutputArray eigenvalues,OutputArray eigenvectors = noArray() )
- 解析:
src:输入矩阵,只能是 CV_32FC1 或 CV_64FC1 类型的方阵(即矩阵转置后还是自己),可以使用定义方法cv::Mat matA(pointSelNum, 3, CV_32F, cv::Scalar::all(0));
eigenvalues:输出的特征值组成的向量,数据类型同输入矩阵,排列从大到小
eigenvectors:输出的特征向量组成的矩阵,数据类型同输入矩阵,每一行是一个特征向量,对应相应位置的特征值 - 备注: 对于非对称矩阵,可以使用
cv::eigenNonSymmetric()
计算特征值和特征向量。
1.4.10.2 对矩阵求转置 cv::transpose()
cv::Mat matA(pointSelNum, 3, CV_32F, cv::Scalar::all(0));cv::Mat matAt(3, pointSelNum, CV_32F, cv::Scalar::all(0));cv::transpose(matA, matAt);
1.4.10.3 矩阵的复制mat.copy()
//将matV复制给matV2cv::Mat matV(3, 3, CV_32F, cv::Scalar::all(0));cv::Mat matV2(3, 3, CV_32F, cv::Scalar::all(0));matV.copyTo(matV2);
1.4.11 cv对数据的转换
1.4.11.1 eigen矩阵转换为cv::Mat矩阵cv::eigen2cv()
Eigen::Matrix3d R_initial;cv::Mat tmp_r;cv::eigen2cv(R_initial, tmp_r);
1.4.11.2 cv::Mat矩阵转换为eigen矩阵cv::cv2eigen()
cv::Mat r;MatrixXd R_pnp;cv::cv2eigen(r, R_pnp);
1.4.11.3 cv::Mat的旋转矩阵转换为cv::Mat格式的旋转向量cv::Rodrigues()
cv::Mat rvec, t, tmp_r;cv::Rodrigues(tmp_r, rvec);
1.4.11.4 cv::Mat的旋转向量转换为cv::Mat格式的旋转矩阵cv::Rodrigues()
cv::Mat rvec, r;cv::Rodrigues(rvec, r);
1.5 进阶使用
1.5.1 OpenCV的ORB特征
- 参考【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】的1 OpenCV的ORB特征
1.5.1.1 头文件
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>#include <opencv2/calib3d/calib3d.hpp>
1.5.1.2 初始化
1.5.1.2.1 定义特征点和描述子
std::vector<cv::KeyPoint> keypoints_1, keypoints_2;cv::Mat descriptors_1, descriptors_2;
1.5.1.2.1.1 遍历descriptors_1
for(int i = 0; i < descriptors_1.rows; i++){...}
1.5.1.2.1.2 获取匹配对matches里的某一对的点
points1.push_back(keypoints_1[matches[i].queryIdx].pt);//匹配点对中第一张图片上的点points2.push_back(keypoints_2[matches[i].trainIdx].pt);//匹配点对中第二张图片上的点
1.5.1.2.2 定义ORB提取器
cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();cv::Ptr<cv::DescriptorExtractor> descriptor = cv::ORB::create();
1.5.1.2.2.1 补充:定义FAST提取器
cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();detector->detect(img_1, keypoints);
1.5.1.2.2.2 补充:定义GFTT角点提取器
- 之前角点检测的算法中,一个是cornerHarris计算角点,但是这种角点检测算法容易出现聚簇现象以及角点信息有丢失和位置偏移现象,
- 所以后面又提出一种名为goodFeatureToTrack的角点检测算法,opencv的feature2D接口集成了这种算法,名称为 GFTTDetector。
Ptr<GFTTDetector> detector = GFTTDetector::create(500, 0.01, 20); // maximum 500 keypointsdetector->detect(img1, keypoints);
参数1 500
:即maxorners,也就是最大的角点数量参数2 0.01
:没搞明白,默认就是0.01参数3 20
:角点之间最小距离
1.5.1.2.2.3 补充:提取器的使用的三个参数
- 正常就是前面使用的类似
detector->detect(img1, keypoints);
,但是要知道这个是可以有第三个参数的,默认是不是用该功能 - 函数声明如下:
CV_WRAP virtual void detect( InputArray image,CV_OUT std::vector<KeyPoint>& keypoints,InputArray mask=noArray() );
image
:就是输入的图像keypoints
:就是提取得到的特征点mask
:指定在何处查找关键点的mask(可选)。它必须是8位整数在感兴趣区域中具有非零值的矩阵。我的理解是在这一块不进行特征点提取,跳过,如果有误,之后再改,暂时先这样理解
- 这个矩阵的定义类似于使用
cv::rectangle()
函数,具体的使用参考本文的1.4.9.1 矩阵形状
1.5.1.2.3 定义匹配器
cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
1.5.1.3 检测Oriented FAST角点位置
detector->detect(img_1, keypoints_1);detector->detect(img_2, keypoints_2);
1.5.1.4 根据角点位置计算BRIFE描述子
descriptor->compute(img_1,keypoints_1,descriptors_1);descriptor->compute(img_2,keypoints_2,descriptors_2);
1.5.1.4.1 同时检测ORB特征点和计算描述子
detector->detectAndCompute( img_1, Mat(), keypoints_1, descriptors_1 );//检测和计算
- 具体的使用可以参考:【slam十四讲第二版】【课本例题代码向】【第十一讲~回环检测】【DBoW3的安装】【创建字典】【相似度检测】【增加字典规模】的2 创建字典
1.5.1.5 将图片和特征点绘制在一起
cv::Mat outimg1;cv::drawKeypoints(img_1,keypoints_1,outimg1,cv::Scalar::all(-1),cv::DrawMatchesFlags::DEFAULT);cv::imshow("ORB features", outimg1);cv::waitKey(0);
1.5.1.6 对两张图像的BRIFE描述子进行匹配cv::DMatch
- 使用Hamming距离
1.5.1.6.1 定义用于匹配关键点描述子的类
vector<cv::DMatch> matches; //cv::DMatch 用于匹配关键点描述子的类
1.5.1.6.1.1 输出匹配对的距离
double dist = matches[i].distance;
1.5.1.6.1.2 输出匹配对的大小
- 这个应该是vector的内置函数,而不是
cv::DMatch
的
cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;
1.5.1.6.1.3 输出其匹配点对在各自点云的下标
- keypoints_1是1.5.1.2.1介绍
- points1是1.4.4介绍
- 主要图
matches[i].queryIdx
- 使用举例:
points1.push_back(keypoints_1[matches[i].queryIdx].pt);//匹配点对中第一张图片上的点
- 配对图
matches[i].trainIdx
- 使用举例
points2.push_back(keypoints_2[matches[i].trainIdx].pt);//匹配点对中第二张图片上的点
1.5.1.6.2 进行匹配
matcher->match(descriptors_1,descriptors_2,matches);
1.5.1.7 匹配点筛选
1.5.1.7.1 计算最小距离和最大距离
//--第四步:匹配点对筛选//计算最小距离和最大距离auto min_max = minmax_element(matches.begin(),matches.end(),[](const cv::DMatch &m1, const cv::DMatch &m2){return m1.distance<m2.distance;});double min_dist = min_max.first->distance;double max_dist = min_max.second->distance;
1.5.1.7.2 筛选匹配失误的匹配
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误。但有时最小距离会非常小,所以要设置一个经验值30作为下限std::vector<cv::DMatch> good_matches;for(int i = 0; i < descriptors_1.rows;i++){if(matches[i].distance <= max(2 * min_dist, 30.0)){good_matches.push_back(matches[i]);}}
1.5.1.7.3 将两张图像与它们的特征点和匹配对绘制在一起
//--第五步:绘制匹配结果cv::Mat img_match;cv::Mat img_goodmatch;cv::drawMatches(img_1, keypoints_1, img_2,keypoints_2,matches,img_match);cv::drawMatches(img_1, keypoints_1, img_2,keypoints_2,good_matches,img_goodmatch);cv::imshow("all matches", img_match);cv::imshow("good matches", img_goodmatch);cv::waitKey(0);
1.5.2 手写ORB特征
- 参考【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】的2 手写ORB特征
1.5.2.1 头文件
#include <opencv2/opencv.hpp>
1.5.2.2 利用FAST从图中提取关键点keypoints
//ORB使用FAST算法检测特征点//OpenCV中的ORB采用了图像金字塔来解决尺度变换一致性//****自定义ComputeORB函数来描述ORB特征点,并旋转使其具备旋转尺度不变性vector<cv::KeyPoint> keypoints1;// ORB提取图1特征threshold=40//利用FAST从图1中提取关键点keypoints1cv::FAST(first_image,keypoints1,40);
1.5.3 对极约束求解相机运动
- 参考【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】的3 对极约束求解相机运动
1.5.3.1 计算基础矩阵cv::findFundamentalMat()
- 基础矩阵F(p167)
//-- 计算基础矩阵cv::Mat fundamental_matrix;//计算给定一组对应点的基本矩阵 八点法fundamental_matrix = cv::findFundamentalMat(points1, points2,CV_FM_8POINT);cout << "fundamental_matrix is" << endl << fundamental_matrix << endl;
1.5.3.2 计算本质矩阵cv::findEssentialMat()
- 本质矩阵E(p167~p168)
- 相机光心,就是相机内参数的cx、cy
- 相机焦距,这里我不确定,但是我感觉是fx、fy,这俩相等?
//-- 计算本质矩阵cv::Point2d principal_point (325.1, 249.7); //相机光心, TUM dataset标定值double focal_length = 521; //相机焦距, TUM dataset标定值cv::Mat essential_matrix;essential_matrix = cv::findEssentialMat(points1,points2,focal_length,principal_point);cout<<"essential_matrix is "<<endl<< essential_matrix<<endl;
1.5.3.3 计算单应矩阵cv::findHomography()
- 单应矩阵H(p170)
RANSAC
:(Random Sample Concesus, RANSAC,随即采样一致性),非最小二乘法。其适用于很多带错误数据的情况,可以处理带有错误匹配的数据。
//-- 计算单应矩阵cv::Mat homography_matrix;homography_matrix = cv::findHomography(points1, points2, cv::RANSAC, 3);cout<<"homography_matrix is "<<endl<<homography_matrix<<endl;
1.5.3.4 从本质矩阵中恢复旋转和平移信息cv::recoverPose()
- 相机光心principal_point,就是相机内参数的cx、cy
- 相机焦距focal_length,这里我不确定,但是我感觉是fx、fy,这俩相等?
- 本质矩阵essential_matrix
//-- 从本质矩阵中恢复旋转和平移信息.cv::recoverPose(essential_matrix, points1, points2, R,t,focal_length,principal_point);cout<<"R is "<<endl<<R<<endl;cout<<"t is "<<endl<<t<<endl;
1.5.3.5 把像素坐标转换为相机归一化坐标函数pixel2cam ()
- 函数声明
// 像素坐标转相机归一化坐标
cv::Point2d pixel2cam (const cv::Point2d& p, const cv::Mat& K);
- 函数实现
// 像素坐标转相机归一化坐标
cv::Point2d pixel2cam (const cv::Point2d& p, const cv::Mat& K)
{return cv::Point2d( //at是内参数矩阵(p.x - K.at<double> (0,2)) / K.at<double>(0,0),(p.y - K.at<double> (1,2)) / K.at<double>(1,1));
}
1.5.3.6 验证对级约束
- 参考书上p167的公式p7.8
//-- 验证对极约束 // 相机内参cv::Mat K = (cv::Mat_<double>(3,3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0,0,1);for(cv::DMatch m:matches){cv::Point2d pt1 = pixel2cam(keypoints_1[m.queryIdx].pt,K);cv::Mat y1 = (cv::Mat_<double>(3,1) << pt1.x,pt1.y,1);cv::Point2d pt2 = pixel2cam ( keypoints_2[ m.trainIdx ].pt, K );cv::Mat y2 = ( cv::Mat_<double> ( 3,1 ) << pt2.x, pt2.y, 1 );cv::Mat d = y2.t() * t_x * R * y1;cout << "epipolar constraint = " << d << endl;}
1.5.4 三角测量
- 参考【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】的4 三角测量
1.5.4.1 实现三角测量的函数triangulation()
pixel2cam()函数
参考本文的1.5.3.5
- 函数声明
//加入了三角测量部分
void triangulation(const vector<cv::KeyPoint>& keypoint_1, const vector<cv::KeyPoint>& keypoint_2,const std::vector<cv::DMatch>& matches, const cv::Mat& R, const cv::Mat& t,vector<cv::Point3d>& points);
- 函数实现
//加入了三角测量部分
void triangulation(const vector<cv::KeyPoint>& keypoint_1, const vector<cv::KeyPoint>& keypoint_2,const std::vector<cv::DMatch>& matches, const cv::Mat& R, const cv::Mat& t,vector<cv::Point3d>& points)
{cv::Mat T1 = (cv::Mat_<float> (3,4) <<1,0,0,0,0,1,0,0,0,0,1,0);cv::Mat T2 = (cv::Mat_<float> (3,4) <<R.at<double>(0,0), R.at<double>(0,1), R.at<double>(0,2), t.at<double>(0,0),R.at<double>(1,0), R.at<double>(1,1), R.at<double>(1,2), t.at<double>(1,0),R.at<double>(2,0), R.at<double>(2,1), R.at<double>(2,2), t.at<double>(2,0));cv::Mat K = (cv::Mat_<double> (3,3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);vector<cv::Point2f> pts_1,pts_2;for(cv::DMatch m : matches){// 将像素坐标转换至相机坐标pts_1.push_back(pixel2cam(keypoint_1[m.queryIdx].pt, K));pts_2.push_back(pixel2cam(keypoint_2[m.trainIdx].pt, K));}cv::Mat pts_4d;//第一个相机的3x4投影矩阵。//第2个相机的3x4投影矩阵。cv::triangulatePoints(T1,T2,pts_1,pts_2,pts_4d);// 转换成非齐次坐标for(int i = 0; i < pts_4d.cols; i++){cv::Mat x = pts_4d.col(i);x /= x.at<float>(3,0);// 归一化cv::Point3d p(x.at<float>(0,0), x.at<float>(1,0), x.at<float>(2,0));points.push_back(p);}
}
1.5.4.2 验证三角化点与特征点的重投影关系
- 输出结果查看【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】的4.3 三角测量
- 这里主要是将原本的点与经过三角测量计算深度并还原后对比
//-- 验证三角化点与特征点的重投影关系cv::Mat K = (cv::Mat_<double>(3,3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );for (int i = 0; i < matches.size(); i++){//第一个图cv::Point2d pt1_cam = pixel2cam(keypoints_1[matches[i].queryIdx].pt,K);cv::Point2d pt1_cam_3d(points[i].x/points[i].z, points[i].y/points[i].z);cout<<"point in the first camera frame: "<<pt1_cam<<endl;cout<<"point projected from 3D "<<pt1_cam_3d<<", d="<<points[i].z<<endl;// 第二个图cv::Point2f pt2_cam = pixel2cam(keypoints_2[matches[i].trainIdx].pt,K);cv::Mat pt2_trans = R * (cv::Mat_<double>(3,1) << points[i].x,points[i].y,points[i].z) + t;pt2_trans /= pt2_trans.at<double>(2,0);cout<<"point in the second camera frame: "<<pt2_cam<<endl;cout<<"point reprojected from second frame: "<<pt2_trans.t()<<endl;cout<<endl;}
1.5.5 求解PnP
- 参考【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】的5求解PnP
1.5.5.1 cv的PnP计算函数
1.5.5.1.1 直接线性变换(默认)cv::SOLVEPNP_ITERATIVE
.
参数pts_3d
:为目标点在该图片相机坐标系下的三维坐标,输入量参数pts_2d
:为匹配点在该相机坐标系下的归一化非齐次二位坐标,输入量参数K
,相机内参,输入量cv:Mat()
:这个参数指的是畸变系数向量参数r
:求得的旋转向量形式,可以使用cv::Rodrigues(r, R);
将其转换为旋转矩阵参数t
:求得的平移向量参数false
:false表示输入的r t不作为初始化值 如果是true则此时会把t r作为初始值进行迭代参数flags
:求解的方法返回值bool
:计算成功就返回true
SOLVEPNP_ITERATIVE = 0, //默认值SOLVEPNP_EPNP = 1, //!< EPnP: Efficient Perspective-n-Point Camera Pose Estimation @cite lepetit2009epnpSOLVEPNP_P3P = 2, //!< Complete Solution Classification for the Perspective-Three-Point Problem @cite gao2003completeSOLVEPNP_DLS = 3, //!< A Direct Least-Squares (DLS) Method for PnP @cite hesch2011directSOLVEPNP_UPNP = 4, //!< Exhaustive Linearization for Robust Camera Pose and Focal Length Estimation @cite penate2013exhaustiveSOLVEPNP_AP3P = 5, //!< An Efficient Algebraic Solution to the Perspective-Three-Point Problem @cite Ke17SOLVEPNP_MAX_COUNT //!< Used for count
- 代码实例如下
cv::Mat r,t;//Mat()这个参数指的是畸变系数向量// 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法cv::solvePnP( pts_3d, pts_2d, K, cv::Mat(), r, t, false ,cv::SOLVEPNP_ITERATIVE);
1.5.5.1.1 EPNP方法cv::SOLVEPNP_EPNP
- 仅需将
参数flags
改为cv::SOLVEPNP_EPNP
1.5.5.1.2 UPNP方法cv::SOLVEPNP_UPNP
- 仅需将
参数flags
改为cv::SOLVEPNP_UPNP
1.5.5.1.3 DLS方法cv::SOLVEPNP_DLS
*仅需将参数flags
改为cv::SOLVEPNP_DLS
1.5.5.1.4 P3P方法cv::SOLVEPNP_P3P
- 由于其只需要4个点对
vector<cv::Point3f> pts_p3p_3d;//创建容器pts_3d存放3d点(图1对应的特征点的相机坐标下的3d点)vector<cv::Point2f> pts_p3p_2d;//创建容器pts_2d存放图2的特征点//取出其中的4个点对for (int i = 0; i < 4; i++){pts_p3p_3d.push_back(pts_3d[i]);pts_p3p_2d.push_back(pts_2d[i]);}t1 = chrono::steady_clock::now();//Mat()这个参数指的是畸变系数向量cv::solvePnP(pts_p3p_3d, pts_p3p_2d, K, cv::Mat(), r, t, false,cv::SOLVEPNP_P3P); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
1.5.5.2 利用RGB图和深度图还原三维点
pixel2cam()函数
参考本文的1.5.3.5
// 建立3D点,把深度图信息读进来,构造三维点//d1 = cv::imread(argv[3], -1);// 深度图为16位无符号数,单通道图像cv::Mat K = (cv::Mat_<double>(3,3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );vector<cv::Point3f> pts_3d;//创建容器pts_3d存放3d点(图1对应的特征点的相机坐标下的3d点)for(cv::DMatch m : matches){//把对应的图1的特征点的深度信息拿出来ushort d = d1.ptr<unsigned short> (int (keypoints_1[m.queryIdx].pt.y))[int (keypoints_1[m.queryIdx].pt.x)];if(d == 0) // bad depthcontinue;float dd = d/5000.0;//用dd存放换算过尺度的深度信息cv::Point2d p1 = pixel2cam(keypoints_1[m.queryIdx].pt, K);//p1里面放的是图1特征点在相机坐标下的归一化坐标(只包含 x,y)pts_3d.push_back(cv::Point3f (p1.x *dd, p1.y*dd,dd));//得到图1特征点在相机坐标下的3d坐标}cout << "3d-2d pairs: " << pts_3d.size() << endl;//3d-2d配对个数得用pts_3d的size
1.5.6 光流追踪
使用带金字塔的迭代Lucas-Kanade方法计算稀疏特征集的光流。
1.5.6.1 头文件的使用
#include <opencv2/video/tracking.hpp>
1.5.6.2 光流追踪函数cv::calcOpticalFlowPyrLK()
1.5.6.1.1 声明函数
CV_EXPORTS_W void calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,InputArray prevPts, InputOutputArray nextPts,OutputArray status, OutputArray err,Size winSize = Size(21,21), int maxLevel = 3,TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),int flags = 0, double minEigThreshold = 1e-4 );
参数
prevPts
:由buildOpticalFlowPyramid构建的第一个8位输入图像或金字塔。参数
nextImg
:第二输入图像或与prevImg相同大小和类型的金字塔。参数
prevPts
:需要找到LK流的2D点的向量;点坐标必须是单精度浮点数。参数
nextPts
:2D点的输出向量(具有单精度浮点坐标),其包含第二图像中输入特征的计算出的新位置;当传递OPTFLOW_USE_INITIAL_FLOW标志时,向量的大小必须与输入中的大小相同。参数
status
:输出状态向量(无符号字符);如果找到了相应特征的流,则将向量的每个元素设置为1,否则,将其设置为0。参数
err
:误差的输出矢量;向量的每个元素都被设置为对应特征的误差,误差度量的类型可以在flags参数中设置;如果未找到流,则未定义错误(使用状态参数查找此类情况)。参数
winSize
:每个金字塔级别的搜索窗口大小。参数
maxLevel
:基于0的最大金字塔级数;如果设置为0,则不使用金字塔(单个级别),如果设置为1,则使用两个级别,依此类推;若金字塔被传递给输入,那个么算法将使用和金字塔一样多的级别,但不超过maxLevel。参数
criteria
:参数,指定迭代搜索算法的终止条件(在指定的最大迭代次数criteria.maxCount之后或搜索窗口移动小于Criterias.epsilon时)。参数
flags
:操作标志:
-OPTFLOW_USE_INITIAL_FLOW使用存储在下一个TPTS中的初始估计;如果未设置该标志,则将prevPts复制到下一个TPTS,并将其视为初始估计。
-OPTFLOW_LK_GET_MIN_EIGENVALS使用最小特征值作为误差度量(请参见minEigThreshold描述);如果未设置该标志,则使用原始点和移动点周围的面片之间的L1距离除以窗口中的像素数作为误差度量。参数
minEigThreshold
:该算法计算光流方程的2x2法向矩阵(该矩阵在@cite Bouguet00中称为空间梯度矩阵)的最小特征值,除以窗口中的像素数;如果该值小于minEigThreshold,则会过滤出相应的功能,并且不会处理其流程,因此可以消除缺点并提高性能。
1.5.6.1.2 应用实例一
- 参考【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅱ】【使用LK光流(cv)】【高斯牛顿法实现单层光流和多层光流】【实现单层直接法和多层直接法】的1 使用LK光流(cv)
cv::calcOpticalFlowPyrLK( last_color, color, prev_keypoints, next_keypoints, status, error );
其中
参数1 last_color
:cv::Mat格式,这是上一个图片的Mat格式;已知量参数2 color
:cv::Mat格式,这是当前图片的Mat;已知量参数3 prev_keypoints
:vectorcv::Point2f格式,这是上一个图片所提取的fast特征点;已知量参数4 next_keypoints
:vectorcv::Point2f格式,这是经过计算得到的当前图片的fast特征点;所求量参数5 status
:vector格式,根据其是否为1可以确定对应的点是否被正确的追踪到。参数6 error
:vector格式,这是每个特征点在上一个和当前图片之间的特征点的偏差
1.5.6.1.3 应用实例二
- 摘自:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】的3.2.6.15 FindFeaturesInRight()
cv::calcOpticalFlowPyrLK(current_frame_->left_img_, current_frame_->right_img_, kps_left,kps_right, status, error, cv::Size(11, 11), 3,cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30,0.01),cv::OPTFLOW_USE_INITIAL_FLOW);
【Opencv】【OpenCV实践】【OpenCV的使用学习记录】【fmt学习记录】相关推荐
- openCV【实践系列】5——使用OpenCV进行Delaunay三角剖分
使用OpenCV进行Delaunay三角剖分和Voronoi图 图1:左图奥巴马总统使用dlib检测到标志点,中间Delaunay三角剖分的标志点,右图:相应的Voronoi图 在面部标志的众 ...
- openCV【实践系列】6——使用OpenCV进行面部交换(换脸)
在本教程中,我们将学习如何使用C ++和Python中的OpenCV和DLib将一个图像上的面部换成另一个完全不同的面部. 为什么Face-Swap很难? 主要存在一下的四个方面的问题: 首先,不同的 ...
- 【学习OpenCV】基于opencv的直线和曲线拟合与绘制(最小二乘法)
自动驾驶工具箱-车道保持辅助与车道检测 最小二乘法多项式曲线拟合,是常见的曲线拟合方法,有着广泛的应用,这里在借鉴最小二乘多项式曲线拟合原理与实现的原理的基础上,介绍如何在OpenCV来实现基于最小二 ...
- openCV【实践系列】4——使用Opencv进行斑点(blob)检测
本教程阐述了使用opencv进行简单的斑点检测 什么是斑点? 斑点是图像中的一组连接像素,它们共享一些共同属性(例如灰度值).在下图中,暗连通区域是斑点,斑点检测的目标是识别和标记这些区域. 简单的斑 ...
- 【opencv】1.opencv安装、编译、运行等踩坑记录
1.安装python2环境下opencv2.4.9安装不成功的解决方法 2.查看linux下的各种安装库的安装位置和版本,以opencv为例 3.cmake与g++: opencv之在Linux下编译 ...
- OpenCV简介、OpenCV的发展历程
OpenCV简介.OpenCV的发展历程 近年来,人工智能是伴随着科技发展出现的一个重要词汇,全球多个国家提出了发展人工智能的规划方案.我国也在大力发展人工智能,众多高校也纷纷成立了关于人工智能的学院 ...
- 解决OpenCV问题:OpenCV Error: Assertion failed (!empty()) in cv::CascadeClassifier::detectMultiScale,
解决OpenCV问题:OpenCV Error: Assertion failed (!empty()) in cv::CascadeClassifier::detectMultiScale, 目录 ...
- OpenCV内置OpenCV的实例(附完整代码)
OpenCV内置OpenCV的实例 OpenCV内置OpenCV的实例 OpenCV内置OpenCV的实例 #include "opencv2/core.hpp" #include ...
- 【OpenCV实战】OpenCV实现人脸检测详解(含代码)
OpenCV中有许多可以进行人脸.人眼检测的特征文件,今天我们利用OpenCV中自带的特征文件haarcascade_frontalface_default.xml来进行人脸检测. [OpenCV实战 ...
最新文章
- 腾讯云存储产品矩阵全面升级,发布三维生态战略
- PHP开发经常遇到的几个错误
- 非极大值抑制(Non-maximum suppression)在物体检测领域的应用
- 利用世界杯,读懂 Python 装饰器 1
- 对学生成绩表按计算机应用成绩排序,计算机等级考试EXCEL练习题-12学生成绩表2...
- java线程使用不当,java多线程使用不当造成的问题
- js实现kmp算法_搜索算法 与 随机算法 (JS实现)
- Ubuntu不能挂载移动硬盘问题Error mounting /dev/sda1 at /media/XXXX: Command-line `mount -t ntfs -o
- SELECTORS模块实现并发简单版FTP
- educoder MongoDB 数据库基本操作
- javascript的数组和数组元素的遍历,实现全国省份和城市一览表
- js表单提交 php,提交表单:js中form表单是如何提交的
- 3PAR存储双活LUN扩容方法
- 个人陈述怎么写计算机专业自招,自主招生个人陈述范文
- 十年Android程序员图解:用图帮你了解https的原理
- 阿里云服务器没有公网IP
- Java poi XWPFDocument 操作2007Word,实现参数替换、新增 插入 替换 表格数据、创建饼状图、柱形图、折线图
- Tomcat是干嘛用的?企业级Tomcat部署实践及安全调优
- 你的童年经历过放牛,放羊,干农活吗?然后你现在对这些都怀念吗?
- 借助于经济学数学计算机科学,借助于经济学、数学、计算机科学、统计学、概率论以及帮助决策的理论来进行逻辑分析和推论。这一概...
热门文章
- 猿创征文|瑞吉外卖——移动端_订单明细
- python识别花草_荐 【python】TensorFlow框架下CNN神经网络的花卉识别系统
- 全网最硬核 JVM TLAB 分析(额外加菜) 7. TLAB 相关 JVM 日志解析
- 使用Mybatis实现点菜功能
- 产品定制设计:从“原型”到“个性”的对立倾向
- Cocos2dx游戏开发系列笔记11:解刨《战神传说》完结篇
- (3) s3cmd get object 代码流程
- 软工个人作业-提问回顾与个人总结
- 微慕小程序开源版A标签优化说明
- niva mysql_CentOS下,MySQL数据库的导出导入功能验证