第 1 节:计算机视觉和神经网络概论
在本节中,您将加深对理论的理解,并学习有关卷积神经网络在图像处理中的应用的动手技术。 您将学习关键概念,例如图像过滤,特征映射,边缘检测,卷积运算,激活函数,以及与图像分类和对象检测有关的全连接和 softmax 层的使用。 本章提供了许多使用 TensorFlow,Keras 和 OpenCV 的端到端计算机视觉管道的动手示例。 从这些章节中获得的最重要的学习是发展对不同卷积运算背后的理解和直觉-图像如何通过卷积神经网络的不同层进行转换。
在本节结束之前,您将能够执行以下操作:
- 了解图像过滤器如何转换图像(第 1 章)
- 应用各种类型的图像过滤器进行边缘检测(第 1 章)
- 使用 OpenCV 轮廓检测和定向梯度直方图(HOG)检测简单对象(第 1 章)
- 使用尺度不变特征变换(SIFT),本地二进制模式(LBP)模式匹配以及颜色匹配来查找对象之间的相似性(第 1 章和第 2 章)
- 使用 OpenCV 级联检测器进行面部检测(第 3 章)
- 从 CSV 文件列表将大数据输入到神经网络中,并解析数据以识别列,然后可以将其作为
x
和y
值馈入神经网络(第 3 章) - 面部关键点和面部表情识别(第 3 章)
- 为面部关键点开发标注文件(第 3 章)
- 使用 Keras 数据生成器方法将大数据从文件输入到神经网络(第 4 章)
- 构建自己的神经网络并优化其参数以提高准确率(第 4 章)
- 编写代码以通过卷积神经网络的不同层来变换图像(第 4 章)
本节包括以下章节:
- “第 1 章”,“计算机视觉和 TensorFlow 基础知识”
- “第 2 章”,“使用本地二进制模式的内容识别”
- “第 3 章”,“使用 OpenCV 和 CNN 进行面部检测”
- “第 4 章”,“图像深度学习”
一、计算机视觉和 TensorFlow 基础知识
随着深度学习方法增强了传统技术(例如图像阈值,过滤和边缘检测)的应用,计算机视觉在许多不同的应用中正在迅速扩展。 TensorFlow 是 Google 创建的一种广泛使用的,功能强大的机器学习工具。 它具有用户可配置的 API,可用于在本地 PC 或云中训练和构建复杂的神经网络模型,并在边缘设备中进行大规模优化和部署。
在本章中,您将了解使用 TensorFlow 的高级计算机视觉概念。 本章讨论计算机视觉和 TensorFlow 的基本概念,以使您为本书的后续更高级章节做好准备。 我们将研究如何执行图像哈希和过滤。 然后,我们将学习特征提取和图像检索的各种方法。 接下来,我们将了解应用中的可视搜索,其方法以及我们可能面临的挑战。 然后,我们将概述高级 TensorFlow 软件及其不同的组件和子系统。
我们将在本章中介绍的主题如下:
- 使用图像哈希和过滤检测边缘
- 从图像中提取特征
- 使用轮廓和 HOG 检测器的对象检测
- TensorFlow,其生态系统和安装概述
技术要求
如果尚未安装,请从这里安装 Anaconda。 Anaconda 是 Python 的包管理器。 您还需要使用pip install opencv-python
为要执行的所有计算机视觉工作安装 OpenCV。 OpenCV 是用于计算机视觉工作的内置编程函数的库。
使用图像哈希和过滤检测边缘
图像哈希是一种用于查找图像之间相似性的方法。 散列涉及通过转换将输入图像修改为固定大小的二进制向量。 使用不同的转换有多种用于图像哈希的算法:
- 永久哈希(phash):余弦变换
- 差异哈希(dhash):相邻像素之间的差异
经过哈希转换后,可以将图像与汉明距离快速比较。 以下代码显示了用于应用哈希转换的 Python 代码。 0
的汉明距离表示相同的图像(重复),而较大的汉明距离表示图像彼此不同。 以下代码段导入 Python 包,例如PIL
,imagehash
和distance
。 imagehash
是支持各种哈希算法的 Python 包。 PIL
是 Python 图像库,distance
是 Python 包,用于计算两个散列图像之间的汉明距离:
from PIL import Image import imagehash import distance import scipy.spatial hash1 = imagehash.phash(Image.open(…/car1.png)) hash2 = imagehash.phash(Image.open(…/car2.png)) print hamming_distance(hash1,hash2)
图像过滤是一种基本的计算机视觉操作,它通过对输入图像的每个像素应用核或过滤器来修改输入图像。 以下是图像过滤所涉及的步骤,从进入相机的光到最终的变换图像开始:
- 使用拜耳过滤器形成彩色图案
- 创建图像向量
- 变换图像
- 线性过滤 - 使用核的卷积
- 混合高斯和拉普拉斯过滤器
- 检测图像边缘
使用拜耳过滤器形成彩色图案
拜耳过滤器通过应用去马赛克算法将原始图像转换为自然的,经过颜色处理的图像。 图像传感器由光电二极管组成,光电二极管产生与光的亮度成比例的带电光子。 光电二极管本质上是灰度的。 拜耳过滤器用于将灰度图像转换为彩色图像。 来自拜耳过滤器的彩色图像经过图像信号处理(ISP),该过程涉及数周的各种参数手动调整,以产生所需的人眼图像质量。 当前正在进行一些研究工作,以将手动 ISP 转换为基于 CNN 的处理以生成图像,然后将 CNN 与图像分类或对象检测模型合并以生成一个采用 Bayer 彩色图像并使用边界框检测对象的相干神经网络管道 。 此类工作的详细信息可以在 Sivalogeswaran Ratnasingam 在 2019 年发表的题为《深度相机:用于图像信号处理的全卷积神经网络》的论文中找到。 本文的链接显示在此处。
这是一个拜耳过滤器的示例:
在上图中,我们可以观察到以下内容:
- 拜耳过滤器由红色(
R
),绿色(G
)和蓝色(B
)通道以预定义的模式,因此 G 通道的数量是 B 和 R 的两倍。 - G,R 和 B 通道交替分布。 大多数通道组合是 RGGB,GRGB 或 RGBG。
- 每个通道只会让一种特定的颜色通过,不同通道的颜色组合会产生如上图所示的图案。
创建图像向量
彩色图像是 R,G 和 B 的组合。颜色可以表示为强度值,范围从0
到255
。 因此,每个图像都可以表示为三维立方体,其中x
和y
轴表示宽度和高度,而z
轴表示三种颜色通道(R,G,B),代表每种颜色的强度。 OpenCV 是一个具有为 Python 和 C++ 编写的用于图像处理和对象检测的内置编程函数的库。
我们将从编写以下 Python 代码开始以导入图像,然后我们将看到如何将图像分解为具有 RGB 的 NumPy 向量数组。 然后,我们将图像转换为灰度,并查看当我们仅从图像中提取一种颜色分量时图像的外观:
import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline import matplotlib.pyplot as plt from PIL import Image image = Image.open('../car.jpeg'). # enter image path in .. plt.imshow(image) image_arr = np.asarray(image) # convert image to numpy array image_arr.shape
前面的代码将返回以下输出:
Output: (296, 465, 4) gray = cv2.cvtColor(image_arr, cv2.COLOR_BGR2GRAY) plt.imshow(gray, cmap='gray')
下图显示了基于上述变换的彩色图像和相应的灰度图像:
以下是我们将用于将图像转换为 R,G 和 B 颜色分量的 Python 代码:
plt.imshow(image_arr[:,:,0]) # red channel plt.imshow(image_arr[:,:,1]) # green channel plt.imshow(image_arr[:,:,2]) # blue channel
下图显示了仅提取一个通道(R,G 或 B)后的汽车变形图像:
上图可以表示为具有以下轴的 3D 体积:
x
轴,代表宽度。y
轴,代表高度。- 每个颜色通道代表图像的深度。
让我们看一下下图。 它以 3D 体积显示在x
和y
坐标不同的情况下汽车图像的 R,G 和 B 像素值; 值越高表示图像越亮:
变换图像
图像变换涉及图像的平移,旋转,放大或剪切。 如果(x
,y
)是图像像素的坐标,则新像素的变换后图像坐标(u
,v
)可以表示为:
- 转换:转换常数值的一些示例为
c11 = 1
,c12 = 0
,并且c13 = 10
;c21 = 0
,c22 = 1
,c23 = 10
。结果方程变为u = x + 10
和v = y + 10
:
- 旋转:一些旋转常数值的示例是
c11 = 1
,c12 = 0.5
,c13 = 0
;c21 = -0.5
,c22 = 1
,c23 = 0
。
所得的等式变为u = x + 0.5y
和v = -0.5x + y
:
- 旋转 + 平移:旋转和平移组合常数的一些示例为
c11 = 1
,c12 = 0.5
,c13 = 10
;c21 = -0.5
,c22 = 1
,c23 = 10
。结果方程变为u = x + 0.5y + 10
和v = -0.5x + y + 10
:
- 剪切:一些剪切常数值示例为
c11 = 10
,c12 = 0
和c13 = 0
;c21 = 0
,c22 = 10
,c23 = 0
。所得方程变为u = 10x
和v = 10y
:
图像转换在计算机视觉中特别有用,可以从同一图像中获取不同的图像。 这有助于计算机开发对平移,旋转和剪切具有鲁棒性的神经网络模型。 例如,如果在训练阶段仅在卷积神经网络(CNN)中输入汽车前部的图像,在测试阶段将汽车旋转 90 度的角度,则该模型将无法检测到该图像。
接下来,我们将讨论卷积运算的机制以及如何应用过滤器来变换图像。
线性过滤 - 使用核的卷积
计算机视觉中的卷积是两个数组(其中一个是图像,另一个是小数组)的线性代数运算,以生成形状与原始图像数组不同的已滤波图像数组。 卷积是累积和关联的。 它可以用数学方式表示如下:
前面的公式解释如下:
F(x,y)
是原始图像。G(x,y)
是过滤后的图像。U
是图像核。
根据核类型U
,输出映像将有所不同。 转换的 Python 代码如下:
import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline import matplotlib.pyplot as plt from PIL import Image image = Image.open(‘…/carshort.png') plt.imshow(image) image_arr = np.asarray(image) # convert image to numpy array image_arr.shape gray = cv2.cvtColor(image_arr, cv2.COLOR_BGR2GRAY) plt.imshow(gray, cmap='gray') kernel = np.array([[-1,-1,-1], [2,2,2], [-1,-1,-1]]) blurimg = cv2.filter2D(gray,-1,kernel) plt.imshow(blurimg, cmap='gray')
前面代码的图像输出如下:
左边是输入图像,右边是通过对图像应用水平核而获得的图像。 水平核仅检测水平边缘,这可以通过水平线的白色条纹看到。 有关水平核的详细信息,请参见“图像梯度”部分。
前面的代码导入了用于机器学习和计算机视觉工作的必要 Python 库,例如 NumPy 处理数组,cv2
用于 openCV 计算机视觉工作,PIL 处理 Python 代码中的图像,以及 Matplotlib 绘制结果。 然后,它使用 PIL 导入图像,并使用 OpenCV BGr2GRAY
缩放函数将其转换为灰度。 它使用 NumPy 数组创建用于边缘过滤的核,使用核模糊图像,然后使用imshow()
函数显示图像。
过滤操作分为三类:
- 图像平滑
- 图像梯度
- 图像锐化
图像平滑
在图像平滑中,通过应用低通过滤器来消除图像中的高频噪声,例如:
- 均值过滤器
- 中值过滤器
- 高斯过滤器
这使图像模糊,并且通过施加其端值不改变正负号并且值没有明显不同的像素来执行。
图像过滤通常是通过在图像上滑动框式过滤器来完成的。 框式过滤器由n x m
核除以(n * m
)表示,其中n
是行数,m
是行数。 列。 对于3 x 3
核,其外观如下:
假设此核已应用于先前描述的 RGB 图像。 作为参考,此处显示3 x 3
图像值:
均值过滤器
在对图像执行盒核的卷积运算之后,均值过滤器会使用平均值过滤图像。 矩阵相乘后的结果数组如下:
平均值为42
,它将替换图像中166
的中心强度值,如下面的数组所示。 图像的剩余值将以类似的方式转换:
中值过滤器
在对图像执行盒核的卷积运算之后,中值过滤器用中值过滤图像值。 矩阵相乘后的结果数组如下:
中值是48
,并替换图像中166
的中心强度值,如下数组所示。 图像的剩余值将以类似的方式转换:
高斯过滤器
高斯核由以下方程式表示:
是分布的标准差,k
是仁大小。
对于1
的标准差(σ
)和3 x 3
核(k = 3
),高斯核看起来如下:
在此示例中,当应用高斯核时,图像的转换如下:
因此,在这种情况下,中心强度值为54
。 将该值与中值和均值过滤器值进行比较。
OpenCV 图像过滤
通过将过滤器应用于真实图像,可以更好地理解先前描述的图像过滤器概念。 OpenCV 提供了一种方法。 我们将使用的 OpenCV 代码可以在这个页面中找到。
重要代码在以下代码段中列出。 导入图像后,我们可以添加噪点。 没有噪声,图像过滤器效果将无法很好地显现。 之后,我们需要保存图像。 对于均值和高斯过滤器,这不是必需的,但是如果我们不使用中值过滤器保存图像,然后再次将其导回,则 Python 将显示错误。
请注意,我们使用plt.imsave
而不是 OpenCV 保存图像。 使用imwrite
直接保存将导致黑色图像,因为在保存之前需要将图像规格化为 255 比例。 plt.imsave
没有该限制。
之后,我们使用blur
,medianBlur
和GaussianBlur
通过均值,中值和高斯过滤器来转换图像:
img = cv2.imread('car.jpeg') imgnoise = random_noise(img, mode='s&p',amount=0.3) plt.imsave("car2.jpg", imgnoise) imgnew = cv2.imread('car2.jpg') meanimg = cv2.blur(imgnew,(3,3)) medianimg = cv2.medianBlur(imgnew,3) gaussianimg = cv2.GaussianBlur(imgnew,(3,3),0)
下图显示了使用matplotlib pyplot
绘制的结果图像:
请注意,在这三种情况下,过滤器都会去除图像中的噪点。 在此示例中,似乎中值过滤器是从图像中去除噪声的三种方法中最有效的方法。
图像梯度
图像梯度可计算给定方向上像素强度的变化。 像素强度的变化是通过对带有核的图像执行卷积运算获得的,如下所示:
选择核时,两个极端的行或列将具有相反的符号(正负),因此在对图像像素进行乘和求和时会产生差运算符。 让我们看下面的例子:
- 水平核:
- 垂直核:
此处描述的图像梯度是计算机视觉的基本概念:
- 可以在
x
和y
方向上计算图像梯度。 - 通过使用图像梯度,可以确定边缘和角落。
- 边缘和角落包含有关图像形状或特征的许多信息。
- 因此,图像梯度是一种将低阶像素信息转换为高阶图像特征的机制,卷积运算将其用于图像分类。
图像锐化
在图像锐化中,通过应用高通过滤器(差分算子)可以消除图像中的低频噪声,从而导致线条结构和边缘变得更加清晰可见。 图像锐化也称为拉普拉斯运算,由二阶导数表示,如下所示:
由于存在差异算符,因此相对于核中点的四个相邻单元始终具有相反的符号。 因此,如果核的中点为正,则四个相邻单元为负,反之亦然。 让我们看下面的例子:
请注意,二阶导数相对于一阶导数的优势在于,二阶导数将始终经过零交叉。 因此,可以通过查看零交叉点(0
值)来确定边缘,而不是通过观察一阶梯度的梯度大小(可以在图像之间和给定图像内变化)来确定边缘。
混合高斯和拉普拉斯运算
到目前为止,您已经了解到高斯运算会使图像模糊,而拉普拉斯运算会使图像锐化。 但是为什么我们需要每个操作,在什么情况下使用每个操作?
图像由特征,特征和其他非特征对象组成。 图像识别就是从图像中提取特征并消除非特征对象。 我们将图像识别为诸如汽车之类的特定物体,因为与非特征相比,其特征更为突出。 高斯滤波是一种从特征中抑制非特征的方法,该方法会使图像模糊。
多次应用它会使图像更加模糊,并同时抑制特征和非特征。 但是由于特征更强,可以通过应用拉普拉斯梯度来提取它们。 这就是为什么我们将高斯核的 sigma 卷积两次或更多次,然后应用 Laplacian 运算来清晰显示特征的原因。 这是大多数卷积操作中用于对象检测的常用技术。
下图显示了输入3 x 3
图像部分,核值,卷积运算后的输出值以及结果图像:
上图显示了各种高斯和倾斜核,以及如何通过应用核来转换图像的3 x 3
截面。 下图是上一个的延续:
前面的表示根据卷积运算的类型清楚地显示了图像如何变得更加模糊或清晰。 对卷积运算的这种理解是基本的,因为我们了解更多有关在 CNN 各个阶段使用 CNN 优化核选择的信息。
检测图像边缘
边缘检测是计算机视觉中基于亮度和图像强度变化来查找图像特征的最基本处理方法。 亮度的变化是由于深度,方向,照明或角落的不连续而导致的。 边缘检测方法可以基于一阶或二阶:
下图以图形方式说明了边缘检测机制:
在这里,您可以看到图像的强度在中点附近从暗变亮,因此图像的边缘在中间点。 一阶导数(强度梯度)在中点先升后降,因此可以通过查看一阶导数的最大值来计算边缘检测。 但是,一阶导数方法的问题在于,取决于输入函数,最大值可能改变,因此不能预先确定最大值的阈值。 但是,如图所示,二阶导数始终在边缘处穿过零点。
Sobel 和 Canny 是一阶边缘检测方法,而二阶方法是 Laplacian 边缘检测器。
Sobel 边缘检测器
Sobel 运算符通过计算图像强度函数的梯度(以下代码中的Sobelx
和Sobely
)来检测边缘。 通过将核应用于图像来计算梯度。 在以下代码中,核大小(ksize
)为5
。 此后,通过取梯度的比率(sobely
/sobelx
)来计算 Sobel 梯度(SobelG):
Sobelx=cv2.Sobel(gray,cv2.CV_64F,1,0,ksize=5) Sobely=cv2.Sobel(gray,cv2.CV_64F,0,1,ksize=5) mag,direction = cv2.cartToPolar(sobelx,sobely,angleInDegrees =True) sobelG = np.hypot(sobelx,sobely)
Canny 边缘检测仪
Canny 边缘检测器使用二维高斯过滤器去除噪声,然后应用具有非最大抑制的 Sobel 边缘检测来挑选x
和y
之间的最大比值。 在任何像素点进行梯度,最后应用边缘阈值检测是否存在边缘。 以下代码显示了灰度图像上的 Canny 边缘检测。 min
和max
值是用于比较图像梯度以确定边缘的阈值:
Canny = cv2.Canny(gray,minVal=100,maxVal=200)
下图显示了应用Sobel-x
,Sobel-y
和 Canny 边缘检测器后的汽车图像:
我们可以看到,Canny 在检测汽车方面的表现比 Sobel 好得多。 这是因为 Canny 使用二维高斯过滤器消除了噪声,然后应用具有非最大抑制的 Sobel 边缘检测来挑选x
和y
之间的最大比值。 在任何像素点上进行梯度,最后应用边缘阈值检测是否存在边缘。
从图像中提取特征
一旦我们知道了如何检测边缘,下一个任务就是检测特征。 许多边缘合并形成特征。 特征提取是识别图像中的视觉图案并提取与未知对象的图像匹配的任何可辨别局部特征的过程。 在进行特征提取之前,了解图像直方图很重要。 图像直方图是图像色彩强度的分布。
如果直方图相似,则图像特征与测试图像匹配。 以下是用于创建汽车图像直方图的 Python 代码:
import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline import matplotlib.pyplot as plt from PIL import Image image = Image.open('../car.png') plt.imshow(image) image_arr = np.asarray(image) # convert image to numpy array image_arr.shape color = ('blue', 'green', 'red') for i,histcolor in enumerate(color): carhistogram = cv2.calcHist([image_arr],[i],None,[256],[0,256]) plt.plot(carhistogram,color=histcolor) plt.xlim([0,256])
前面的 Python 代码首先导入必要的 Python 库,例如cv2
(OpenCV),NumPy(用于数组计算),PIL(用于导入图像)和 Matplotlib(用于绘制图形)。 之后,它将图像转换成数组并循环遍历每种颜色,并绘制每种颜色(R,G 和 B)的直方图。
下图显示了汽车图像的直方图输出。x
轴表示从0
(黑色)到256
(白色)的颜色强度值,y
轴表示出现的频率:
直方图显示 R,G 和 B 的峰值颜色强度在100
附近,第二个峰值在150
附近。 这意味着汽车的平均颜色是灰色。 以200
的强度值显示的0
频率(在图像的最右侧)显示该车肯定不是白色的。 类似地,以50
的强度值出现的0
频率显示图像不是完全黑色。
OpenCV 图像匹配
图像匹配是一种将两个不同的图像匹配以找到共同特征的技术。 图像匹配技术具有许多实际应用,例如匹配指纹,使地毯颜色与地板或墙壁颜色匹配,使照片匹配以找到同一个人的两个图像,或者比较制造缺陷以将它们分为相似的类别以进行更快的分析。 本节概述了 OpenCV 中可用的图像匹配技术。 这里描述了两种常用的方法: 暴力破解(BFMatcher)和用于近似最近的邻居的快速库**(FLANN)。 在本书的稍后部分,我们还将讨论其他类型的匹配技术,例如“第 2 章”,“使用局部二进制模式的内容识别”中的直方图匹配和。 “第 6 章”,“使用迁移学习的视觉搜索”中的局部二进制模式。
在 BFMatcher 中,将比较测试图像和目标图像各部分之间的汉明距离,以实现最佳匹配。 另一方面,FLANN 速度更快,但只会找到最接近的邻居–因此,它找到了很好的匹配项,但不一定是最佳匹配项。 KNN 工具假定相似的事物彼此相邻。 它根据目标与源之间的距离找到最接近的第一近邻。 可以在这个页面上找到用于图像匹配的 Python 代码。
请注意,在下图中,BFMatcher 找到了更相似的图像。 该图是前面的代码(preface_SIFT.ipynb
)返回的输出。 我们来看一下:
上图显示了如何应用 BFMatcher 和 FLANN 的 KNN 匹配器将单个瓷砖匹配到整个浴室地板。 显然,与 FLANN 匹配器(红线)相比,BFMatcher(蓝线)找到更多的平铺点。
前面描述的图像匹配技术还可以用于查找两点之间的相对距离-一个点可以是参考点,例如从中获取图像的汽车,而另一个可以是道路上的另一辆汽车。 然后使用该距离来开发防撞系统。
使用轮廓和 HOG 检测器的对象检测
轮廓是图像中形状相似的封闭区域。 在本节中,我们将使用轮廓来分类和检测图像中的简单对象。 我们将使用的图像由苹果和橙子组成,我们将使用轮廓和 Canny 边缘检测方法来检测对象并将图像类名称写在边界框上。 该部分的代码可以在这个页面中找到。
以下各小节介绍了该方法。
轮廓检测
我们首先需要导入图像,然后使用 Canny 边缘检测器在图像中找到边缘。 因为我们的对象的形状是带有圆角的圆,所以效果很好。 以下是所需的详细代码:
threshold =100 canny_output = cv2.Canny(img, threshold, threshold * 2) contours, hierarchy = cv2.findContours(canny_output, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
如前面的代码所示,在 Canny 边缘检测之后,我们应用了 OpenCV findContours()
方法。 此方法具有三个参数:
- 图像,在本例中为 Canny 边缘检测器输出。
- 检索方法,有很多选择。 我们正在使用的是一种外部方法,因为我们有兴趣在对象周围绘制边界框。
- 轮廓近似法。
检测边界框
该方法本质上包括理解图像及其各种类别的特征以及对图像类别进行分类的开发方法。
请注意,OpenCV 方法不涉及任何训练。 对于每个轮廓,我们使用 OpenCV boundingRect
属性定义一个边界框。
我们将使用两个重要的特征来选择边界框:
- 兴趣区域的大小:我们将消除尺寸小于
20
的所有轮廓。
请注意,20
不是通用编号,它仅适用于此图像。 对于更大的图像,该值可以更大。
- 兴趣区域的颜色:在每个边界框中,我们需要定义宽度从
25%
到75%
的兴趣区域,以确保我们不考虑圈子外的矩形的空白区域。 这对于最小化变化很重要。 接下来,我们使用CV2.mean
定义平均颜色。
我们将通过观察包围它的三个橙色图像来确定颜色的平均阈值和最大阈值。 以下代码使用 OpenCV 的内置方法通过cv2.boundingRect
绘制边界框。 然后根据宽度和高度选择绘制兴趣区域(ROI),并找到该区域内的平均颜色:
count=0 font = cv2.FONT_HERSHEY_SIMPLEX for c in contours: x,y,w,h = cv2.boundingRect(c) if (w >20 and h >20): count = count+1 ROI = img[y+int(h/4):y+int(3*h/4), x+int(h/4):x+int(3*h/4)] ROI_meancolor = cv2.mean(ROI) print(count,ROI_meancolor) if (ROI_meancolor[0] > 30 and ROI_meancolor[0] < 40 and ROI_meancolor[1] > 70 and ROI_meancolor[1] < 105 and ROI_meancolor[2] > 150 and ROI_meancolor[2] < 200): cv2.putText(img, 'orange', (x-2, y-2), font, 0.8, (255,255,255), 2, cv2.LINE_AA) cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,255),3) cv2.imshow('Contours', img) else: cv2.putText(img, 'apple', (x-2, y-2), font, 0.8, (0,0,255), 2, cv2.LINE_AA) cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),3) cv2.imshow('Contours', img)
在前面的代码中,请注意两个if
语句-基于大小的w,h
和基于颜色的ROI_meancolor[0,1,2]
:
- 基于大小的语句消除了所有小于
20
的轮廓。 ROI_meancolor [0,1,2]
表示平均颜色的 RGB 值。
在这里,第三,第四和第八行表示橙色,if
语句将颜色限制为B
组件在30
和40
之间,对于G
则在70
和105
之间 ]组件,以及R
组件的150
和200
。
输出如下。 在我们的示例中,3
,4
和8
是橙色:
1 (52.949200000000005, 66.38640000000001, 136.2072, 0.0) 2 (43.677693761814744, 50.94659735349717, 128.70510396975425, 0.0) 3 (34.418282548476455, 93.26246537396122, 183.0893351800554, 0.0) 4 (32.792241946088104, 78.3931623931624, 158.78238001314926, 0.0) 5 (51.00493827160494, 55.09925925925926, 124.42765432098766, 0.0) 6 (66.8863771564545, 74.85960737656157, 165.39678762641284, 0.0) 7 (67.8125, 87.031875, 165.140625, 0.0) 8 (36.25, 100.72916666666666, 188.67746913580245, 0.0)
请注意,OpenCV 将图像处理为BGR
而不是RGB
。
HOG 检测器
定向梯度的直方图(HOG)是有用的特征,可用于确定图像的局部图像强度。 此技术可用于查找图像中的对象。 局部图像梯度信息可用于查找相似图像。 在此示例中,我们将使用 scikit-image 导入 HOG,并使用它绘制图像的 HOG。 如果尚未安装 scikit-image,则可能需要使用pip install scikit-image
安装它:
from skimage.feature import hog from skimage import data, exposure fruit, hog_image = hog(img, orientations=8, pixels_per_cell=(16, 16), cells_per_block=(1, 1), visualize=True, multichannel=True) hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10)) cv2.imshow('HOG_image', hog_image_rescaled)
下图说明了示例代码中前面代码的结果:
在上图中,我们可以观察到以下内容:
- 左侧显示边界框,而右侧显示图像中每个对象的 HOG 梯度。
- 请注意,每个苹果和橘子都可以正确检测到,并且包围水果的边界框没有任何重叠。
- HOG 描述符显示一个矩形的边界框,其中的梯度表示圆形图案。
- 桔子和苹果之间的梯度显示出相似的图案,唯一的区别是大小。
轮廓检测方法的局限性
在对象检测方面,上一节中显示的示例看起来非常好。 我们无需进行任何训练,并且只需对一些参数进行少许调整,就可以正确检测出橙子和苹果。 但是,我们将添加以下变体,并查看我们的检测器是否仍然能够正确检测对象:
- 我们将添加除苹果和橙子以外的对象。
- 我们将添加另一个形状类似于苹果和橙子的对象。
- 我们将改变光的强度和反射率。
如果我们执行上一部分中的相同代码,它将检测每个对象,就好像它是一个苹果一样。 这是因为所选的width
和height
参数太宽,并且包括所有对象以及 RGB 值,它们在此图像中的显示方式与以前不同。 为了正确检测对象,我们将对if
语句的大小和颜色进行以下更改,如以下代码所示:
if (w >60 and w < 100 and h >60 and h <120): if (ROI_meancolor[0] > 10 and ROI_meancolor[0] < 40 and ROI_meancolor[1] > 65 and ROI_meancolor[1] < 105
请注意,前面的更改对if
语句施加了以前不存在的约束。
RGB 颜色如下:
1 (29.87429111531191, 92.01890359168242, 182.84026465028356, 0.0) 82 93 2 (34.00568181818182, 49.73605371900827, 115.44163223140497, 0.0) 72 89 3 (39.162326388888886, 62.77256944444444, 148.98133680555554, 0.0) 88 96 4 (32.284938271604936, 53.324444444444445, 141.16493827160494, 0.0) 89 90 5 (12.990362811791384, 67.3078231292517, 142.0997732426304, 0.0) 84 84 6 (38.15, 56.9972, 119.3528, 0.0) 82 100 7 (47.102716049382714, 80.29333333333334, 166.3264197530864, 0.0) 86 90 8 (45.76502082093992, 68.75133848899465, 160.64901844140394, 0.0) 78 82 9 (23.54432132963989, 98.59972299168975, 191.97368421052633, 0.0) 67 76
上面的代码在更改后的图像上的结果如下所示:
在上图中可以看到一个遥控器,叉子,刀子和一个塑料杯。 请注意,苹果,橘子和塑料杯的 HOG 功能如何相似,这是预期的,因为它们都是圆形的:
- 塑料杯周围没有包围框,因为未检测到。
- 与苹果和橘子相比,叉子和刀子的 HOG 角形非常不同。
- 遥控器具有矩形的 HOG 形状。
这个简单的示例表明,这种对象检测方法不适用于较大的图像数据集,我们需要调整参数以考虑各种照明,形状,大小和方向条件。 这就是为什么我们将在本书其余部分中讨论 CNN 的原因。 一旦我们使用此方法在不同条件下训练图像,无论对象的形状如何,它将在新的条件下正确检测到对象。 但是,尽管上述方法有局限性,我们还是学习了如何使用颜色和大小将一个图像与另一个图像分开。
ROI_meancolor
是一种用于检测边界框内对象平均颜色的强大方法。 例如,您可以使用它根据边界框内的球衣颜色,绿色苹果与红色苹果或任何类型的基于颜色的分离方法,将一个团队的球员与另一个团队的球员区分开。
TensorFlow,生态系统和安装概述
在前面的部分中,我们介绍了计算机视觉技术的基础知识,例如图像转换,图像过滤,使用核进行卷积,边缘检测,直方图和特征匹配。 这种理解及其各种应用应该为深度学习的高级概念打下坚实的基础,这将在本书的后面部分进行介绍。
计算机视觉中的深度学习是通过许多中间(隐藏)层的卷积运算对许多不同图像特征(例如边缘,颜色,边界,形状等)的累积学习,以全面了解图像类型。 深度学习增强了计算机视觉技术,因为它堆叠了有关神经元行为的许多计算层。 通过组合各种输入以基于数学函数和计算机视觉方法(例如边缘检测)产生输出来完成此操作。
TensorFlow 是一个端到端(E2E)机器学习平台,其中图像和数据被转换为张量以由神经网络进行处理。 例如,大小为224 x 224
的图像可以表示为等级4
的张量为128, 224, 224, 3
,其中128
是神经网络的批量大小,224
是高度和宽度, 3
是颜色通道(R,G 和 B)。
如果您的代码基于 TensorFlow 1.0,那么将其转换为 2.0 版可能是最大的挑战之一。 请遵循这个页面上的说明,以转换为 2.0 版。 大多数时候,当您使用终端在 TensorFlow 中执行 Python 代码时,转换问题会在低级 API 中发生。
Keras 是 TensorFlow 的高级 API。 以下三行代码是安装 Keras 的起点:
from __future__ import absolute_import, division, print_function, unicode_literals import tensorflow as tf from tensorflow import keras
如果不使用最后一行,则可能必须对所有函数使用from tensorflow.keras
导入。
TensorFlow 使用tf.data
从简单的代码构建复杂的输入管道,从而简化并加快了数据输入过程。 您将在 “第 6 章”,“使用迁移学习的视觉搜索”中了解这一点。
在 Keras 中,模型的各层以顺序的形式堆叠在一起。 这由model=tf.keras.Sequential()
引入,并使用model.add
语句添加每一层。 首先,我们需要使用model.compile
编译模型,然后可以使用model.train
函数开始训练。
TensorFlow 模型将保存为检查点并保存模型。 检查点捕获模型使用的参数,过滤器和权重的值。 检查点与源代码关联。 另一方面,保存的模型可以部署到生产设置中,不需要源代码。
TensorFlow 针对多个 GPU 提供分布式训练。 TensorFlow 模型输出可以使用 Keras API 或 TensorFlow 图可视化。
TensorFlow 与 PyTorch
PyTorch 是另一个类似于 TensorFlow 的深度学习库。 它基于 Torch,由 Facebook 开发。 在 TensorFlow 创建静态图的同时,PyTorch 创建动态图。 在 TensorFlow 中,必须首先定义整个计算图,然后运行模型,而在 PyTorch 中,可以平行于模型构建来定义图。
TensorFlow 安装
要在 PC 上安装 TensorFlow 2.0,请在终端中键入以下命令。 确保单击Enter
:
pip install --upgrade pip pip install tensorflow
除 TensorFlow 之外,上述命令还将在终端中下载并提取以下包:
- Keras(用 Python 编写的高级神经网络 API,能够在 TensorFlow 的顶部运行)
protobuf
(用于结构化数据的序列化协议)- TensorBoard(TensorFlow 的数据可视化工具)
- PyGPU(Python 功能,用于图像处理,GPU 计算以提高性能)
cctools
(适用于 Android 的本地 IDE)c-ares
(函数库)clang
(C,C++,Objective-C,OpenCL 和 OpenCV 的编译器前端)llvm
(用于生成前端和后端二进制代码的编译器架构)theano
(用于管理多维数组的 Python 库)grpcio
(用于 Python 的gRPC
包,用于实现远程过程调用)libgpuarray
(可用于 Python 中所有包的常见 N 维 GPU 数组)termcolor
(Python 中的颜色格式输出)absl
(用于构建 Python 应用的 Python 库代码集合)mock
(用虚拟环境替换真实对象以帮助测试)gast
(用于处理 Python 抽象语法的库)
在安装过程中,在询问时按y
表示是:
Downloading and Extracting Packages Preparing transaction: done Verifying transaction: done Executing transaction: done
如果一切都正确安装,您将看到前面的消息。
安装后,根据您的 PC 是 CPU 还是 CPU 和 GPU,输入以下两个命令之一检查 TensorFlow 版本。 请注意,对于所有计算机视觉工作,最好使用 GPU 来加速图像的计算。 对于 Python 3.6 或更高版本,请使用pip3
,对于 Python 2.7,请使用pip
:
pip3 show tensorflow pip3 show tensorflow-gpu pip show tensorflow
输出应显示以下内容:
Name: tensorflow Version: 2.0.0rc0 Summary: TensorFlow is an open source machine learning framework for everyone. Home-page: https://www.tensorflow.org/ Author: Google Inc. Author-email: packages@tensorflow.org License: Apache 2.0 Location: /home/.../anaconda3/lib/python3.7/site-packages Requires: gast, google-pasta, tf-estimator-nightly, wrapt, tb-nightly, protobuf, termcolor, opt-einsum, keras-applications, numpy, grpcio, keras-preprocessing, astor, absl-py, wheel, six Required-by: gcn
有时,您可能会注意到,即使在安装 TensorFlow 之后,Anaconda 环境也无法识别已安装 TensorFlow。 在这种情况下,最好在终端中使用以下命令卸载 TensorFlow,然后重新安装它:
python3 -m pip uninstall protobuf python3 -m pip uninstall tensorflow-gpu
总结
在本章中,我们学习了图像过滤如何通过卷积运算修改输入图像,以产生检测特征的一部分(称为边缘)的输出。 这是计算机视觉的基础。 正如您将在以下各章中了解到的那样,图像过滤的后续应用会将边缘转换为更高级别的图案,例如特征。
我们还学习了如何计算图像直方图,如何使用 SIFT 进行图像匹配以及如何使用轮廓和 HOG 检测器绘制边界框。 我们学习了如何使用 OpenCV 的边界框颜色和大小方法将一个类与另一个类隔离。 本章以 TensorFlow 简介作为结束,这将为本书的其余章节奠定基础。
在下一章中,我们将学习另一种称为模式识别的计算机视觉技术,并将使用它来对具有模式的图像内容进行分类。
二、使用局部二进制模式的内容识别
局部二进制模式(LBP)于 1994 年由 Timo Ojala,Matti Pietikäinen 和 David Harwood 在国际模式识别会议上,在论文《Performance evaluation of texture measures with classification based on Kullback discrimination of distributions》中首次提出。
在本章中,您将学习如何创建 LBP 图像类型的二进制特征描述符和 LBP 直方图,以对纹理图像和非纹理图像进行分类。 您将了解可用于计算直方图之间的差异以找到各种图像之间的匹配的不同方法,以及如何调整 LBP 参数以优化其表现。
本章将涵盖以下主题:
- 使用 LBP 处理图像
- 将 LBP 应用于纹理识别
- 使面部颜色与基础色匹配 - LBP 及其局限性
- 使用基础色匹配面部颜色 - 颜色匹配技术
使用 LBP 处理图像
LBP 是一种灰度图像阈值操作,用于基于不同的模式对图像进行分类。 通过将邻域像素值与中心像素值进行比较来开发二进制模式,并将其用于构建直方图块。 在以下部分中,我们将详细描述 LBP 操作。
生成 LBP 模式
LBP 模式生成的主要步骤如下:
- 将 RGB 图像
A
转换为灰度图像G
。 - 对于图像
G
中每个具有强度I[c](x, y)
的像素,选择P
相邻点(p[0], p[1], ..., p[P-1]
),其半径[I[0], I[1], ..., I[P-1]
具有相应的强度。R
。 半径以像素为单位定义为两个像素之间的差。 像素和相邻点代表图像G
的滑动窗口W
。对于半径R = 1
,P
变为 8,如下所示。
滑动窗口W[0]
用W[0] = [I[c], I[0], I[1], I[P-1]]
表示为数组。 在这里,点 0 到P-1
代表围绕中心像素c
的P
个点的强度:
确定半径R
和相邻点P
之间的关系,以使附近的每个像元恰好具有一个像素。 如上图中的前三个圆圈所示,周长中的每个像元恰好具有一个像素,而最后一个像元在周长中填充了多个像素。 从前三个圆圈,我们可以表示,为了使每个单元格都有一个像素,点数P
可以表示为(8R + 16) / 3
。 下图显示了线性关系和离群值,离群值由左起第四个圆圈显示,在相邻单元格中有重叠点:
- 计算相邻像素和中心像素之间的强度差,并删除第一个值 0。该数组可以表示如下:
W[1] ~ [I[0] - I[c], I[1] - I[c], ..., I[P-1] - I[c]]
- 现在,对图像进行阈值处理。 为此,如果强度差小于 0,则将值分配为 0;如果强度差大于 0,则将值分配为 1,如以下等式所示:
应用阈值函数f
之后的差数组如下:
W[2] = [f(I[0] - I[c]), f(I[1] - I[c]), ..., f(I[P-1] - I[c])
例如,假设第一个差异小于 0 且第二个和最后一个差异大于 0,则数组可以表示如下:
W[2] = [0, 1, ... 1]
- 将差数组
W[2]
乘以二项式权重2^p
,将二进制数组W[2]
转换为表示十进制数组W[LBP]
的代码 3:
请注意,本节中描述的五个步骤将在接下来的几节中引用。
下图显示了在灰度图像的滑动窗口上 LBP 操作的图形表示:
在上图中,我们可以看到以下内容:
- 起始的
3 x 3
核只是图像的一部分。 - 接下来的
3 x 3
是二进制表示形式。 - 左上角的值为 1,因为我们正在比较 120 和 82。
- 顺时针旋转,因为我们将 51 与 82 进行了比较,所以最后一个值为 0。
- 接下来的
3 x 3
核只是2^n
操作。 - 第一个值是 1(
2^0
),最后一个值是顺时针为 128(2^7
)。
了解 LBP 直方图
LBP 数组W[3]
以直方图形式表示如下:
W4 = histogram(W3, bins=P, range=W3(min) to W3(max))
对训练后的图像和测试图像重复上一节中的“步骤 1”至 5,以创建图像(W_train, W_test
)的 LBP 直方图,每个都包含P
个箱子,然后使用直方图比较方法对其进行比较。
直方图比较方法
可以使用不同的直方图比较方法来计算直方图之间的距离。 这些如下:
- 交叉方法:
在 Python 中,这表示如下:
minima = np.minimum(test_hist,train_hist) intersection = np.true_divide(np.sum(minima),np.sum(train_hist))
- 卡方方法:
- 欧几里得方法:
- 城市街区方法:
- Bhattacharya 方法:
- Wasserstein 方法:
- 给定
W_test = N(μ_test, σ_test)
和W_train = N(μ_train, σ_train)
,此处μ
是分布的平均值(第一矩),o
(第二矩)是分布的标准差,而ρ[QQ]
为两个分布W_test
和W_train
的分位数彼此之间的相关性。
前面的距离度量具有以下特征:
- 每种方法的距离绝对值都不相同。
- 除 Wasserstein 方法外,所有方法的测试直方图和训练直方图值之间的最小距离都相似。 Wasserstein 方法根据位置(均值差异),大小(标准差差异)和形状(相关系数)计算距离。
下面显示了给定半径R = 5
时的原始灰度图像和相应的 LBP 图像:
接下来,我们将评估半径变化对图像清晰度的影响。 为此,需要通过将半径值从 1 更改为 10 来执行 Python 代码。下图显示了对 LBP 图像清晰度的最终影响。
根据相关性P = (8R + 16) / 3
获得到多个点的半径。请注意,随着半径的增加,图像中的图案变得更加清晰。 围绕半径 5 和点 20-25,该图案在主拱的主要图案和次要图案之间变得更加清晰。 在很大的半径上,辅助图案变得不太明显:
从前面的图像中还可以清楚地看到以下内容:
- 选择
R
和P
对于模式识别很重要。 - 可以通过
P = (8R + 16) / 3
来选择初始值,但是对于给定的R
,超过此值的P
的值并不意味着不良表现,如先前示例中的R = 5
,P = 25
所示。 - 选择的模式明显优于
R = 4
,P = 16
示例,并且与R = 5.5
,P = 20
非常相似。
另外,请注意,此处的示例仅提供适用于此图像的准则。 对于不同尺寸的图像,请从此示例中学习,首先选择P
的初始值,然后调整R
和P
以获得所需模式。
LBP 的计算成本
与传统的神经网络方法相比,LBP 在计算上更便宜。 LBP 的计算成本由 Li Li,Paul Fieguth,Wang Xiaogang Wang,Matti Pietikäinen 和 Dewen Hu 在他们的论文《使用新的稳健性基准评估 LBP 和深纹理描述符》中提出。 论文的详细信息可以在这里找到。
作者确定了在 2.9 GHz Intel Quad-Core CPU 和 16 GB RAM 上对 480 张图像进行特征提取所花费的平均时间,这些图像的大小为128 x 128
。 该时间不包括训练时间。 研究发现,与被认为中等的 AlexNet 和 VGG 相比,LBP 特征提取非常快。
将 LBP 应用于纹理识别
既然我们了解了 LBP 的基础知识,我们将把它应用于纹理识别示例。 对于此示例,已将 11 张训练后的图像和 7 张尺寸为50 x 50
的测试图像分为以下几类:
- 训练图像
- 图案图像(7)
- 普通图像(4)
- 测试图像
- 图案图像(4)
- 普通图像(3)
执行“生成 LBP 模式”部分的“步骤 1”至 5 ,然后将每个测试图像的 LBP 直方图与所有训练图像进行比较,以找到最佳匹配。 尽管已使用了不同的直方图比较方法,但是对于此分析,将使用卡方检验作为确定匹配的主要方法。 具有正确匹配项的最终摘要输出用绿线显示,而错误匹配项将用红线显示。 实线是最短距离的第一个匹配项,而虚线是下一个最佳匹配项。 如果用于下一个最佳匹配的直方图之间的距离比最小距离远得多,则仅显示一个值(最小距离),这表明系统对此输出具有相当高的置信度。
下图显示了使用 LBP 在测试和训练灰度图像之间的匹配过程。 实线表示最接近的匹配,而虚线表示第二最接近的匹配。 第三张测试图像(从左起)只有一个匹配项,这意味着当图像转换为灰度时,模型对其预测非常有信心。 第二个,第三个和第六个训练图像(从右侧开始)没有相应的测试图像匹配:
在这里,我们可以看到,基于有限的训练数据(11 个样本),LBP 通常会产生很好的匹配,在考虑的七个测试样本中只有一个错误。 要了解在上一张图像中如何完成相关性,我们需要绘制 LBP 直方图并比较训练图像和测试图像之间的直方图。 下图分析每个测试图像,并将其直方图与相应测试图像的直方图进行比较,以找出最匹配的图像。
n_points = 25
表示 LBP 中有 25 点。 使用 LBP 直方图的主要优点是可实现平移的归一化,从而使其旋转不变。 我们将逐一分析每个直方图。 直方图的x
轴为 25,表示点数(25),而y
轴为 LBP 直方图块。
下图中显示的两个图像都具有图案并且看起来相似:
先前图像的直方图分析显示了相似的模式,使用 LBP 可以显示正确的匹配。 下图中显示的两个图像都具有图案并且看起来相似。 实际上,它们是从不同的方向和不同的阴影获取的同一张地毯图像:
先前图像的直方图分析显示了相似的模式,使用 LBP 可以显示正确的匹配。 下图中显示的两个图像具有图案,但是它们来自不同的地毯:
先前图像的直方图分析显示了相似的模式。 它们的样式看起来相似,但图像实际上不同。 因此,这是匹配不良的一个示例。
下一张图像中的第一张图像具有图案(与我们已经看到的图像相比,它是一种较弱的图案),而经过训练的图像则完全没有图案,但是在地毯上似乎有污渍:
先前图像的直方图分析显示了相似的模式。 它们的样式看起来相似,但图像实际上不同。 这是比赛不佳的另一个例子。 由于红地毯上的污渍,LBP 似乎认为图像相似。
下图显示了 LBP 将灰色地毯与前面相同的红色地毯相匹配:
LBP 直方图显示了类似的趋势–这是合理的,因为 LBP 是一种灰度图像识别技术。
下图显示了 LBP 将硬木地板与地毯相匹配:
请注意,训练图像没有硬木地板,因此 LBP 发现具有叶子形状的地毯是与具有木纹的木地板最接近的匹配。
最后一个 LBP 图片显示相似的图片,几乎没有图案:
在这里,LBP 预测似乎是正确的。
比较顶部直方图和底部直方图,以可视化直方图如何比较测试图像和训练图像。 可以在这个页面中找到详细的 Python 代码。
使面部颜色与基础颜色匹配 - LBP 及其局限性
由于我们在纹理识别方面使用 LBP 取得了相对良好的成功,因此让我们尝试另一个示例来了解 LBP 的局限性。 在此示例中,从浅色到深色(测试)的七种面部颜色将与 10 种基础色(训练)相匹配,这些基础色是50 x 50
的图像。
与纹理识别示例类似,将应用“生成 LBP 模式”部分中的“步骤 1”至 5,然后将每个面部彩色图像 LBP 直方图与与所有基础彩色图像 LBP 直方图一起比较来找到最佳匹配。 尽管已使用不同的直方图比较方法,但对于此分析,将使用卡方检验作为确定匹配的主要方法。 下图显示了最终的摘要输出:
如我们所见,LBP 的效果不佳,所有脸部颜色都导致底色为 4 或 8。 为了理解这种情况,已绘制了 RGB,灰度和 LBP 图像的两个级别(一个带有R2.5, P12
,另一个带有R5.5, P20
)。 这是由两个因素引起的:
- 面部颜色从 RGB 到灰度的转换会导致图像中不必要的亮度,这在比较过程中会产生误导。
- LBP 转换采用这些模式并生成无法正确解释的任意灰色阴影。
下图显示了两个图像-面部颜色 1 和 7-分别代表肤色和深色皮肤的颜色,以及 LBP 不同步骤的结果。 每个图像都会转换为灰度,这表明两个图像的中间都有一个亮点,而原始彩色图像无法看到该亮点。 然后,将两个 LBP 操作应用于图像:一个半径为 2.5,另一个半径为 5.5。 在这里,我们可以看到在应用 LBP 之后有很多相似之处,这是原始彩色图像所没有的。 让我们看一下下面的图片:
解决第一个问题的可能方法是应用高斯滤波,我们已在“第 1 章”,“计算机视觉和 TensorFlow 基本原理”中进行了研究,以抑制该模式。 下图可以看到应用高斯过滤器然后进行 LBP 的结果:
即使在应用过滤器之后,也无法清晰地区分浅灰和深灰这两种灰色阴影。 由此可以得出结论,LBP 并不是用于面部颜色识别的好方法。
使脸部颜色与基础颜色匹配 – 颜色匹配技术
对于这种方法,RGB 图像不会转换为灰度; 而是使用以下 Python 代码(针对每种情况重复)确定七种面部颜色和 10 种基础颜色中的每一种的颜色强度值:
facecol1img = Image.open('/…/faceimage/facecol1.JPG') facecol1arr = np.asarray(facecol1img) (mfc1, sfc1) = cv2.meanStdDev(facecol1arr) statfc1 = np.concatenate([mfc1, sfc1]).flatten() print ("%s statfc1" %(statfc1))
输出具有六个元素。 前三个是 RGB 平均值,而后三个是 RGB 值的标准差。
面部和底色之间的强度差计算如下:
让我们看一下下面的图像,它代表了脸部和底色:
矩阵中差异最小的值是最佳匹配。 我们可以看到,对于每种脸部颜色,匹配(如对角线上的最小限度点所示)可得出合理的值,这表明颜色匹配技术应该是与基础色进行脸部颜色匹配的首选方法 。
总结
在本章中,我们学习了如何获取图像像素并将其与给定半径内的相邻像素阈值化,然后执行二进制和积分运算以创建 LBP 模式。 LBP 模式是无监督机器学习的一个很好的例子,因为我们没有用输出训练分类器。 相反,我们学习了如何调整 LBP 的参数(半径和点数)以达到正确的输出。 发现 LBP 是用于纹理分类的非常强大且简单的工具。 但是,当图像为非纹理图像时,LBP 无法返回良好的结果,我们学习了如何开发 RGB 颜色匹配模型来匹配彩色的非纹理图像,例如面部和基础色。 要创建 LBP 表示,必须将图像转换为灰度。
在下一章中,我们将结合各种边缘检测方法来识别人脸,眼睛和耳朵,介绍积分图像的概念。 然后,我们将介绍卷积神经网络,并使用它来确定面部关键点和面部表情。
三、使用 OpenCV 和 CNN 的人脸检测
面部检测是计算机视觉的重要组成部分,并且是近年来发展迅速的领域。 在本章中,您将从 Viola-Jones 面部和关键特征检测的简单概念开始,然后继续介绍基于神经网络的面部关键点和面部表情检测的高级概念。 本章将以 3D 人脸检测的高级概念作为结尾。
本章将涵盖以下主题:
- 应用 Viola-Jones AdaBoost 学习和 Haar 级联分类器进行人脸识别
- 使用深度神经网络预测面部关键点
- 使用 CNN 预测面部表情
- 3D 人脸检测概述
应用 Viola-Jones AdaBoost 学习和 Haar 级联分类器进行人脸识别
2001 年,微软研究院的保罗·维奥拉和三菱电机的迈克尔·琼斯通过开发名为 Haar 级联分类器 的分类器,开发了一种检测图像中人脸的革命性方法。Haar 级联分类器基于 Haar 特征,这些特征是矩形区域中像素值差异的总和。 校准差值的大小以指示面部给定区域(例如,鼻子,眼睛等)的特征。 最终的检测器具有 38 个级联分类器,这些分类器具有 6,060 个特征,包括约 4,916 个面部图像和 9,500 个非面部图像。 总训练时间为数月,但检测时间非常快。
首先,将图像从 RGB 转换为灰度,然后应用图像过滤和分割,以便分类器可以快速检测到对象。 在以下各节中,我们将学习如何构造 Haar 级联分类器。
选择类似 Haar 的特征
Haar 级联分类器算法基于这样的思想,即人脸的图像在脸部的不同区域具有强度的独特特征,例如,脸部的眼睛区域比眼睑底部暗,鼻子区域比旁边的两个面部区域更亮。 类似于 Haar 的特征由黑色和白色的相邻矩形表示,如下图所示。 在此图像中,存在几个潜在的类似 Haar 的特征(两个矩形,三个矩形和四个矩形):
请注意,矩形部分放置在面部的特征上。 由于眼睛区域的强度比脸部暗,因此矩形的黑色区域靠近眼睛,白色区域低于眼睛。 同样,由于鼻子区域比周围的环境明亮,鼻子上出现白色矩形,而两侧则是黑色矩形。
创建一个完整的图像
积分图像可用于一次快速计算矩形特征像素值。 为了更好地理解积分图像,让我们看一下其计算的以下细分:
- 可以将 Haar 样特征的值计算为白色区域中的像素值之和与黑色区域中的像素值之和之差。
- 像素
I
的总和(x
,y
)可以由当前像素位置左上方和上方所有像素的值[i
(x
,y
),包括当前像素值,可以表示为:
在下图中,I(x, y)
是由九个像素值组成的最终积分图像值(62
,51
,51
,111
,90
,77
,90
,79
和73
)。 将所有这些总和得出 684 的值:
下图显示了脸部的眼睛区域的像素强度和相应的积分图像:
上图显示,矩形区域的像素强度总和是通过将上方和左侧的所有像素值相加得出的,例如,917
通过将866
(这是73, 79, 90, 111, ..., 68
的总和)和10
,14
,9
和18
。 注意,917
也可以通过将717
求和,然后将其加到50
,64
和68
和18
的总和来获得。
前面的像素方程式的总和可以重写如下:
在积分图像中,可以通过将四个数组(如前面的方程式所示)相加来计算图像中任何矩形区域的面积,而不是针对所有单个像素的总和进行六个内存访问。 Haar 分类器的矩形总和可以从前面的方程式获得,如以下方程式所示:
前面的等式可以重新安排如下:
下图显示了转换为整数图像像素值的图像像素值:
右侧的积分图像就是左侧像素值的总和-所以113 = 62 + 51
,依此类推。 黑色阴影区域像素值表示黑色 Haar 矩形,如前所述。 为了计算阴影区域的强度值,我们取整数强度值 1,063,然后从中减去 268。
进行 AdaBoost 训练
图像被划分为T
窗口,在其中应用了类似 Haar 的特征,并如前所述计算其值。 AdaBoost 通过迭代T
窗口的训练集,从大量弱分类器中构建出一个强分类器。 在每次迭代中,基于多个正样本(面部)和多个负样本(非面部)来调整弱分类器的权重,以评估分类错误的项目的数量。 然后,对于下一次迭代,将为错误分类的项目的权重分配更高的权重,以增加检测到这些权重的可能性。 最终的强分类器h
(x
)是根据弱分类器的误差加权的组合。
- 弱分类器:每个弱分类器都具有一个特征
f
。 它具有极性p
和阈值θ
:
- 强分类器:最终的强分类器
h
(x
)具有最小的误差,E[t]
,并由以下给出:
在此,E[t] = log(1 / E[t])
和E[t] = E[t] / (1 - E[t])
:
权重(W[t]
)初始化如下:
在此,P
和N
分别是正样本和负样本的数量。 权重值更新如下:
每个弱分类器都会计算一个特征。 请注意,弱分类器无法单独进行分类,但是将其中几个组合在一起可以很好地进行分类。
注意级联分类器
前面描述的每个强分类器形成一个级联,其中每个弱分类器代表一个阶段,可以快速删除负子窗口并保留正子窗口。 来自第一个分类器的肯定响应表示已检测到脸部区域(例如,眼睛区域),然后算法继续进行下一个特征(例如,鼻子区域)以触发第二个区域的分类器的评估,依此类推。 任何时候的负面结果都会导致该阶段立即被拒绝。 下图说明了这一点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSHTT58k-1681784327300)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/master-cv-tf-2x/img/5d972796-d061-4cea-aa39-5d6e98320b04.png)]
此图显示负特征已被立即消除。 随着分类从左向右移动,其准确率会提高。
训练级联检测器
开发了整个训练系统,以最大程度地提高检测率并最大程度地降低假阳性率。 Viola 和 Jones 通过为级联检测器的每个阶段设置目标检测率和假阳性率,实现了以下目标:
- 级联检测器每一层中的特征数量都会增加,直到达到该层的目标检测率和假阳性目标为止。
- 如果总体假阳性率不够低,则添加另一个阶段。
- 矩形特征将添加到当前阶段,直到达到其目标速率。
- 当前阶段的假阳性目标被用作下一阶段的阴性训练集。
下图显示了 OpenCV Python Haar 级联分类器,用于分类正面和眼睛以检测面孔和眼睛。 左图和右图均显示它可以正确检测到脸部; 然而,在第一图像中,由于左眼的眩光而仅检测到右眼,而在第二图像中检测到双眼。 Viola-Jones 级联检测方法查找强度梯度(眼睛区域比下面的区域暗),在这种情况下,由于眼镜右镜片的眩光,它无法在右眼中检测到 :
可在这个页面中找到用于摄像头视频中的面部和眼睛检测的 OpenCV Python 代码。 请注意,为了使代码起作用,您需要在文件夹中指定 Haar 级联检测器所在的路径。
到目前为止,我们已经了解了 Haar 级联分类器,以及如何使用内置的 OpenCV 代码将 Haar 级联分类器应用于面部和眼睛检测。 前述概念基于使用积分图像检测类似 Haar 的特征。 该方法非常适合面部,眼睛,嘴巴和鼻子的检测。 但是,可以将不同的面部表情和皮肤纹理用于情感(快乐与悲伤)或年龄确定等。 Viola-Jones 方法不适用于处理这些不同的面部表情,因此我们需要使用 Viola-Jones 方法进行面部检测,然后应用神经网络确定面部边界框内的面部关键点。 在下一节中,我们将详细学习此方法。
使用深度神经网络预测面部关键点
在本节中,我们将讨论面部关键点检测的端到端管道。 面部关键点检测对于计算机视觉来说是一个挑战,因为它要求系统检测面部并获取有意义的关键点数据,将这些数据绘制在面部上,并开发出神经网络来预测面部关键点。 与对象检测或图像分类相比,这是一个难题,因为它首先需要在边界框内进行面部检测,然后再进行关键点检测。 正常的对象检测仅涉及检测代表对象周围矩形边界框的四个角的四个点,但是关键点检测需要在不同方向上的多个点(超过 10 个)。 可以在这个页面上找到大量的关键点检测数据及其使用教程。 Kaggle 关键点检测挑战涉及一个 CSV 文件,该文件包含指向 7,049 个图像(96 x 96
)的链接,每个图像包含 15 个关键点。
在本部分中,我们将不使用 Kaggle 数据,但将向您展示如何准备自己的数据以进行关键点检测。 有关该模型的详细信息,请参见这里。
准备用于关键点检测的数据集
在本节中,您将学习如何创建自己的数据。 这涉及编写代码并执行代码,以使 PC 中的网络摄像头点亮。 将您的脸部移动到不同的位置和方向,然后按空格键,在裁剪掉图像中的所有其他内容后,它将保存您的脸部图像。 此过程的关键步骤如下:
- 首先,我们从指定 Haar 级联分类器的路径开始。 它应该位于您的
OpenCV/haarcascades
目录中。 那里将有很多.xml
文件,因此请包含frontalface_default.xml
的路径:
face_cascade = cv2.CascadeClassifier('path tohaarcascade_frontalface_default.xml')
- 接下来,我们将使用
videoCapture(0)
语句定义网络摄像头操作。 如果您的计算机已插入外部摄像机,则可以使用videoCapture(1)
:
cam = cv2.VideoCapture(0)
- 相机帧使用
cam.read()
读取数据,然后在每个帧内,使用“步骤 1”中定义的 Haar 级联检测器检测面部。 使用(x,y,w,h)
参数在检测到的面部周围绘制一个边框。 使用cv2.imshow
参数,屏幕上仅显示检测到的面部:
while(True): ret, frame = cam.read() faces = face_cascade.detectMultiScale(frame, 1.3, 5) for (x,y,w,h) in faces: if w >130: detected_face = frame[int(y):int(y+h), int(x):int(x+w)] cv2.imshow("test", detected_face) if not ret: break k = cv2.waitKey(1)
- 接下来,将图像调整为
img_size
,将其定义为299
,并将结果图像保存在数据集目录中。 注意,在本练习中我们使用299
的图像大小,但是可以更改。 但是,如果您决定更改它,请确保在标注文件的创建以及最终模型中进行更改,以避免标注和图像之间的不匹配。 现在,在此 Python 代码所在的目录中创建一个名为dataset
的文件夹。 请注意,每次按空格键时,图像文件号都会自动增加:
faceresize = cv2.resize(detected_face, (img_size,img_size)) img_name = "dataset/opencv_frame_{}.jpg".format(img_counter) cv2.imwrite(img_name, faceresize)
为不同面部表情的不同人创建约 100 幅或更多图像(对于该测试,我总共拍摄了 57 张图像)。 如果您有更多图像,则检测会更好。 请注意,Kaggle 面部点检测使用 7,049 张图像。 拍摄所有图像并使用 VGG 标注器执行面部关键点标注,您可以从这里获得该标注器。 您可以使用其他选择的标注工具,但是我发现此工具(免费)非常有用。 它绘制边界框以及不规则形状和绘制点。 在本练习中,我加载了所有图像,并使用点标记在图像中绘制了 16 个点,如下图所示:
上图中的 16 个点代表左眼(1-3),右眼(4-6),鼻子(7),嘴唇(8-11)和外面(12-16)。 请注意,当我们以数组形式显示图像关键点时,它们将被表示为 0–15 而不是 1–16。 为了获得更高的准确率,您只能捕获面部图像,而不是任何周围环境。
处理关键点数据
VGG 标注器工具会生成一个输出 CSV 文件,该文件需要进行两次预处理才能为每个图像的 16 个关键点分别生成(x, y)
坐标。 对于大数据处理来说,这是一个非常重要的概念,您可以将其用于其他计算机视觉任务,主要有以下三个原因:
- 我们的 Python 代码不会直接在目录中搜索大量图像文件,而是会在输入 CSV 中搜索数据路径。
- 对于每个 CSV 文件,有 16 个相应的关键点需要处理。
- 这是使用 Keras
ImageDataGenerator
和flow_from_directory
方法浏览目录中每个文件的替代方法。
为了澄清此内容,本节分为以下两个小节:
- 在输入 Keras-Python 代码之前进行预处理
- Keras–Python 代码中的预处理
让我们详细讨论每个。
输入 Keras-Python 代码之前的预处理
VGG 标注器工具会生成一个输出 CSV 文件,该文件需要以我们的 TensorFlow 代码可接受的格式进行预处理。 标注的输出是一个 CSV 文件,该 CSV 文件以行格式显示每个关键点,每个图像有 16 行。 我们需要对该文件进行预处理,以便每个图像有一行。 共有 33 列,指示 32 个关键点值和1
图像值,如下所示:
(x0, y0), (x1, y1), (x2, y2), …, (x15, y15)
,图像文件名
您可以使用自定义 Python 程序对此进行转换,尽管此处未显示。 GitHub 页面包含已处理的 CSV 文件,在这里供您参考。
Keras–Python 代码中的预处理
在本节中,我们将以X
和Y
数据的形式读取 CSV 文件,其中X
是与每个文件名相对应的图像,而Y
具有 16 个关键点坐标的 32 个值。 然后,我们将每个关键点的Y
数据切片为 16 Yx
和Yy
坐标。 详细步骤如下所示:
- 使用标准 Python 命令阅读上一部分的 CSV 文件。 在此,我们使用位于
faceimagestrain
目录中的两个 CSVtrainimgface.csv
和testimgface.csv
。 如果需要,可以使用其他文件夹:
train_path = 'faceimagestrain/trainimgface.csv' test_path = 'faceimagestrain/testimgface.csv' train_data = pd.read_csv(train_path) test_data = pd.read_csv(test_path)
- 接下来,我们在 CSV 文件中找到与图像文件相对应的列。 在以下代码中,图像文件的列名称为
'image'
:
coltrn = train_data['image'] print (coltrn.shape[0])
- 接下来,我们初始化两个图像数组
imgs
和Y_train
。 我们读取train_data
数组以向image
列添加路径,并在for
循环中读取coltrn.shape[0]
定义的 50 个图像文件中的每个图像文件,并将其附加到图像数组中。 使用OpenCV BGR2GRAY
命令将读取的每个图像转换为灰度。 在同一for
循环中,我们还使用training.iloc[i,:]
命令读取 32 列中的每一列,并将其附加到Y_train
的数组中:
imgs = [] training = train_data.drop('image',axis = 1) Y_train = [] for i in range (coltrn.shape[0]): p = os.path.join(os.getcwd(), 'faceimagestrain/'+str(coltrn.iloc[i])) img = cv2.imread(p, 1) gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) imgs.append(gray_img) y = training.iloc[i,:] Y_train.append(y)
- 最后,使用以下代码将图像转换为称为
X_train
的 NumPy 数组,这是输入 Keras 模型所必需的:
X_train = np.asarray(imgs) Y_train = np.array(Y_train,dtype = 'float') print(X_train.shape, Y_train.shape)
- 对测试数据重复相同的过程。 现在我们已经准备好训练和测试数据。 在继续之前,我们应该可视化图像中的关键点,以确保它们看起来不错。 使用以下命令完成此操作:
x0=Y_trainx.iloc[0,:] y0=Y_trainy.iloc[0,:] plt.imshow(np.squeeze(X_train[0]),cmap='gray') plt.scatter(x0, y0,color ='red') plt.show()
在前面的代码中,np.squeeze
用于删除最后一个尺寸,因此图像中只有x
和y
值。 plt.scatter
在图像顶部绘制关键点。 输出如下图所示:
上图显示了叠加在图像顶部的 16 个关键点,表示图像和关键点对齐。 左图和右图表示训练和测试图。 此视觉检查对于确保所有预处理步骤都不会导致不正确的面对点对齐至关重要。
定义模型架构
该模型涉及使用卷积神经网络(CNN)处理面部图像及其 16 个关键点。 有关 CNN 的详细信息,请参阅“第 4 章”,“图像深度学习”。 CNN 的输入是训练图像和测试图像及其关键点,其输出将是与新图像相对应的关键点。 CNN 将学习预测关键点。 下图显示了模型架构的详细信息:
先前模型的代码如下:
model = Sequential() model.add(Conv2D(32, (3, 3), input_shape=(299,299,1), padding='same', activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.2)) model.add(Conv2D(128, (3, 3), activation='relu')) model.add(Conv2D(128, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.2)) model.add(Conv2D(256, (3, 3), activation='relu')) model.add(Conv2D(256, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.2)) model.add(Flatten()) model.add(Dense(500, activation='relu')) model.add(Dense(500, activation='relu')) model.add(Dense(32))
该代码拍摄一张图像,并应用 32 个大小为(3, 3)
的卷积过滤器,然后激活和最大池化层。 它重复相同的过程多次,并增加过滤器的数量,然后是平坦且密集的层。 最终的密集层包含 32 个元素,分别代表我们要预测的关键点的x
和y
值。
为关键点预测训练模型
现在我们已经定义了模型,在本小节中,我们将编译模型,重塑模型的输入,并通过执行以下步骤开始训练:
- 我们将从定义模型损失参数开始,如下所示:
adam = Adam(lr=0.001) model.compile(adam, loss='mean_squared_error', metrics=['accuracy'])
- 然后,对数据进行整形以输入到 Keras 模型。 重塑数据很重要,因为 Keras 希望以 4D 形式显示数据-数据数(50),图像宽度,图像高度 1(灰度):
batchsize = 10 X_train= X_train.reshape(50,299,299,1) X_test= X_test.reshape(7,299,299,1) print(X_train.shape, Y_train.shape, X_test.shape, Y_test.shape)
模型X
和Y
参数说明如下:
X_train (50, 299, 299, 1)
1 的训练数据,图像宽度,图像高度,灰度Y_train (50, 32)
#训练数据,关键点数-在这里,我们有x
和y
值的 16 个关键点,使其成为 32X_test (7, 299, 299, 1)
1 个测试数据,图像宽度,图像高度,灰度Y_test (7, 32)
#测试数据,#关键点-在这里,我们为x
和y
值有 16 个关键点,使其成为 32
- 训练是通过
model.fit
命令启动的,如下所示:
history = model.fit(X_train, Y_train, validation_data=(X_test, Y_test), epochs=20, batch_size=batchsize)
训练步骤的输出如下图所示:
该模型在大约 10 个时间周期内获得了相当好的准确率,但损失项约为 7000。我们需要收集更多的图像数据,以将损失项降至 1 以下:
- 我们将使用
model.predict
根据测试数据X_test
预测模型输出y_val
。 测试数据X_test
是图像,但是它们已经以模型可以理解的数组形式进行了预处理:
y_val = model.predict(X_test)
- 请注意,此处
y_val
对于每个预处理的图像数组输入都有 32 个点。 接下来,我们将 32 个点细分为代表 16 个关键点的x
和y
列:
yvalx = y_val[::1,::2] yvaly = y_val[:, 1::2]
- 最后,使用以下代码在图像上方绘制预测的 16 个关键点:
plt.imshow(np.squeeze(X_test[6]),cmap='gray') plt.scatter(yvalx[6], yvaly[6], color = 'red') plt.show()
请注意,对于 50 张图片,模型预测效果不是很好; 这里的想法是向您展示该过程,以便您随后可以通过收集更多图像在此代码的基础上进行构建。 随着图像数量的增加,模型精度将提高。 尝试为不同的人和不同的方向拍摄图像。 如“第 9 章”,“使用多任务深度学习的动作识别”中所述,可以将此处描述的技术扩展为与身体关键点检测一起使用。 此外,“第 11 章”,“通过 CPU/GPU 优化在边缘设备上进行深度学习”,在 Raspberry Pi 上针对 OpenVINO 提供了一个部分,其中提供了 Python 代码,基于 OpenVINO 工具包预训练模型的集成来预测和显示 35 个面部关键点 。
使用 CNN 预测面部表情
面部表情识别是一个具有挑战性的问题,因为面部,光线和表情(嘴巴,眼睛睁开的程度等)各不相同,并且还需要开发一种架构并选择可以持续获得较高对比度的参数。 准确率。 这意味着挑战不仅在于在一个照明条件下为一个人正确确定一个面部表情,而且要在所有照明条件下正确识别所有戴着或不戴眼镜,帽子等的人的所有面部表情。 以下 CNN 示例将情感分为七个不同的类别:愤怒,反感,害怕,快乐,悲伤,惊讶和中立。 面部表情识别所涉及的步骤如下:
- 导入函数-
Sequential
,Conv2D
,MaxPooling2D
,AvgPooling2D
,Dense
,Activation
,Dropout
和Flatten
。 - 导入
ImageDataGenerator
-通过实时增强(定向)生成一批张量图像。 - 确定分类的批量大小和周期。
- 数据集-训练,测试和调整大小
(48, 48)
。 - 建立 CNN 架构(如下图所示)。
- 使用
fit-generator()
函数训练开发的模型。 - 评估模型。
下图显示了 CNN 架构:
下图显示了模型的结果。 在大多数情况下,它可以正确预测面部表情:
很清楚地检测到强烈的情感(笑脸或生气的脸)。 CNN 模型能够正确预测各种情感,甚至微妙的情感。
3D 人脸检测概述
3D 面部识别涉及测量面部中刚性特征的几何形状。 通常是通过使用飞行时间,测距相机生成 3D 图像或从对象的 360 度方向获取多个图像来获得的。 传统的 2D 相机将 3D 空间转换为 2D 图像,这就是为什么深度感应是计算机视觉的基本挑战之一的原因。 基于飞行时间的深度估计基于光脉冲从光源传播到物体再返回到相机所需的时间。 同步光源和图像获取深度。 飞行时间传感器能够实时估计全深度帧。 飞行时间的主要问题是空间分辨率低。 3D 人脸识别可以分为以下三个部分:
- 3D 重建的硬件设计概述
- 3D 重建和跟踪概述
- 参数化跟踪概述
3D 重建的硬件设计概述
3D 重建涉及相机,传感器,照明和深度估计。 3D 重建中使用的传感器可以分为三类:
- 多视图设置:具有受控照明的经过校准的密集立体摄像机数组。 从每个立体对中,使用三角剖分重构面部几何形状,然后在加强几何一致性的同时进行聚合。
- RGB 照相机:组合多个 RGB 照相机以基于飞行时间方法计算深度。
- RGBD 摄像机:RGBD 摄像机同时捕获颜色和深度-例如 Microsoft Kinect,Primesense Carmine 和 Intel Realsense。
3D 重建和跟踪概述
3D 面部重建包括通过构造 CNN 通过使深度回归来从对应的 2D 图像估计 3D 面部的坐标。 下图以图形方式说明了这一点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZINNSj6-1681784327301)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/master-cv-tf-2x/img/59370f0a-70ba-46da-a36c-85616d149f32.png)]
实时 3D 曲面贴图的一些流行算法描述如下:
- Kinect Fusion:使用 Kinect 深度传感器的实时 3D 构造。 Kinect 是一种商品传感器平台,其中包含 30 Hz 的基于结构的飞行时间深度传感器。
- 动态融合:使用单个 Kinect 传感器使用体积 TSDF(截断有符号距离融合)技术的动态场景重建系统。 它需要一个嘈杂的深度图,并通过估计体积 6D 运动场来重建实时 3D 运动场景。
- Fusion4D:这使用多个实时 RGBD 摄像机作为输入,并使用体积融合以及使用密集对应字段的非刚性对齐来处理多个图像。 该算法可以处理较大的帧到帧运动和拓扑更改,例如,人们快速脱下外套或从左到右更改其面部方向。
- Motion2Fusion:此方法是用于实时(每秒 100 帧)重建的 360 度表现捕获系统。 它基于具有学习的 3D 嵌入的非刚性对齐策略,快速匹配策略,用于 3D 对应估计的机器学习以及用于复杂拓扑更改的后向/向前非刚性对齐策略。
参数化跟踪概述
面部跟踪模型将投影的线性 3D 模型用于摄像机输入。 它执行以下操作:
- 跟踪从前一帧到当前帧的视觉特征
- 对齐 2D 形状来跟踪特征
- 根据深度测量计算 3D 点云数据
- 最小化损失函数
总结
尽管由于各种肤色,方向,面部表情,头发颜色和光照条件而引起的复杂性,面部识别仍然是计算机视觉的成功故事。 在本章中,我们学习了面部检测技术。 对于每种技术,您都需要记住,面部检测需要大量训练有素的图像。 人脸检测已在许多视频监控应用中广泛使用,并且 Google,亚马逊,微软和英特尔等公司的基于云的设备和边缘设备均可使用标准 API。 我们将在“第 11 章”,“对具有 CPU/GPU 优化功能的边缘设备进行深度学习”中了解基于云的 API,并在“第 4 章”,“图像深度学习”,和第 5 章,“神经网络架构和模型”。 在本章中,简要介绍了用于面部检测和表情分类的 CNN 模型。
在下一章中,将详细说明 CNN。 这将帮助您了解 CNN 的构造块,为何选择某些功能块以及每个块在最终对象检测指标中的作用。 之后,我们将参考 “第 3 章”,“使用 OpenCV 和 CNN 进行面部检测”的示例,以评估如何优化 CNN 参数以更好地进行面部检测。
四、用于图像的深度学习
边缘检测的概念在“第 1 章”,“计算机视觉和 TensorFlow 基础知识”中进行了介绍。 在本章中,您将学习如何使用边缘检测在体积上创建卷积运算,以及不同的卷积参数(例如过滤器大小,尺寸和操作类型(卷积与池化))如何影响卷积体积(宽度与深度)。 本章将为您提供有关神经网络如何查看图像以及图像如何使用可视化对图像进行分类的非常详细的概述。 您将从建立第一个神经网络开始,然后在图像通过其不同层时对其进行可视化。 然后,您将网络模型的准确率和可视化与诸如 VGG 16 或 Inception 之类的高级网络进行比较。
请注意,本章和下一章将提供神经网络的基础理论和概念以及当今实际使用的各种模型。 但是,这个概念是如此广泛,以至于不可能将您需要了解的所有内容都放在这两章中。 因此,为了便于阅读,将为每章讨论的主题引入其他概念,从“第 6 章”,“使用迁移学习的视觉搜索”开始,请参考这些章节,以防您不得不阅读本书。
在本章中,我们将介绍以下主题:
- 了解 CNN 及其参数
- 优化 CNN 参数
- 可视化神经网络的各个层
了解 CNN 及其参数
卷积神经网络(CNN)是一种自学习网络,它通过观察不同类别的图像来对类似于人类大脑学习方式的图像进行分类。 CNN 通过应用图像滤波并处理各种过滤器大小,数量和非线性运算的方法来学习图像的内容。 这些过滤器和操作应用于多个层,以便在图像转换过程中,每个后续层的空间尺寸减小,并且其深度增大。
对于每个过滤应用,所学内容的深度都会增加。 首先从边缘检测开始,然后识别形状,然后识别称为特征的形状集合,依此类推。 当我们理解信息时,这类似于人脑。 例如,在阅读理解测试中,我们需要回答关于段落的五个问题,每个问题都可以视为一类,需要从段落中获得特定信息的必须回答的问题:
- 首先,我们浏览整个通道,这意味着空间维度是完整的通道,而深度(我们对通道的理解)很小,因为我们只是浏览了整个通道。
- 接下来,我们浏览问题以了解每个类的特征(问题),即在文章中寻找的内容。 在 CNN 中,这等效于考虑要使用哪些卷积和池化操作来提取特征。
- 然后,我们阅读文章的特定部分,以找到与全类相似的内容,并深入探讨这些部分–在这里,空间维度很小,但深度很大。 我们重复此过程 2 至 3 次,以回答所有问题。 我们将继续加深我们的理解深度,并更加专注于特定领域(缩小维度),直到我们有一个很好的理解为止。 在 CNN 中,这等效于逐渐增加深度并缩小尺寸–卷积操作通过更改过滤器数量来更改深度; 合并会缩小尺寸。
- 为了节省时间,我们倾向于跳过段落以找到与答案匹配的相关段落。 在卷积中,这等效于跨步,跨步会缩小尺寸,但不会更改深度。
- 下一步是将问题与文章的答案相匹配。 我们通过在精神上使问题的答案保持一致来做到这一点。 在这里,我们不会做得更深入–我们将问题和答案并排放置,以便我们进行匹配。 在 CNN 中,这等效于展平并使用全连接层。
- 在此过程中,我们可能会有过多的信息–我们将其删除,以便仅与段落中的问题相关的信息可供我们使用。 在 CNN 中,这等效于丢弃。
- 最后一个阶段实际上是进行匹配练习以回答问题。 在 CNN 中,这等效于 Softmax 操作。
CNN 的图像过滤和处理方法包括执行多种操作,所有这些操作都可以通过以下方式进行:
- 卷积(Conv2D)
- 卷积 –
3 x 3
过滤器 - 卷积 –
1 x 1
过滤器 - 池化
- 填充
- 跨步
- 激活
- 全连接层
- 正则化
- 丢弃
- 内部协方差平移和批量归一化
- Softmax
下图说明了 CNN 及其组件:
让我们研究一下每个组件的功能。
卷积
卷积是 CNN 的主要构建块。 它包括将图像的一部分与核(过滤器)相乘以产生输出。 卷积的概念在“第 1 章”,“计算机视觉和 TensorFlow 基础知识”中进行了简要介绍。 请参考该章以了解基本概念。 通过在输入图像上滑动核来执行卷积操作。 在每个位置执行逐元素矩阵乘法,然后在乘法范围内进行累加和。
每次卷积操作之后,CNN 都会从图像中学到更多信息–它首先是学习边缘,然后是下一次卷积中的形状,然后是图像的特征。 在卷积操作期间,过滤器的大小和过滤器的数量可以改变。 通常,在通过卷积,合并和跨步操作减小特征映射的空间尺寸之后,增加过滤器的数量。 当过滤器尺寸增加时,特征映射的深度也会增加。 下图说明了当我们有两个不同的边缘检测核选择时的 Conv2D:
上图显示了以下要点:
- 如何通过在输入图像上滑动
3 x 3
窗口来执行卷积操作。 - 逐元素矩阵乘法和总和结果,用于生成特征映射。
- 随着多次卷积运算而堆叠的多个特征映射将生成最终输出。
卷积 – 3 x 3
过滤器
在前面的示例中,我们在二维图像(灰度)上应用了3 x 3
卷积。 在本节中,我们将学习具有三个通道(红色,绿色和蓝色(RGB)的三维图像,如何使用卷积运算的3 x 3
边缘过滤器对)进行变换。 下图以图形方式显示了此转换:
上图显示了如何使用3 x 3
过滤器(边缘检测器)在宽度减小和深度增加(从 3 到 32)方面转换3 x 3
图像的图的一部分。 核(f[i]
)中 27 个(3 x 3 x 3
)单元中的每一个都乘以输入(A[i]
)。 然后,将这些值与整流线性单元(ReLU)激活函数(b[i]
)相加在一起,来形成单个元素(Z
),如以下等式所示:
通常,在卷积层中,有许多执行不同类型边缘检测的过滤器。 在前面的示例中,我们有 32 个过滤器,这将导致 32 个不同的栈,每个栈由5 x 5
层组成。
3 x 3
过滤器将在本书的其余部分中广泛用于神经网络开发。 例如,您将在 ResNet 和 Inception 层中看到大量使用它,我们将在“第 5 章”,“神经网络架构和模型”中进行讨论。 TensorFlow 中可以将大小为3 x 3
的 32 个过滤器表示为.tf.keras.layers.Conv2D(32, (3,3))
。 在本章的后面,您将学习如何将此卷积与 CNN 的其他层一起使用。
卷积 – 1 x 1
过滤器
在本节中,我们将学习1 x 1
卷积的重要性及其用例。 1 x 1
卷积过滤器是图像的直倍数,如下图所示:
在上图中,在上一节输出的5 x 5
图像上使用了5 x 5
卷积过滤器值 1,但实际上它可以是任何数字。 在这里,我们可以看到使用5 x 5
过滤器可以保留其高度和宽度,而深度则增加到过滤器通道的数量。 这是5 x 5
过滤器的基本优点。 三维核(f[i]
)中三个(1 x 1 x 3
)单元中的每一个都与输入的相应三个单元相乘 (A[i]
)。 然后,将这些值与 ReLU 激活函数(b[i]
)一起加在一起,以形成单个元素(Z
):
上图显示1 x 1
过滤器使深度增加,而相同的1 x 1
过滤器可用于减小值,如下图所示:
上图显示了1 x 1 x 128
图像过滤器如何将卷积深度减少到 32 个通道。
1 x 1
卷积在所有 128 个通道中与5 x 5
输入层执行逐元素乘法–将其在深度维度上求和,并应用 ReLU 激活函数在5 x 5
输出中创建单个点,表示 128 的输入深度。本质上,通过使用这种机制(卷积 + 整个深度的和),它会将三维体积折叠为具有相同宽度和高度的二维数组。 然后,它应用 32 个过滤器以创建5 x 5 x 32
输出,如前所示。 这是有关 CNN 的基本概念,因此请花一些时间来确保您理解这一点。
本书将使用1 x 1
卷积。 稍后,您将了解到池化会减小宽度,而1 x 1
卷积会保留宽度,但可以根据需要收缩或扩展深度。 例如,您将看到在网络和 Inception 层中使用了1 x 1
卷积(在“第 5 章”,“神经网络架构和模型”中。具有1 x 1
的 32 过滤器) 的卷积可以在 TensorFlow 中表示为.tf.keras.layers.Conv2D(32, (1,1))
。
池化
池化是卷积之后的下一个操作。 它用于减小尺寸和特征映射的大小(宽度和高度),而无需更改深度。 轮询参数的数量为零。 池的两种最受欢迎的类型如下:
- 最大池化
- 平均池化
在最大池化中,我们在特征映射上滑动窗口并获取窗口的最大值,而在进行平均池化时,我们获取窗口中的平均值。 卷积层和池化层一起执行特征提取的任务。 下图显示了在7 x 7
图像上使用的最大和平均池化操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wr5CM14c-1681784327303)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/master-cv-tf-2x/img/e008ce29-8194-4fe6-baa0-058c073a2f98.png)]
请注意,由于合并,3 x 3
窗口如何缩小(由绿线显示)为单个值,从而导致5 x 5
矩阵尺寸更改为5 x 5
矩阵。
填充
填充用于保留特征映射的大小。 通过卷积,可能会发生两个问题,并且填充会同时解决两个问题:
- 每次卷积操作时,特征映射的大小都会缩小。 例如,在上图中,由于卷积,一个
7 x 7
的特征映射缩小为5 x 5
。 - 由于边缘上的像素仅更改一次,因此边缘上的信息会丢失,而中间的像素会通过多次卷积操作进行多次更改。
下图显示了在7 x 7
输入图像上使用大小为 1 的填充操作:
请注意填充如何保留尺寸,以便输出与输入的尺寸相同。
跨步
通常,在卷积中,我们将核移动一步,对那一步应用卷积,依此类推。 跨步使我们可以跳过一步。 让我们来看看:
- 当跨步为 1 时,我们应用普通卷积而不跳过。
- 当跨步为 2 时,我们跳过一步。 这会将图像大小从
7 x 7
减小到3 x 3
(请参见下图):
在这里,每个3 x 3
窗口显示跳过一个步骤的结果。 大步的结果是缩小尺寸,因为我们跳过了可能的x
,y
位置。
激活
激活层为神经网络增加了非线性。 这是至关重要的,因为图像和图像中的特征是高度非线性的问题,而 CNN 中的大多数其他功能(Conv2D,池化,全连接层等)仅生成线性变换。 激活函数在将输入值映射到其范围时生成非线性。 没有激活函数,无论添加多少层,最终结果仍然是线性的。
使用了多种类型的激活函数,但最常见的激活函数如下:
- Sigmoid
- Tanh
- ReLU
下图显示了上述激活函数:
每个激活函数都显示非线性行为,当输入大于 3 时,Sigmoid 和 Tanh 接近 3,而 ReLU 继续增加。
下图显示了不同的激活函数对输入大小的影响:
与 Tanh 和 Sigmoid 激活函数相比,ReLU 激活函数具有以下优点:
- 与 ReLU 相比,Sigmoid 和 Tanh 的梯度问题(学习缓慢的人)消失了,因为它们在输入值大于 3 时都接近 1。
- 对于小于 0 的输入值,Sigmoid 激活函数仅具有正值。
- ReLU 函数对计算有效。
全连接层
全连接层(也称为密集层)通过对它们施加权重和偏差来将当前层中的每个连接神经元连接到上一层中的每个连接神经元。 权重和偏差的向量称为过滤器。 这可以用以下等式表示:
如“卷积”部分中所述,过滤器可以采用边缘过滤器的形式来检测边缘。 在神经网络中,许多神经元共享同一过滤器。 权重和过滤器允许全连接层充当分类器。
正则化
正则化是一种用于减少过拟合的技术。 为此,可以在模型误差函数中添加一个附加项(模型输出-训练值),以防止模型权重参数在训练过程中取极端值。 CNN 中使用三种类型的正则化:
- L1 正则化:对于每个模型权重,
w
,一个附加参数,λ |w|
添加到模型目标。 这种正则化过程使优化过程中的权重因子稀疏(接近零)。 - L2 正则化:对于每个模型权重,
w
,附加参数1/2 λw^2
被添加到模型目标 。 这种正则化使得权重因子在优化过程中扩散。 可以期望 L2 正则化比 L1 正则化具有更好的表现。 - 最大范数正则化:这种类型的正则化为 CNN 的权重添加了最大限制,因此
|w| < c
,其中c
可以为 3 或 4。即使学习率很高,最大范数约束也可以防止神经网络过拟合。
丢弃
丢弃是一种特殊的正则化类型,指的是忽略神经网络中的神经元。 具有dropout = 0.2
的全连接层意味着仅 80% 的全连接神经元连接到下一层。 在当前步骤中神经元被丢弃,但在下一步中处于活动状态。 丢弃可防止网络依赖少量神经元,从而防止过拟合。 丢弃应用于输入神经元,但不应用于输出神经元。 下图显示了带有和不带有丢弃的神经网络:
以下是丢弃的优点:
- 丢弃迫使神经网络学习更强大的特征。
- 每个周期的训练时间较少,但是迭代次数增加了一倍。
- 丢弃可提高准确率-约 1-2%。
内部协方差平移和批量归一化
在训练过程中,每层输入的分布会随着上一层的权重因子的变化而变化,从而导致训练变慢。 这是因为它要求较低的学习率和权重因子选择。 谢尔盖·艾菲(Sergey Ioffe)和克里斯汀·塞格迪(Christian Szegedy)在题为《批量归一化:通过减少内部协方差漂移加速深度网络训练》的论文中称这种现象内部协方差漂移。 有关详细信息,请参阅这里。
批量归一化通过从当前输入中减去上一层的批量平均值并将其除以批量标准差来解决协方差移位的问题。 然后将此新输入乘以当前权重系数,再乘以偏置项以形成输出。 下图显示了带有和不带有批量规范化的神经网络的中间输出特征:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJ1gh7qa-1681784327304)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/master-cv-tf-2x/img/9f7bea49-6361-4095-b359-0cea59b897c5.png)]
当应用批量归一化时,我们在大小为m
的整个迷你批量中计算均值(μ
)和方差(σ
)。 然后,利用这些信息,我们计算归一化的输入。 微型批量的输出计算为比例(γ
)乘以归一化输入,再加上偏移量(β
)。 在 TensorFlow 中,这可以表示如下。 除方差ε
外,所有项均在上图中进行了解释,方差ε
是归一化输入计算中的ε
项,以避免被零除:
tf.nn.batch_normalization(x,mean,variance,offset,scale,variance_epsilon,name=None)
麻省理工学院的 Shibani Santurkar,Dimitris Tsipras,Andrew Ilyas 和 Aleksander Madry 在其论文中详细阐述了批量规范化的优势。 可以在这个页面中找到该论文的详细信息。
该论文的作者发现,批量归一化并不能减少内部协方差漂移。 批量归一化的学习速度可以归因于归一化输入的平滑性,这归因于归一化输入而不是常规输入数据的使用,规则数据可能由于扭结,尖锐的边缘和局部最小值或最大值而具有较大的差异。 这使梯度下降算法更加稳定,从而允许它使用更大的步长以实现更快的收敛。 这样可以确保它不会出现任何错误。
Softmax
Softmax 是在 CNN 的最后一层中使用的激活函数。 它由以下等式表示,其中P
是每个类别的概率,n
是类别的总数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEMfCDo6-1681784327305)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/master-cv-tf-2x/img/87a750bf-5242-423b-86b0-af8851ad2df5.png)]
下表显示了使用前面描述的 Softmax 函数时七个类中每个类的概率:
这用于计算每个类别的分布概率。
优化 CNN 参数
CNN 具有许多不同的参数。 训练 CNN 模型需要许多输入图像并执行处理,这可能非常耗时。 如果选择的参数不是最佳参数,则必须再次重复整个过程。 这就是为什么理解每个参数的功能及其相互关系很重要的原因:这样可以在运行 CNN 之前优化其值,以最大程度地减少重复运行。 CNN 的参数如下:
- 图像尺寸为
(n x n)
- 过滤器为
(f[h], f[w])
,f[h]
为应用于图像高度的过滤器,f[w]
为应用于图像宽度的过滤器 - 过滤器数量为
n[f]
- 填充为
p
- 跨步为
s
- 输出大小为
{(n + 2p - f)/s +1} x {(n + 2p - f)/s + 1}
- 参数数量为
(f[h] x f[w] + 1) x n[f]
关键任务是选择上述参数(过滤器大小(f
),过滤器数量(nf
),跨步(s
),填充 CNN 每一层的值(p
),激活(a
)和偏差。 下表显示了各种 CNN 参数的特征映射:
上表的每个参数说明如下:
- 输入图像:第一输入层是大小为
(48 x 48)
的灰度图像,因此深度为1
。 特征映射大小48 x 48 x 1 = 2,304
。 它没有参数。 - 第一卷积层:
CONV1 (filter shape =5*5, stride=1)
层的高度,宽度为(48-5+1) =44
,特征映射大小为44 x 44 x 64 = 123904
,参数数为(5 x 5 + 1) x 64 = 1,664
。 - 第一池化层(
POOL1
):池化层没有参数。 - 剩余的卷积和池化层:剩余的计算 –
CONV2, CONV3, CONV4, CONV5, POOL2
– 遵循与第一卷积层相同的逻辑。 - 全连接(
FC
)层:对于全连接层(FC1, FC2)
,参数的数量为[(current layer n * previous layer n) + 1] = 128 * 1,024 + 1 = 131,073
。 - 丢弃(
DROP
):对于丢弃,将丢弃 20% 的神经元。 剩余的神经元为1,024 * 0.8 = 820
。 第二次删除的参数数为820 x 820 + 1 = 672401
。 - CNN 的最后一层始终是 Softmax:对于 Softmax,参数数为
7 x 820 + 1 = 5,741
。
在“第 3 章”,“使用 OpenCV 和 CNN 进行面部检测”的第一张图中,用于面部表情识别的神经网络类别有 7 个类别,其准确率约为 54%。
在以下各节中,我们将使用 TensorFlow 输出优化各种参数。 我们从基准情况开始,然后尝试通过调整此处描述的参数进行五次迭代。 该练习应该使您对 CNN 的参数以及它们如何影响最终模型的输出有很好的了解。
基准案例
基线情况由神经网络的以下参数表示:
- 实例数:35,888
- 实例长度:2,304
- 28,709 个训练样本
- 3,589 个测试样本
模型迭代如下:
Epoch 1/5 256/256 [=======================] - 78s 306ms/step - loss: 1.8038 - acc: 0.2528 Epoch 2/5 256/256 [=======================] - 78s 303ms/step - loss: 1.6188 - acc: 0.3561 Epoch 3/5 256/256 [=======================] - 78s 305ms/step - loss: 1.4309 - acc: 0.4459 Epoch 4/5 256/256 [=======================] - 78s 306ms/step - loss: 1.2889 - acc: 0.5046 Epoch 5/5 256/256 [=======================] - 79s 308ms/step - loss: 1.1947 - acc: 0.5444
接下来,我们将优化 CNN 参数,以确定哪些参数在更改准确率时影响最大。 我们将分四次迭代运行此实验。
迭代 1 – CNN 参数调整
删除一个Conv2D
64 和一个Conv2D
128,以使 CNN 仅具有一个Conv2D
64 和一个Conv2D
128:
Epoch 1/5 256/256 [========================] - 63s 247ms/step - loss: 1.7497 - acc: 0.2805 Epoch 2/5 256/256 [========================] - 64s 248ms/step - loss: 1.5192 - acc: 0.4095 Epoch 3/5 256/256 [========================] - 65s 252ms/step - loss: 1.3553 -acc: 0.4832 Epoch 4/5 256/256 [========================] - 66s 260ms/step - loss: 1.2633 - acc: 0.5218 Epoch 5/5 256/256 [========================] - 65s 256ms/step - loss: 1.1919 - acc: 0.5483
结果:放下Conv2D
层不会对表现产生不利影响,但也不能使其更好。
接下来,我们将保留我们在此处所做的更改,但是我们会将平均池转换为最大池。
迭代 2 – CNN 参数调整
删除一台Conv2D
64 和一台Conv2D
128,以使 CNN 仅具有一台Conv2D
64 和一台Conv2D
128。 另外,将平均池转换为最大池,如下所示:
Epoch 1/5 256/256 [========================] - 63s 247ms/step - loss: 1.7471 - acc: 0.2804 Epoch 2/5 256/256 [========================] - 64s 252ms/step - loss: 1.4631 - acc: 0.4307 Epoch 3/5 256/256 [========================] - 66s 256ms/step - loss: 1.3042 - acc: 0.4990 Epoch 4/5 256/256 [========================] - 66s 257ms/step - loss: 1.2183 - acc: 0.5360 Epoch 5/5 256/256 [========================] - 67s 262ms/step - loss: 1.1407 - acc: 0.5691
结果:同样,此更改对准确率没有明显影响。
接下来,我们将大大减少隐藏层的数量。 我们将更改输入层,并完全删除第二个Conv2D
和关联的池。 在第一个Conv2D
之后,我们将直接移至第三个Conv2D
。
迭代 3 – CNN 参数调整
第二个卷积层完全掉落; 输入层从5 x 5
更改为3 x 3
:
model.add(Conv2D(64, (3, 3), activation='relu', input_shape=(48,48,1)))
第三卷积层保持不变。 该层如下:
model.add(Conv2D(128, (3, 3), activation='relu')) model.add(AveragePooling2D(pool_size=(3,3), strides=(2, 2)))
密集层没有变化。 输出如下:
Epoch 1/5 256/256 [==========================] - 410s 2s/step - loss: 1.6465 - acc: 0.3500 Epoch 2/5 256/256 [==========================] - 415s 2s/step - loss: 1.3435 - acc: 0.4851 Epoch 3/5 256/256 [==========================] - 412s 2s/step - loss: 1.0837 - acc: 0.5938 Epoch 4/5 256/256 [==========================] - 410s 2s/step - loss: 0.7870 - acc: 0.7142 Epoch 5/5 256/256 [==========================] - 409s 2s/step - loss: 0.4929 - acc: 0.8242
结果:计算时间很慢,但精度达到最佳状态,即 82%。
迭代 4 – CNN 参数调整
在此迭代中,所有参数均与上一个迭代中的参数相同,除了strides = 2
,它在第一个Conv2D
之后添加。
接下来,我们将所有内容保持不变,但是在第一个Conv2D
之后添加一个池化层:
model.add(Conv2D(64, (3, 3), activation='relu', input_shape=(48,48,1))) model.add(AveragePooling2D(pool_size=(3,3), strides=(2, 2))) model.add(Conv2D(128, (3, 3), activation='relu')) model.add(AveragePooling2D(pool_size=(3,3), strides=(2, 2)))
在此迭代中,不会更改密集层。 计算时间更快,但准确率下降了:
Epoch 1/5 256/256 [========================] - 99s 386ms/step - loss: 1.6855 - acc: 0.3240 Epoch 2/5 256/256 [========================] - 100s 389ms/step - loss: 1.4532 - acc: 0.4366 Epoch 3/5 256/256 [========================] - 102s 397ms/step - loss: 1.3100 - acc: 0.4958 Epoch 4/5 256/256 [=======================] - 103s 402ms/step - loss: 1.1995 - acc: 0.5451 Epoch 5/5 256/256 [=======================] - 104s 407ms/step - loss: 1.0831 - acc: 0.5924
结果类似于基线迭代中的结果,即 1 和 2。
从这个实验中,我们可以得出以下有关 CNN 参数优化的结论:
- 减少
Conv2D
的数量并以stride = 2
消除一个合并层具有最显着的效果,因为它提高了准确率(大约 30%)。 但是,这是以速度为代价的,因为 CNN 的大小没有减小。 - 合并类型(平均合并与最大合并)对测试准确率的影响不明显。
产生最高准确率的 CNN 架构如下:
请注意,与原始架构相比,该架构要简单得多。 在下一章中,您将了解一些最新的卷积模型以及为什么要使用它们。 然后,我们将回到这个优化问题,并学习如何更有效地选择参数以获得更好的优化。
可视化神经网络的各个层
在本章中,我们了解了如何将图像转换为边缘然后转换为特征映射,并且通过这样做,神经网络能够通过组合许多特征映射来预测类。 在前几层中,神经网络可视化线和角,而在后几层中,神经网络识别复杂的模式,例如特征映射。 可以分为以下几类。
- 建立自定义图像分类器模型并可视化其层
- 训练现有的高级图像分类器模型并可视化其层
让我们看一下这些类别。
建立自定义图像分类器模型并可视化其层
在本节中,我们将开发自己的家具分类器网络。 这将分为三类:沙发,床和椅子。 基本过程描述如下。
请注意,在“第 6 章”,“使用迁移学习的视觉搜索”中,我们将执行更高级的编码,并使用相同的三个类提供详细的说明。
神经网络输入和参数
在本部分中,模型输入各种 Keras 库和 TensorFlow。 在下面的代码中可以看到。 现在,先加少许盐。 这将在“第 6 章”,“使用迁移学习的视觉搜索”中全面说明:
from __future__ import absolute_import, division, print_function, unicode_literals, import tensorflow as tf, from tensorflow.keras.applications import VGG16\n from keras.applications.vgg16 import preprocess_input, from keras import models from tensorflow.keras.models import Sequential, Model from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, GlobalAveragePooling2D, MaxPooling2D from tensorflow.keras.preprocessing.image import ImageDataGenerator\n", from tensorflow.keras.optimizers import SGD, Adam import os import numpy as np import matplotlib.pyplot as plt
输入图像
在本节中,我们定义训练和验证目录路径,并使用os.path.join
函数和类名在训练和验证目录中定义目录。 之后,我们使用len
命令计算每个类目录中的图像总数:
train_dir = 'furniture_images/train' train_bed_dir = os.path.join(train_dir, 'bed') num_bed_train = len(os.listdir(train_bed_dir))
训练目录中的图像总数是通过将每个类别中的图像总数相加得出的。 验证使用相同的方法,最后导致输出为训练和验证目录中图像的总数。 训练期间神经网络将使用此信息。
定义训练和验证生成器
训练生成器和验证生成器使用一种称为图像数据生成和流的方法。 他们在目录上使用它输入张量图像。 有关此过程的详细信息,请参阅 Keras 文档。
一个典型的例子如下。 如 Keras 文档中所述,图像数据生成器具有许多参数,但此处仅使用其中一些。 预处理输入将图像转换为张量。 输入旋转范围将图像旋转 90 度并垂直翻转以进行图像增强。 我们可以使用在“第 1 章”,“计算机视觉和 TensorFlow 基础知识”中学习到的图像变换,并使用rotation
命令。 图像增强可以增加训练数据集,从而在不增加测试数据量的情况下提高模型的准确率:
train_datagen = ImageDataGenerator(preprocessing_function=preprocess_input,rotation_range=90,horizontal_flip=True,vertical_flip=True)
构建模型
准备好图像后,我们就可以开始构建模型了。 Keras 顺序模型使我们能够做到这一点。 这是彼此堆叠的模型层的列表。 接下来,我们通过堆叠卷积,激活,最大池,丢弃和填充来构建顺序模型,如以下代码所示:
model = Sequential([Conv2D(96, 11, padding='valid', activation='relu',input_shape=(img_height, img_width,3)), MaxPooling2D(),Dropout(0.2),Conv2D(256, 5, padding='same', activation='relu'),MaxPooling2D(),Conv2D(384, 3, padding='same', activation='relu'),Conv2D(384, 3, padding='same', activation='relu'), Conv2D(256, 3, padding='same', activation='relu'),MaxPooling2D(), Dropout(0.2),Conv2D(1024, 3, padding='same', activation='relu'), MaxPooling2D(),Dropout(0.2),Flatten(),Dense(4096, activation='relu'), Dense(3)])
该模型的基本思想与 AlexNet 相似,将在“第 5 章”,“神经网络架构和模型”中进行介绍。 该模型大约有 16 层。
编译和训练模型
接下来,我们编译模型并开始训练。 编译option
指定三个参数:
- 优化器:我们可以使用的优化器是
adam
,rmsprop
,sgd
,adadelta
,adagrad
,adamax
和nadam
。 有关 Keras 优化器的列表,请参考这里:
sgd
代表随机梯度下降。 顾名思义,它使用梯度值作为优化程序。adam
代表自适应力矩。 它在最后一步中使用梯度来调整梯度下降参数。 Adam 运作良好,几乎不需要调整。 在本书中将经常使用它。adagrad
适用于稀疏数据,并且几乎不需要调整。 对于adagrad
,不需要默认学习率。
- 损失函数:用于图像处理的最常用损失函数是二进制交叉熵,分类交叉熵,均方误差或
sparse_categorical
交叉熵。 当分类任务是二进制时,例如处理猫和狗图像或停车标志与无停车标志图像时,将使用二进制交叉熵。 当我们有两个以上的类时(例如,配有床,椅子和沙发的家具店),将使用分类交叉熵。 稀疏分类交叉熵与分类交叉熵类似,不同之处在于,该类被其索引代替-例如,我们将传递 0、1 和 2 而不是将床,椅子和沙发作为类, 指定类时出现错误,可以使用稀疏分类交叉熵来解决此问题。 Keras 中还有许多其他损失函数。 有关更多详细信息,请参阅这里。 - 指标:这些指标用于设置准确率。
在下面的代码中,我们使用adam
优化器。 编译模型后,我们使用 Keras model.fit()
函数开始训练。 model.fit()
函数将序列生成器用作我们先前定义的输入图像向量。 它还需要周期数(迭代参数),每个周期的步骤(每个周期的批量数),验证数据和验证步骤。 请注意,将在“第 6 章”,“使用迁移学习的视觉搜索”中详细描述每个参数:
model.compile(optimizer='adam',loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), metrics=['accuracy']) history = model.fit(train_generator,epochs=NUM_EPOCHS,steps_per_epoch=num_train_images // batchsize,validation_data=val_generator, validation_steps=num_val_images // batchsize)
训练持续 10 个周期。 在训练过程中,模型的准确率随周期数的增加而增加:
WARNING:tensorflow:sample_weight modes were coerced from ... to ['...'] Train for 10 steps, validate for 1 steps Epoch 1/10 10/10 [==============================] - 239s 24s/step - loss: 13.7108 - accuracy: 0.6609 - val_loss: 0.6779 - val_accuracy: 0.6667 Epoch 2/10 10/10 [==============================] - 237s 24s/step - loss: 0.6559 - accuracy: 0.6708 - val_loss: 0.5836 - val_accuracy: 0.6693 Epoch 3/10 10/10 [==============================] - 227s 23s/step - loss: 0.5620 - accuracy: 0.7130 - val_loss: 0.5489 - val_accuracy: 0.7266 Epoch 4/10 10/10 [==============================] - 229s 23s/step - loss: 0.5243 - accuracy: 0.7334 - val_loss: 0.5041 - val_accuracy: 0.7292 Epoch 5/10 10/10 [==============================] - 226s 23s/step - loss: 0.5212 - accuracy: 0.7342 - val_loss: 0.4877 - val_accuracy: 0.7526 Epoch 6/10 10/10 [==============================] - 226s 23s/step - loss: 0.4897 - accuracy: 0.7653 - val_loss: 0.4626 - val_accuracy: 0.7604 Epoch 7/10 10/10 [==============================] - 227s 23s/step - loss: 0.4720 - accuracy: 0.7781 - val_loss: 0.4752 - val_accuracy: 0.7734 Epoch 8/10 10/10 [==============================] - 229s 23s/step - loss: 0.4744 - accuracy: 0.7508 - val_loss: 0.4534 - val_accuracy: 0.7708 Epoch 9/10 10/10 [==============================] - 231s 23s/step - loss: 0.4429 - accuracy: 0.7854 - val_loss: 0.4608 - val_accuracy: 0.7865 Epoch 10/10 10/10 [==============================] - 230s 23s/step - loss: 0.4410 - accuracy: 0.7865 - val_loss: 0.4264 - val_accuracy: 0.8021
输入测试图像并将其转换为张量
到目前为止,我们已经开发了图像目录并准备和训练了模型。 在本节中,我们将图像转换为张量。 我们通过将图像转换为数组来从图像中生成张量,然后使用 NumPy 的expand_dims()
函数扩展数组的形状。 随后,我们对输入进行预处理以准备图像,使其具有模型所需的格式:
img_path = 'furniture_images/test/chair/testchair.jpg' img = image.load_img(img_path, target_size=(150, 150)) img_tensor = image.img_to_array(img) img_tensor = np.expand_dims(img_tensor, axis=0) img_tensor = preprocess_input(img_tensor) featuremap = model.predict(img_tensor)
最后,我们使用 Keras model.predict()
函数输入图像张量,然后将张量转换为特征映射。 现在您知道了如何通过将图像张量传递给我们刚刚开发的模型来计算特征映射。
可视化激活的第一层
为了计算激活,我们计算每个层的模型输出。 在此示例中,共有 16 层,因此我们使用model.layers[:16]
指定所有 16 层。 我们用于执行此操作的代码如下:
layer_outputs = [layer.output for layer in model.layers[:16]] activation_modelfig = Model(inputs=model.input, outputs=layer_outputs) activationsfig = activation_modelfig.predict(img_tensor)
为了使用激活,我们使用 Keras Model
函数式 API,当给定a
时,该 API 计算计算b
所需的所有层:
model = Model(inputs=[a1, a2], outputs=[b1, b2, b3])
对于我们的练习,输入是我们先前计算的图像张量,而输出是激活层。
接下来,我们使用以下命令可视化第一层,其中activationsfig[0]
表示第一层。 要绘制它,我们使用plt.matshow()
。 在这里,95
是第一神经网络层的倒数第二个激活过滤器:
first_layer_activation = activationsfig[0] print(first_layer_activation.shape) plt.matshow(first_layer_activation[0, :, :, 95], cmap='viridis')
可视化多层激活
按照之前的操作,我们运行for
循环并使用plt.imshow
方法在给定神经网络层的第一个,中间和最后一个过滤器值中显示激活层:
for i in range(0,12): current_layer_activation = activationsfig[i] ns = current_layer_activation.shape[-1] plt.imshow(current_layer_activation[0, :, :, 0], cmap='viridis') plt.imshow(current_layer_activation[0, :, :, int(ns/2)], cmap='viridis') plt.imshow(current_layer_activation[0, :, :, ns-1], cmap='viridis')
下图显示了椅子图像的最终输出值:
在上图中,n
表示给定层的最大过滤器数。 n
的值在不同的层可以不同。 例如,对于第一层,n
的值为96
,而对于第四层,其为256
。 下表显示了自定义神经网络不同层的参数,输出形状和过滤器:
如您所见,每个层都有许多不同的激活过滤器,因此对于我们的可视化,我们正在查看给定层的第一个过滤器,中间过滤器和最后一个过滤器的可视化值。
最初的第一层代表椅子,但是随着我们深入模型的层,结构变得越来越抽象。 这意味着图像看起来不太像椅子,而更像代表类的东西。 此外,您可以看到某些层根本没有激活,这表明模型的结构变得复杂且效率不高。
自然而然产生的一个大问题是,神经网络如何处理来自最后一层的看似抽象的图像并从中提取出一个类? 这是人类无法做的事情。
答案在于全连接层。 如上表所示,共有 16 层,但是i
的可视化代码在(0, 12)
范围内,因此我们仅可视化前 12 层。 如果您尝试可视化更多,则会收到错误消息。 在第 12 层之后,我们展平该层并映射该层的每个元素-这称为全连接层。 这本质上是一个映射练习。 该神经网络将全连接层的每个元素映射到特定类别。 对所有类都重复此过程。 将抽象层映射到类是机器学习练习。 通过这样做,神经网络能够预测类别。
下图显示了床图像的输出值:
就像椅子的图像一样,初始激活从类似于床的输出开始,但是当我们深入网络时,我们开始看到床与椅子相比的独特特征。
下图显示了沙发图像的输出值:
就像椅子和床的示例一样,前面的图从具有不同特征的顶层开始,而最后几层则显示了特定于该类的非常抽象的图像。 请注意,第 4、5 和 6 层很容易被替换,因为这些层根本没有激活。
训练现有的高级图像分类器模型并可视化其层
我们发现,我们开发的模型约有 16 层,其验证准确率约为 80%。 由此,我们观察了神经网络如何看到不同层的图像。 这带来了两个问题:
- 我们的自定义神经网络与更高级的神经网络相比如何?
- 与我们的自定义神经网络相比,高级神经网络如何看待图像? 所有的神经网络都以相似或不同的方式看到图像吗?
为了回答这些问题,我们将针对两个高级网络 VGG16 和 InceptionV3 训练分类器,并在网络的不同层上可视化椅子图像。 “第 5 章”,“神经网络架构和模型”提供了网络的详细说明,而“第 6 章”,“使用迁移学习的视觉搜索”,提供了代码的详细说明。 因此,在本节中,我们将只专注于可视化部分。 在完成“第 5 章”,“神经网络架构和模型”和“第 6 章”之后,您可能需要重新访问编码部分 ,以便您对代码有深刻的了解。 VGG 16 中的层数为 26。您可以在这个页面上找到有关此代码的代码。
请注意,前面的代码同时运行自定义网络和 VGG 16 模型。 在此练习中,请勿运行标记为自定义网络的单元,以确保仅执行 VGG16 模型。 Keras 有一个简单的 API,可以在其中导入 VGG16 或 InceptionV3 模型。 这里要注意的关键是 VGG 16 和 InceptionV3 均在具有 1,000 个类的 ImageNet 数据集上进行了训练。 但是,在这种情况下,我们将使用三个类来训练该模型,以便我们可以仅使用 VGG 16 或 Inception 模型。 Keras 将针对不兼容的形状抛出错误:[128,1000]
与[128,3]
相对,其中128
是批量大小。 要解决此问题,请在模型定义中使用include_top = False
,该模型将删除最后的全连接层,并仅用三个类将它们替换为我们自己的层。 同样,“第 6 章”,“使用迁移学习的视觉搜索”对此进行了详细描述。 在训练 135 个步骤并验证 15 个步骤后,VGG 16 模型的验证准确率约为0.89
:
Epoch 1/10 135/135 [==============================] - 146s 1s/step - loss: 1.8203 - accuracy: 0.4493 - val_loss: 0.6495 - val_accuracy: 0.7000 Epoch 2/10 135/135 [==============================] - 151s 1s/step - loss: 1.2111 - accuracy: 0.6140 - val_loss: 0.5174 - val_accuracy: 0.8067 Epoch 3/10 135/135 [==============================] - 151s 1s/step - loss: 0.9528 - accuracy: 0.6893 - val_loss: 0.4765 - val_accuracy: 0.8267 Epoch 4/10 135/135 [==============================] - 152s 1s/step - loss: 0.8207 - accuracy: 0.7139 - val_loss: 0.4881 - val_accuracy: 0.8133 Epoch 5/10 135/135 [==============================] - 152s 1s/step - loss: 0.8057 - accuracy: 0.7355 - val_loss: 0.4780 - val_accuracy: 0.8267 Epoch 6/10 135/135 [==============================] - 152s 1s/step - loss: 0.7528 - accuracy: 0.7571 - val_loss: 0.3842 - val_accuracy: 0.8333 Epoch 7/10 135/135 [==============================] - 152s 1s/step - loss: 0.6801 - accuracy: 0.7705 - val_loss: 0.3370 - val_accuracy: 0.8667 Epoch 8/10 135/135 [==============================] - 151s 1s/step - loss: 0.6716 - accuracy: 0.7906 - val_loss: 0.4276 - val_accuracy: 0.8800 Epoch 9/10 135/135 [==============================] - 152s 1s/step - loss: 0.5954 - accuracy: 0.7973 - val_loss: 0.4608 - val_accuracy: 0.8533 Epoch 10/10 135/135 [==============================] - 152s 1s/step - loss: 0.4926 - accuracy: 0.8152 - val_loss: 0.3550 - val_accuracy: 0.8933
下图显示了在椅子上使用 VGG 16 模型后神经网络的可视化效果:
上图显示了 VGG 16 模型如何在前 16 层看到椅子。 请注意,与我们的自定义模型相比,VGG 16 模型效率更高,因为每一层都在执行某种类型的图像激活。 不同层的图像特征不同,但总体趋势是相同的–随着我们深入层,图像将转换为更抽象的结构。
接下来,我们使用 Inception V3 模型执行相同的练习。 在“第 6 章”,“使用迁移学习的视觉搜索”中描述了此代码。 下图显示了 Inception V3 模型如何形象化椅子图像:
Inception V3 模型的验证准确率约为 99%。 如您所见,与 VGG16 相比,Inception V3 模型中的层数更多。 先前的图像难以可视化。 下图显示了第一层,最后一层和一些选择的中间层:
前面的图像清楚地显示了当我们更深入地进入神经网络时,椅子图像如何失去锐度并变得越来越模糊。 最终图像看起来不太像椅子。 神经网络可以看到许多相似的椅子图像,并根据它们来解释椅子。
在本节中,我们描述了如何在训练过程中查看中间激活层,以了解如何在神经网络上变换特征映射。 但是,如果您想了解神经网络如何将特征和隐藏层转换为输出,请参阅 TensorFlow 神经网络游乐场。
总结
CNN 是事实上的图像分类模型,这是因为 CNN 能够自己学习每个类别的独特特征,而无需推导输入和输出之间的任何关系。 在本章中,我们了解了 CNN 的组件,这些组件负责学习图像特征,然后将其分类为预定义的类。 我们了解了卷积层如何彼此堆叠以从简单的形状(例如边缘)学习以创建复杂的形状(例如眼睛),以及特征映射的维数如何因卷积和合并层而改变。 我们还了解了非线性激活函数,Softmax 和全连接层的功能。 本章重点介绍如何优化不同的参数以减少过拟合的问题。
我们还构建了用于分类目的的神经网络,并使用我们开发的模型来创建图像张量,该图像张量被神经网络用于开发可视化的激活层。 可视化方法可帮助我们了解如何在神经网络中变换特征映射,以及神经网络如何使用全连接层从此变换后的特征映射中分配类别。 我们还学习了如何将自定义神经网络可视化与高级网络(例如 VGG16 和 Inception V3)的可视化进行比较。
在下一章中,您将学习一些最著名的神经网络模型,以便对 CNN 参数选择有更深入的了解。