3.1 认识图像数据
学习OpenCV的第一件事,就是要搞明白图像的存在形式。话不多说,下面基于程序具体讲解。本章C++示例代码将采用CMake方式编译(参见第2章)。
3.1.1 获取图像数据
利用OpenCV可以从图片文件、视频文件或相机设备获取图像数据,下面结合例子进行讲解。关于新建C++工程和CMakeLists.txt编译配置文件的内容就不再展开讲解了,还不熟悉的朋友可以回顾一下第2章的相关内容。从图片文件中获取图像数据的方法image_from_img.cpp,如代码清单3-1所示。
代码清单3-1 image_from_img.cpp文件内容
1 #include <opencv2/opencv.hpp> 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章相关内容。
上面通过cv::imread方法从图片文件中获取图像数据,只需要将cv::imread方法替换成cv::VideoCapture方法就能实现从视频文件或相机中获取图像数据,即利用cv::VideoCapture可以直接访问视频文件或相机设备。当然对于机器人来说,还可以用ROS来驱动相机设备,并将图像直接发布到ROS话题以便于其他ROS节点订阅。用ROS来驱动相机的具体内容将在第4章中介绍。
3.1.2 访问图像数据
通过相机或其他设备扫描现实世界就能得到图像,这种图像在计算机中由一个个的像素点组成,如图3-1所示。图像处理的过程,其实就是对这些像素点做各种运算。
图3-1 图像中的像素点
在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,即蓝绿红。
各种复杂的图像处理算法,都是通过对每个像素点做运算完成的。因此需要知道如何访问图像中的每个像素点,也就是像素遍历,通过Mat类的at方法就能遍历对应行和列坐标下的像素,对于多通道的图像,还需要对每个通道再进行一次遍历。遍历像素的方法还有很多种,由于篇幅限制就不过多讲解了,用到再去查阅具体资料就行了。
图像通常由多个通道组成,有时候需要对单个通道进行处理,这时就需要将图像的通道分离出来。OpenCV通过split函数和merge函数实现通道分离与通道混合。