C++学习笔记——opencv2模块(图像处理)

C++学习笔记——opencv2模块(图像处理)

  • 1. 使用方式
  • 2. 注意点
  • 3. 图片读写与属性
    • 3.1 图片基本操作
    • 3.2 遍历像素点
    • 3.3 提取指定像素值
    • 3.4 初始化为0的矩阵
    • 3.5 计算某个灰度切片区域的像素和
  • 4. 切片,浅拷贝与深拷贝
  • 5. 图片操作函数
    • 5.1 图片翻转
    • 5.2 二值化
    • 5.3 轮廓检测
    • 5.4 计算均值与标准差
  • 6. 一些代码样例
    • 6.1 去畸变代码
    • 6.2 ORB特征匹配代码
    • 6.3 将特征数据写入磁盘/从磁盘读取特征数据并匹配输出
  • 7. cv::Mat与Eigen::Matrix互相转换
  • 8. 在窗口中显示放大后的图像

用于计算图像处理的opencv2,只不过这次用的不是python的版本,而是C++的版本。

参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔

1. 使用方式

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} )

2. 注意点

读取数据的数据类型使用unsigned char:因为int类型在不同操作系统平台下长度不同,而uchar类型在所有平台上长度都是一样的。

3. 图片读写与属性

3.1 图片基本操作

#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

3.2 遍历像素点

较慢的遍历图片中每个像素点(灰度)

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];
    }

3.3 提取指定像素值

用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]);

3.4 初始化为0的矩阵

cv::Mat temp_img;
// 初始化为0
temp_img = cv::Mat::zeros(rows, cols, CV_8UC1);

3.5 计算某个灰度切片区域的像素和

float sum_bright  = cv::sum(GrayImg(cv::Rect(col_min,row_min,w,h)))[0]

4. 切片,浅拷贝与深拷贝

#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Å

5. 图片操作函数

5.1 图片翻转

// 翻转图片
// 水平翻转
cv::flip(image_input, image_output, 1);
// 垂直翻转
cv::flip(image_input, image_output, 0);
// 水平垂直翻转
cv::flip(image_input, image_output, -1);

5.2 二值化

// 固定阈值
cv::threshold(img, binary, Threshold, 255, cv::THRESH_BINARY);

// 大津法
int Threshold = cv::threshold(img, binary, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);

5.3 轮廓检测

// 对二值化图像进行轮廓检测
std::vector< std::vector< cv::Point> > contours;
cv::findContours(bianry, contours, cv::noArray(),cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE);

5.4 计算均值与标准差

cv::meanStdDev()

6. 一些代码样例

6.1 去畸变代码

来自参考书籍:《视觉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;
}

6.2 ORB特征匹配代码

来自参考书籍:《视觉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;
}

6.3 将特征数据写入磁盘/从磁盘读取特征数据并匹配输出

自己写的代码,记录一下
将图片与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; 
} 

7. cv::Mat与Eigen::Matrix互相转换

需要导入额外库文件

#include 

互相转换

// 将cv::Mat转换为Eigen::Matrix
cv::cv2eigen(mat_cv, matrix_eigen);

// 将Eigen::Matrix转换为cv::Mat
cv::eigen2cv(matrix_eigen, mat_cv);

8. 在窗口中显示放大后的图像

cv::namedWindow("show", 0);
cv::resizeWindow("show", cv::Size(1920, 1080));
cv::imshow("show", img);

你可能感兴趣的:(C++/slam学习笔记,图像处理,c++,计算机视觉)