OpenCV 4机器学习算法原理与编程实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1 基本图像操作

OpenCV的HightGUI模块提供了读取不同类型图像、视频和相机的接口,下面使用部分接口读取、显示和存储图像。

2.1.1 读取、显示和存储图像

1. 实现方法

新建一个名为“示例2-1”的项目,使用opencv450属性配置文件一键配置好OpenCV,并输入示例代码2-1。

示例代码2-1 读取、显示和存储图像代码

2. 代码分析

1)OpenCV头文件

想要使用OpenCV的类与函数,首先要引用相关头文件,头文件定义了所需的类和函数。示例代码2-1中引入了:

这是OpenCV 3.0及以后版本的头文件引用方式,该头文件包含了OpenCV所有模块的头文件。OpenCV的主要头文件如表2-1所示。只需引用一个头文件即可使用OpenCV里的所有函数与功能。需要注意的是,contrib模块需要单独安装。

表2-1 OpenCV的主要头文件

2)类与函数的访问方式

在OpenCV的C++API中,所有的类和函数都在命名空间cv内定义。访问OpenCV类和函数的方法有两种。

◎ 第一种,在main函数之前声明使用cv的命名空间:

此时,当在代码中声明OpenCV变量或调用其函数时,便可以不加cv::前缀。

◎ 第二种,在所有的OpenCV类和函数加前缀cv::。为了提高代码的可读性,本书使用第二种方法。

3)定义图像变量

在main函数中,定义一个图像变量。在OpenCV 4中,声明的是cv::Mat类型的变量img:

这个定义创建了一幅尺寸为0×0的图像。下面用cv:Mat类的size属性进行验证:

4)读取图像

使用cv::imread函数将图像文件读取到内存:

关于OpenCV函数的更多使用方法,可以阅读官方在线文档。打开官方在线文档,在右侧搜索栏中输入相关函数名称进行搜索,例如搜索imread,即可获得简单的介绍与详细的接口描述。

cv::imread函数

函数参数

filename:图像文件名。

flags:读取时使用的色彩模式标志位,从cv::imreadModes中取值,1为源图彩色模式,0为灰度模式。

支持读取的图像文件类型

◎ Windows bitmaps:*.bmp、*.dib(全部支持)。

◎ JPEG files:*.jpeg、*.jpg、*.jpe(部分支持)。

◎ JPEG 2000 files:*.jp2(部分支持)。

◎ Portable Network Graphics:*.png(部分支持)。

◎ WebP:*.webp(部分支持)。

◎ Portable image format:*.pbm、*.pgm、*.ppm、*.pxm、*.pnm(全部支持)。◎ Sun rasters:*.sr、*.ras(全部支持)。

◎ TIFF files:*.tiff、*.tif(部分支持)。

◎ OpenEXR Image files:*.exr(部分支持)。

◎ Radiance HDR:*.hdr、*.pic(全部支持)。

◎ Raster and Vector geospatial data supported by Gdal(部分支持)。

在读取图像后,可以使用cv::Mat类的empty方法判断是否正确读取了图像,如果读取失败,则退出程序:

5)获取图像的宽和高

在处理图像时,经常需要获取图像的宽和高,OpenCV中的cv:Mat类的本质是矩阵,图像是二维矩阵,因而可以通过列数与行数,即cols和rows,读取二维矩阵的宽和高:

6)对图像进行高斯滤波处理

在示例代码2-1中,对图像进行了高斯滤波处理,用来显示并存储处理后的图像。

高斯滤波是一种常见的低通滤波器,它能抑制输入图像的高频分量(图像的边缘和细节信息),保留低频分量(图像中变化缓慢的部分)。因此,通过高斯滤波可以得到一幅边缘模糊的图像。关于高斯滤波的更多内容将在后续章节详细介绍,这里只提供其调用方式及说明。

cv::GaussianBlur函数:

函数参数

◎ src:输入图像。图像可以拥有多个通道,每个通道在滤波时都独立处理。图像位深度必须是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F,分别表示8位无符号整型、16位无符号整型等。关于图像位深度与OpenCV数据类型的更多信息,将在下节详细介绍。

◎ dst:输出图像。与输入图像的类型和大小相等。

◎ ksize:高斯核尺寸。高斯核的高和宽可以不同,但必须是奇数或零。如果是零,则可通过sigma参数计算得到。cv::Size表示数据类型,高斯核尺寸越大,模糊程度越高。

◎ sigmaX:高斯核在x轴上的标准差。

◎ sigmaY:高斯核在y轴上的标准差。如果sigmaY为0,则被设置为与sigmaX相等。如果两个sigma的参数都为0,则高斯核在x轴和y轴上的标准差将分别通过ksize.width参数和ksize.height参数计算得到。

◎ borderType:像素外插法,用来定义在指定滤波时如何处理图像边界。

7)显示图像

新建图像显示窗口,并在此窗口中显示载入的图像:

也可以不新建图像显示窗口,而是直接调用imshow函数显示图像。为了让显示窗口能够停留以方便查看,还需要增加如下代码,等待按键响应后再退出:

该函数参数为整型,默认值为0。当参数大于0时,表示等待的毫秒数;当参数小于或等于0时,表示永久等待直至按下键盘。

8)存储图像

使用OpenCV存储图像非常简单,cv::imwrite函数既可以将图像存储为指定格式的图像文件;也可以使用FileStorage I/O功能将图像保存为XML或YAML格式。在示例代码2-1中,cv::imwrite函数将高斯滤波处理后的图像存储为.jpg文件。如果不在cv::imwrite函数的参数中指定存储路径,则存储的文件与main函数在同一目录中:

cv::imwrite函数实现了把图像存储为指定格式的文件的功能,存储格式可从文件名的扩展名中获取。当使用此函数将图像保存成.png、.jpeg或.tiff格式的文件时,只有位深度为8位或16位无符号整型数据,且单通道或3通道(通道顺序为BGR)的图像可以。如果图像数据不满足上述要求,则可以使用Mat::convertTo和cv::cvtColor函数把图像转换成可存储的格式。此外,cv::imwrite函数还支持把图像存储为透明图像(.png格式)。

cv::imwrite函数

函数参数:

◎ filename:要保存的文件名,包含文件扩展名。

◎ img:待存储的图像。

◎ params:与存储格式相关的参数对,如压缩比等。参数类型为std::vector<int>。参数的构成方式如下:(paramId_1, paramValue_1, paramId_2, paramValue_2, …)

更多相关说明可在官方在线文档中查阅cv::ImwriteFlags获取。

9)销毁窗口,退出程序

最后销毁窗口并退出程序:

注意:销毁窗口的步骤可以省略,OpenCV会自动处理变量和窗口的销毁问题。

3. 运行结果

屏幕打印结果如下:

示例代码2-1的运行结果如图2-1所示,存储的高斯滤波处理后的图像如图2-2所示。

图2-1 示例代码2-1的运行结果

图2-2 存储的高斯滤波图像

计算机读取图像是为了对其做进一步处理。图像处理包含的内容非常广,这里简单介绍在机器学习任务中常用的几种图像处理方法,包括彩色空间转换、平移、缩放、旋转和在图像上绘制文字与矩形框等。

2.1.2 颜色空间转换

1. 基本原理

颜色空间又称为彩色模型或彩色系统,其目的是在某些标准下用通常可以接受的方式方便地对彩色进行说明。在数字图像处理中,实际上最通用的面向硬件的是RGB(红、绿、蓝)空间,该空间用于彩色监视器和一大类彩色摄像机;CMY(青、洋红、黄)空间和CMYK(青、洋红、黄、黑)空间是针对彩色打印机的;HSI(色调、饱和度、亮度)空间是一种更符合人描述和解释颜色的一种模型。HSI空间还有一个优点,即它可以解除图像中的颜色和灰度信息的联系,使其更适合某些灰度处理技术,并且对样本量需求较大的机器学习算法而言,还可以通过改变色调和饱和度的方式增加样本数量,提高训练出来的空间对不同光照条件下的稳健性。本节重点介绍RGB空间、HSI空间和灰度空间的基本概念及其转换方式。

1)RGB空间

RGB空间基于笛卡儿坐标系,如图2-3所示。图中RGB原色值位于立方体的3个顶点上;二次色青色、深红色和黄色位于另外3个顶点上,黑色位于原点处,白色位于离原点最远的顶点上。立方体中黑色与白色顶点的连线为灰度级,位于灰度级上的彩色值,其RGB各分量相等。

图2-3 RGB彩色空间

图2-3所示的各颜色分量的取值范围是归一化到[0, 1]后的表示。实际上,在RGB彩色空间中,假定使用CV_8UC3(3通道8位无符号整型)数据类型,则R, G, B=0, 1, 2, …, 255。每个像素点依次用红、绿、蓝三种颜色分量表示,如图2-4所示,每个像素位置都用一个三维向量来表示该像素的RGB 3个分量值。例如,(255, 0, 0)表示红色分量为最大值255,绿色和蓝色分量均为最小值0,即该像素点的颜色为红色。当各分量均为0时,为黑色(0, 0, 0),当各分量均为最大值255时,为白色(255, 255, 255)。

图2-4 RGB颜色分量示意图

需要说明的是,OpenCV默认的RGB彩色空间的通道顺序为BGR,即蓝色、绿色和红色。例如,(255, 0, 0)表示蓝色,(0, 255, 0)表示绿色,(0, 0, 255)表示红色。

2)HSI空间

HSI(色调、饱和度和亮度)空间可以从彩色图像携带的彩色信息(色调和饱和度)中消去强度分量的影响。因此,HIS空间对于开发基于彩色描述的图像处理算法来说是一个理想的彩色空间。我们在某些基于深度学习的分类识别任务中,通常将原始图像转换成HSI空间来削弱光线变化的影响,增强算法的稳健性。

HSI空间的色调、饱和度和亮度可以从RGB空间的立方体中得到,也就是说,可以把任何一个RGB空间中的点转换为对应的HSI空间中的点。

文献[1]中定义,给定一幅RGB彩色格式的图像,每个RGB像素和色调分量H由下式得到:

式中,

饱和度分量S由下式得到:

亮度分量I由下式得到:

值得注意的是,在OpenCV中可以使用HSV(色调、饱和度和值)空间来隔离图像颜色与亮度信息。RGB空间与HSV空间的转换更为高效,转换公式如下:

3)灰度空间

很多类型的图像都没有色彩信息,只有亮度信息,通常使用灰度图表示,例如红外图像。灰度空间是视觉机器学习任务中最基本也是最常用的颜色空间。RGB空间转换为灰度空间的公式如下:

灰度空间转换为RGB空间的公式如下:

2. 实现方法

新建工程,完成配置,编写示例代码2-2。

示例代码2-2 颜色空间转换

3. 代码分析

首先,声明5个cv::Mat变量,分别用来存储源图和转换后的图:

然后,使用cv::cvtColor函数对读取的源图img_rgb进行转换。例如,下面的代码可以将RGB图转换为HSV图:

cv::cvtColor函数将输入的源图从某个颜色空间转换至另一个颜色空间。OpenCV的默认颜色空间为BGR(即RGB颜色空间中R与B分量交换位置),因此对于一个标准的24bit彩色图,第一字节为8bit蓝色分量,第二字节为8bit绿色分量,第三字节为8bit红色分量,第4、5、6字节表示下一个像素的蓝色、绿色和红色分量,依此类推。

cv::cvtColor函数:

函数参数:

◎ src:输入图像,可以是8位无符号整型(CV_8U)、16位无符号整型(CV_16U)或单精度浮点型数据。

◎ dst:输出图像,与输入图像的类型、大小和位深度均相等。

◎ code:颜色空间转换码,指定颜色空间转换的类型。

◎ dstCn:目标图像的通道数。当dstCn为0时,通道数将根据code与源图通道数自动确定。

注意:cv::cvtColor函数对R、G、B通道数据转换的范围如下。

CV_8U类型图像:0~255。

CV_16U类型图像:0~65535。

CV_32F类型图像:0~1。

4. 运行结果

示例代码2-2的运行结果如图2-5所示。

图2-5 示例代码2-2的运行结果

屏幕打印结果如下:

RGB image通道数:3。

HSV image通道数:3。

gray image通道数:1。

从图2-5可以看出,RGB空间与HSV空间可以进行可逆转换,而在把RGB空间转成灰度空间后,再转换回RGB空间,颜色分量信息就丢失了。

2.1.3 图像的几何变换

图像的平移、缩放、镜像和旋转都属于几何变换。在视觉机器学习任务中,几何变换既可用于归一化图像中目标区域的尺度与角度,也可用于扩充图像数据。例如,通过对图像进行平移、缩放、镜像和旋转,可获得不同尺度与角度的样本,提高模型在旋转和尺度上的稳健性。

1. 基本原理

在数字图像处理中,几何变换由两个基本操作构成:① 坐标变换;② 灰度内插,即对空间变换后的像素赋灰度值。

1)坐标变换

坐标变换由下式表示:

式中,(v, w)是源图像中的像素坐标;(x, y)是变换后图像中的像素坐标。例如,变换(x, y)=T[(v, w)]=(v/2, w/2),表示在水平和垂直两个方向上将坐标缩小为原来的一半。最常用的空间变换之一是仿射变换(Affine Transformations),其一般形式如下:

仿射变换可以根据矩阵T中元素的取值,对一组坐标点做恒等变换、尺度变换、旋转变换、平移变换、垂直剪切变换或水平剪切变换。仿射变换矩阵如表2-2所示,表中列举了实现上述仿射变换的矩阵值。

表2-2 仿射变换矩阵

从表2-2中的坐标公式可以看出,仿射变换可简化为:

定义一个2×3的矩阵M,令:

则:

即:

式(2-12)即为OpenCV中变换矩阵的形式,OpenCV中的仿射变换矩阵如表2-3所示。

表2-3 OpenCV中的仿射变换矩阵

2)灰度内插

式(2-11)可将一幅图像上的像素重新定位到新位置,接下来必须对这些新位置上的像素赋灰度值,该任务可使用灰度内插完成。从根本上看,内插是用已知数据估计未知位置的数据。常见的插值算法有最邻近插值、双线性插值、二次立方插值和三次立方插值等。下面以图像缩放为例,介绍最邻近插值法和双线性插值的实现原理。

假设任务是将一幅2像素×2像素图像的长宽分别放大2倍,即4像素×4像素。最简单的做法是先创建一个4×4的网格,再将其缩小,使之与源图像的大小相同,然后对4×4的每一个网格赋予灰度值。

如果把最靠近该网格的原始图像的像素灰度值作为该网格的灰度值,就是最邻近插值,又称为零阶内插。采用最邻近插值放大的图像如图2-6(b)所示。这种插值算法速度快,但是效果不佳,会产生棋盘格效应,当放大倍数很高时,棋盘格效应特别明显。

图2-6 灰度差值示意图

一种改进的算法是采用4个最邻近点的双线性插值。令(x, y)表示放大后的图像中某一点的坐标,例如上文所述的网格上的点,并令g(x, y)表示它被赋予的灰度值。所赋灰度值由下式给出:

g(x, y)=ax+by+cxy+d

式中的4个系数由点(x, y)的4个最邻近点写出的4个未知方程决定。对于日常图像缩放而言,双线性插值是首选算法。

假设2像素×2像素图像的原始灰度如下:

则使用最邻近插值放大后的灰度如下:

而使用双线性插值放大后的灰度如下:

上述模拟图像显示的直观效果如图2-6所示。

图2-7为实际图像使用最邻近插值和双线性插值放大后的效果。

图2-7 实际图像使用最邻近插值和双线性插值放大后的效果

2. 实现方法

新建工程,完成配置,编写示例代码2-3。

示例代码2-3 仿射变换

3. 代码分析

示例代码2-3通过OpenCV完成了尺度变换、平移变换、旋转变换和剪切变换。

1)尺度变换

尺度变换又称为图像缩放,在OpenCV中,最常见的实现尺度变换的函数为cv::resize函数。

cv::resize函数:

函数参数:

◎ src:输入图像。

◎ src:输出图像。当dsize不为0时,输出图像与输入图像的尺寸相同。

◎ dsize:输出图像尺寸。如果dsize为0,则由下式计算:

dsize = cv::Size(round(fx*src.cols), round(fy*src.rows))

fx和fy两组参数不能全为0 。

◎ fx:水平轴缩放比例因子。如果fx=0,则由下式计算:

(double)dsize.width/src.cols

◎ fy:垂直轴缩放比例因子。如果fy=0,则由下式计算:

(double)dsize.height/src.rows

◎ interpolation:插值算法。

当使用cv::resize函数时,图像会缩放为原始图像的0.5倍。在OpenCV中使用cv::Size类型的变量表示图像的尺寸。

当然,也可以通过上文介绍的仿射变换矩阵实现缩放。在OpenCV中,实现仿射变换矩阵的函数为cv::warpAffine函数。

cv::warpAffine函数:

函数参数:

◎ src:输入图像。

◎ dst:输出图像,与输入图像的尺寸和类型相同。

◎ M:2×3变换矩阵。

◎ dsize:输出图像尺寸。

◎ flags:组合标志位。插值算法和可选标志表示M是(dst→src)逆变换。

◎ borderMode:像素外推方法。旋转、平移和剪切等变换会造成目标图像中的某些区域没有像素值,此时需要用到像素外推方法(又称为边界填充方法)来填补这些“空白”区域。

当borderMode = BORDER_TRANSPARENT时,意味着此函数不会修改与源图像中“域外值”对应的目标图像中的像素。

◎ borderValue:在边界为恒定值的情况下使用的数值,默认为0。

如式(2-12)所示,该函数对输入的源图像使用下式做仿射变换:

实现仿射变换矩阵的步骤如下:

第1步,在变换前新建一个2×3的全零浮点型M矩阵warp_matrix_resize:

第2步,根据表2-3所示的尺度变换形式,修改M矩阵的对应元素M11M12

.at<type>(x, y)方法可以修改矩阵中指定(x, y)位置的值,其值的类型由<>中的type指定。

第3步,调用cv::warpAffine函数实施尺度变换:

下面的几类变换都是在缩小后的图像上进行的。

2)平移变换

第1步,根据表2-3设置平移变换矩阵,新建平移变换M矩阵warp_matrix_translation。

初始化M矩阵的对角线为全1:

第2步,设置水平方向和垂直方向的平移量。

第3步,为了让平移后的图像能完整地显示,此处先做扩边处理,将图像temp_image在上、下、左、右四个方向分别扩充相应的像素,并且对扩充的区域填充恒定灰度值200。

第4步,执行平移变换。

3)旋转变换

第1步,扩边。为了让旋转后的图像在目标图像中能完全显示,需要做扩边操作,方法与前文介绍的相同。

需要说明的是,扩边的大小可根据需要计算。例如,做旋转变换时,一般可以让扩充后的目标图像宽度等于源图像的对角线长度,这样无论旋转角度为何值,均能保证源图像中的所有像素点不被裁剪。因此,上、下、左、右分别扩充宽度border=/2×image. width。

第2步,根据表2-3设置旋转变换M矩阵warp_rotate_matrix。OpenCV提供了自动计算旋转变换M矩阵的cv::getRotationMatrix2D函数。

在cv::getRotationMatrix2D函数中:第一个参数center为旋转中心坐标(图像左上角点为坐标原点);第二个参数angle为旋转角度(以°为单位);第三个参数scale为比例因子,scale=1.0表示在旋转时不做缩放操作。绕任意点旋转的原理是先对图像做平移变换,再做旋转变换。

绕原点旋转是二维旋转中最基础的,在上面的示例中需要绕任意点旋转(示例中为绕图像中心点旋转),可以把这种情况转换为绕原点旋转,思路如下:

(1)把旋转点移到原点处。

(2)执行绕原点旋转。

(3)将旋转点移回到原来的位置。

cv::getRotationMatrix2D函数既执行了上述步骤,又集成了尺度变换。下面给出的是该函数对M矩阵的计算过程:

其中,

α=scale∙cos(angle)

β=scale∙sin(angle)

cv::getRotationMatrix2D函数获取的旋转变换矩阵默认为绕旋转中心逆时针旋转,而前文在介绍旋转仿射变换原理时,是绕图像坐标原点顺时针旋转得到的公式,因此这两种方法得到的M矩阵中的数值略有不同,在下文中将做进一步的说明。

第3步,执行旋转变换。

边界填充方式选择BORDER_REPLICATE,即复制图像边界的像素灰度填充旋转后产生的“空白”区域。

4)剪切变换

第1步,扩边。

第2步,根据表2-3设置垂直剪切变换矩阵(warp_matrix_shear_vertical)和水平剪切变换矩阵(warp_matrix_shear_horizontal)。

第3步,执行剪切变换。

4. 运行结果

在屏幕上依次打印尺度变换矩阵、平移变换矩阵、旋转变换矩阵、水平剪切变换矩阵和垂直剪切变换矩阵。

值得指出的是,上面打印的旋转矩阵是旋转中心为图像中心时的数据,加入了平移变换。在OpenCV中,使用cv::getRotationMatrix2D函数获取的旋转矩阵,默认为绕旋转中心逆时针旋转,而上文在描述仿射变换原理时,旋转变换矩阵是绕图像原点顺时针旋转得到的,旋转方向相反,体现在旋转矩阵中就是M矩阵中的sinθ位置的正负号对调了。这里不再赘述,感兴趣的读者可以自己试一试改变后的效果。

源图如图2-8所示,使用resize函数缩小后的图像如图2-9所示,使用仿射矩阵缩小后的图像如图2-10所示,平移变换如图2-11所示,旋转变换如图2-12所示,垂直剪切变换和水平剪切变换如图2-13所示。

图2-8 源图

图2-9 使用resize函数缩小后的图像

图2-10 使用仿射矩阵缩小后的图像

图2-11 平移变换

图2-12 旋转变换

图2-13 垂直剪切变换和水平剪切变换

2.1.4 直方图均衡化

1. 基本原理

1)直方图

灰度级在区间[0, L-1]的数字图像的直方图是离散函数h(rk)=nk,其中,rk是第k级灰度,nk是图像中灰度级为rk的像素个数。用图像中的像素总数(用n表示)除以它的每一个值,即可得到归一化的直方图。因此归一化的直方图由P(rk)=nk/n给出,这里k=0, 1, …, L-1。简单来说,P(rk)给出了灰度级为rk发生的概率估值。一个归一化的直方图的所有概率估值之和为1。

2)直方图均衡化

直方图是多种空间域处理的基础,直方图均衡化能有效地提升图像的视觉效果。在很多视觉机器学习任务中,图像在进入模型之前,通常会使用直方图均衡化对图像做归一化处理。

图2-14所示为某机场遥感图像在偏暗、正常和偏亮时的直方图。偏暗的图像直方图像素集中于左侧,偏亮的图像直方图像素集中于右侧,正常图像的直方图像素分布较为均匀。也就是说,如果一幅图像的像素占有全部可能的灰度级并分布均匀,则这样的图像将具有高对比度和多变的灰度色调,从而拥有较好的视觉效果。直方图均衡化就是通过一个变换函数,使输出图像具有上面描述的效果。该函数仅依靠输入图像的直方图即可自动完成对输入图像的变换。

图2-14 机场遥感图像及其直方图

考虑连续函数并且让变量r代表待增强图像的灰度级。假设r被归一化到区间[0, 1],且r=0表示黑色,r=1表示白色。考虑一个离散公式,并允许像素值在区间[0, L−1]内。对于任意一个满足上述条件的r, 将注意力集中在变换形式上:

s=T(r) 0≤r≤1

在原始图像中,每个像素值r产生一个灰度值s。假设T(r)满足下列两个条件:

T(r)在区间[0, 1]中为单值且单调递增。

②当0≤r≤1时,0≤T(r)≤1。

条件①保证源图各灰度级在变换后仍保持从黑到白(或从白到黑)的排列顺序;条件②保证输出图像的灰度级与输入图像的灰度级一致。图2-15所示的单值且单调递增的灰度级变换函数就是一个满足上述两个条件的例子。

图2-15 单值单调递增的灰度级变换函数

通过分析推导,可以得到如式(2-14)所示的函数T(∙),输入图像经过该函数变换后会得到一个随机变量s,可以证明该随机变量有一个均匀的概率密度函数,从而实现直方图均衡化。

式(2-14)可将输入图像中灰度级为rk的各像素映射到输出图像中为灰度级为sk的各像素。n为图像中的像素总和,nk为输入图像中灰度级为rk的像素个数,L为图像可能的灰度级总数。已知一幅图像,直方图均衡化处理只需要执行式(2-14)即可,该式是从已知图像中提取信息,不需要额外的参数。这一特点使得直方图均衡化操作变得简单实用。

2. 实现方法

新建工程,完成配置,编写示例代码2-4。

示例代码2-4 直方图均衡化

3. 代码分析

在示例代码2-4中,首先定义一个绘制直方图的函数drawHist (string windowName, cv::Mat&srcImg),两个参数分别为直方图显示窗口的名字与待计算直方图的源图像。该函数计算输入图像的直方图并显示出来,调用的函数为cv::calcHist函数。

cv::calcHist函数:

函数参数:

◎ images:源图像组的指针。可以是多幅图像,所有图像必须有相同的深度(如CV_8U、CV_16U、CV_32F)和尺寸。同一幅图像可以有多个通道。

◎ nimages:源图像组中的图像个数。

◎ channels:用于计算直方图的dims(函数参数之一)个通道列表。第一个数组通道的编号从0到images[0].channels()−1,第二个数组通道的编号从images[0].channels()到images[0].channels()+images[1].channels()−1,依此类推。

◎ mask:可选掩码。如果矩阵不为空,则它必须是与images[i]大小相同的8位数组。非零掩码的位置可用来标记要计算直方图的位置。

◎ hist:输出直方图,是一个密集或稀疏的dims维数组。

◎ dims:直方图维度,必须为正且不大于CV_MAX_DIMS(在当前OpenCV版本中等于32)。

◎ histSize:每个维度中的直方图数组大小。

◎ ranges:直方图每一维的数据统计范围。

◎ uniform:直方图是否均匀的标志位。

◎ accumulate:积累计算标志位。如果已设置,则直方图在分配时不会在开头清除。此功能可以从多组数组中计算单个直方图,或者及时更新直方图。

代码中的main函数主要完成如下步骤:

第1步,读入3幅原始图像。

第2步,使用直方图均衡化函数cv::equalizeHist(srcImg, dstImg)对每幅图像做均衡化处理。

第3步,调用drawHist函数绘制均衡化操作前后共6幅图像的直方图。

第4步,绘制6幅图像用于对比。

其中,cv::equalizeHist函数使用式(2-14)自动完成了直方图均衡化处理。

cv::equalizeHist函数:

函数参数:

◎ src:8bit单通道图像。

◎ dst:与src同样尺寸与类型的目标图像。

该函数完成如下步骤。

第1步,计算src图像的直方图H

第2步,归一化直方图,使得bin的个数为255。

第3步,计算直方图的积分:

第4步,将H'作为查询表完成直方图均衡化变换:

dst(x, y)=H'(src(x, y))

4. 运行结果

原始图像与直方图均衡化后的图像如图2-16所示,图中第1列为原始图像,第2列为直方图均衡化后的图像,第3列为原始图像的直方图,第4列为直方图均衡化后的图像的直方图。

图2-16 原始图像与直方图均衡化后的图像

2.1.5 标注文字和矩形框

1. 基本原理

在机器学习检测、识别等任务中,通常需要在图像中将计算结果标注出来。OpenCV不仅提供了在图像上添加文字的cv::putText函数,还提供了多个图形绘制函数,用于标注图像中特定位置和大小的物体。例如,cv::circle函数、cv::ellipse函数、cv::rectangle函数和cv::line函数分别可以绘制圆形、椭圆形、矩形框和直线。其中,最常用的是矩形框和直线。示例代码2-5将简要描述如何使用这些函数。

2. 实现方法

新建工程,编写示例代码2-5。

示例代码2-5 标注文字和矩形框

3. 代码分析

在示例代码2-5中,用矩形框和旋转矩形框对读入的遥感图像中的飞机进行了标注,并添加了文字(目前OpenCV尚不支持中文显示)。其中,cv::putText函数可用来添加文字。

cv::putText函数:

函数参数:

◎ img:输入图像。

◎ text:待添加的字符。

◎ org:字符左下角在图像中的坐标。

◎ fontFace:字体类型,可选字体如下。

FONT_HERSHEY_SIMPLEX、FONT_HERSHEY_PLAIN、FONT_HERSHEY_DUPLEX、FONT_HERSHEY_COMPLEX、FONT_HERSHEY_TRIPLEX、FONT_HERSHEY_COMPLEX_SMALL、FONT_HERSHEY_SCRIPT_SIMPLEX和FONT_HERSHEY_SCRIPT_COMPLEX,当以上类型与FONT_HERSHEY_ITALIC配合使用时,可产生斜体效果。

◎ fontScale:字体大小比例因子,字体大小由基准尺寸乘以因子得到。

◎ color:字体颜色,在下文介绍。

◎ thickness:字体粗细。

◎ lineType:字体线条类型。

◎ bottomLeftOrigin:默认为false,图像数据原点在左上角;当为true时,图像数据原点在左下角。

cv::rectangle函数可用来绘制矩形框。

Cv::rectangle函数:

函数参数:

◎ img:输入图像。

◎ rec:cv::Rect类型的矩形框变量。

◎ color:矩形框颜色,如果是灰度图,则color表示亮度。

◎ thickness:矩形框线条粗细,当为负时,可绘制一个实心矩形。

◎ lineType:线段类型,包括4邻接、8邻接直线和抗锯齿直线。

◎ shift:点坐标中的小数位数。

因此,在使用该函数之前,需要先初始化一个cv::Rect类型的变量rect1,其中,(x, y)是矩形框的左上角坐标,width和height分别是矩形框的宽和高:

再将图像srcImg和rect1作为参数传递给矩形绘制函数cv::rectangle,完成矩形的绘制。

该函数的另一个重载形式是通过矩形左上角和右下角的点坐标绘制矩形,感兴趣的读者可以自行查询并尝试。

如果标注的是旋转矩形,则需要用第1章介绍过的cv::RotatedRectangle函数。

4. 运行结果

示例代码2-5将机场遥感图像中的三架民航客机依次用文字与方框的形式标注并显示出来,标注文字与边框后的机场图像如图2-17所示。

图2-17 标注文字与边框后的机场图像