时隔多年,又回来搞14讲后边的实践部分了

5.3 实践:计算机中的图像
5.3.1 OpenCV的基本使用方法
安装OpenCV,build之后别忘了sudo make install将OpenCV安装到电脑上
实验源代码为imageBasics.cpp,因为创建了build文件夹,所以可执行文件在build里
经过cmake ..和make之后,使用如下指令运行:

./imageBasics /home/lihaha/桌面/ch5_1.0/imageBasics/ubuntu.png

前者是可执行文件,后者是某张图片的绝对路径
分别对应argv[0]argv[1],程序中用后者作为了图片的原始路径,所以在命令里要放进去
代码如下:

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

该代码展示了OpenCV最基本的用法,可供复习使用

5.3.2 图像去畸变
源代码为undistortImage.cpp
由透镜形状引起的畸变叫做径向畸变,包括桶形畸变和枕形畸变
图像中一条直线,往里弯是桶形,往外弯是枕形【我个人习惯感觉桶形畸变是鼓起来,枕形相反】
组装过程导致的成像平面与透镜不平行,带来的是切向畸变
去畸变的思路是这样的:

创建一个和原来一样大的图片区域
遍历这个新图片的所有像素(u v)
通过畸变参数,计算(u,v)处畸变后的坐标
把原图上的畸变后的坐标处的像素值赋值给新图片(涉及到插值)
遍历完成后,新图片就是去畸变的了
上述思路很关键奥!
编译之后直接运行就好,注意修改代码中的图片路径
(与上边的实验不同,这里使用的路径是在程序里写好的)
代码如下:

#include <opencv2/opencv.hpp>
#include <string>

using namespace std;

string image_file = "/home/lihaha/桌面/ch5_1.0/imageBasics/distorted.png"; // 请确保路径正确

int main(int argc, char **argv)
{

  // 本程序实现去畸变部分的代码。尽管我们可以调用OpenCV的去畸变,但自己实现一遍有助于理解。
  // 畸变参数
  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::Mat image = cv::imread(image_file, 0); // 图像是灰度图,CV_8UC1
  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) + p1 * (r * r + 2 * y * y) + 2 * p2 * x * 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) = 255;
      }
    }
  }

  // 画图去畸变后图像
  cv::imshow("distorted", image);
  cv::imshow("undistorted", image_undistort);
  cv::waitKey();
  return 0;
}

5.4 实践:3D视觉

5.4.1 双目视觉

这里的代码没看太懂,需要注意的也是路径在程序里写好了,要改一下,改成自己的路径
而且发现一个神奇的操作,vscode可以直接复制文件的路径,如图所示:

在这里插入图片描述

这个好方便哦!程序把双目视觉恢复到pangolin中,超级好看

在这里插入图片描述

它是立体的,很漂亮!5.4.2 RGB-D视觉这个也没看太懂,先放上结果吧~

在这里插入图片描述