Python+OpenCV实用案例应用教程:基于OpenCV的图像处理

在进行图像处理时,你迟早会发现需要转换图像——一般通过应
用艺术滤镜、推断某些部分、混合两幅图像,或者任何你能够想到的
方法完成。本章将介绍一些可以转换图像的技术。最后,你还能够执
行图像锐化、标记主体的轮廓、利用线段检测器检测人行横道。
本章将介绍以下主题:
·在不同颜色模型之间进行图像转换。
·理解频率和傅里叶变换在图像处理中的重要性。
·应用高通滤波器(High-Pass Filter,HPF)、低通滤波器(Low-
Pass Filter,LPF)、边缘检测滤波器和自定义卷积滤波器。
·检测并分析轮廓、线、圆和其他几何形状。
·编写用于封装滤波器实现的类和函数。


3.1  技术需求
本章使用了Python、OpenCV、NumPy以及SciPy。安装说明请参阅
第1章。
本章的完整代码可以在本教程的GitHub库
(https://github.com/PacktPublishing/Learning-OpenCV-4-
Computer-Vision-with-Python-Third-Edition)的chapter03文件夹
中找到。示例图像在本教程GitHub库的images文件夹中。


3.2  在不同颜色模型之间进行图像转换
OpenCV实现了数百个与颜色模型转换相关的公式。一些颜色模型
常用于摄像头等输入设备,而其他模型则常用于电视机、计算机显示
器和打印机等输出设备。在输入和输出之间,在我们将计算机视觉技
术应用于图像时,通常使用3种类型的颜色模型:灰度、BGR(蓝–绿
–红)和HSV(色调–饱和度–值)。让我们简单回顾一下:
·灰度模型是通过将颜色信息转换为灰度或亮度来减少颜色信息
的一种模型。在只有亮度信息就足够的问题中(如人脸检测),这个
模型对于图像的中间处理非常有用。通常,灰度图像中的每个像素都
是由一个8位值表示的,范围从0(黑色)到255(白色)。
·BGR表示蓝–绿–红颜色模型,其中每个像素都有一个三元组
值表示的蓝、绿、红分量或者像素颜色的通道。Web开发人员以及任
何从事计算机图形工作的人员除了反向通道顺序(RGB)外,还都熟
悉类似的颜色定义。通常,BGR图像中的每个像素都由一个8位的三元
组值来表示,例如[0,0,0]表示黑色,[255,0,0]表示蓝色,[0,255,0]表示绿
色,[0,0,255]表示红色,[255,255,255]表示白色。
·HSV模型使用一个不同的三元组通道。色调(hue)是颜色的基
调,饱和度(saturation)是颜色的强度,值(value)表示颜色的亮
度。
默认情况下,OpenCV使用BGR颜色模型(每个通道8位)表示其从
文件加载或从摄像头抓取的任何图像。
既然我们已经定义了将要使用的颜色模型,那么就来考虑一下默
认模型与我们对颜色的直观理解有什么不同吧。
光不是绘画颜料
对于刚接触BGR颜色空间的人来说,有些颜色叠加在一起看起来似
乎不太合适:例如,(0,255,255)三元组(无蓝色、全绿色和全红
色)产生黄色。如果你有艺术背景,甚至不需要拿起颜料和画笔就知
道绿色和红色颜料混合在一起会变成棕色。但是,在计算中使用的颜
色模型称为加法(additive)模型,处理的是光。光的表现与绘画颜
料(遵循减色模型)不同,因此软件在以发光显示器为媒介的计算机
上运行,参考的颜色模型是加法模型。


3.3  探索傅里叶变换
在OpenCV中,大部分应用于图像和视频的处理都在一定程度上涉
及傅里叶变换的概念。约瑟夫·傅里叶(Joseph Fourier)是18世纪
法国的一名数学家,他发现并推广了许多数学概念。他研究了热物理
以及所有可以用波形函数表示的数学。特别是他注意到所有波形都是
不同频率的简单正弦波的和。
或者说,你从周围观察到的所有波形都是其他波形的和。在进行
图像处理时,这个概念非常有用,因为它让我们能识别图像中信号
(如图像像素的值)变化大的区域,以及变化不是很显著的区域。然
后,我们可以任意地将这些区域标记为噪声或者感兴趣区域、背景或
者前景,等等。这些是组成原始图像的频率,我们有能力对它们进行
分割,从而理解图像并推断出有趣的数据。
OpenCV实现了很多算法,使我们能够处理图像并理解图像中
包含的数据,为了让我们的工作更方便,NumPy中也重新实现了这些
算法。NumPy有一个包含fft2方法的快速傅里叶变换(Fast Fourier
Transform,FFT)包。这个方法允许我们计算图像的离散傅里叶变换
(Discrete Fourier Transform,DFT)。
我们用傅里叶变换来研究图像的幅度频谱(magnitude
spectrum)的概念。图像的幅度频谱是提供表示原始图像变化的另一
幅图像。将其想象成把所有最亮像素都拖到中间的一幅图像。接着,
慢慢地把最暗的像素推到边界处。你立刻就能看到图像中包含了多少
亮的像素、多少暗的像素,以及这些像素分布的百分比。
傅里叶变换是许多常用图像处理操作算法(如边缘检测或者线条
和形状检测)的基础。
在详细研究这些内容之前,我们先来看两个概念——HPF和LPF,
它们和傅里叶变换一起形成了上述处理操作的基础。
HPF和LPF
HPF是一种滤波器,可以检查图像的一个区域,并根据周围像素的
强度差异增强某些像素的强度。
以下面的核为例:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第1张图片

一个核就是一组权值,这组权值应用于源图像中的某个区域
可以生成目标图像中的单个像素。例如,如果我们调用拥有一个参数
的OpenCV函数来指定一个核的大小为7或者ksize为7,这就表示在生成
每个目标像素时需要考虑49(7×7)个源像素。我们可以把核看成是在
源图像上移动的一块磨砂玻璃,让光源的光线扩散混合通过。
前面的核给出了中心像素与其所有直接水平邻域像素之间的平均
强度差。如果某个像素从周围的像素中脱颖而出,那么结果值就会很
高。这种类型的核表示了一个高增益滤波器,是一种HPF,在边缘检测
中特别有效。
请注意,在边缘检测核中,通常值的总和为0。我们将在本章
的3.6节中介绍这一内容。
我们来看一个将HPF应用于图像的例子:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第2张图片

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第3张图片

 在初始导入之后,我们定义了一个3×3的核和一个5×5的核,然
后加载了一幅灰度图像。之后,我们想要将图像和每个核进行卷积。
有几个库函数可用于这一目标。NumPy提供了convolve函数,但是,该
函数只接受一维数组。尽管也可以用NumPy实现多维数组的卷积,但是
会有点复杂。SciPy的ndimage模块也提供了一个convolve函数,并且
支持多维数组。最后,OpenCV提供了一个filter2D函数(用于与二维
数组卷积)以及一个sepFilter2D函数(用于可以分解为两个一维核的
二维核的特例)。前面的代码示例展示了ndimage.convolve函数。我
们将在3.6节的其他例子中使用cv2.filter2D函数。
通过应用2个HPF以及我们定义的2个卷积核,我们继续执行脚本。
最后,通过应用一个LPF并计算原始图像之间的差值,我们还实现了获
得HPF的另一种方法。我们来看一下每个滤波器的样子。首先以图3-1
所示的图片作为输入,得到的输出如图3-2所示。
你会注意到微分HPF(如图3-2右下角的图片所示)产生最佳寻边
结果。因为这个微分方法涉及一个低通滤波器,我们来详细介绍一下
这种类型的滤波器。如果HPF增强了某个像素的强度,给定它与相邻像
素的差异,如果与周围像素的差异低于某一阈值,那么LPF将会平滑像
素。这适用于去噪和模糊。例如,其中一个最流行的模糊/平滑滤波器
——高斯模糊,它就是一个低通滤波器,可以衰减高频信号的强度。
高斯模糊的结果如图3-2的左下角图片所示。
既然我们已经在一个基本示例中尝试了这些滤波器,接下来考虑
一下如何将它们集成到一个更大、更具交互性的应用程序中。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第4张图片

3.4  创建模块
我们来回顾一下第2章开始创建的Cameo项目。我们可以修改Cameo
使它能应用滤波器实时抓取图像。与在CaptureManager和
WindowManager类中的情况一样,滤波器在Cameo之外应该也可重用。
因此,我们应该把滤波器分离到它们自己的Python模块或者文件中。
我们在与cameo.py相同的目录下,创建一个名为filters.py的文
件。在filters.py中,我们需要以下import语句:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第5张图片

在同样的目录下,我们再创建一个名为utils.py的文件。它应该
包含以下import语句:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第6张图片

我们将在filters.py中添加滤波器函数和类,而更通用的数学函
数将放入utils.py中。
3.5  边缘检测
边缘在人的视觉和计算机视觉中都扮演着重要的角色。对于人来
说,通过背光轮廓或者粗略的草图,可以很容易地识别出许多物体的
类型及其姿态。的确,当艺术作品强调边缘和姿态时,通常它似乎传
达了原型的想法,如罗丹的《思想者》或者乔·舒斯特的《超人》。
软件也可以推断出边缘、姿态和原型。我们将在后续章节中讨论这些
类型的推理。
OpenCV提供了许多边缘搜索滤波器,包括Laplacian、Sobel和
Scharr。这些滤波器将非边缘区域变为黑色,将边缘区域变为白色或
者饱和颜色。但是,它们很容易把噪声误认为边缘。在试图寻找边缘
之前模糊图像可以减少这类缺陷。OpenCV还提供了许多模糊滤波器,
包括blur(一个简单的平均)、medianBlur和GaussianBlur。边缘搜
索和模糊滤波器的参数各不相同,但是始终包括ksize——这是一个奇
数整数,表示滤波器的核的宽和高(以像素为单位)。
对于模糊,我们使用medianBlur,它可以有效地去除数字视频噪
声,尤其是在彩色图像中。对于边缘搜索,我们使用Laplacian,它产
生粗的边缘线条,尤其是在灰度图像中。在应用medianBlur之后且应
用Laplacian之前,应该将图像从BGR转换为灰度。
一旦得到Laplacian结果,就可以将其转换得到白色背景上的黑色
边缘。然后,对其进行归一化(使其值在0到1之间),再将其与源图
像相乘,使边缘变暗。我们在filters.py中实现这个方法:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第7张图片

请注意,我们允许把核大小指定为strokeEdges的参数。
参数blurKsize用作medianBlur的ksize,而edgeKsize用作
Laplacian的ksize。对于典型的网络摄像头,blurKsize的值为7,
edgeKsize的值为5,可能会产生最令人满意的效果。可是,具有较大
的ksize参数(比如7)时,medianBlur计算成本很高。
如果在运行strokeEdges时遇到性能问题,请尝试减小blurKsize
值。要关闭模糊效果,就将其设置为小于3的值。
在3.7节中,我们将这个滤波器集成到Cameo之后会看到它的效
果。
3.6  自定义核:获取卷积
正如我们刚看到的,OpenCV的许多预定义滤波器都使用了一个
核。请记住,一个核就是一组权值,决定如何根据一个输入像素的邻
域计算每个输出像素。核的另一个术语是卷积矩阵。它将一个区域内
的像素混合或者卷积。类似地,基于核的滤波器也称为卷积滤波器。
OpenCV提供了一个非常通用的filter2D()函数,可以应用我们指
定的所有核或者卷积矩阵。要理解如何使用这个函数,我们先来了解
一下卷积矩阵的格式。它是一个拥有奇数行和奇数列的二维数组。中
心元素对应于感兴趣的像素,其他元素对应于该像素的邻域。每个元
素包含一个整数值或者浮点值,这是一个应用于输入像素值的权值。
考虑以下这个例子:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第8张图片

这里,感兴趣的像素的权值是9,其直接相邻的每个像素的权值
是-1。对于感兴趣的像素,输出颜色是其输入颜色的9倍减去8个相邻
像素的输入颜色。如果感兴趣的像素与其邻域有一些不同,这个差异
就会增强。其效果是,当邻域之间的对比度增强时,图像看起来更清
晰。
继续这个例子,我们可以将卷积矩阵分别应用于源图像和目标图
像,如下所示:

第2个参数指定目标图像每个通道的深度(例如,cv2.CV_8U表示
每个通道8位)。负值(例如,此例中为-1)表示目标图像和源图像有
相同的深度。
对于彩色图像,请注意,filter2D()将核同等地应用于每个通
道。为了在不同的通道使用不同的核,还必须使用split()和merge()函
数。
基于这个简单的例子,我们把2个类添加到filters.py:一个类为
VConvolution-Filter,表示一般的卷积滤波器;另一个为子类
SharpenFilter,专门表示锐化滤波器。编辑filters.py,这样我们就
可以实现这2个新类,如下所示:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第9张图片

请注意,权值总和是1。这应该是我们想要保持图像整体亮度不变
的情况。如果稍微修改一个锐化核,使其权值之和是0,就会得到一个
边缘检测核,使边缘变为白色,非边缘变为黑色。例如,将下面的边
缘检测滤波器添加到filters.py:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第10张图片

接下来,我们制作一个模糊滤波器。通常,对于模糊效果,权值
的总和应该是1,而且整个邻域像素的权值都应该是正的。例如,我们
可以简单地对邻域像素求平均,如下所示: 

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第11张图片

锐化、边缘检测和模糊滤波器都使用高度对称的核。然而,有时
不是很对称的核会产生有趣的效果。我们来考虑这样一个核:使一边
模糊(正权值),另一边锐化(负权值)。它会产生脊状或者浮雕的
效果。下面是可以添加到filters.py中的一个实现:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第12张图片

这组自定义卷积滤波器非常基础。事实上,它比OpenCV现成的滤
波器组更基本。但是,通过一些实验,你应该能够自己编写产生独特
外观的核。

3.7  修改应用程序

既然已经为几个滤波器提供了高级函数和类,那么应用其中任何
一个滤波器抓取Cameo中的帧都是很简单的。我们编辑cameo.py,并添
加下面粗体显示的行。首先,需要把filters模块添加到导入列表中,
如下所示:         

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第13张图片

现在,我们需要初始化将使用的所有滤波器对象。如下面修改后
的__init__方法中的示例所示:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第14张图片

最后,需要修改run方法以便应用所选择的滤波器。参考下面的例
子:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第15张图片

这里,我们应用了两种效果:描边和模仿一种名为柯达胶卷的彩
色胶片。你可以随意修改代码以应用你所喜欢的任意滤波器。
有关如何实现胶卷仿真效果的详细说明,请参阅附录。
描边和类胶片颜色的Cameo屏幕截图如图3-3所示。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第16张图片

既然我们已经获得了一些可以用简单滤波器实现的视觉效果,我
们来考虑如何使用其他简单函数进行分析,特别是进行边缘和形状检
测。
3.8  基于Canny的边缘检测
OpenCV提供了一个很方便的名为Canny(以该算法的发明者John
F.Canny的名字命名)的函数,它非常受欢迎,不仅因为它的有效性,
还因为它在OpenCV程序中的实现很简单(只有一行代码):

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第17张图片

Canny的实现结果如图3-4所示,边缘识别非常清晰。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第18张图片

Canny边缘检测算法比较复杂,但是也很有趣。这是一个5步过
程:
(1)用高斯滤波器去除图像的噪声。
(2)计算梯度。
(3)在边缘上应用非极大值抑制(Non-Maximum Suppression,
NMS)。这意味着算法从一组重叠边缘中选取最好的边缘,我们将在第
7章详细讨论非极大值抑制的概念。
(4)将双阈值应用于所有检测到的边缘,淘汰所有的假正例结
果。
(5)分析所有的边缘及其之间的连接,保留真正的边缘,并丢弃
弱边缘。
在找到Canny边缘后,我们可以对边缘做进一步分析,以确定它们
是否符合常见形状,如线或者圆。霍夫变换就是以这种方式使用Canny
边缘的一种算法。我们将在3.10节对其进行实验。
现在,我们将基于寻找相似像素斑点的概念(而不是基于边缘检
测)来研究分析形状的其他方法。
3.9  轮廓检测
轮廓检测是计算机视觉中的一项重要任务。我们希望检测图像或
者视频帧中包含的主体轮廓,这不仅是其本身的目的,而且也是其他
操作的一个步骤。这些操作包括多边形边界、近似形状以及常见感兴
趣区域(Region Of Interest,ROI)的计算。感兴趣区域极大地简化
了与图像数据的交互,因为在NumPy中很容易用数组切片定义矩形区
域。在探索物体检测(包括人脸检测)和物体跟踪的概念时,我们会
经常使用轮廓检测和感兴趣区域。
我们通过一个例子来熟悉一下这个API:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第19张图片

首先,创建一幅空白的黑色图像,大小为200×200像素。然后,
利用数组的能力为切片赋值,在其中心放置一个白色的正方形。
接着,阈值化图像并调用findContours函数。这个函数有3个参
数:输入图像、层次结构类型以及轮廓近似方法。第2个参数指定函数
返回的层次结构树类型。其中一个值是cv2.RETR_TREE,它让函数检索
外部轮廓和内部轮廓的完整结构。如果在较大物体(或者较大区域)
内搜索较小的物体(或者较小的区域),这些关系可能很重要。如果
只想检索最外部的轮廓,请使用cv2.RETR_EXTERNAL。在物体出现于普
通的背景上并且我们不关心是否搜索物体内的对象的情况下,这可能
是一种好的选择。
回顾代码示例,请注意,findContours函数返回2个元素:轮廓及
其层次结构。我们使用轮廓线在彩色图像上绘制绿色的轮廓。最后,
显示图像。
结果是轮廓用绿色绘制的一个白色正方形——一个斯巴达场景,
但是有效地展示了此概念!我们再来看一个更有意义的例子。

3.9.1  边框、最小矩形区域以及最小外接圆
找出正方形的轮廓非常简单,不规则、倾斜和旋转的形状则需要
充分发挥OpenCV的cv2.findContours函数的潜力。我们来看如图3-5所
示的图像。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第20张图片

图3-5  示例图像
在实际应用中,我们最感兴趣的是确定主体的边框、最小外接矩
形及其外接圆。cv2.findContours函数结合一些其他OpenCV实用程
序,使这一任务非常容易实现。首先,下面的代码从文件读取一幅图
像并将其转换为灰度图像,对灰度图像应用阈值,并在阈值化图像中
找到轮廓:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第21张图片

其次,针对每个轮廓寻找并画出边框、最小外接矩形和最小外接
圆,如下列代码所示:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第22张图片

最后,使用下列代码绘制轮廓并在窗口中显示图像,直到用户按
下某个键:        

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第23张图片

请注意,轮廓检测是在阈值化图像上进行的,因此在这一阶段颜
色信息已经丢失了,但是我们是在原始彩色图像上绘制,所以显示的
是彩色结果。
我们回过头来更仔细地看一下之前的for循环中执行的步骤——在
for循环中处理每个检测到的轮廓。首先,计算一个简单的边框: 

这是一个非常简单的方法,可以把轮廓信息转换为矩形的(x,y)坐
标、高度和宽度。绘制矩形非常简单,可以用下面的代码实现:

接下来,计算包围主体的最小矩形区域:        

 这里使用的机制特别有趣:OpenCV没有可以直接从轮廓信息计算
最小矩形顶点坐标的函数。相反,我们先计算最小矩形区域,然后计
算矩形的顶点。请注意,计算的顶点是用浮点数表示的,而像素是用
整数访问的(就OpenCV的绘图函数而言,不能访问半个像素),因此
我们需要进行变换。接下来,画一个框,这样才有机会引入
cv2.drawContours函数:

这个函数就像所有的OpenCV绘图函数一样,都会修改原始图像。
请注意,它的第2个参数接受一个轮廓线数组,这样就可以在一个操作
中绘制多条轮廓线。因此,如果一组点代表一个多边形的轮廓,那么
需要将这些点封装在一个数组中,就像前面的示例中对边框所做的那
样。第3个参数指定要绘制的contours数组的索引:值为-1,则绘制所
有的轮廓线;否则,就绘制contours数组(第2个参数)中指定的索引
处的轮廓线。
大多数绘图函数将绘制的颜色(表示为BGR元组)及线宽(以像素
为单位)作为最后2个参数。
我们要研究的最后一个边界轮廓是最小外接圆:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第24张图片

cv2.minEnclosingCircle函数的唯一特点是它返回一个二元组,
其中第一个元素本身就是一个元组,表示圆心的坐标,第二个元素是
圆的半径。在将所有的值转换为整数之后,画圆就非常简单了。
将前面的代码应用于原始图像,最终的结果如图3-6所示。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第25张图片

这是一个很好的结果,因为圆和矩形紧紧包围着物体。显然,这
个物体不是圆形或者矩形的,所以我们可以用更适合的其他形状。接
下来就来完成这个任务吧。
3.9.2  凸轮廓和Douglas-Peucker算法
在处理轮廓时,我们可能会遇到各种形状的主体,包括凸形主
体。凸形是指在形状中没有两个点的连接线在形状四周边界之外。
OpenCV提供的计算形状的近似边界多边形的第一个工具是
cv2.approxPolyDP。这个函数有3个参数:
·轮廓。
·表示原始轮廓和近似多边形之间最大误差的ε值(值越低,近似
值越接近原始轮廓)。
·布尔标志。如果是True,则表示多边形是闭合的。
ε值对于获得有用的轮廓非常重要,所以我们要理解它代表什
么。ε是近似多边形周长和原始轮廓线周长之差的最大值。差值越
小,近似多边形就越接近原始轮廓。
你可能会问自己,已经有精确表示的轮廓时,为什么还需要一个
近似的多边形?因为多边形是用一组直线表示的,如果可以定义多边
形,那么许多计算机视觉任务将变得简单,这样它们就可以划分区
域,以便进一步操作和处理。
既然我们知道了ε是什么,那么需要获得轮廓周长信息作为参考
值。这可以通过OpenCV的cv2.arcLength函数获得:

实际上,我们正在指示OpenCV计算一个近似多边形,使其周长与
原始轮廓周长之间只相差ε比率,即原弧长的1%。
OpenCV还提供了一个cv2.convexHull函数,用于获取凸形的轮廓
处理信息。这是一个简单的一行表达式:

我们将原始轮廓、近似多边形轮廓和凸包放在同一幅图像中以观
察它们之间的差异。为了简化,我们将在黑色背景上绘制轮廓,这样
原始主体就不可见了,但是它的轮廓可见,如图3-7所示。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第26张图片

如你所见,凸包包围了整个主体,近似多边形是最内层的多边
形,两者之间是主要由弧线组成的原始轮廓。
通过将上述所有步骤组合到一个脚本中,进而加载文件,寻找轮
廓,将轮廓近似为多边形,寻找凸包并显示可视化效果,代码如下:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第27张图片

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第28张图片

这样的代码可以很好地处理简单的图像——只有一个或几个对
象,而且只有几种颜色,可以很容易地用阈值分割。可是,在包含多
个对象或者多种颜色对象的复杂图像中,颜色阈值和轮廓检测的效果
较差。对于这些更具挑战性的情况,我们必须考虑更复杂的算法。

3.10  检测线、圆以及其他形状
检测边缘和寻找轮廓不仅是常见且重要的任务,也是构成其他复
杂操作的基础。线条和形状检测与边缘和轮廓检测携手并进,让我们
来看看OpenCV是如何实现这些的。
线条和形状检测背后的理论基于一种名为霍夫变换(Hough
transform)的技术,霍夫变换是由理查德·杜达(Richard Duda)和
彼得·哈特(Peter Hart)发明的,他们扩展(推广)了20世纪60年
代早期保罗·霍夫(Paul Hough)的成果。我们来看看用于霍夫变换
的OpenCV的API。
3.10.1  检测线
首先,我们来检测一些线,这可以用HoughLines函数或者
HoughLinesP函数实现。HoughLines函数使用标准霍夫变换,而
HoughLinesP函数使用概率霍夫变换(因此名称中有P)。之所以称作
概率霍夫变换,是因为它只分析图像点的子集,并估计这些点属于同
一条线的概率。它是标准霍夫变换的优化版本,计算强度更小,执行
速度更快。HoughLinesP的实现返回每个检测线段的两个端点,而
HoughLines的实现返回每条线,表示形式为一个单点和一个角度,不
包含端点的信息。
我们来看一个非常简单的例子:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第29张图片

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第30张图片

这个简单脚本的关键部分(除了HoughLines函数调用)是设置最
小线段长度(丢弃较短的线)和最大线段间距(即将两条线段视为单
独线段之前,线段的最大间距)。
同时,请注意,HoughLines函数采用单通道二值图像,并通过
Canny边缘检测滤波器进行处理。Canny并非严格要求,但是已经去噪
且只表示边缘的图像是霍夫变换的理想源,所以你会发现这是一种常
见的做法。
HoughLinesP的参数如下:
·图像。
·搜索线时使用的分辨率或者步长。rho是以像素为单位的位置步
长,而theta是以弧度为单位的旋转步长。例如,如果指定rho=1且
theta=np.pi/180.0,则搜索的线之间的间隔只有1像素和1度。
·threshold表示丢弃低于该阈值的线。霍夫变换类似于箱和投票的
系统,每个箱表示一条线,如果候选线至少拥有阈值数的选票就保
留,否则就将其丢弃。
·minLineLength和maxLineGap,如前所述。
3.10.2  检测圆
OpenCV还有一个函数,名为HoughCircles,用于检测圆。它的工
作方式和HoughLines非常相似,但是在HoughLines中minLineLength和
maxLineGap是用于丢弃或保留线的参数,而在HoughCircles中则是圆
心之间的距离最小,并且还有表示圆半径的最大值和最小值参数。下
面是一个必做的例子:

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第31张图片

图3-8是结果的可视化表示。

Python+OpenCV实用案例应用教程:基于OpenCV的图像处理_第32张图片

 3.10.3  检测其他形状
OpenCV实现的霍夫变换仅限于检测线和圆,但是,在讨论
approxPolyDP时,我们已经隐含地探讨了一般意义的形状检测。这个
函数涉及多边形的近似,所以如果图像包含多边形,那么可以通过
cv2.findContours和cv2.approxPolyDP的联合使用来精确地检测。

3.11  本章小结
此时,你应该已经对OpenCV提供的用于处理图像的颜色模型、傅
里叶变换以及各种滤波器有了很好的理解。
你还应该熟练地掌握了边、线、圆以及一般形状的检测。此外,
你也应该能够找到轮廓,并利用轮廓所提供的关于图像中包含的主体
信息。这些概念是对下一章主题的补充,下一主题为根据深度对图像
进行分割并估计图像中一个主体的距离。

你可能感兴趣的:(python,opencv,图像处理)