用于计算图像处理的opencv2,只不过这次用的不是python的版本,而是C++的版本。
参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔
CMakeLists.txt
写法样例:
# 添加c++11标准支持
set(CMAKE_CXX_FLAGS "-std=c++11")
# cmake最低版本需求
cmake_minimum_required(VERSION 2.8)
# 创建项目
project( test )
# 创建执行程序
add_executable( cv2_test cv2_test.cpp )
# 寻找opencv
find_package( OpenCV REQUIRED )
# 添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})
# 链接opencv库
target_link_libraries( cv2_test ${OpenCV_LIBS} )
读取数据的数据类型使用unsigned char
:因为int类型在不同操作系统平台下长度不同,而uchar类型在所有平台上长度都是一样的。
#include
// 导入cv2模块
#include
#include
// 在不考虑细致实现的情况下,粗暴导入所有cv2模块
// #include
using namespace std;
using namespace cv;
int main(int argc, char **argv) {
// 图片路径,可以写字符串,也可以用argv从启动参数导入
// 注意在IDE里面编译时需要考虑到当前运行路径在build文件夹内
string img_path = "../test.jpeg";
// 创建图片变量
cv::Mat image;
// 读图片
image = cv::imread(img_path);
// 验证图片是否正确读取,如果data为空指针说明读取失败
if (image.data == nullptr) {
cerr << "图片" << img_path << "读取失败。" << endl;
return -1;
}
// 打开一个新窗口,显示读取成功的图片
cv::imshow("窗口的标题", image);
// 保持窗口打开等待关闭
cv::waitKey(0);
// 输出图片类型,直接输出会是一个整数
cout << "图片类型为:" << image.type() << endl;
// 但是作为哈希值可以直接用来判断图片类型
// CV_8UC3表示8bit,unsigned int,图像3通道,灰度图就是1通道
// float 32位,double64位
// S(signed int),U(unsigned int),F(float)
if (image.type() == CV_8UC3) {
cout << "这是彩色8比特图片。" << endl;
}
else {
cout << "这不是彩色8比特图片。" << endl;
}
// 获取图片尺寸信息
cout << "宽度:" << image.cols;
cout << ",高度:" << image.rows;
cout << ",频道数:" << image.channels() << endl;
// 创建新图像[以灰度图为例]
// int rows = image.rows, cols = image.cols;
// cv::Mat image_new = cv::Mat(rows, cols, CV_8UC1);
// 遍历图像与指针
for (size_t y = 0; y < image.rows; y++) {
// 行指针类型cv::Mat::ptr
// 使用每一行的头部指针作为后续遍历基础
unsigned char *row_ptr = image.ptr<unsigned char>(y);
// 然后对每一行进行遍历
for (size_t x = 0; x < image.cols; x++) {
// 指向一个具体的像素点的指针
// 按照【像素数*通道属性】向后移动
unsigned char *data_ptr = &row_ptr[x*image.channels()];
// 遍历每一个通道
for (int c = 0; c != image.channels(); c++) {
unsigned char data = data_ptr[c];
// 显示时需要做个类型转换修改为int类型
cout << (int)data << " ";
}
// 以每一行一个像素点的方式显示
// 例如:217 215 221
cout << endl;
}
}
// 直接取出指定像素
// 此处彩色图片,灰色单通道图片则使用image.at
// 第0行第0列的像素
cv::Vec3b pixel = image.at<cv::Vec3b>(0, 0);
cout << "第一个像素点是:" << pixel << endl;
cout << "第一个像素点的第一个通道是:" << (int)pixel[0] << endl;
// 保存图片
cv::imwrite( "../test2.jpeg", image);
}
运行得到:
【图就不放了】
图片类型为:16
这是彩色8比特图片。
宽度:550,高度:827,频道数:3
254 254 254
206 206 218
207 207 219
208 208 220
208 208 220
……
第一个像素点是:Ä254, 254, 254Å
第一个像素点的第一个通道是:254
较慢的遍历图片中每个像素点(灰度)
for (auto p = img.begin<uchar>(); p != img.end<uchar>(); ++p)
{
uchar value = uchar((*p));
}
更快的像素点指针遍历法(灰度):
for (int i = 0; i <img.cols * img.rows; i++)
{
uchar value = img.data[i];
}
用at的方法按照行列提取像素值(慢)
// 遍历行
for (int row = 0; row < grayImg.rows; row++)
{
// 遍历列
for (int col = 0; col < grayImg.cols; col++)
{
point_value = int(grayImg.at<uchar>(row, col));
}
}
用指针按照行列提取像素值(快)
point_value = float(grayImg.data[row * grayImg.cols + col]);
cv::Mat temp_img;
// 初始化为0
temp_img = cv::Mat::zeros(rows, cols, CV_8UC1);
float sum_bright = cv::sum(GrayImg(cv::Rect(col_min,row_min,w,h)))[0]
#include
// 导入cv2模块
#include
#include
// 在不考虑细致实现的情况下,粗暴导入所有cv2模块
// #include
using namespace std;
using namespace cv;
int main(int argc, char **argv) {
// 图片路径,可以写字符串,也可以用argv从启动参数导入
// 注意在IDE里面编译时需要考虑到当前运行路径在build文件夹内
string img_path = "../test.jpeg";
// 创建图片变量
cv::Mat image;
// 读图片
image = cv::imread(img_path);
// 验证图片是否正确读取,如果data为空指针说明读取失败
if (image.data == nullptr) {
cerr << "图片" << img_path << "读取失败。" << endl;
return -1;
}
// 浅拷贝,并不会复制内存数据
cv::Mat image_shallow_copy = image;
// “切片”,将左上角100*100的块置零
image_shallow_copy( cv::Rect(0, 0, 100, 100)).setTo(0);
// 显示原图片的像素,发现被改变了
cout << "浅拷贝图片的第一个像素:" << image_shallow_copy.at<cv::Vec3b>(0, 0) << endl;
cout << "原始图片的第一个像素:" << image.at<cv::Vec3b>(0, 0) << endl;
// 深拷贝,会复制内存数据
cv::Mat image_deep_copy = image.clone();
// “切片”,将左上角100*100的块置255
image_deep_copy( cv::Rect(0, 0, 100, 100)).setTo(255);
// 显示原图片的像素,发现被改变了
cout << "深拷贝图片的第一个像素:" << image_deep_copy.at<cv::Vec3b>(0, 0) << endl;
cout << "原始图片的第一个像素:" << image.at<cv::Vec3b>(0, 0) << endl;
}
运行得到:
浅拷贝图片的第一个像素:Ä0, 0, 0Å
原始图片的第一个像素:Ä0, 0, 0Å
深拷贝图片的第一个像素:Ä255, 255, 255Å
原始图片的第一个像素:Ä0, 0, 0Å
// 翻转图片
// 水平翻转
cv::flip(image_input, image_output, 1);
// 垂直翻转
cv::flip(image_input, image_output, 0);
// 水平垂直翻转
cv::flip(image_input, image_output, -1);
// 固定阈值
cv::threshold(img, binary, Threshold, 255, cv::THRESH_BINARY);
// 大津法
int Threshold = cv::threshold(img, binary, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
// 对二值化图像进行轮廓检测
std::vector< std::vector< cv::Point> > contours;
cv::findContours(bianry, contours, cv::noArray(),cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE);
cv::meanStdDev()
来自参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔
#include
#include
using namespace std;
// 也可以直接通过库函数cv::Undisort()实现
// 图片路径
string image_file = "../distorted.png";
int main(int argc, char **argv) {
// 配置畸变参数
double k1 = -0.28340811, k2 = 0.07395907, p1 = 0.00019359, p2 = 1.76187114e-05;
// 配置内部参数
double fx = 458.654, fy = 457.296, cx = 367.215, cy = 248.375;
// CV_8UC1灰度图
cv::Mat image = cv::imread(image_file, 0);
// 定义去畸变后的新图像
int rows = image.rows, cols = image.cols;
cv::Mat image_undistort = cv::Mat(rows, cols, CV_8UC1);
// 计算去畸变后的图像
for (int v=0; v<rows; v++) {
for (int u=0; u<cols; u++) {
// 将当前的(u, v)对应到畸变图像中的(u_distorted, v_distorted)
double x = (u-cx) / fx, y = (v-cy) / fy;
double r = sqrt(x * x + y * y);
double x_distorted = x * (1+ k1*r*r + k2*r*r*r*r) + 2*p1*x*y + p2*(r*r+2*x*x);
double y_distorted = y * (1+ k1*r*r + k2*r*r*r*r) + 2*p2*x*y + p1*(r*r+2*y*y);
double u_distorted = fx * x_distorted + cx;
double v_distorted = fy * y_distorted + cy;
// 使用最近邻插值进行赋值
if (u_distorted >= 0 && v_distorted >=0 && u_distorted < cols && v_distorted < rows) {
image_undistort.at<uchar>(v, u) = image.at<uchar>((int) v_distorted, (int) u_distorted);
}
else {
image_undistort.at<uchar>(v, u) = 0;
}
}
}
// 显示去畸变图
cv::imshow("distored", image);
cv::imshow("undistored", image_undistort);
cv::waitKey();
return 0;
}
来自参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
// 使用opencv实现ORB特征匹配
int main(int argc, char **argv)
{
// 为了在ide里面方便运行,暂时改为写死的固定路径,注释掉原作者的这部分代码
// if (argc != 3) {
// cout << "usage: feature_extraction img1 img2" << endl;
// return 1;
// }
// 读取图像,修改为从固定路径读取
// 因为在build文件夹内,因此进入上级目录
Mat img_1 = imread("../1.png", CV_LOAD_IMAGE_COLOR);
Mat img_2 = imread("../2.png", CV_LOAD_IMAGE_COLOR);
// 用断言确保两张图片都已经成功被读取
assert(img_1.data != nullptr && img_2.data != nullptr);
// 创建两组关键点的vector
std::vector<KeyPoint> keypoints_1, keypoints_2;
// 创建两个矩阵
Mat descriptors_1, descriptors_2;
// 初始化
// 提取FAST关键点用的工具
Ptr<FeatureDetector> detector = ORB::create();
// 提取BRIEF描述子用的工具
Ptr<DescriptorExtractor> descriptor = ORB::create();
// 使用汉明距离作为匹配标准
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
// 第一步:检测 Oriented FAST 角点位置
// 检测半径为3的圆上是否有足够多的点和当前点比起来亮度差异超过一定比例
// 基于图像金字塔的上下层来构建尺度不变性
// 基于灰度质心法构建旋转不变性
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
detector->detect(img_1, keypoints_1);
detector->detect(img_2, keypoints_2);
// 第二步:根据FAST角点位置计算 BRIEF 描述子
descriptor->compute(img_1, keypoints_1, descriptors_1);
descriptor->compute(img_2, keypoints_2, descriptors_2);
// 结束检测部分,计算消耗时间
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "extract ORB cost = " << time_used.count() << " seconds. " << endl;
Mat outimg1;
// 绘制并显示特征图
drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("ORB features", outimg1);
// 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离作为匹配标准
vector<DMatch> matches;
t1 = chrono::steady_clock::now();
// 匹配2张图像的描述子
matcher->match(descriptors_1, descriptors_2, matches);
// 再次计算消耗时间
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "match ORB cost = " << time_used.count() << " seconds. " << endl;
// 第四步:匹配点对筛选
// 计算vector matches中的最小距离和最大距离
// 此处使用了C++中的lambda函数[](){}
auto min_max = minmax_element(matches.begin(), matches.end(), [](const DMatch &m1, const DMatch &m2) { return m1.distance < m2.distance; });
double min_dist = min_max.first->distance;
double max_dist = min_max.second->distance;
printf("-- Max dist : %f \n", max_dist);
printf("-- Min dist : %f \n", min_dist);
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
// 将所有符合条件的匹配放入了一个新的vector容器内
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("all matches", img_match);
imshow("good matches", img_goodmatch);
waitKey(0);
return 0;
}
自己写的代码,记录一下
将图片与std::vector
// 开始保存
if (need_save >= 0) {
if (need_save == 0) {
// 将数据保存到这里
// 保存图片
string save_path = "存储目录";
cv::imwrite( save_path + "cur_img.jpg", cur_img);
cv::imwrite( save_path + "forw_img.jpg", forw_img);
// 保存特征点
ostringstream out1;
// 将vector中的转换为字符串储存
for (auto p = cur_pts.begin(); p != cur_pts.end(); p++) {
out1 << to_string((*p).x) << " " << to_string((*p).y) << " " << endl;
}
// 写入文件
ofstream fout1(save_path + "cur_pts.txt");
if (fout1) {
//将out流转换为string类型,写入到文件流中
fout1 << out1.str() << endl;
fout1.close();
}
ostringstream out2;
// 将vector中的转换为字符串储存
for (auto p = forw_pts.begin(); p != forw_pts.end(); p++) {
out2 << to_string((*p).x) << " " << to_string((*p).y) << " " << endl;
}
// 写入文件
ofstream fout2(save_path + "forw_pts.txt");
if (fout2) {
//将out流转换为string类型,写入到文件流中
fout2 << out2.str() << endl;
fout2.close();
}
}
need_save -= 1;
}
从磁盘读取图片与float型的特征点坐标,用ORB进行匹配并输出匹配连接图像:
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char **argv) {
string save_path = "存储目录";
// 从磁盘上读图片
cv::Mat cur_img = cv::imread(save_path + "cur_img.jpg", 0);
cv::Mat forw_img = cv::imread(save_path + "forw_img.jpg", 0);
// 从磁盘上读取特征点,并以vector的形式储存
string temp;
int pos;
float x;
float y;
ifstream cur_pts_file(save_path + "cur_pts.txt");
// 以vector的形式储存
vector<cv::KeyPoint > cur_pts;
if (!cur_pts_file.is_open())
{
cout << "未成功打开文件cur_pts.txt" << endl;
}
while(getline(cur_pts_file, temp))
{
if (temp.length() != 0) {
// 获得字符串
// 找到用于分割的空格位置
pos = temp.find(" ");
// x坐标
x = stof(temp.substr (0, pos));
// y坐标
y = stof(temp.substr (pos+1, temp.length()-pos-1));
// 将转换为
KeyPoint temp_keypoint;
temp_keypoint.pt = Point2f(x, y);
// 将放入vector
cur_pts.push_back(temp_keypoint);
}
}
cur_pts_file.close();
ifstream forw_pts_file(save_path + "forw_pts.txt");
vector<cv::KeyPoint > forw_pts;
if (!forw_pts_file.is_open())
{
cout << "未成功打开文件forw_pts.txt" << endl;
}
while(getline(forw_pts_file, temp))
{
if (temp.length() != 0) {
// 获得字符串
// 找到分割的空格位置
pos = temp.find(" ");
// x坐标
x = stof(temp.substr (0, pos));
// y坐标
y = stof(temp.substr (pos+1, temp.length()-pos-1));
// 将转换为
KeyPoint temp_keypoint;
temp_keypoint.pt = Point2f(x, y);
// 将放入vector
forw_pts.push_back(temp_keypoint);
}
}
forw_pts_file.close();
// // 打印行数进行验证是否都加载正常
// cout << cur_pts.size() << endl;
// cout << forw_pts.size() << endl;
// 创建匹配
vector<DMatch> matches;
BFMatcher bfMatcher(NORM_L2);
//计算特征点描述子,特征向量提取
Mat dst1, dst2;
// 使用SIFT会产生double free or corruption (!prev) 报错
// Ptr descriptor = SiftDescriptorExtractor::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// 计算描述子
descriptor->compute(cur_img, cur_pts, dst1);
descriptor->compute(forw_img, forw_pts, dst2);
// 进行匹配
bfMatcher.match(dst1, dst2, matches);
// 用于输出的图像
cv::Mat out_image;
// 绘制输出图像
drawMatches(cur_img, cur_pts, forw_img, forw_pts, matches, out_image);
// 显示输出图像
imshow("连线图像", out_image);
waitKey(0);
return 0;
}
需要导入额外库文件
#include
互相转换
// 将cv::Mat转换为Eigen::Matrix
cv::cv2eigen(mat_cv, matrix_eigen);
// 将Eigen::Matrix转换为cv::Mat
cv::eigen2cv(matrix_eigen, mat_cv);
cv::namedWindow("show", 0);
cv::resizeWindow("show", cv::Size(1920, 1080));
cv::imshow("show", img);