回顾
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <fstream>
using namespace std;
using namespace cv;
Point2f pixel2cam(const Point2d& p, const Mat& K)
{
return Point2f
(
(p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
(p.y - K.at<double>(1, 2)) / K.at<double>(1, 1)
);
}
int main()
{
//-- 读取图像
cv::Mat img_1 = imread("D:/opencv_c++/source/repos/Project1/5.jpg", cv::IMREAD_COLOR);//"CV_LOAD_IMAGE_COLOR" 为旧版本中的宏定义名
cv::Mat img_2 = imread("D:/opencv_c++/source/repos/Project1/6.jpg", cv::IMREAD_COLOR);
resize(img_1, img_1, cv::Size(), 0.4, 0.4);
resize(img_2, img_2, cv::Size(), 0.4, 0.4);
//-- 初始化
/*
* 这是一个 C++ 中使用 OpenCV 库时定义两个名为 keypoints_1 和 keypoints_2 的向量(vector),
* 其存储的元素类型为 KeyPoint。
* KeyPoint 是 OpenCV 中用于表示图像中关键点的结构体,包含关键点的坐标、响应值、方向等属性。
* 这段代码的作用是定义了两个存储关键点的向量,可以用来存储从图像中提取出的关键点信息。
*/
std::vector<cv::KeyPoint> keypoints_1, keypoints_2;
/*
* 定义两个 Mat 对象 descriptors_1 和 descriptors_2,用于存储特征点的描述符
* 但是并没有指定它们的大小。
* 如果有 N 个特征点,每个特征点的描述符是一个 D 维向量,那么可以定义一个大小为 N×D 的 Mat 对象来存储
*/
cv::Mat descriptors_1, descriptors_2;
/*
* Ptr<FeatureDetector> 是一个模板类,用于创建一个指向 FeatureDetector 类型对象的智能指针。
* 这个智能指针可以自动管理其所指向对象的内存,避免了手动释放内存的繁琐操作。
* 在 C++ 中,<> 表示模板参数列表,
* Ptr 是一个智能指针类,可以指向任何 OpenCV 中的对象,并自动管理其内存。
* 使用 Ptr 类型的智能指针,可以避免手动释放内存的繁琐操作,提高代码的可读性和可维护性。
* 在 OpenCV 中,很多对象都是通过 Ptr 类型的智能指针来管理的,
* 例如 Mat、FeatureDetector、DescriptorExtractor 等等。
*/
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
//-- 第一步:检测 Oriented FAST 角点位置
/*
* 在C++中,箭头符号(->)用于访问类的成员函数和成员变量。
* 使用detector对象调用detect()方法。
* 使用OpenCV库中的特征检测器(detector)来检测图像(img_1)中的特征点,
* 并将检测到的特征点存储在一个向量(keypoints_1)中
*/
detector->detect(img_1, keypoints_1);
detector->detect(img_2, keypoints_2);
//-- 第二步:根据角点位置计算 BRIEF 描述子
/*
*
*/
descriptor->compute(img_1, keypoints_1, descriptors_1);
descriptor->compute(img_2, keypoints_2, descriptors_2);
//Mat outimg1;
//drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
//imshow("ORB特征点", outimg1);
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> matches;
//BFMatcher matcher ( NORM_HAMMING );
matcher->match(descriptors_1, descriptors_2, matches);
double min_dist = 10000, max_dist = 0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for (int i = 0; i < descriptors_1.rows; i++)
{
double dist = matches[i].distance;
if (dist < min_dist) min_dist = dist;
if (dist > max_dist) max_dist = dist;
}
仅供娱乐的写法
//min_dist = min_element(matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance < m2.distance; })->distance;
//max_dist = max_element(matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance < m2.distance; })->distance;
printf("-- Max dist : %f \n", max_dist);
printf("-- Min dist : %f \n", min_dist);
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
std::vector< 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]);
}
}
//-- 第五步:绘制匹配结果
Mat img_match;
Mat img_goodmatch;
//drawMatches(img_1, keypoints_1, img_2, keypoints_2, matches, img_match);
drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch);
//imshow("所有匹配点对", img_match);
//imshow("优化后匹配点对", img_goodmatch);
//waitKey(0);
//imshow("img_1", img_1);
//imshow("img_2", img_2);
waitKey(0);
cout << "一共找到了" << good_matches.size() << "组匹配点" << endl;
//-- 估计两张图像间运动//
//-- 把匹配点转换为vector<Point2f>的形式
vector<Point2f> points1;
vector<Point2f> points2;
for (int i = 0; i < (int)matches.size(); i++)
{
points1.push_back(keypoints_1[matches[i].queryIdx].pt);
points2.push_back(keypoints_2[matches[i].trainIdx].pt);
}
//-- 计算基础矩阵
Mat fundamental_matrix;
fundamental_matrix = findFundamentalMat(points1, points2, FM_8POINT);
cout << "fundamental_matrix is " << endl << fundamental_matrix << endl;
//-- 计算本质矩阵
Point2d principal_point(325.1, 249.7); //相机光心, TUM dataset标定值
double focal_length = 521; //相机焦距, TUM dataset标定值
Mat essential_matrix;
essential_matrix = findEssentialMat(points1, points2, focal_length, principal_point);
cout << "essential_matrix is " << endl << essential_matrix << endl;
//-- 计算单应矩阵
Mat homography_matrix;
homography_matrix = findHomography(points1, points2, RANSAC, 3);
cout << "homography_matrix is " << endl << homography_matrix << endl;
Mat R, t;
// 相机内参,TUM Freiburg2
Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
//-- 从本质矩阵中恢复旋转和平移信息.
recoverPose(essential_matrix, points1, points2, R, t, focal_length, principal_point);
cout << "R is " << endl << R << endl;
cout << "t is " << endl << t << endl;
//-- 三角化
vector<Point3d> points;
Mat T1 = (Mat_<float>(3, 4) <<
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0);
Mat T2 = (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)
);
//
vector<Point2f> pts_1, pts_2;
for (DMatch m : matches)
{
// 将像素坐标转换至相机坐标
pts_1.push_back(pixel2cam(keypoints_1[m.queryIdx].pt, K));
pts_2.push_back(pixel2cam(keypoints_2[m.trainIdx].pt, K));
}
//
Mat pts_4d;
cv::triangulatePoints(T1, T2, pts_1, pts_2, pts_4d);
//std::cout << "Triangulated points shape: " << pts_4d.size() << std::endl;
// 转换成非齐次坐标
for (int i = 0; i < pts_4d.cols; i++)
{
Mat x = pts_4d.col(i);
x /= x.at<float>(3, 0); // 归一化
Point3d p(
x.at<float>(0, 0),
x.at<float>(1, 0),
x.at<float>(2, 0)
);
points.push_back(p);
}
int num_points = points.size();
std::cout << "Number of points: " << num_points << std::endl;
// 将点云数据保存为文本文件
ofstream file("points.txt");
for (int i = 0; i < points.size(); i++) {
Point3d p = points[i];
file << p.x << " " << p.y << " " << p.z << endl;
}
file.close();
std::ofstream outfile("points.bin", std::ios::out | std::ios::binary);
outfile.write((char*)&points[0], points.size() * sizeof(cv::Point3d));
outfile.close();
return 0;
}
光流法
#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <chrono>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
associate.txt
1305031457.827699 rgb/1305031457.827699.png 1305031457.842853 depth/1305031457.842853.png
1305031457.859623 rgb/1305031457.859623.png 1305031457.875920 depth/1305031457.875920.png
1305031457.891593 rgb/1305031457.891593.png 1305031457.906126 depth/1305031457.906126.png
1305031457.927633 rgb/1305031457.927633.png 1305031457.942604 depth/1305031457.942604.png
1305031457.991644 rgb/1305031457.991644.png 1305031457.976744 depth/1305031457.976744.png
c++读取文件流
fin
是一个变量名,可能是来源于英文单词file input
的缩写。在C++中,通常用于表示输入文件流对象,用于从文件中读取数据。在上述代码中,fin
是一个ifstream
对象,它被用来打开和读取associate.txt文件。
// 打开associate.txt文件,创建输入文件流对象
string path_to_dataset = "D:/opencv_c++/source/repos/Project1/data";
string associate_file = path_to_dataset + "/associate.txt";
ifstream fin(associate_file);
if (!fin) {
cerr << "无法打开文件" << associate_file << endl;
return 1;
}
getline(fin, line)
函数会从文件输入流fin
中读取一行文本,并将其存储到line
中,直到读取到行末或者文件结尾。istringstream iss(line)
表示将字符串变量line
作为输入流,并创建一个名为iss
的istringstream
对象。
istringstream
是C++标准库中的一个输入流类,它可以将字符串作为输入流进行处理。它的作用类似于从文件中读取数据的输入流类ifstream,istringstream
对象也拥有输入流运算符>>。
当使用istringstream对象的输入流运算符>>来解析字符串时,默认情况下它会以空格为分隔符。下面是istringstream流的例子:
将字符串解析为整型数值,并将其存储在变量x、y、z中。
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
string str = "10 20 30";
//将字符串str作为参数传递给istringstream对象iss
istringstream iss(str);
int x, y, z;
iss >> x >> y >> z;
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
return 0;
}
我们可以这样使用,从标准输入流中读取三个整型数值,并分别存储到变量x、y、z中。
int x, y, z;
cin >> x >> y >> z;
从流iss中解析
string rgb_file, depth_file;
long time_rgb, time_depth;
iss >> time_rgb >> rgb_file >> time_depth >> depth_file;
完整解析associate.txt文件
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main() {
// 打开associate.txt文件,创建输入文件流对象
string path_to_dataset = "D:/opencv_c++/source/repos/Project1/data";
string associate_file = path_to_dataset + "/associate.txt";
ifstream fin(associate_file);
if (!fin) {
cerr << "无法打开文件" << associate_file << endl;
return 1;
}
// 逐行读取数据
string line;
string rgb_file, depth_file;
double time_rgb, time_depth;
/*
* 在C++中,最长的数据类型是long double。它通常使用10或16个字节的内存空间,具体取决于编译器和操作系统。
* int 是 C++ 中的一种整数数据类型,通常由编译器分配 4 个字节的内存空间来存储整数值。
* double 是 C++ 中的一种浮点数数据类型,通常由编译器分配 8 个字节的内存空间来存储浮点数值。
*/
while (getline(fin, line)) {
istringstream iss(line);
iss >> time_rgb >> rgb_file >> time_depth >> depth_file;
// 处理读取到的数据
cout << "RGB文件:" << rgb_file << endl;
cout << "深度文件:" << depth_file << endl;
cout << "RGB时间戳:" << time_rgb << endl;
cout << "深度时间戳:" << time_depth << endl;
// TODO: 将RGB和深度图像数据进行匹配等操作
}
// 关闭文件流
fin.close();
return 0;
}
得到彩色图和深度图
// TODO: 将RGB和深度图像数据进行匹配等操作
cv::Mat color, depth;
color = cv::imread(path_to_dataset + "/" + rgb_file);
depth = cv::imread(path_to_dataset + "/" + depth_file, -1);
cv::imshow("corners", color);
cv::imshow("corner", depth);
cv::waitKey(0);
这段代码没有显式地调用 fin
的 getline()
或 >>
操作符来读取一整行,而是直接用 fin >> ... >> ... >> ...
的方式逐个读取 fin
中的每个单词。这种方式只能保证在单词之间正确分割字符串,但无法保证每行都能完整读取。如果一行中的单词数目不符合预期,或者某个单词中包含空格、制表符等空白字符,就会导致读取出错。因此,建议使用 getline()
函数来逐行读取文本文件。
#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <chrono>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
int main()
{
string path_to_dataset = "D:/opencv_c++/source/repos/Project1/data";
string associate_file = path_to_dataset + "/associate.txt";
ifstream fin(associate_file);
if (!fin)
{
cerr << "I cann't find associate.txt!" << endl;
return 1;
}
string rgb_file, depth_file, time_rgb, time_depth;
cv::Mat color, depth;
for (int index = 0; index < 100; index++)
{
fin >> time_rgb >> rgb_file >> time_depth >> depth_file;
color = cv::imread(path_to_dataset + "/" + rgb_file);
depth = cv::imread(path_to_dataset + "/" + depth_file, -1);
if (index == 0)
{
// 对第一帧提取FAST特征点
}
// 对其他帧用LK跟踪特征点
}
提取FAST特征
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
int main() {
// 打开associate.txt文件,创建输入文件流对象
string path_to_dataset = "D:/opencv_c++/source/repos/Project1/data";
string associate_file = path_to_dataset + "/associate.txt";
ifstream fin(associate_file);
if (!fin) {
cerr << "无法打开文件" << associate_file << endl;
return 1;
}
// 逐行读取数据
string line;
string rgb_file, depth_file;
double time_rgb, time_depth;
/*
* 在C++中,最长的数据类型是long double。它通常使用10或16个字节的内存空间,具体取决于编译器和操作系统。
* int 是 C++ 中的一种整数数据类型,通常由编译器分配 4 个字节的内存空间来存储整数值。
* double 是 C++ 中的一种浮点数数据类型,通常由编译器分配 8 个字节的内存空间来存储浮点数值。
*/
while (getline(fin, line)) {
istringstream iss(line);
iss >> time_rgb >> rgb_file >> time_depth >> depth_file;
// 处理读取到的数据
cout << "RGB文件:" << rgb_file << endl;
cout << "深度文件:" << depth_file << endl;
cout << "RGB时间戳:" << time_rgb << endl;
cout << "深度时间戳:" << time_depth << endl;
// TODO: 将RGB和深度图像数据进行匹配等操作
cv::Mat color, depth;
color = cv::imread(path_to_dataset + "/" + rgb_file);
depth = cv::imread(path_to_dataset + "/" + depth_file, -1);
// 提取FAST特征点
vector<cv::KeyPoint> kps;
//使用cv::FastFeatureDetector::create()创建了一个FAST特征检测器的指针detector
cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
//调用detector->detect(color, kps)来检测输入图像color中的特征点,并将检测到的特征点存储在kps中
detector->detect(color, kps);
// 画出 keypoints
cv::Mat img_show = color.clone();
cv::drawKeypoints(color, kps, img_show, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imshow("corners", img_show);
cv::imshow("color", color);
cv::imshow("depth", depth);
cv::waitKey(0);
}
// 关闭文件流
fin.close();
return 0;
}
读取NuSences数据集并提取FAST特征
#include <iostream>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
namespace fs = std::filesystem;
using namespace std;
using namespace cv;
void FAST(string file_path);
int main(int argc, char** argv)
{
// 指定文件夹路径
string folder_path = "E:/v1.0-mini/sweeps/CAM_FRONT";
for (const auto& file : fs::directory_iterator(folder_path))
{
if (file.path().extension() == ".jpg" || file.path().extension() == ".png")
{
FAST(file.path().string());
}
}
return 0;
}
void FAST(string file_path)
{
cv::Mat image = cv::imread(file_path);
if (!image.empty())
{
// 提取FAST特征点
vector<cv::KeyPoint> kps;
//使用cv::FastFeatureDetector::create()创建了一个FAST特征检测器的指针detector
cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
//调用detector->detect(color, kps)来检测输入图像color中的特征点,并将检测到的特征点存储在kps中
detector->detect(image, kps);
// 画出 keypoints
cv::Mat img_show = image.clone();
cv::drawKeypoints(image, kps, img_show, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imshow("corners", img_show);
cv::waitKey(0);
}
}
status
是一个布尔类型的数组,与prev_points
和curr_points
具有相同的长度。它指示每个点在当前帧中是否被跟踪到。如果对应点在当前帧中找到了,status[i]
将为真;否则为假。
cv::calcOpticalFlowPyrLK(last_color, color, prev_keypoints, next_keypoints, status, error);
/*
* 这是一个计算光流的函数,使用的是金字塔Lucas-Kanade算法。
* 这个函数需要输入上一帧和当前帧的彩色图像,上一帧的关键点
* 输出参数,如下一帧的关键点、每个关键点的状态和误差。
*/
SLAM/ch8/KLflow代码
#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <chrono>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
int main()
{
string path_to_dataset = "D:/opencv_c++/source/repos/Project1/data";
string associate_file = path_to_dataset + "/associate.txt";
ifstream fin(associate_file);
if (!fin)
{
cerr << "I cann't find associate.txt!" << endl;
return 1;
}
string rgb_file, depth_file, time_rgb, time_depth;
list< cv::Point2f > keypoints; // 因为要删除跟踪失败的点,使用list
cv::Mat color, depth, last_color;
for (int index = 0; index < 100; index++)
{
fin >> time_rgb >> rgb_file >> time_depth >> depth_file;
color = cv::imread(path_to_dataset + "/" + rgb_file);
depth = cv::imread(path_to_dataset + "/" + depth_file, -1);
if (index == 0)
{
// 对第一帧提取FAST特征点
vector<cv::KeyPoint> kps;
cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
detector->detect(color, kps);
for (auto kp : kps)
keypoints.push_back(kp.pt);
last_color = color;
continue;
}
if (color.data == nullptr || depth.data == nullptr)
continue;
// 对其他帧用LK跟踪特征点
vector<cv::Point2f> next_keypoints;
vector<cv::Point2f> prev_keypoints;
for (auto kp : keypoints)
prev_keypoints.push_back(kp);
vector<unsigned char> status;
vector<float> error;
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
cv::calcOpticalFlowPyrLK(last_color, color, prev_keypoints, next_keypoints, status, error);
/*
* 这是一个计算光流的函数,使用的是金字塔Lucas-Kanade算法。
* 这个函数需要输入上一帧和当前帧的彩色图像,上一帧的关键点
* 输出参数:下一帧的关键点、每个关键点的状态和误差。
*/
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "LK Flow use time:" << time_used.count() << " seconds." << endl;
// 把跟丢的点删掉
int i = 0;
for (auto iter = keypoints.begin(); iter != keypoints.end(); i++)
{
if (status[i] == 0)
{
iter = keypoints.erase(iter);
continue;
}
*iter = next_keypoints[i];
iter++;
}
cout << "tracked keypoints: " << keypoints.size() << endl;
if (keypoints.size() == 0)
{
cout << "all keypoints are lost." << endl;
break;
}
// 画出 keypoints
cv::Mat img_show = color.clone();
for (auto kp : keypoints)
cv::circle(img_show, kp, 2, cv::Scalar(0, 240, 0), 3);
cv::imshow("corners", img_show);
cv::waitKey(0);
last_color = color;
}
return 0;
}
光流法可视化
#include <iostream>
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
int main() {
// 指定图像文件夹路径
std::string image_folder = "E:/v1.0-mini/sweeps/CAM_FRONT";
// 获取图像文件路径
std::vector<cv::String> image_paths;
cv::glob(image_folder, image_paths);
// 定义关键点和颜色
std::vector<cv::Point2f> prev_keypoints, curr_keypoints;
cv::Scalar prev_color = cv::Scalar(0, 0, 255), curr_color = cv::Scalar(0, 255, 0);
// 读取第一张图像
cv::Mat prev_image = cv::imread(image_paths[0]);
cv::cvtColor(prev_image, prev_image, cv::COLOR_BGR2GRAY);
cv::goodFeaturesToTrack(prev_image, prev_keypoints, 500, 0.01, 10);
// 遍历每一张图像
for (int i = 1; i < image_paths.size(); i++) {
// 读取当前帧图像
cv::Mat curr_image = cv::imread(image_paths[i]);
cv::cvtColor(curr_image, curr_image, cv::COLOR_BGR2GRAY);
// 计算光流
std::vector<uchar> status;
std::vector<float> err;
cv::calcOpticalFlowPyrLK(prev_image, curr_image, prev_keypoints, curr_keypoints, status, err);
// 绘制轨迹
for (int j = 0; j < prev_keypoints.size(); j++) {
if (status[j]) {
cv::line(curr_image, prev_keypoints[j], curr_keypoints[j], curr_color, 2);
}
}
// 更新关键点和颜色
prev_keypoints = curr_keypoints;
prev_color = curr_color;
// 重新检测关键点
cv::goodFeaturesToTrack(curr_image, curr_keypoints, 500, 0.01, 10);
// 更新上一帧图像
prev_image = curr_image.clone();
cv::imshow("prev_image", prev_image);
cv::waitKey(0);
}
return 0;
}
升级版
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
// 读取文件夹下所有图片
vector<String> filenames;
String folder = "E:/v1.0-mini/sweeps/CAM_FRONT/*.jpg";
glob(folder, filenames);
// 初始化轨迹点
vector<Point2f> prev_pts, curr_pts;
Mat prev_frame, curr_frame;
// 循环处理每张图片
for (size_t i = 100; i < filenames.size(); i++)
{
// 读取当前帧
curr_frame = imread(filenames[i]);
// 如果是第一张图片,先初始化轨迹点
if (i == 100)
{
// 选择初始轨迹点
vector<KeyPoint> keypoints;
Ptr<FeatureDetector> detector = FastFeatureDetector::create();
detector->detect(curr_frame, keypoints);
for (auto kp : keypoints)
{
prev_pts.push_back(kp.pt);
}
}
// 如果不是第一张图片,计算光流
if (i > 100)
{
// 计算当前轨迹点
vector<uchar> status;
vector<float> err;
calcOpticalFlowPyrLK(prev_frame, curr_frame, prev_pts, curr_pts, status, err);
// 可视化轨迹点
for (size_t j = 0; j < prev_pts.size(); j++)
{
if (status[j] == 1)
{
line(curr_frame, prev_pts[j], curr_pts[j], Scalar(0, 255, 0), 2);
circle(curr_frame, curr_pts[j], 3, Scalar(0, 0, 255), -1);
}
}
// 更新轨迹点
prev_pts = curr_pts;
}
// 显示结果
imshow("result", curr_frame);
waitKey(1);
// 更新上一帧
curr_frame.copyTo(prev_frame);
}
return 0;
}
升级版pro
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
// 读取文件夹下所有图片
String folder = "E:/v1.0-mini/sweeps/CAM_FRONT";
vector<String> filenames;
glob(folder, filenames);
// 创建随机颜色
RNG rng(12345);
Scalar color;
// 读取第一张图片并进行光流计算
Mat prev_img, prev_gray;
prev_img = imread(filenames[0]);
cvtColor(prev_img, prev_gray, COLOR_BGR2GRAY);
vector<Point2f> prev_points;
goodFeaturesToTrack(prev_gray, prev_points, 500, 0.01, 10);
// 遍历文件夹下的所有图片并计算光流
for (size_t i = 1; i < filenames.size(); i++)
{
// 读取当前图片
Mat curr_img, curr_gray;
curr_img = imread(filenames[i]);
cvtColor(curr_img, curr_gray, COLOR_BGR2GRAY);
// 使用calcOpticalFlowPyrLK函数计算光流
vector<Point2f> curr_points;
vector<uchar> status;
vector<float> err;
calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_points, curr_points, status, err);
// 可视化对应点轨迹
for (size_t j = 0; j < prev_points.size(); j++)
{
if (status[j])
{
color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
line(curr_img, prev_points[j], curr_points[j], color, 2);
circle(curr_img, curr_points[j], 5, color, -1);
}
}
// 显示当前图片并等待
imshow("Optical Flow", curr_img);
waitKey(0);
// 更新变量
prev_gray = curr_gray.clone();
prev_points = curr_points;
}
return 0;
}
最终版
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
// 读取文件夹下所有图片
String folder = "E:/v1.0-mini/sweeps/CAM_FRONT";
vector<String> filenames;
glob(folder, filenames);
// 创建随机颜色
RNG rng(12345);
Scalar color;
// 遍历文件夹下的所有图片
for (size_t i = 1200; i < filenames.size(); i++)
{
// 读取第一张图片
Mat prev_img, prev_gray;
prev_img = imread(filenames[i]);
cvtColor(prev_img, prev_gray, COLOR_BGR2GRAY);
// 提取特征点
vector<Point2f> prev_points;
goodFeaturesToTrack(prev_gray, prev_points, 500, 0.01, 10);
// 读取第二张图片并进行光流计算
Mat curr_img, curr_gray;
curr_img = imread(filenames[i+1]);
cvtColor(curr_img, curr_gray, COLOR_BGR2GRAY);
// 使用calcOpticalFlowPyrLK函数计算光流
vector<Point2f> curr_points;
vector<uchar> status;
vector<float> err;
calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_points, curr_points, status, err);
// 可视化对应点轨迹
for (size_t j = 0; j < prev_points.size(); j++)
{
if (status[j])
{
color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
line(curr_img, prev_points[j], curr_points[j], color, 2);
circle(curr_img, curr_points[j], 5, color, -1);
}
}
// 显示光流
imshow("Optical Flow", curr_img);
waitKey(0);
}
return 0;
}
构造函数
构造函数是一种特殊类型的函数,用于在创建类的实例时对其进行初始化。
RNG
RNG是OpenCV库中的一个随机数生成器类,用于生成随机数。
它定义在opencv2/core/types_c.h头文件中。
RNG的构造函数可以接受一个整数参数作为种子值,用于初始化随机数生成器。
例如,RNG rng(12345);
就是创建一个种子值为12345的随机数生成器对象。
rng是一个变量名,用于存储生成的随机数。
使用了RNG类的uniform()方法生成随机数,并将随机数赋值给像素的RGB通道,如 pixel[0] = rng.uniform(0, 256);
cv::RNG rng(12345); // 创建随机数生成器对象
Mat
Mat和RNG都是C++中的类,它们都是OpenCV库中提供的类。
cv::Mat image(512, 512, CV_8UC3); // 创建空白图像
Mat是OpenCV中代表矩阵的类,它可以用于表示图像、向量、矩阵和张量等各种数学实体。Mat类提供了一系列的方法和运算符,用于处理矩阵数据。例如,可以使用Mat类的at()方法来访问矩阵中的每个元素,使用create()方法来创建一个指定大小和数据类型的矩阵,使用copyTo()方法将一个矩阵复制到另一个矩阵中等等。
RNG是OpenCV中代表随机数生成器的类,它可以用于生成随机数。RNG类提供了一系列的方法,用于生成不同类型的随机数,例如,可以使用uniform()方法生成指定范围内的均匀分布随机数,使用gaussian()方法生成正态分布随机数等等。
Scalar
Scalar也是OpenCV中的一个类,用于表示颜色或灰度值。
cv::Scalar color(255, 0, 0); // 创建一个蓝色Scalar对象
// 赋值一个随机的颜色
cv::Scalar color;
color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
这里RNG类的uniform方法没有指定<>,是因为在OpenCV中,RNG类的uniform方法根据参数的类型自动选择生成整型或浮点型随机数。如果参数是int或unsigned int类型,则生成整型随机数;如果参数是float、double或long double类型,则生成浮点型随机数。
数据类型
Vec3b
Vec3b类型是OpenCV C++库中的一个数据类型,它是由三个字节构成的向量,用于表示图像或颜色空间中的三维向量。其中,b代表的是byte类型,每个字节范围在0到255之间。因此,Vec3b类型可用于存储和处理彩色图像中的像素值。
cv::Mat image(512, 512, CV_8UC3); // 创建空白图像
Vec3b& pixel = image.at<Vec3b>(i, j);
这里的<Vec3b>指明了图像元素的数据类型。
<>
<>表示模板实参列表。在C++中,<>语法通常用于模板编程中,用于指定模板参数类型。1)最常见的,在声明一个std::vector时,可以使用<>语法来指定该向量存储的数据类型,<>语法可以用于指定任何类型的模板参数,而不仅仅是基本数据类型。
std::vector<int> myVector;
2)C++中的尖括号(<>)语法可以在成员函数上使用,通常用于表示模板参数。 这样才能让编译器知道该函数是属于模板类的某个特化版本。比如at
函数有很多重载方法,指定<Vec3b>是为了返回位于指定像素坐标的图像中的像素值的三个浮点数值。
uchar
在OpenCV中,Mat类的at()方法可以用于访问矩阵(图像)中的单个元素。它需要传递两个参数:行和列索引。访问矩阵的第一行第二列元素:
Mat img = imread("image.png");
uchar pixel_value = img.at<uchar>(0, 1);
at()
方法需要指定返回类型,否则编译器将无法确定正确的返回类型
指定了uchar作为返回类型,则读取灰度图像并且每个像素值都是一个无符号字符
Vec3f
彩色图像,则需要指定Vec3b或Vec3f等返回类型,具体取决于像素的颜色深度和通道数。
重载方法
C++中的函数重载是指在同一作用域内定义多个名称相同但参数类型或数量不同的函数,编译器会根据调用时提供的参数类型和数量选择匹配的函数。函数重载可以实现更加灵活的程序设计,提高代码的可读性和可维护性。
C++中,成员函数可以使用"对象名.成员函数名()"来进行调用。另外,也可以使用"指针->成员函数名()"来调用。
成员函数调用
假设有一个类名为Person,其中有一个成员函数printName(),可以使用以下两种方式来调用它:
- 使用对象名调用:用于在对象上直接调用成员函数
Person p; p.printName();
- 使用指针调用:用于在指针指向的对象上调用成员函数
Person *p = new Person(); p->printName(); delete p;
goodFeaturesToTrack 与FastFeatureDetector->detect
Mat prev_img, prev_gray;
prev_img = imread(filenames[i]);
cvtColor(prev_img, prev_gray, COLOR_BGR2GRAY);
vector<Point2f> prev_points;
goodFeaturesToTrack(prev_gray, prev_points, 500, 0.01, 10);
vector<cv::KeyPoint> kps;
cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
detector->detect(color, kps);
这里不清楚,是使用cv::FastFeatureDetector::create()
创建了一个FAST特征检测器的指针对象 "detector"吗?
因为detector是一个智能指针,它指向FastFeatureDetector类的对象,而不是对象本身。因此,需要使用箭头运算符->来访问被指向的对象的成员函数detect()。
::
"::"是作用域解析运算符,用于指定命名空间、类或结构体的作用域。也可以用于访问静态成员函数和静态成员变量
可以用于访问命名空间、类、结构体、枚举等的成员或静态成员。例如在cv::Scalar中,双冒号用于指示Scalar是cv命名空间中的一个类。
引用类型&
带有一个 & 符号表示它是一个引用(reference)类型。引用类型是一个指向内存中已存在的对象的别名,因此对引用类型的更改将影响原始对象。在C++中,使用引用类型可以避免创建临时变量并提高代码的效率。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
RNG rng(12345); // 创建随机数生成器
Mat image(512, 512, CV_8UC3); // 创建空白图像
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
Vec3b& pixel = image.at<Vec3b>(i, j); // 获取像素
pixel[0] = rng.uniform(0, 256); // 随机生成蓝色通道值
pixel[1] = rng.uniform(0, 256); // 随机生成绿色通道值
pixel[2] = rng.uniform(0, 256); // 随机生成红色通道值
}
}
imshow("random_image.jpg", image);
waitKey(0);
return 0;
}
glob函数
glob函数可以用来获取指定文件夹下的所有文件名。
接受两个参数:
第一个参数是一个字符串类型的路径,表示要获取的文件夹路径;
第二个参数是一个vector类型的变量,表示获取到的文件名列表。
注意:glob函数搜索文件时会按照操作系统的文件名排序规则进行排序。如果需要按照自定义规则排序,可以先获取文件名列表,然后使用sort函数对其进行排序。
// 读取文件夹下所有图片
String folder = "E:/v1.0-mini/sweeps/CAM_FRONT";
vector<String> filenames;
glob(folder, filenames);
size_t
是一个无符号整数类型,通常用于表示内存中对象的大小或容器的大小。当然for循环的计数器可以使用任何整数类型,包括int、unsigned int、short、long、long long等。但是,建议使用size_t类型作为数组或容器的索引,因为size_t被设计用于存储对象大小和容量,以及作为数组和容器的索引类型。同时,使用size_t也可以避免无符号整数溢出产生的错误。
// 遍历文件夹下的所有图片
for (size_t i = 0; i < filenames.size(); i++)
{
}