【SLAM十四讲】CH5. imageBasics.cpp 学习笔记

先放书里的源码。
ch5/imageBasics/imageBasics.cpp

#include <iostream>
#include <chrono>

using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int main(int argc, char **argv) {
  // 读取argv[1]指定的图像
  cv::Mat image;
  image = cv::imread(argv[1]); //cv::imread函数读取指定路径下的图像

  // 判断图像文件是否正确读取
  if (image.data == nullptr) { //数据不存在,可能是文件不存在
    cerr << "文件" << argv[1] << "不存在." << endl;
    return 0;
  }

  // 文件顺利读取, 首先输出一些基本信息
  cout << "图像宽为" << image.cols << ",高为" << image.rows << ",通道数为" << image.channels() << endl;
  cv::imshow("image", image);      // 用cv::imshow显示图像
  cv::waitKey(0);                  // 暂停程序,等待一个按键输入

  // 判断image的类型
  if (image.type() != CV_8UC1 && image.type() != CV_8UC3) {
    // 图像类型不符合要求
    cout << "请输入一张彩色图或灰度图." << endl;
    return 0;
  }

  // 遍历图像, 请注意以下遍历方式亦可使用于随机像素访问
  // 使用 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;

  // 关于 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::waitKey(0);

  // 使用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);

  // 对于图像还有很多基本的操作,如剪切,旋转,缩放等,限于篇幅就不一一介绍了,请参看OpenCV官方文档查询每个函数的调用方法.
  cv::destroyAllWindows();
  return 0;
}

这段代码首先需要完成以下几个功能:

  1. 使用opencv读取图片、并对其中像素进行修改。
  2. 将修改后的图片,使用GUI显示出来。
  3. 并且记录其中操作的耗费时间。

所以可以使用这几个包:

  1. opencv core.OpenCV的核心包,能够满足几乎所有的基本图像操作要求。
  2. opencv highgui.OpenCV自带的gui包,虽然OpenCV也可以结合其他比如Qt之类的GUI包使用,但是在测试一些单一功能时,使用highgui会更方便些。
  3. chrono.常见的计时器包。

那么接下来要拟定算法流程。
1.使用OpenCV读取图像文件。
2.对其中图像像素进行操作,满足修改条件。
3.使用highgui读取图像并输出显示。

//只需要调用这一个函数
//cv::imshow("这是GUI的title",一个Mat类型的图像数据);
cv::imshow("修改前的image",image);

4.给以上模块套上chrono计时器。

//读取当前时间,使用chrono::steady_clock::now()。
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
//读取程序运行完成后时间。
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
//注意time_point是一个复杂的数据结构,所以想要得出时间差需要调用chrono包内的函数。
chrono::duration<double> time_used = chrono::duration_cast < chrono::duration < double >> (t2 - t1);
cout << "遍历图像用时:" << time_used.count() << " 秒。" << endl;

首先完成第一部分的功能:

//首先创建一个OpenCV的图像矩阵cv::Mat。
//这个数据结构可以理解为以矩阵的形式存储了图像的像素,并且附注了一些其他信息。不过调用方法和常见的矩阵数据类型有较大差异。
//该数据结构有五种基本属性。图像相关信息flags、维度数dims、列数cols、行数rows、实际数据data。
//以上属性在读取完成后进行输出检测是否符合预期,图像类型flags一般使用成员方法cv::Mat::type()进行检测。
cv::Mat image;
//然后使用imread函数对图片进行读取。
//imread函数是opencv的一个常用图像读取函数,
//输入参数const String & filename为图像路径,支持jpg jpge png webp等几乎一切常见图像类型。
//另一个参数int flags则表示读取图像的形式,有-1、0、1三种取值,从左到右,分别代表以原始图像读取、以灰度图、以彩色图,无特殊需求则不需要变更。
image = cv::imread(argv[1]);
//现在对读取后的图像进行检测。
//首先检测其是否已成功读入。这里使用成员函数cv::Mat::empty进行条件检测。
//如果希望读取一个空图片(那为什么不直接在内存中创建一个呢),则应使用教材中的检测条件 image.data == nullptr
if(!image.empty()){
  //返回错误信息
  cerr << "文件" << argv[1] << "不存在。" << endl;
}
//接下来检测行列信息、通道数与图像类型。
//Mat::channels()方法是对flags属性的解析,返回通道数。
cout << "rows:" << image.rows << ".cols:" << cols << ".channels:" << image.channels() << endl;
//Mat::type()对flags属性解析后返回int数据,所以需要与宏定义进行对比来检查。8U代表8位、无符号,C1、C3代表通道数1、通道数3。
if (image.type() != CV_8UC1 && image.type() != CV_8UC3) {
  // 图像类型不符合要求
  cout << "请输入一张彩色图或灰度图." << endl;
  return 0;
}

接下来完成程序第二部分的功能,对图像的像素进行操作。

首先实现对指定像素中指定通道的数值读取与操作。
关于使用OpenCV对Mat数据结构的像素操作进行操作,有三种方法。详情可看这里:Mat中像素的获取与赋值
使用cv::Mat::ptr()成员方法,虽然可读性可能有些不便,但是效率和健全性上更有保障。
ptr()方法输入行数y(数据类型由模板指定),返回图像第y行的首地址(数据类型为uchar*)。
要访问Mat中的第y行,第x列像素的第c个通道的值,可以使用以下方法。

//首先创建一个行指针,因为上文检测的图像类型都要求为8U,所以模板指定uchar类型。
uchar * row_ptr = image.ptr<uchar>(y);
//然后再创建一个执行第y行,第x列的像素指针。
//可将row_ptr看做数组,存储了这一行所有像素的信息,访问参数为列数x乘以图像通道数。
//再对访问后的row_ptr[argv]进行取地址,将值赋给data_ptr。
uchar * data_ptr = &row_ptr[x * image.channels()];
//data_ptr此时可看做存储了第y行,第x列像素信息的数组,其长度等于通道数。所以现在访问第c个通道的值。
//前文提到过,允许的两种像素都是8U型,所以读取到的值应为uchar
uchar data = data_ptr[c];
//如果想改变这个通道的值,应该直接对这个数组进行操作,因为我们是使用地址来进行复制,所以可以影响到原本的数据。
data_ptr[c] = 0 ;

在教材中,要求我们将一张图像复制,并对复制品左上角100*100的图像全改成白色。
虽然以上方法也可以实现,但是opencv也提供了方便的函数可供调用。

//首先,使用clone方法对图像数据进行复制。因为Mat数据类型是一个结构体,并且Mat.data是一个指针。
image_clone = image.clone();
//对复制后的图像进行展示
cv::imshow("image_clone",image_clone);
//然后对克隆得到的image_clone进行重载,输入一个Rect类实例来确定范围,重载后得到ROI(感兴趣区域)
//再对这个ROI调用.setTo方法即可仅修改原图像上ROI区域范围的像素。
image_clone(cv::Rect(0,0,100,100)).setTo(255);
//对修改后的图像进行展示
cv::imshow("image_clone",image_clone);

接下来,再将以上的代码部分拼接在一起,便可完成imageBasics程序的要求。
记得先把包都导进来。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值