天天看点

《OpenCV图像处理》——1.5 读取和写入图像文件

本节书摘来自华章计算机《opencv图像处理》一书中的第1章,第1.5节,作者:[西]葛罗瑞亚·布埃诺·加西亚(gloria bueno garcía)著,更多章节内容可以访问云栖社区“华章计算机”公众号查看

图像处理依赖于得到一幅图像(例如,一张照片和一个视频帧)并通过应用信号处理技术的“播放”(playing)来得到预期的结果。本节展示如何使用由opencv提供的函数从文件中读取图像。

1.5.1 基本api概念

mat类是存储和操作opencv中图像的主要数据结构。这个类是在core模块中定义的。opencv已经实现了对于这些数据结构自动分配和释放内存的机制。但是,当数据结构共享相同的缓冲存储器时,程序员仍然应该特别注意。例如,赋值运算符并没有从一个对象(mat a)到另一个对象(mat b)复制内存内容;而只是对其引用(相应内容的存储地址)的复制。之后,一个对象(a或b)的改变对两个对象都有影响。为了复制一个mat对象的内存内容,应该使用成员函数mat::clone()。

opencv中的许多函数在处理密集的单通道或多通道数组时,常使用mat类。但是在某些场合,使用一个不同的数据类型可能很方便,例如,std::vector< >、matx< >、vec< >或scalar。为此,opencv提供了代理类inputarray和outputarray,允许前面的任意类型作为函数的参数使用。

mat类用于密集的n维单通道或多通道数组。实际上它可以存储实数或复数值向量和矩阵、彩色图像或灰度图像、直方图、点云(point cloud)等。

有许多种不同的方式可用来创建一个mat对象,最流行的方法是构造函数,其数组的大小和类型被指定为:

《OpenCV图像处理》——1.5 读取和写入图像文件

数组元素的初始值可以由scalar类设置为一个典型的四元素向量(对于存储在数组中的图像的每个rgb和透明度分量)。下面展示mat的一个使用示例:

《OpenCV图像处理》——1.5 读取和写入图像文件

datatype类定义opencv的基本数据类型。基本数据类型可以是bool、unsigned char、signed char、unsigned short、signed short、int、f?loat、double或者是以这些基本类型之一的值构成的一个元组(或称数组)。任何基本类型都可以用一个标识符以下面的形式定义:

《OpenCV图像处理》——1.5 读取和写入图像文件

在上面的代码中,u、s和f分别代表unsigned、signed和f?loat数据类型。对于单通道数组,可应用下面的枚举类型,其数据类型的描述为:

《OpenCV图像处理》——1.5 读取和写入图像文件

此处,需要注意的是,这三个声明是等价的:cv_8u、cv_8uc1和cv_8uc(1)。该单通道声明最适合于灰度图像的整型数组,然而一个数组的三通道声明更适合于具有三个分量(例如,rgb、brg、hsv等)的图像。对于线性代数运算,可以使用f?loat(f)类型的数组。

我们可以为多通道数组(高达512个通道)定义上面所有的数据类型。图1-4说明一幅具有单个通道(cv_8u,灰度)图像的内部表示和具有三个通道(cv_8uc3,rgb)的同一幅图像。图1-4是通过在一个opencv可执行文件(showimage示例)的窗口中所显示的一幅图像的放大来获得的:

《OpenCV图像处理》——1.5 读取和写入图像文件

使用opencv函数正确地保存一幅rgb图像是很重要的,该图像必须按照bgr通道顺序在内存中存储。按照同样的方式,当从一个文件中读取一幅rgb图像时,应按照bgr通道顺序将其存储在内存中。而且,需要补充第四个通道(alpha)来处理具有三个通道(rgb)的图像,加上了一个透明度。对于rgb图像,整数值越大,意味着像素更亮或alpha通道更透明。

所有的opencv类和函数都在cv命名空间(namespace)中,因此,在源代码中还有如下两个选项:

在包含头文件后还应添加使用命名空间cv的声明(using namespace cv)(这是本书所有代码示例所使用的选项)。

附加cv::前缀到用到的所有opencv类、函数和数据结构上。如果由opencv提供的外部名字与常用的标准模块库(standard template library,stl)或其他库冲突,那么就推荐使用这个选项。

1.5.2 支持图像文件的格式

opencv支持最常见的图像格式。但是,某些图像格式需要(免费提供的)第三方类库。由opencv支持的主要格式有:

windows bitmaps (.bmp、dib)

portable image formats (.pbm、.pgm、*.ppm)

sun rasters (.sr、.ras)

需要辅助库的格式有:

jpeg (.jpeg、.jpg、*.jpe)

jpeg 2000 (*.jp2)

portable network graphics (*.png)

tiff (.tiff、.tif)

webp (*.webp).

对于opencv 3.0版本,除了上面列出的格式外,它还包含一个由地理数据抽象库(geographic data abstraction library,gdal)所支持格式(nitf、dted、srtm等)的驱动器,通过cmake的选项with_gdal来设置。注意,在windows os上,对gdal的支持还没有经过广泛测试。在windows和os x中,默认对这些格式(libjpeg、libjasper、libpng和libtiff)使用opencv附带的编解码器。之后,在这些操作系统中,可以读取jpeg、png和tiff格式。linux(和其他类unix开源操作系统)会寻找安装在系统中的编解码器。在opencv之前,可以安装编解码器或从opencv包中通过在cmake中设置正确的选项(例如,build_jasper、build_jpeg、build_png和build_tiff)来构建其他库。

1.5.3 示例代码

为了说明如何使用opencv读、写图像文件,现在,我们将描述showimage示例,如图1-5所示。从命令行执行该示例,对应的输出窗口如下:

《OpenCV图像处理》——1.5 读取和写入图像文件

在本示例中,两个文件名称作为参数给出。第一个是读取的输入图像文件。第二个是使用输入图像的一个灰度副本写入的图像文件,接下来,展示源代码及其说明:

《OpenCV图像处理》——1.5 读取和写入图像文件
《OpenCV图像处理》——1.5 读取和写入图像文件

此处,使用#include指令包含opencv.hpp头文件,实际上,它包含所有的opencv头文件。通过包含该单个文件,不再需要包含其他文件。声明所使用的cv命名空间之后,在这个命名空间内的所有变量和函数都不再需要cv::前缀。在主函数中要做的第一件事情就是检查命令行中传递的参数个数。之后,如果出现错误,则显示一个帮助消息。

1.?读取图像文件

如果参数个数正确,那么使用函数imread(argv[1], imread_unchanged)将图像文件读入到mat对象in_image中。这里,第一个参数是在命令行中传递的第一个实参(argv[1]),第二个参数是一个标志(imread_unchanged),这就意味着存储到内存图像中的图像不会被改变。函数imread决定图像类型(编解码器)来自文件内容而不是来自文件扩展名。

函数imread的原型如下:

《OpenCV图像处理》——1.5 读取和写入图像文件

f?lag指定读取图像的颜色,并在imgcodecs.hpp头文件中由如下枚举类型定义和

解释:

《OpenCV图像处理》——1.5 读取和写入图像文件

在opencv 3.0版本中,函数imread是在imgcodecs模块中,而不是像opencv 2.x在highgui模块中。

因为一些函数和声明被移入到opencv 3.0中,所以连接器可能会由于找不到一个或多个声明(符号和/或函数)而得到一些编译错误。为了弄清楚在什么地方(*.hpp)定义一个符号并且链接到哪个库,推荐使用qt生成器ide的如下技巧:

向代码添加声明#include 。将鼠标光标放在该符号或函数上并按f2功能键;这样就会打开声明了该符号或函数的*.hpp文件。

读取输入的图像文件之后,应检查操作是否成功。可使用成员函数in_image.empty()来实现这个检查。如果读取图像文件时没有发生错误,会创建两个窗口分别显示输入图像和输出图像。使用如下函数进行窗口的创建:

《OpenCV图像处理》——1.5 读取和写入图像文件

opencv窗口是通过程序中一个意义明确的名字来识别的。通过下面highgui.hpp头文件中的枚举给出该标志的定义及其说明:

《OpenCV图像处理》——1.5 读取和写入图像文件
《OpenCV图像处理》——1.5 读取和写入图像文件

一个窗口的创建不会在屏幕上显示任何内容。在一个窗口中显示一幅图像的函数(属于highgui模块)是:

《OpenCV图像处理》——1.5 读取和写入图像文件

如果使用window_autosize标志创建该窗口(winname),那么所显示的是原始大小的图像(mat)。

在showimage示例中,第二个窗口显示输入图像的一个灰度副本。为了将一幅彩色图像转换为灰度图像,使用imgproc模块的函数cvtcolor。实际上使用这个函数来改变图像的颜色空间。

在一个程序中创建的任何窗口都可以从默认设置下调整大小和进行移动。当不再需要任何窗口时,应该销毁窗口,以便释放其资源。像示例中那样,在一个程序结束时,会隐式地完成资源的释放。

2.?在内部循环中处理事件

如果在一个窗口上显示一幅图像之后不再做任何事情,出乎意料地,将不再显示图像。在一个窗口显示一幅图像之后,我们应该开始一个循环,以获取和处理与用户和窗口交互有关的事件。通过如下函数可执行这样一个任务(从highgui模块中):

《OpenCV图像处理》——1.5 读取和写入图像文件

这个函数在数毫秒(delay>0)内等待一个按键操作,并返回键的编码,如果延迟结束时没有按键则返回-1。如果delay是0或负数,那么函数一直等待直到一个键被

按下。

记住,只有至少创建和激活一个窗口时,函数waitkey才会工作。

3.?写入图像文件

imgcodecs模块中的另一个重要函数是:

《OpenCV图像处理》——1.5 读取和写入图像文件

这个函数将一幅图像(img)保存到一个文件(f?ilename),作为第三个可选参数,一个“属性-值”对的向量指定编解码器的参数(为使用默认值将其设置为空)。编解码器由文件的扩展名决定。

继续阅读