一起自学SLAM算法:3.1 认识图像数据

  连载文章,长期更新,欢迎关注:


写在前面

第1章-ROS入门必备知识

第2章-C++编程范式

第3章-OpenCV图像处理

        3.1 认识图像数据

        3.2 图像滤波

        3.3 图像变换

        3.4 图像特征点提取

...

更多精彩内容,持续更新中......


大家如有任何技术问题,欢迎加入QQ技术群(117698356),进群一起交流讨论

学习OpenCV的第一件事,就是要搞明白图像的存在形式。话不多说,从程序中具体讲解。本章C++示例代码将采用CMake方式编译,关于CMake编译的内容已经在第2章中讲解过了,不在重复。

3.1.1 获取图像数据

利用OpenCV可以从图片文件、视频文件和相机设备获取图像数据,下面结合例子分别对这三种方式进行讲解。关于新建C++工程和CMakeLists.txt编译配置文件的内容不再展开讲解,不熟悉的朋友可以回头参考第2章的内容。

1.从图片文件获取

首先,来看一下image_from_img.cpp文件,见代码清单3-1所示。

代码清单3-1  image_from_img.cpp文件内容

 1 #include 
 2 
 3 int main(int argc, char** argv)
 4 {
 5     cv::Mat img=cv::imread("1.jpg");
 6     cv::imshow("[img1]",img);
 7     cv::waitKey(0);
 8 
 9     return 0;
10 }

第1行,这个是使用OpenCV库必须包含的头文件。

第5行,利用imread函数来载入1.jpg这个图片文件,载入后的图像数据存储在Mat类对象中。OpenCV库中的函数和类都需要加上cv作用域来使用。

第6行,利用imshow函数显示图像数据。

第7行,等待键盘输入任意值,终止图像显示。

然后,看一下CMakeLists.txt文件,见代码清单3-2所示。

代码清单3-2  CMakeLists.txt文件内容

1 cmake_minimum_required(VERSION 2.8)
2 project(image_from_img)
3 
4 find_package(OpenCV REQUIRED)
5 add_executable(image_from_img image_from_img.cpp)
6 target_link_libraries(image_from_img ${OpenCV_LIBS})

第4行,添加OpenCV库的依赖项。

第5行,添加可执行文件。

第6行,将可执行文件和OpenCV库进行连接。

编译和运行就不赘述了,不熟悉的朋友请阅读第2章相关内容。

2.从视频文件获取

首先,来看一下image_from_vid.cpp文件,见代码清单3-3所示。

代码清单3-3  image_from_vid.cpp文件内容

 1 #include 
 2 
 3 int main(int argc, char** argv)
 4 {
 5     cv::VideoCapture cap("1.mp4");
 6 
 7     cv::Mat frame;
 8     while(1)
 9     {
10         cap>>frame;
11         if(frame.empty()){break;}
12         cv::imshow("[vid1]",frame);
13         cv::waitKey(10);        
14     }
15     return 0;
16 }

第1行,这个是使用OpenCV库必须包含的头文件。

第5行,创建视频捕获类的对象,并利用1.mp4视频文件进行初始化。

第7行,创建Mat类的对象,用于存放从视频中取出的帧。

第8~14行,循环从视频中取出帧,并显示。

然后,看一下CMakeLists.txt文件,见代码清单3-4所示。

代码清单3-4  CMakeLists.txt文件内容

1 cmake_minimum_required(VERSION 2.8)
2 project(image_from_vid)
3 
4 find_package(OpenCV REQUIRED)
5 add_executable(image_from_vid image_from_vid.cpp)
6 target_link_libraries(image_from_vid ${OpenCV_LIBS})

第4行,添加OpenCV库的依赖项。

第5行,添加可执行文件。

第6行,将可执行文件和OpenCV库进行连接。

编译和运行就不赘述了,不熟悉的朋友请阅读第2章相关内容。

3.从相机设备获取

首先,来看一下image_from_cam.cpp文件,见代码清单3-5所示。

代码清单3-5  image_from_cam.cpp文件内容

 1 #include 
 2 
 3 int main(int argc, char** argv)
 4 {
 5     cv::VideoCapture cap(0);
 6 
 7     cv::Mat frame;
 8     while(1)
 9     {
10         cap>>frame;
11         if(frame.empty()){break;}
12         cv::imshow("[cam1]",frame);
13         cv::waitKey(10);        
14     }
15     return 0;
16 }

不难发现,从相机设备获取图像的示例和从视频文件获取图像的示例基本上一样。只不过区别在于,将VideoCapture类的对象用相机设备编号来初始化。如果系统中只插入了一个相机,相机编号就是0。如果继续插入相机设备,相机编号就递增。

然后,看一下CMakeLists.txt文件,见代码清单3-6所示。

代码清单3-6  CMakeLists.txt文件内容

1 cmake_minimum_required(VERSION 2.8)
2 project(image_from_cam)
3 
4 find_package(OpenCV REQUIRED)
5 add_executable(image_from_cam image_from_cam.cpp)
6 target_link_libraries(image_from_cam ${OpenCV_LIBS})

编译和运行就不赘述了,本示例说明了用OpenCV驱动相机设备的方法,更一般的方法是用ROS来驱动相机设备,将图像直接发布到ROS话题上,方便其他ROS节点订阅。关于用ROS来驱动相机的具体内容,将在第4章展开讲解。

3.1.2 访问图像数据

通过相机或其他设备扫描现实世界就能得到图像,计算机中的图像由一个一个的像素点组成,如图3-1所示。图像处理的过程,其实就是对这些像素点做各种运算。

一起自学SLAM算法:3.1 认识图像数据_第1张图片

图3-1  图像中的像素点

1.cv::Mat类

在OpenCV中,图像被存储在Mat类中,Mat类由矩阵头矩阵指针组成。矩阵头中存放矩阵尺寸、存储方法、存储地址等信息;矩阵指针用于指向像素值被存放的具体内存区域。Mat类非常智能,不必手动为其开辟和释放内存空间,OpenCV采用引用机制来智能管理。依据像素的通道组合和像素值编码方式,将创建不同的Mat对象。比如下面的例子,创建一个2×2大小的图像,像素点为3通道RGB彩色,像素值用8位unsigned char数据类型表示:

cv::Mat img(2,2,CV_8UC3,cv::Scalar(1,100,255));

当然,创建Mat类对象的构造函数有多种形式,这里篇幅限制不过多讲解了,在学习过程中接触到了再具体查阅资料就行了。

除了Mat类,还有一些类在OpenCV中也经常用到,这里给大家列出了,便于学习查找。

  • cv::Point类:表示点;
  • cv::Scalar类:表示颜色;
  • cv::Size类:表示尺寸;
  • cv::Rect类:表示矩阵。

然后,还有一个重要的函数——颜色空间转换函数cv::cvtColor(),可以实现RGB、HSV、HSI等颜色空间的转换。颜色空间,其实就是各个彩色分量的组合方式,最常见的就是RGB颜色空间。这里特别提醒,OpenCV中默认的图片通道存储顺序是BGR,即蓝绿红。

2.像素遍历

各种复杂的图像处理算法,都是通过对每个像素点做运算完成的。因此,需要知道如何访问图像中的每个像素点,也就是像素遍历。下面举一个像素值减半的函数,来说明像素遍历的过程:

void PixelHalf(cv::Mat& in,cv::Mat& out)
{
    out=in.clone();
    int row=out.rows;
    int col=out.cols;
    for(int i=0;i(i,j)[0]=out.at(i,j)[0]/2;
            out.at(i,j)[1]=out.at(i,j)[0]/2;
            out.at(i,j)[2]=out.at(i,j)[0]/2;
        }
}

上面只是给出了函数主体,如果想要构建完整的可运行程序,可以将该函数放到一个完整的代码工程中调用。不难发现,通过Mat类的at方法,就能遍历对应行和列坐标下的像素。由于例子中是遍历3通道的图像,所以还要对每个通道再进行一遍历。当然,遍历像素的方法还有很多种,这里篇幅限制不过多讲解了,在学习过程中接触到了再具体查阅资料就行了。

3.通道分离

上面已经讲到过,图像通常由多个通道组成,比如RGB彩色图像由R、G和B三个通道组成。有时候需要对单个通道进行处理,这时就需要将图像的通道分离出来。OpenCV通过split和merge两个函数实现通道分离和通道混合。分离和混合的过程如下:

std::vector channels;
cv::Mat blue_channel;
cv::Mat green_channel;
cv::Mat red_channel;
cv::Mat img=cv::imread(“1.jpg”);

//通道分离
cv::split(img,channels);
blue_channel=channels.at(0);
green_channel=channels.at(1);
red_channel=channels.at(2);

//通道混合
cv::Mat merge_img;
cv::merge(channels,merge_img);

上述代码,先创建一个vector容器的对象channels用于存放分离开来的各个通道数据,然后调用split函数进行通道分离,将分离出来的通道取出来分别转存到blue_channel、green_channel和red_channel中,这样通道分离就完成了。最后只需要用merge函数就能将分离的通道混合起来。

参考文献

【1】 张虎,机器人SLAM导航核心技术与实战[M]. 机械工业出版社,2022.

你可能感兴趣的:(一起自学SLAM算法,opencv,算法,c++,计算机视觉,自动驾驶)