精通 Python OpenCV4:第三、四部分

2023-04-27 15:45:52 浏览数 (1)

第 3 部分:OpenCV 中的机器学习和深度学习

在本书的第三部分中,您将体验一下机器学习和深度学习。 我们将探索和利用 OpenCV 的机器学习模块。 此外,您还将学习如何使用与人脸检测,跟踪和识别相关的最新算法来创建人脸处理项目。 最后,将向您介绍 OpenCV 和一些深度学习 Python 库(TensorFlow 和 Keras)的深度学习领域。

本节将介绍以下章节:

  • 第 10 章,“使用 OpenCV 的机器学习”
  • 第 11 章,“人脸检测,跟踪和识别”
  • 第 12 章,“深度学习简介”

十、使用 OpenCV 的机器学习

机器学习是人工智能的一种应用,它为计算机(以及具有一定计算能力的其他系统)提供了自动根据经验进行预测或决策的能力,而无需进行明确编程即可执行任务。 机器学习的概念已经存在了很长时间,但是在过去的几年中,它的发展势头强劲,这主要归因于以下三个关键因素:

  • 数据量大大增加。
  • 有明显改进的算法。
  • 实质上有更强大的计算机硬件。 虚拟个人助理(例如,智能扬声器或移动应用),通勤时的预测(交通预测或导航服务),视频系统(监控摄像头系统或车牌识别系统)以及电子商务应用(推荐系统或自动价格识别系统) 比较应用)只是我们日常生活中机器学习应用的一些示例。

在本章中,我们将看到 OpenCV 提供的一些最常见的机器学习算法和技术,它们可以解决计算机视觉项目中的实际问题,例如分类和回归问题。

我们将涵盖以下主题:

  • 机器学习入门
  • K 均值聚类
  • K 最近邻
  • 支持向量机

技术要求

技术要求如下:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib 包
  • Git 客户端

有关如何安装这些要求的更多详细信息,请参见第 1 章,“设置 OpenCV”。 可通过 Github 访问《精通 Python OpenCV 4》的 GitHub 存储库,其中包含从本书第一章到最后的所有必要的支持项目文件。

机器学习入门

在第 1 章,“设置 OpenCV”中,我们介绍了计算机视觉,人工智能,机器学习,神经网络和深度学习的概念,这些概念可以按层次结构进行构建, 如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uOE6Zqrj-1681870549410)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3066b65c-52cf-4467-9f9f-0412438e9759.png)]

可以看出,人工智能主题包括所有其他主题。 在本章中,我们将专注于机器学习

如果您想对这些概念进行复习,请参阅第 1 章,“设置 OpenCV”。

机器学习是对计算机进行编程以从历史数据中学习以对新数据进行预测的过程。 机器学习是人工智能的一个子学科,是指统计技术,通过这些技术,机器可以在学习到的相互关系的基础上执行操作。 基于收集或收集的数据,算法是由计算机独立学习的*。*

机器学习的上下文中,有三种主要方法-监督机器学习无监督机器学习半监督的机器学习技术。 这些方法可以在下图中看到。 为了完成它,我们包含了三种最常见的技术来解决分类回归聚类问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JsJE4eMO-1681870549411)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/159e6630-15f1-496e-820a-5a092994e8b5.png)]

这些方法之间的主要区别是学习过程,我们将在下面讨论。

监督机器学习

使用样本集合进行监督学习,每个样本具有相应的输出值(期望的输出)。 这些机器学习方法称为监督,因为我们知道每个训练示例的正确答案,并且有监督的学习算法会分析训练数据,以便对训练数据做出预测。 此外,可以基于预测与相应的期望输出之间的差异来校正这些预测。 基于这些更正,该算法可以从误差中学习以调整其内部参数。 这样,在监督学习中,算法会迭代地调整一个函数,该函数可以最佳地近似样本集合与相应所需输出之间的关系。

监督学习问题可以进一步分为以下几类:

  • 分类:当输出变量是类别(例如颜色(红色,绿色或蓝色),尺寸(大,中或小)或性别(男性或女性))时,可能被视为分类问题。 在分类问题中,该算法将输入映射到输出标签。
  • 回归:当输出变量是真实值(例如年龄或体重)时,监督学习问题可以归类为回归问题。 在回归问题中,该算法将输入映射到连续输出。

在监督学习中,有一些主要问题需要考虑,为了完整起见,接下来将进行评论:

  • 偏差方差权衡:偏差方差折衷是机器学习中的一个常用术语,指的是模型-数据不足的模型具有较高的偏差,而模型的数据过拟合则偏高。 数据差异很大:
    • 偏差可以看作是学习算法中错误假设产生的误差,可以定义为模型预测与我们尝试预测的正确值之间的差异。 这导致算法通过不考虑数据中的所有信息(拟合不足)来学习错误的东西。 因此,具有高偏差的模型无法在数据中找到所有模式,因此它不太适合训练集,也不太适合测试集。
    • 方差可以定义为算法通过拟合模型来密切学习数据中的误差/噪声(过拟合),而该趋势倾向于学习错误的事物,而与真实信号无关。 因此,具有高方差的模型非常适合训练集,但是由于它也已获悉数据中的误差/噪声,因此无法推广到测试集。 请查看下图,以更好地理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ReSNQggq-1681870549411)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/1debbbe1-ab51-48fb-9060-8104b2563135.png)]

  • 函数复杂度和训练数据量模型复杂度是指机器学习算法正尝试以与多项式次数相似的方式学习函数的复杂性。 模型复杂性的适当级别通常由训练数据的性质决定。 例如,如果您需要少量的数据来训练模型,则低复杂度的模型是可取的。 这是因为高复杂度模型将适合较小的训练集。
  • 输入空间的维数:在处理高/非常高维的特征空间时,学习问题可能会非常困难,因为许多额外的特征会混淆学习过程, 结果差异很大。 因此,当处理高/非常高维的特征空间时,一种常见的方法是将学习算法修改为具有高偏差和低方差。 此问题与维度诅咒有关,后者指的是在分析和组织在低维空间中找不到的高维空间中的数据时出现的各个方面。
  • 输出值中的噪声:如果所需的输出值不正确(由于人为或传感器误差),则学习算法尝试过于紧密地拟合数据时,可能会发生过拟合。 有几种常见的策略可用于减轻输出值中的误差/噪声影响。 例如,在训练算法之前检测并去除嘈杂的训练示例是一种常见的方法。 另一个策略是尽早停止,可以用来防止过拟合。

无监督机器学习

在无监督学习中,没有标记输出。 从这个意义上说,这里有一个样本集合,但是每个样本的相应输出值都丢失了(样本集合没有被标记,分类或分类)。 无监督学习的目标是对样本集合中的基础结构或分布进行建模和推断。 因此,在无监督学习中,该算法无法找到正确的输出,但是可以探索数据并可以从数据中进行推断,以试图揭示其中的隐藏结构。 聚类或降维是无监督学习中最常用的两种算法。

半监督机器学习

顾名思义,半监督学习可以看作是监督学习和无监督学习之间的折衷,因为它使用标记和未标记的数据进行训练。 从这个意义上讲,您拥有大量输入数据并且仅对其中一些数据进行了标记的问题可以归类为半监督学习问题。

许多现实世界中的机器学习问题可以归类为半监督问题,因为正确标记所有数据可能非常困难,昂贵或耗时,而未标记的数据更易于收集。

在这些情况下,仅标记了少量的训练数据,您可以探索有监督和无监督学习技术:

  • 您可以使用无监督学习技术来发现和学习输入变量中的结构。
  • 您可以使用监督学习技术来使用标记的数据训练分类器,然后使用此模型对未标记的数据进行预测。 此时,您可以将该数据作为训练数据反馈到监督学习算法中,以迭代地增加标记数据的大小,并使用重新训练的模型对新的未标记数据进行预测。

K 均值聚类

OpenCV 提供cv2.kmeans()函数,该函数实现了 K 均值聚类算法,该算法查找聚类的中心并对聚类周围的输入样本进行分组。

K 均值聚类算法的目标是将n个样本划分(或聚类)为K聚类,其中每个样本将属于具有最均值的聚类。 cv2.kmeans()函数的签名如下:

代码语言:javascript复制
retval, bestLabels, centers=cv.kmeans(data, K, bestLabels, criteria, attempts, flags[, centers])

data代表用于聚类的输入数据。 它应为np.float32数据类型,并且每个特征都应放在单个列中。 K指定最后所需的群集数。 使用criteria参数指定算法终止标准,该参数设置最大迭代次数和/或所需的精度。 当满足这些条件时,算法终止。 criteria是三个参数typemax_itermepsilon的元组:

  • type:这是终止条件的类型。 它具有三个标志:
    • cv2.TERM_CRITERIA_EPS:当达到指定的精度epsilon时,算法停止。
    • cv2.TERM_CRITERIA_MAX_ITER:当达到指定的迭代次数max_iterm时,算法停止。
    • cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER:达到两个条件中的任何一个时,算法将停止。
  • max_iterm:这是最大迭代次数。
  • epsilon:这是必需的精度。

条件的示例如下:

代码语言:javascript复制
criteria = (cv2.TERM_CRITERIA_EPS   cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

在这种情况下,最大迭代次数设置为20max_iterm = 20),所需的精度为1.0epsilon = 1.0)。

attempts参数指定使用不同的初始标签执行算法的次数。 该算法返回产生最佳紧密度的标签。 flags参数指定如何获取初始中心。 cv2.KMEANS_RANDOM_CENTERS标志在每次尝试中选择随机的初始中心。 cv2.KMEANS_PP_CENTERS标志使用 Arthur 和 Vassilvitskii 提出的 K 均值 中心初始化(请参阅《K 均值 :精心播种的优势》(2007))。

cv2.kmeans()返回以下内容:

  • bestLabels:一个整数数组,存储每个样本的聚类索引
  • centers:一个数组,其中包含每个集群的中心
  • compactness:每个点到其相应中心的距离的平方之和

在本节中,我们将看到两个如何在 OpenCV 中使用 K 均值聚类算法的示例。

在第一个示例中,期望实现对 K 均值聚类的直观理解,而在第二个示例中,K 均值聚类将应用于颜色量化问题。

了解 K 均值聚类

在此示例中,我们将使用 K 均值聚类算法对一组 2D 点进行聚类。 这组 2D 点可以看作是对象的集合,已使用两个特征对其进行了描述。 可以使用k_means_clustering_data_visualization.py脚本创建和显示这组 2D 点。

下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wyMYA4Os-1681870549412)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/9c824982-da30-44f3-bbf6-67fd4c79546f.png)]

这组 2D 点由150点组成,它们是通过以下方式创建的:

代码语言:javascript复制
data = np.float32(np.vstack(
    (np.random.randint(0, 40, (50, 2)), np.random.randint(30, 70, (50, 2)), np.random.randint(60, 100, (50, 2)))))

这将代表用于聚类的数据。 如前所述,它应为np.float32类型,并且每个特征都应放在单个列中。

在这种情况下,每个点都有对应于(x, y)坐标的两个特征。 这些坐标可以表示例如每个150人的身高和体重,或每个150房屋的卧室数量和大小。 在第一种情况下,K 均值聚类算法将决定 T 恤的尺寸(例如,如果K=3为小,中或大),而在第二种情况下,K 均值聚类算法将决定房子的价格(例如K = 4,便宜,中等,昂贵或非常昂贵)。 总之,data将是我们的聚类算法的输入。

在接下来的脚本中,我们将看到如何使用K的不同值及其相应的可视化效果对其进行聚类。 为此,我们编写了三个脚本:

  • k_means_clustering_k_2.py:在此脚本中,data已分为两个组(K = 2)。
  • k_means_clustering_k_3.py:在此脚本中,data已分为三个组(K = 3)。
  • k_means_clustering_k_4.py:在此脚本中,data已分为四个组(K = 4)。

k_means_clustering_k_2.py脚本中,数据已集群为2集群。 第一步是定义算法终止标准。 在这种情况下,最大迭代次数设置为20max_iterm = 20),ε设置为1.0epsilon = 1.0):

代码语言:javascript复制
criteria = (cv2.TERM_CRITERIA_EPS   cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

下一步是使用cv2.kmeans()函数应用 K 均值算法:

代码语言:javascript复制
ret, label, center = cv2.kmeans(data, 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

此时,我们可以使用label输出分离数据,该输出存储每个样本的聚类索引。 因此,我们可以根据标签将数据分为不同的群集:

代码语言:javascript复制
A = data[label.ravel() == 0]
B = data[label.ravel() == 1]

最后一步是在不进行聚类的情况下绘制AB以及原始的data,以便更好地了解聚类过程:

代码语言:javascript复制
# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(12, 6))
plt.suptitle("K-means clustering algorithm", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Plot the 'original' data:
ax = plt.subplot(1, 2, 1)
plt.scatter(data[:, 0], data[:, 1], c='c')
plt.title("data")

# Plot the 'clustered' data and the centroids
ax = plt.subplot(1, 2, 2)
plt.scatter(A[:, 0], A[:, 1], c='b')
plt.scatter(B[:, 0], B[:, 1], c='g')
plt.scatter(center[:, 0], center[:, 1], s=100, c='m', marker='s')
plt.title("clustered data and centroids (K = 2)")

# Show the Figure:
plt.show()

下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5wtZTU4p-1681870549412)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/4ea77223-5ebd-4fb8-82b6-33457e733868.png)]

您可以看到我们还绘制了center,它是一个包含每个群集中心的数组。

k_means_clustering_k_3.py脚本中,采用了相同的步骤对数据进行聚类,但是我们决定将数据分组为3聚类(K = 3)。 因此,在调用cv2.kmeans()函数时,K参数设置为3

代码语言:javascript复制
ret, label, center = cv2.kmeans(data, 3, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

此外,使用label输出分离数据时,将获得三组:

代码语言:javascript复制
A = data[label.ravel() == 0]
B = data[label.ravel() == 1]
C = data[label.ravel() == 2]

最后一步是显示ABC,以及质心和原始数据:

代码语言:javascript复制
# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(12, 6))
plt.suptitle("K-means clustering algorithm", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Plot the 'original' data:
ax = plt.subplot(1, 2, 1)
plt.scatter(data[:, 0], data[:, 1], c='c')
plt.title("data")

# Plot the 'clustered' data and the centroids
ax = plt.subplot(1, 2, 2)
plt.scatter(A[:, 0], A[:, 1], c='b')
plt.scatter(B[:, 0], B[:, 1], c='g')
plt.scatter(C[:, 0], C[:, 1], c='r')
plt.scatter(center[:, 0], center[:, 1], s=100, c='m', marker='s')
plt.title("clustered data and centroids (K = 3)")

# Show the Figure:
plt.show()

在上一个代码段中,我们在同一图中绘制了原始数据和“聚类”数据以及质心。 下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iloHcnW-1681870549412)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/87acc06a-a268-49a7-98ea-0ecade2b8df3.png)]

为了完整起见,我们还对k_means_clustering_k_4.py脚本进行了编码,其输出可以在下一个屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7J9jnc1-1681870549413)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3d06f2a2-de2c-4ec0-9321-bbb1416f1560.png)]

可以看出,群集的数量被设置为4K = 4)。

使用 K 均值聚类的颜色量化

在本小节中,我们将 K 均值聚类算法应用于颜色量化问题,可以将其定义为减少图像中颜色数量的过程。 对于在只能显示有限数量的颜色(通常是由于内存限制)的某些设备上显示图像,颜色量化是至关重要的一点。 因此,通常需要在相似度和颜色数量减少之间进行权衡。 这种权衡是通过正确设置K参数来建立的,我们将在下面的示例中看到。

k_means_color_quantization.py脚本中,我们执行 K 均值聚类算法以执行颜色量化。 在这种情况下,数据的每个元素都由3特征组成,这些特征对应于图像每个像素的BGR值。 因此,关键步骤是通过以下方式将图像转换为data

代码语言:javascript复制
data = np.float32(image).reshape((-1, 3))

在这里,image是我们先前加载的图像。

在此脚本中,我们使用K35102040)的几个值执行了聚类过程,以查看生成的图像如何变化。 例如,如果我们希望生成的图像仅具有3颜色(K = 3),则必须执行以下操作:

  1. 加载 BGR 图片:
代码语言:javascript复制
img = cv2.imread('landscape_1.jpg')
  1. 使用color_quantization()函数执行色彩量化:
代码语言:javascript复制
color_3 = color_quantization(img, 3)
  1. 同时显示两个图像以查看结果。 color_quantization()函数执行颜色量化过程:
代码语言:javascript复制
def color_quantization(image, k):
    """Performs color quantization using K-means clustering algorithm"""

    # Transform image into 'data':
    data = np.float32(image).reshape((-1, 3))
    # print(data.shape)

    # Define the algorithm termination criteria (maximum number of iterations and/or required accuracy):
    # In this case the maximum number of iterations is set to 20 and epsilon = 1.0
    criteria = (cv2.TERM_CRITERIA_EPS   cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

    # Apply K-means clustering algorithm:
    ret, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

    # At this point we can make the image with k colors
    # Convert center to uint8:
    center = np.uint8(center)
    # Replace pixel values with their center value:
    result = center[label.flatten()]
    result = result.reshape(img.shape)
    return result

在上一个函数中,关键是要使用cv2.kmeans()方法。 最后,我们可以用k种颜色构建图像,用每种颜色的像素值替换其相应的中心值。 下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QpBkUOvZ-1681870549413)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/8c378955-0b9a-46e7-bbf6-b58cf7b43e25.png)]

可以将先前的脚本扩展为包括有趣的功能,该功能显示分配给每个中心值的像素数。 可以在k_means_color_quantization_distribution.py脚本中看到。

color_quantization()函数已被修改为包括以下功能:

代码语言:javascript复制
def color_quantization(image, k):
    """Performs color quantization using K-means clustering algorithm"""

    # Transform image into 'data':
    data = np.float32(image).reshape((-1, 3))
    # print(data.shape)

    # Define the algorithm termination criteria (the maximum number of iterations and/or the desired accuracy):
    # In this case the maximum number of iterations is set to 20 and epsilon = 1.0
    criteria = (cv2.TERM_CRITERIA_EPS   cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

    # Apply K-means clustering algorithm:
    ret, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

    # At this point we can make the image with k colors
    # Convert center to uint8:
    center = np.uint8(center)
    # Replace pixel values with their center value:
    result = center[label.flatten()]
    result = result.reshape(img.shape)

    # Build the 'color_distribution' legend.
    # We will use the number of pixels assigned to each center value:
    counter = collections.Counter(label.flatten())
    print(counter)

    # Calculate the total number of pixels of the input image:
    total = img.shape[0] * img.shape[1]

    # Assign width and height to the color_distribution image:
    desired_width = img.shape[1]
    # The difference between 'desired_height' and 'desired_height_colors'
    # will be the separation between the images
    desired_height = 70
    desired_height_colors = 50

    # Initialize the color_distribution image:
    color_distribution = np.ones((desired_height, desired_width, 3), dtype="uint8") * 255
    # Initialize start:
    start = 0

    for key, value in counter.items():
        # Calculate the normalized value:
        value_normalized = value / total * desired_width

        # Move end to the right position:
        end = start   value_normalized

        # Draw rectangle corresponding to the current color:
        cv2.rectangle(color_distribution, (int(start), 0), (int(end), desired_height_colors), center[key].tolist(), -1)
        # Update start:
        start = end

    return np.vstack((color_distribution, result))

如您所见,我们利用collections.Counter()来计算分配给每个中心值的像素数:

代码语言:javascript复制
counter = collections.Counter(label.flatten())

例如,如果K = 3Counter({0: 175300, 2: 114788, 1: 109912})。 构建颜色分布图像后,最后一步是将两个图像连接起来:

代码语言:javascript复制
np.vstack((color_distribution, result))

下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k9gqEtX0-1681870549413)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/17527994-5206-4865-ac7b-32207ba2633f.png)]

在上一个屏幕截图中,您可以看到使用 K 均值聚类算法通过更改参数k35102040)应用颜色量化的结果。 k的值越大,表示图像越真实。

K 最近邻

K 最近邻kNN)被认为是有监督学习类别中最简单的算法之一。 kNN 可用于分类和回归问题。 在训练阶段,kNN 同时存储所有训练样本的特征向量和类别标签。 在分类阶段,将未标记向量(与训练示例位于同一多维特征空间中的查询或测试向量)分类为最接近要分类的未标记向量的k个训练样本中最频繁的类别标签,其中k是用户定义的常数。

在下图中可以以图形方式看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c81gVG6l-1681870549413)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/01707af9-7288-45ba-ba80-f78b05b9ceb0.png)]

在上图中,如果k = 3,则绿色圆圈(未标记的测试样本)将归类为三角形,因为在内圈内有两个三角形,并且只有一个正方形 。 如果k = 5,则绿色圆圈将归类为正方形,因为虚线圆内只有三个正方形,而只有两个三角形。

在 OpenCV 中,使用此分类器的第一步是创建分类器。 cv2.ml.KNearest_create()方法创建一个空的 kNN 分类器,应使用train()方法对其进行训练,以同时提供数据和标签。 最后,findNearest()方法用于查找邻居。 此方法的签名如下:

代码语言:javascript复制
retval, results, neighborResponses, dist=cv2.ml_KNearest.findNearest(samples, k[, results[, neighborResponses[, dist]]])

这里,samples是按行存储的输入样本,k设置最近邻的数量(应大于 1),results存储每个输入样本的预测,neighborResponses存储相应的邻居,并且 dist存储从输入样本到相应邻居的距离。

在本节中,我们将看到两个示例,以了解如何在 OpenCV 中使用 kNN 算法。 在第一个示例中,有望实现对 kNN 的直观理解,而在第二个示例中,kNN 将应用于手写数字识别问题。

了解 K 最近邻

knn_introduction.py脚本对 kNN 进行了简单介绍,其中随机创建了一组点并分配了一个标签(01)。 标签0将代表红色三角形,而标签1将代表蓝色正方形。 我们将使用 kNN 算法基于k最近邻对样本点进行分类。

因此,第一步是创建带有相应标签的点集和用于分类的样本点:

代码语言:javascript复制
# The data is composed of 16 points:
data = np.random.randint(0, 100, (16, 2)).astype(np.float32)

# We create the labels (0: red, 1: blue) for each of the 16 points:
labels = np.random.randint(0, 2, (16, 1)).astype(np.float32)

# Create the sample point to be classified:
sample = np.random.randint(0, 100, (1, 2)).astype(np.float32)

下一步是创建 kNN 分类器,训练分类器,并找到k最近的邻居:

代码语言:javascript复制
# KNN creation:
knn = cv2.ml.KNearest_create()
# KNN training:
knn.train(data, cv2.ml.ROW_SAMPLE, labels)
# KNN find nearest:
k = 3
ret, results, neighbours, dist = knn.findNearest(sample, k)

# Print results:
print("result: {}".format(results))
print("neighbours: {}".format(neighbours))
print("distance: {}".format(dist))

在这种情况下,并且与以下屏幕截图相对应,获得的结果如下:

代码语言:javascript复制
result: [[0.]]
neighbours: [[0. 0. 0.]]
distance: [[ 80. 100. 196.]]

因此,绿点被分类为红色三角形。 在下图中可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RBVnY8gC-1681870549414)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/03177577-b410-4c8d-932e-06629393d361.png)]

前面的屏幕快照使您对 kNN 有了直观的了解。 在下一个示例中,我们将把 kNN 应用于手写数字识别问题。

使用 K 最近邻识别手写数字

我们将看到如何使用 kNN 分类器执行手写数字识别。 我们将从获得可接受的准确率的基本脚本开始,我们将对其进行修改以提高其表现。

在这些脚本中,训练数据由手写数字组成。 OpenCV 提供了很多图像,里面没有手写数字,而不是包含很多图像。 该图像的尺寸为2,000 x 1,000像素。 每个数字为20 x 20像素。 因此,我们总共有 5,000 位数(100 x 50):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIoZQno3-1681870549414)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/d475a4ef-189f-4c00-ba54-7bf7fdfb0e08.png)]

knn_handwritten_digits_recognition_introduction.py脚本中,我们将执行第一种方法,尝试使用 kNN 分类器识别数字。 在第一种方法中,我们将使用原始像素值作为特征。 这样,每个描述符的大小将为 400(20 x 20)。

第一步是从大图像中加载所有数字,并为每个数字分配相应的标签。 这是通过load_digits_and_labels()函数执行的:

代码语言:javascript复制
digits, labels = load_digits_and_labels('digits.png')

load_digits_and_labels()函数的代码如下:

代码语言:javascript复制
def load_digits_and_labels(big_image):
    """Returns all the digits from the 'big' image and creates the corresponding labels for each image"""

    # Load the 'big' image containing all the digits:
    digits_img = cv2.imread(big_image, 0)

    # Get all the digit images from the 'big' image:
    number_rows = digits_img.shape[1] / SIZE_IMAGE
    rows = np.vsplit(digits_img, digits_img.shape[0] / SIZE_IMAGE)

    digits = []
    for row in rows:
        row_cells = np.hsplit(row, number_rows)
        for digit in row_cells:
            digits.append(digit)
    digits = np.array(digits)

    # Create the labels for each image:
    labels = np.repeat(np.arange(NUMBER_CLASSES), len(digits) / NUMBER_CLASSES)
    return digits, labels

在上一个函数中,我们首先加载“大”图像,然后,获取其中的所有数字。 上一个函数的最后一步是为每个数字创建标签。

在脚本中执行的下一步是为每个图像计算描述符。 在这种情况下,原始像素是特征描述符:

代码语言:javascript复制
# Compute the descriptors for all the images.
# In this case, the raw pixels are the feature descriptors
raw_descriptors = []
for img in digits:
    raw_descriptors.append(np.float32(raw_pixels(img)))
raw_descriptors = np.squeeze(raw_descriptors)

此时,我们将数据分为训练和测试(各占 50%)。 因此,将使用 2500 位数字来训练分类器,并使用 2500 位数字来测试经过训练的分类器:

代码语言:javascript复制
partition = int(0.5 * len(raw_descriptors))
raw_descriptors_train, raw_descriptors_test = np.split(raw_descriptors, [partition])
labels_train, labels_test = np.split(labels, [partition])

现在,我们可以使用knn.train()方法训练 kNN 模型,并使用get_accuracy()函数对其进行测试:

代码语言:javascript复制
# Train the KNN model:
print('Training KNN model - raw pixels as features')
knn = cv2.ml.KNearest_create()
knn.train(raw_descriptors_train, cv2.ml.ROW_SAMPLE, labels_train)

# Test the created model:
k = 5
ret, result, neighbours, dist = knn.findNearest(raw_descriptors_test, k)

# Compute the accuracy:
acc = get_accuracy(result, labels_test)
print("Accuracy: {}".format(acc))

如我们所见,k = 5。 我们获得92.60的精度,但我认为它可以提高。

我们要做的第一件事就是尝试使用k的不同值,这是 kNN 分类器中的关键参数。 此修改在knn_handwritten_digits_recognition_k.py脚本中执行。

在此脚本中,我们将创建一个字典来存储在测试k的不同值时的准确率:

代码语言:javascript复制
results = defaultdict(list)

请注意,我们已经从collections导入了defaultdict

代码语言:javascript复制
from collections import defaultdict

下一步是计算knn.findNearest()方法,改变k参数(在这种情况下,在(1-9)的范围内)并将结果存储在字典中:

代码语言:javascript复制
for k in np.arange(1, 10):
    ret, result, neighbours, dist = knn.findNearest(raw_descriptors_test, k)
    acc = get_accuracy(result, labels_test)
    print(" {}".format("%.2f" % acc))
    results['50'].append(acc)

最后一步是绘制结果:

代码语言:javascript复制
# Show all results using matplotlib capabilities:
fig, ax = plt.subplots(1, 1)
ax.set_xlim(0, 10)
dim = np.arange(1, 10)

for key in results:
    ax.plot(dim, results[key], linestyle='--', marker='o', label="50%")

plt.legend(loc='upper left', title="% training")
plt.title('Accuracy of the KNN model varying k')
plt.xlabel("number of k")
plt.ylabel("accuracy")
plt.show()

为了显示结果,我们让您使用 matplotlib 功能来绘制图形。 下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PI9ZgFaP-1681870549414)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/482f1b69-a04d-46c4-a907-a823c9c60ce1.png)]

如您在上一个屏幕截图中所见,通过更改k参数获得的精度为-k=1-93.72k=2-91.96k=3-93.00k=4-92.64k=5-92.60k=6-92.40k=7-92.28k=8-92.44k=9-91.96

如前所述,获得的精度存在一些差异。 因此,不要忘记在应用中适当调整k参数。

在这些示例中,我们一直在训练和测试每个 2500 位数字的模型。

在机器学习中,使用更多数据训练分类器通常是一个好主意,因为分类器可以更好地学习特征的结构。 结合 kNN 分类器,增加训练数字的数量也将增加在特征空间中找到测试数据正确匹配的可能性。

knn_handwritten_digits_recognition_k_training_testing.py脚本中,我们修改了图像的百分比以训练和测试模型,如下所示:

代码语言:javascript复制
# Split data into training/testing:
split_values = np.arange(0.1, 1, 0.1)

for split_value in split_values:
    # Split the data into training and testing:
    partition = int(split_value * len(raw_descriptors))
    raw_descriptors_train, raw_descriptors_test = np.split(raw_descriptors, [partition])
    labels_train, labels_test = np.split(labels, [partition])

    # Train KNN model
    print('Training KNN model - raw pixels as features')
    knn.train(raw_descriptors_train, cv2.ml.ROW_SAMPLE, labels_train)

    # Store the accuracy when testing:
    for k in np.arange(1, 10):
        ret, result, neighbours, dist = knn.findNearest(raw_descriptors_test, k)
        acc = get_accuracy(result, labels_test)
        print(" {}".format("%.2f" % acc))
        results[int(split_value * 100)].append(acc)

可以看出,训练算法的数字百分比为 10%,20%,…,90%,测试算法的数字百分比为 90%,80%,…,10%。

最后,我们绘制结果:

代码语言:javascript复制
# Show all results using matplotlib capabilities:
# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(12, 5))
plt.suptitle("KNN handwritten digits recognition", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

ax = plt.subplot(1, 1, 1)
ax.set_xlim(0, 10)
dim = np.arange(1, 10)

for key in results:
    ax.plot(dim, results[key], linestyle='--', marker='o', label=str(key)   "%")

plt.legend(loc='upper left', title="% training")
plt.title('Accuracy of the KNN model varying both k and the percentage of images to train/test')
plt.xlabel("number of k")
plt.ylabel("accuracy")
plt.show()

下一个屏幕截图中可以看到knn_handwritten_digits_recognition_k_training_testing.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nLhCqHHZ-1681870549414)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/fb83a5cb-5178-4cec-a553-87175ff3c96a.png)]

随着训练图像数量的增加,准确率也会提高。 此外,当我们用 90% 的数字训练分类器时,我们将用剩余的 10% 的数字测试分类器,这等效于用500数字测试分类器,这是一个可观的数字。

到目前为止,我们一直在使用原始像素值作为特征来训练分类器。 在机器学习中,训练分类器之前的常见过程是对输入数据进行某种预处理,以在训练时帮助分类器。 在knn_handwritten_digits_recognition_k_training_testing_preprocessing.py脚本中,我们正在进行预处理,以减少输入数字的可变性。

此预处理在deskew()函数中执行:

代码语言:javascript复制
def deskew(img):
    """Pre-processing of the images"""

    m = cv2.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11'] / m['mu02']
    M = np.float32([[1, skew, -0.5 * SIZE_IMAGE * skew], [0, 1, 0]])
    img = cv2.warpAffine(img, M, (SIZE_IMAGE, SIZE_IMAGE), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)
    return img

deskew()函数通过使用二阶矩来使数字偏斜。 更具体地,可以通过两个中心矩之比(mu11/mu02)来计算偏斜的量度。 计算出的偏斜度用于计算仿射变换,从而使数字偏斜。 请参阅下一个屏幕截图,以欣赏此预处理的效果。 屏幕截图的顶部显示了原始数字(蓝色边框),屏幕截图的底部显示了预处理的数字(绿色边框):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dmqK1aTo-1681870549415)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7a3f8912-873a-448e-a9f0-37b27041a3e3.png)]

通过应用此预处理,可以提高识别率,如下面的屏幕快照所示,图中绘制了识别率:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpsjbF0s-1681870549415)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/05f50edc-520f-4765-bc14-7524e5366142.png)]

如果将在输入数据中执行预处理的此脚本与不执行任何预处理的前一个脚本进行比较,则可以看到整体准确率有所提高。

在所有这些脚本中,我们一直使用原始像素值作为特征描述符。 在机器学习中,一种常见的方法是使用更高级的描述符。 定向梯度直方图HOG)是一种流行的图像描述符。

特征描述符是图像的表示,通过提取描述基本特征(例如形状,颜色,纹理或运动)的有用信息来简化图像。 通常,特征描述符将图像转换为长度为n的特征向量/数组。

HOG 是一种用于计算机视觉的流行特征描述符,最早用于人类在静态图像中的检测。 在knn_handwritten_digits_recognition_k_training_testing_preprocessing_hog.py脚本中,我们将使用 HOG 特征代替原始像素值。

我们定义了get_hog()函数,该函数获取 HOG 描述符:

代码语言:javascript复制
def get_hog():
    """Get hog descriptor"""

    # cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins, derivAperture, winSigma, histogramNormType,
    # L2HysThreshold, gammaCorrection, nlevels, signedGradient)
    hog = cv2.HOGDescriptor((SIZE_IMAGE, SIZE_IMAGE), (8, 8), (4, 4), (8, 8), 9, 1, -1, 0, 0.2, 1, 64, True)
    print("hog descriptor size: '{}'".format(hog.getDescriptorSize()))
    return hog

在这种情况下,每个图像的特征描述符都是144大小。 为了计算每个图像的 HOG 描述符,我们必须执行以下操作:

代码语言:javascript复制
# Compute the descriptors for all the images.
# In this case, the HoG descriptor is calculated
hog_descriptors = []
for img in digits:
    hog_descriptors.append(hog.compute(deskew(img)))
hog_descriptors = np.squeeze(hog_descriptors)

如您所见,我们将hog.compute()应用于每个不倾斜的数字。

结果可以在下一个屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mL4Z9Xss-1681870549415)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b48411f8-9f1c-455f-b079-3eed6d08056d.png)]

k=2和 90% 的数字用于训练,而 10% 的数字用于测试时,则达到 98.60% 的精度。 因此,我们将识别率从 92.60%(在本小节的第一个脚本中获得)提高到 98.60%(在上一个脚本中获得)。

在编写机器学习模型和应用时,一种好的方法是从一个基本近似开始,尝试尽快解决该问题。 然后,如果获得的精度不够好,可以通过添加更好的预处理,更高级的特征描述符或其他机器学习技术来迭代地改进模型。 最后,如果必要,不要忘记收集更多数据来训练和测试模型。

支持向量机

支持向量机SVM)是一种监督式学习技术,通过根据分配的类最佳地分离训练示例,在高维空间中构造一个超平面或一组超平面 。

可以在下一张图中看到,其中绿线表示最能将两个类别分开的超平面的表示,因为到两个类别中每个类别的最近元素的距离最大:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s81GhKFy-1681870549415)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/35a06376-0101-4de5-ac3f-a0da003cd2d2.png)]

在第一种情况下,决策边界是一条线,而在第二种情况下,决策边界是一条圆周。 虚线和虚线虚线表示其他决策边界,但是它们不能最好地将这两个类别分开。

OpenCV 中的 SVM 实现基于《LIBSVM:支持向量机库》(2011)。 要创建空模型,请使用cv2.ml.SVM_create()函数。 接下来,应将主要参数分配给模型:

  • svmType:这设置 SVM 的类型。 有关详细信息,请参见 LibSVM。 可能的值如下:
    • SVM_C_SVC:可用于n类分类的 C-支持向量分类(n ≥ 2
    • NU_SVC:ν-支持向量分类
    • ONE_CLASS:分布估计(一类 SVM)
    • EPS_SVR:ε-支持向量回归
    • NU_SVR:ν-支持向量回归
  • kernelType:设置 SVM 的核类型。 有关详细信息,请参见 LibSVM。 可能的值如下:
    • LINEAR:线性核
    • POLY:多项式核
    • RBF径向基函数RBF),在大多数情况下是个不错的选择
    • SIGMOID:Sigmoid 核
    • CHI2:指数 Chi2 核,类似于RBF
    • INTER:直方图交点核; 快速核

核函数选择可能很棘手,并且取决于数据集。 从这个意义上讲,RBF核通常被认为是一个不错的首选,因为该核将样本非线性地映射到一个高维空间,以处理类标签和属性之间的关系为非线性的情况。 有关更多详细信息,请参见《支持向量分类的实用指南》(2003)。

  • degree:核函数的参数度(POLY
  • gamma:核函数的γ参数(POLY/RBF/SIGMOID/CHI2
  • coef0:核函数的coef0参数(POLY/SIGMOID
  • Cvalue:SVM 优化问题的C参数(C_SVC/EPS_SVR/NU_SVR
  • nu:SVM 优化问题的ν参数(NU_SVC/ONE_CLASS/NU_SVR
  • p:SVM 优化问题(EPS_SVR)的ε参数
  • classWeightsC_SVC问题中的可选权重,分配给特定类别
  • termCrit:SVM 迭代训练过程的终止标准

默认构造器使用以下值初始化结构:

代码语言:javascript复制
svmType: C_SVC, kernelType: RBF, degree: 0, gamma: 1, coef0: 0, C: 1, nu: 0, p: 0, classWeights: 0, termCrit: TermCriteria(MAX_ITER EPS, 1000, FLT_EPSILON )

在本节中,我们将看到两个如何在 OpenCV 中使用 SVM 的示例。 在第一个示例中,将给出对 SVM 的直观理解,在第二个示例中,SVM 将应用于手写数字识别问题。

了解 SVM

svm_introduction.py脚本执行一个简单的示例,以了解如何在 OpenCV 中使用 SVM。 首先,我们创建训练数据和标签:

代码语言:javascript复制
# Set up training data:
labels = np.array([1, 1, -1, -1, -1])
data = np.matrix([[500, 10], [550, 100], [300, 10], [500, 300], [10, 600]], dtype=np.float32)

如您所见,创建了五个点。 前两个点被分配为1类,而其他三个点被分配为-1类。 下一步是使用svm_init()函数初始化 SVM 模型:

代码语言:javascript复制
# Initialize the SVM model:
svm_model = svm_init(C=12.5, gamma=0.50625)

svm_init()函数创建一个空模型并分配主要参数并返回模型:

代码语言:javascript复制
def svm_init(C=12.5, gamma=0.50625):
    """Creates empty model and assigns main parameters"""

    model = cv2.ml.SVM_create()
    model.setGamma(gamma)
    model.setC(C)
    model.setKernel(cv2.ml.SVM_LINEAR)
    model.setType(cv2.ml.SVM_C_SVC)
    model.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6))

    return model

在这种情况下,SVM 核类型设置为LINEAR(不执行任何映射),而 SVM 的类型设置为C_SVC(可用于n类分类,其中n ≥ 2 )。

然后,我们使用svm_train()函数训练 SVM:

代码语言:javascript复制
# Train the SVM:
svm_train(svm_model, data, labels)

在这里,svm_train()函数使用样本和响应来训练模型,然后返回训练后的模型:

代码语言:javascript复制
def svm_train(model, samples, responses):
    """Trains the model using the samples and the responses"""

    model.train(samples, cv2.ml.ROW_SAMPLE, responses)
    return model

下一步是创建将在其中绘制 SVM 响应的图像:

代码语言:javascript复制
# Create the canvas (black image with three channels)
# This image will be used to show the prediction for every pixel:
img_output = np.zeros((640, 640, 3), dtype="uint8")

最后,我们使用show_svm_response()函数显示 SVM 响应:

代码语言:javascript复制
# Show the SVM response:
show_svm_response(svm_model, img_output)

因此,img_ouput图像显示了 SVM 响应。 show_svm_response()函数的代码如下:

代码语言:javascript复制
def show_svm_response(model, image):
    """Show the prediction for every pixel of the image, the training data and the support vectors"""

    colors = {1: (255, 255, 0), -1: (0, 255, 255)}

    # Show the prediction for every pixel of the image:
    for i in range(image.shape[0]):
        for j in range(image.shape[1]):
            sample = np.matrix([[j, i]], dtype=np.float32)
            response = svm_predict(model, sample)

            image[i, j] = colors[response.item(0)]

    # Show the training data:
    # Show samples with class 1:
    cv2.circle(image, (500, 10), 10, (255, 0, 0), -1)
    cv2.circle(image, (550, 100), 10, (255, 0, 0), -1)
    # Show samples with class -1:
    cv2.circle(image, (300, 10), 10, (0, 255, 0), -1)
    cv2.circle(image, (500, 300), 10, (0, 255, 0), -1)
    cv2.circle(image, (10, 600), 10, (0, 255, 0), -1)

    # Show the support vectors:
    support_vectors = model.getUncompressedSupportVectors()
    for i in range(support_vectors.shape[0]):
        cv2.circle(image, (support_vectors[i, 0], support_vectors[i, 1]), 15, (0, 0, 255), 6)

可以看出,该函数显示以下内容:

  • 图像每个像素的预测
  • 所有五个训练数据点
  • 支持向量(定义超平面的向量称为支持向量

下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZ1d8Fs3-1681870549416)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3f1290c5-8aa8-4a69-9a6a-397720ad7246.png)]

如您所见,已使用训练数据和由五个点组成的标签(对两个点分配了类别1,而对其他三个点分配了类别-1)对 SVM 进行了训练,之后将其用于分类图片中的所有像素。 这种分类导致将图像划分为黄色和青色区域。 此外,您可以看到两个区域之间的边界对应于两个类别之间的最佳间隔,因为到两个类别中每个类别的最近元素的距离最大。 支持向量以红线边框显示。

使用 SVM 的手写数字识别

我们刚刚看到了如何使用 kNN 分类器执行手写数字识别。 通过对数字进行预处理(调用deskew()函数)并计算 HOG 描述符作为用于描述每个数字的特征向量,可以获得最佳的精度。 因此,为简单起见,接下来将要使用 SVM 对数字进行分类的脚本将使用上述近似值(预处理和 HOG 特征)。

svm_handwritten_digits_recognition_preprocessing_hog.py脚本使用 SVM 分类执行手写数字识别。 关键代码如下所示:

代码语言:javascript复制
# Load all the digits and the corresponding labels:
digits, labels = load_digits_and_labels('digits.png')

# Shuffle data
# Constructs a random number generator:
rand = np.random.RandomState(1234)
# Randomly permute the sequence:
shuffle = rand.permutation(len(digits))
digits, labels = digits[shuffle], labels[shuffle]

# HoG feature descriptor:
hog = get_hog()

# Compute the descriptors for all the images.
# In this case, the HoG descriptor is calculated
hog_descriptors = []
for img in digits:
    hog_descriptors.append(hog.compute(deskew(img)))
hog_descriptors = np.squeeze(hog_descriptors)

# At this point we split the data into training and testing (50% for each one):
partition = int(0.5 * len(hog_descriptors))
hog_descriptors_train, hog_descriptors_test = np.split(hog_descriptors, [partition])
labels_train, labels_test = np.split(labels, [partition])

print('Training SVM model ...')
model = svm_init(C=12.5, gamma=0.50625)
svm_train(model, hog_descriptors_train, labels_train)

print('Evaluating model ... ')
svm_evaluate(model, hog_descriptors_test, labels_test)

在这种情况下,我们使用了RBF核:

代码语言:javascript复制
def svm_init(C=12.5, gamma=0.50625):
    """Creates empty model and assigns main parameters"""

    model = cv2.ml.SVM_create()
    model.setGamma(gamma)
    model.setC(C)
    model.setKernel(cv2.ml.SVM_RBF)
    model.setType(cv2.ml.SVM_C_SVC)
    model.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6))

    return model

使用仅 50% 的数字来训练算法,所获得的精度为 98.60%。

另外,使用RBF核时,有两个重要参数-Cγ。 在这种情况下,为C=12.5γ=0.50625。 和以前一样,对于给定的问题(取决于数据集),Cγ并不为人所知。 因此,必须进行某种参数搜索。 因此,目标是确定推荐使用Cγ网格搜索的良好(Cγ)。

svm_handwritten_digits_recognition_preprocessing_hog.py脚本相比,在svm_handwritten_digits_recognition_preprocessing_hog_c_gamma.py脚本中进行了两次修改。 第一个是使用 90% 的数字训练模型,其余 10% 用于测试。 第二个修改是对Cγ进行网格搜索:

代码语言:javascript复制
# Create a dictionary to store the accuracy when testing:
results = defaultdict(list)

for C in [1, 10, 100, 1000]:
    for gamma in [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5]:
        model = svm_init(C, gamma)
        svm_train(model, hog_descriptors_train, labels_train)
        acc = svm_evaluate(model, hog_descriptors_test, labels_test)
        print(" {}".format("%.2f" % acc))
        results[C].append(acc)

最后,这是结果:

代码语言:javascript复制
# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(10, 6))
plt.suptitle("SVM handwritten digits recognition", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Show all results using matplotlib capabilities:
ax = plt.subplot(1, 1, 1)
ax.set_xlim(0, 1.5)
dim = [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5]

for key in results:
    ax.plot(dim, results[key], linestyle='--', marker='o', label=str(key))

plt.legend(loc='upper left', title="C")
plt.title('Accuracy of the SVM model varying both C and gamma')
plt.xlabel("gamma")
plt.ylabel("accuracy")
plt.show()

下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRNQRCL9-1681870549416)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b3c63e99-e551-4745-91b5-055d60704b2b.png)]

如图所示,在某些情况下,可获得 99.20% 的精度。

通过比较 kNN 分类器和 SVM 进行手写数字识别,我们可以得出结论,SVM 优于 kNN 分类器。

总结

在本章中,我们涵盖了机器学习的完整介绍。

在第一部分中,我们将机器学习的概念以及它与其他热门话题(如人工智能,神经网络和深度学习)的关联性进行了背景研究。 此外,我们总结了机器学习的三种主要方法,并讨论了解决分类,回归和聚类问题的三种最常用的技术。然后,我们应用了最常用的机器学习技术来解决了一些现实世界中的问题。 更具体地说,我们研究了 K 均值聚类算法,K 最近邻分类器和 SVM。

在下一章中,我们将探讨如何使用与人脸检测,跟踪和识别相关的最新算法来创建人脸处理项目。

问题

  1. 机器学习中的三种主要方法是什么?
  2. 分类和回归问题有什么区别?
  3. OpenCV 提供什么函数来实现 K 均值聚类算法?
  4. OpenCV 提供什么函数来创建 kNN 分类器?
  5. OpenCV 提供什么函数来找到最近的邻居?
  6. OpenCV 提供什么函数来创建 SVM 分类器?
  7. SVM 核的合理首选是什么?

进一步阅读

如果您想深入研究机器学习,请查看以下资源:

  • 《用于 OpenCV 的机器学习》(2017),作者:Michael Beyeler

十一、人脸检测,跟踪和识别

人脸处理是人工智能领域的热门话题,因为可以使用计算机视觉算法从面部自动提取很多信息。 面部在视觉交流中起着重要作用,因为可以从人脸中提取大量非语言信息,例如身份,意图和情感。 对于计算机视觉学习者来说,面部处理是一个非常有趣的主题,因为它涉及到不同的专业领域,例如对象检测,图像处理,标志检测或对象跟踪。

在本章中,将向您介绍与使用最新算法和技术进行面部处理有关的主要主题,以达到令人印象深刻的效果。

我们将涵盖以下主题:

  • 人脸处理简介
  • 人脸检测
  • 检测人脸标志
  • 人脸追踪
  • 人脸识别

在本章中,您将学习如何使用与人脸检测,跟踪和识别相关的最新算法来创建人脸处理项目。 在第 12 章,“深度学习简介”中,将向您介绍使用 OpenCV 进行深度学习的领域以及一些深度学习 Python 库(TensorFlow 和 Keras)。

技术要求

技术要求如下:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib 包
  • Git 客户端
  • Dlib 包
  • face_processing

有关如何安装这些要求的更多详细信息,请参见第 1 章,“设置 OpenCV”。 《精通 Python OpenCV 4》的 GitHub 存储库,其中包含贯穿本书的所有必要的支持项目文件,从第一章到最后一章,都可以在这里访问。

安装 Dlib

Dlib 是一个 C 软件库,包含计算机视觉,机器学习和深度学习算法。 Dlib 也可以在您的 Python 应用中使用。 为了使用 PIP 安装dlib,请使用以下命令:

代码语言:javascript复制
$ pip install dlib

另外,如果您想自己编译dlib,请进入dlib根文件夹并运行以下命令:

代码语言:javascript复制
$ python setup.py install

该命令运行完毕后,就可以使用 Python 的dlib

请注意,您需要同时安装 CMake 和 C 编译器才能正常工作。 还要注意,各种可选功能(例如 GUI 支持(例如dlib.image_window)和 CUDA 加速)将根据计算机上的可用状态启用或禁用。

安装dlib的第三个选项是访问这里并安装所需的dlib车轮包装。 就我而言,我已经下载了dlib-19.8.1-cp36-cp36m-win_amd64.whl文件并使用以下命令进行了安装:

代码语言:javascript复制
$ pip install dlib-19.8.1-cp36-cp36m-win_amd64.whl

车轮文件名是{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl。 例如,distribution-1.0-1-py27-none-any.whl是名为distribution的包的第一个版本,它在任何 CPU 架构上都与 Python 2.7(任何 Python 2.7 实现)兼容,而没有 ABI(纯 Python)。 请参阅这里了解有关 Wheel 二进制包格式的更多详细信息。

要确认安装是否正确执行,只需打开 Python shell 并尝试导入dlib库:

代码语言:javascript复制
python
import dlib

请记住,建议的方法是在虚拟环境中安装包。 有关如何创建和管理虚拟环境的信息,请参见第 1 章,“设置 OpenCV”。

例如,在这种情况下,我们将使用 Anaconda 提示符在虚拟环境中安装dlib

  1. 创建一个虚拟环境:
代码语言:javascript复制
(base) $ conda create -n dlib-env python=3.6
  1. 激活环境:
代码语言:javascript复制
(base) $ activate dlib-env

查看(dlib-env)在此命令后的提示前的显示方式。 这表明虚拟环境已被激活。

  1. 使用以下命令安装dlib
代码语言:javascript复制
(dlib-env) $ pip install dlib

安装face_recognition

为了安装face_recognition包 ,请执行以下命令:

代码语言:javascript复制
$ pip install face_recognition

要检查安装是否正确执行,只需打开 Python shell 并尝试导入face_recognition库:

代码语言:javascript复制
python
import face_recognition

安装cvlib

要安装cvlib包,请先安装所需的包(numpyopencv-pythonrequestsprogressbarpillowtensorflowkeras),使用以下命令:

代码语言:javascript复制
$ pip install -r requirements.txt

然后,安装cvlib包:

代码语言:javascript复制
$ pip install cvlib

要升级到最新版本,请输入以下命令:

代码语言:javascript复制
pip install --upgrade cvlib

请注意,如果您使用的是 GPU,则可以编辑requirements.txt文件以包括tensorflow-gpu而不是tensorflow

要检查安装是否正确执行,只需打开 Python shell 并尝试导入face_recognition库:

代码语言:javascript复制
python
import cvlib

人脸处理简介

在本章中,我们将介绍与面部处理有关的主要主题。 为此,我们将使用 OpenCV 库,也将使用dlib主页和 PyPI dlib主页,Github dlib主页,PyPI face_recognition主页,Github face_recognition主页和PyPI cvlib主页,Github cvlib主页,cvlib主页 Python 包。 在上一节中,您了解了如何安装这些包。

为了介绍本章,我们将在所有部分中使用不同的方法来了解您解决具体的面部处理任务时可能会遇到的不同可能性,并且对所有这些都有一个较高的概述可能会有所帮助。 备择方案。

该图试图捕获前面提到的主题的概念:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpfn42Lw-1681870549417)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/62935876-9c7c-41c1-87ce-0e7722756f0b.png)]

如您所见,这里要解决四个要点:

  • 人脸检测是对象检测的一种特殊情况,其任务是查找图像中所有人脸的位置和大小。
  • 人脸标志检测是标志检测的一种特殊情况,其任务是在面部定位主要标志。
  • 人脸跟踪是对象跟踪的一种特殊情况,其中的任务是通过考虑可在连续帧的帧中提取的额外信息来查找视频中所有移动脸的位置和大小。 视频。
  • 人脸识别是对象识别的一种特殊情况,其中使用从人脸提取的信息从图像或视频中识别或验证人:
    • 人脸识别1:N):任务是在已知人脸的集合中查找与未知人物最接近的匹配项。
    • 面部验证1:1):该任务是检查该人是否是他们所声称的那个人。

如上图所示,本章将使用 OpenCVdlibface_recognitioncvlib

人脸检测

人脸检测可以定义为确定数字图像中人脸的位置和大小的任务,通常是构建人脸处理应用(例如,人脸表情识别,嗜睡检测,性别分类,人脸识别, 头姿势估计或人机交互)。 这是因为上述应用需要将所检测到的面部的位置和大小作为输入。 因此,自动面部检测起着至关重要的作用,并且是人工智能界研究最多的主题之一。

面部检测对于人类来说似乎是一项轻松的任务,但对计算机而言却是一项非常具有挑战性的任务,因为通常涉及许多问题/挑战(例如,外观变化,比例,旋转,面部表情,遮挡或光照条件)。 在 Viola 和 Jones 提出的工作之后,人脸检测取得了令人瞩目的进展。 在本节中,我们将看到 OpenCV 库以及dlibface_processing包提供的一些最流行的面部检测技术,包括上述的 Viola 和 Jones 算法以及其他机器学习和深度学习方法。

使用 OpenCV 的人脸检测

OpenCV 提供了两种面部检测方法:

  • 基于 Haar 级联的面部检测器
  • 基于深度学习的面部检测器

Viola 和 Jones 提出的框架(请参见《使用简单特征的增强级联进行快速对象检测》(2001))是一种有效的对象检测方法。 该框架非常受欢迎,因为 OpenCV 提供了基于该框架的面部检测算法。 另外,该框架还可以用于检测其他物体而不是面部(例如,全身检测器,车牌号检测器,上身检测器或猫脸检测器)。 在本节中,我们将看到如何使用此框架检测人脸。

face_detection_opencv_haar.py脚本使用基于 Haar 特征的级联分类器执行面部检测。 从这个意义上说,OpenCV 提供了四个用于(正面)人脸检测的级联分类器:

  • haarcascade_frontalface_alt.xmlFA1):22 个阶段,20 x 20 Haar 特征
  • haarcascade_frontalface_alt2.xmlFA2):20 个阶段,20 x 20 Haar 特征
  • haarcascade_frontalface_alt_tree.xmlFAT):47 个阶段,20 x 20 Haar 特征
  • haarcascade_frontalface_default.xmlFD):25 个阶段,24 x 24 Haar 特征

在一些可用的出版物中,作者使用不同的标准和数据集评估了这些级联分类器的表现。 总体而言,可以得出结论,这些分类器达到了相似的准确率。 这就是为什么在此脚本中,我们将使用其中两个(以简化内容)。 更具体地说,在此脚本中,加载了两个层叠分类器(先前引入的FA2FD):

代码语言:javascript复制
# Load cascade classifiers:
cas_alt2 = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
cas_default = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

cv2.CascadeClassifier()函数用于从文件加载分类器。 您可以从 OpenCV 信息库下载以下级联分类器文件。 此外,我们在 GitHub 存储库中包含了两个已加载的层叠分类器文件(haarcascade_frontalface_alt2.xmlhaarcascade_frontalface_default.xml)。

下一步是执行检测:

代码语言:javascript复制
faces_alt2 = cas_alt2.detectMultiScale(gray)
faces_default = cas_default.detectMultiScale(gray)

cv2.CascadeClassifier.detectMultiScale()函数检测对象并将其作为矩形列表返回。 最后一步是使用show_detection()函数关联结果:

代码语言:javascript复制
img_faces_alt2 = show_detection(img.copy(), faces_alt2)
img_faces_default = show_detection(img.copy(), faces_default)

show_detection()函数在每个检测到的面部上绘制一个矩形:

代码语言:javascript复制
def show_detection(image, faces):
    """Draws a rectangle over each detected face"""

    for (x, y, w, h) in faces:
        cv2.rectangle(image, (x, y), (x   w, y   h), (255, 0, 0), 5)
    return image

OpenCV 还提供cv2.face.getFacesHAAR()函数来检测面部:

代码语言:javascript复制
retval, faces_haar_alt2 = cv2.face.getFacesHAAR(img, "haarcascade_frontalface_alt2.xml")
retval, faces_haar_default = cv2.face.getFacesHAAR(img, "haarcascade_frontalface_default.xml")

应当注意,cv2.CascadeClassifier.detectMultiScale()需要灰度图像,而cv2.face.getFacesHAAR()需要 BGR 图像作为输入。 此外,cv2.CascadeClassifier.detectMultiScale()将检测到的脸部输出为矩形列表。 例如,两个检测到的面部的输出将如下所示:

代码语言:javascript复制
[[332 93 364 364] [695 104 256 256]]

cv2.face.getFacesHAAR()函数以相似的格式返回人脸:

代码语言:javascript复制
[[[298 524 61 61]] [[88 72 315 315]]

要摆脱无用的一维数组,请调用np.squeeze()

代码语言:javascript复制
faces_haar_alt2 = np.squeeze(faces_haar_alt2)
faces_haar_default = np.squeeze(faces_haar_default)

用于检测和绘制已加载图像中的面部的完整代码如下:

代码语言:javascript复制
# Load image and convert to grayscale:
img = cv2.imread("test_face_detection.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Load cascade classifiers:
cas_alt2 = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
cas_default = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

# Detect faces:
faces_alt2 = cas_alt2.detectMultiScale(gray)
faces_default = cas_default.detectMultiScale(gray)
retval, faces_haar_alt2 = cv2.face.getFacesHAAR(img, "haarcascade_frontalface_alt2.xml")
faces_haar_alt2 = np.squeeze(faces_haar_alt2)
retval, faces_haar_default = cv2.face.getFacesHAAR(img, "haarcascade_frontalface_default.xml")
faces_haar_default = np.squeeze(faces_haar_default)

# Draw face detections:
img_faces_alt2 = show_detection(img.copy(), faces_alt2)
img_faces_default = show_detection(img.copy(), faces_default)
img_faces_haar_alt2 = show_detection(img.copy(), faces_haar_alt2)
img_faces_haar_default = show_detection(img.copy(), faces_haar_default)

最后一步是使用 OpenCV 或 Matplotlib 在这种情况下显示四个创建的图像。 完整的代码可以在face_detection_opencv_haar.py脚本中看到。 在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsdnl7Ii-1681870549417)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/586f6104-36ae-41b3-9792-bf2966f1cb0c.png)]

如您所见,通过使用基于 Haar 特征的叶栅分类器,使用上述四个近似值,检测到的面部会发生变化。 最后,还应该指出,cv2.CascadeClassifier.detectMultiScale()函数具有minSizemaxSize参数,以便确定最小大小(不会检测到小于minSize的对象)和最大大小(不会检测到大于maxSize的对象)。 相反,cv2.face.getFacesHAAR()函数不提供这种可能性。

基于 Haar 特征的级联分类器可用于检测人脸以外的对象。 OpenCV 库还提供了两个用于猫脸检测的级联文件。

为了完整起见,cat_face_detection_opencv_haar.py脚本加载了两个层叠文件,这些文件经过训练可以检测图像中的正面猫脸。 该脚本与face_detection_opencv_haar.py脚本非常相似。 确实,关键的修改是已加载的两个层叠文件。 在这种情况下,这是两个已加载的层叠文件:

  • haarcascade_frontalcatface.xml:一种正面猫脸检测器,使用具有 20 个阶段的基本 Haar 特征集和24 x 24 Haar 特征
  • haarcascade_frontalcatface_extended.xml:正面猫脸检测器,使用全套 20 个阶段的 Haar 特征和24 x 24 Haar 特征

有关这些级联文件的更多信息,请参阅 Joseph Howse 的《面向秘密特工的 OpenCV》。 您可以从 OpenCV 信息库下载以下级联分类器文件。 此外,我们已经在 GitHub 存储库中包含了这两个层叠分类器文件。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J3Zhep5x-1681870549417)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/77b84524-168c-4d04-9602-af00d2b13e18.png)]

此外,OpenCV 提供了基于深度学习的面部检测器。 更具体地说,OpenCV 深度神经网络DNN)面部检测器基于单发多盒检测器SSD)框架, ResNet-10 网络。

从 OpenCV 3.1 开始,提供了 DNN 模块,该模块使用流行的深度学习框架(例如 Caffe,TensorFlow,Torch 和 Darknet)使用经过预训练的深度网络来实现前向传递(推理)。 在 OpenCV 3.3 中,该模块已从opencv_contrib存储库升级到主存储库,并得到了显着加速。 这意味着我们可以使用经过预训练的网络来执行完整的前向传递,并利用输出在我们的应用中进行预测,而不必花费数小时来训练网络。 在第 12 章,“深度学习简介”中,我们将进一步探索 DNN 模块; 在本章中,我们将重点介绍深度学习人脸检测器。

在本节中,我们将使用库中包含的预训练深度学习人脸检测器模型执行人脸检测。

OpenCV 为此面部检测器提供了两种模型:

  • 人脸检测器FP16):原始 Caffe 实现的浮点 16 版本(5.1 MB)
  • 人脸检测器UINT8):使用 TensorFlow 的 8 位量化版本(2.6 MB)

在每种情况下,您都需要两套文件:模型文件和配置文件。 对于 Caffe 模型,这些文件如下:

  • res10_300x300_ssd_iter_140000_fp16.caffemodel:此文件包含实际层的权重。 可以从这里下载,它也包含在 GitHub 仓库中。
  • deploy.prototxt:此文件定义模型架构。 可以从这里下载,并包含在该书的 GitHub 存储库中。

如果您使用 TensorFlow 模型,则需要以下文件:

  • opencv_face_detector_uint8.pb:此文件包含实际层的权重。 可以从这里下载该文件,该文件包含在本书的 GitHub 存储库中。
  • opencv_face_detector.pbtxt:此文件定义模型架构。 可以从这里下载,并包含在该书的 GitHub 存储库中。

face_detection_opencv_dnn.py脚本向您展示如何通过使用面部检测和预训练的深度学习面部检测器模型来检测面部。 第一步是加载预训练的模型:

代码语言:javascript复制
# Load pre-trained model:
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel")
# net = cv2.dnn.readNetFromTensorflow("opencv_face_detector_uint8.pb", "opencv_face_detector.pbtxt")

如您所见,在此示例中,原始 Caffe 实现的浮点 16 版本已加载。 为了获得最佳精度,我们必须在大小分别为300 x 300的 BGR 图像上运行模型,方法是分别对蓝色,绿色和红色通道应用(104, 177, 123)值的均值减法。 此预处理是使用cv2.dnn.blobFromImage() OpenCV 函数执行的:

代码语言:javascript复制
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104., 117., 123.], False, False)

在第 12 章,“深度学习简介”中,我们将更深入地研究此函数。

下一步是将 BLOB 设置为输入以获取结果,并对整个网络执行前向传递以计算输出:

代码语言:javascript复制
# Set the blob as input and obtain the detections:
net.setInput(blob)
detections = net.forward()

最后一步是遍历所有检测并得出结果,仅当相应的置信度大于固定的最小阈值时才考虑检测:

代码语言:javascript复制
# Iterate over all detections:
for i in range(0, detections.shape[2]):
    # Get the confidence (probability) of the current detection:
    confidence = detections[0, 0, i, 2]

    # Only consider detections if confidence is greater than a fixed minimum confidence:
    if confidence > 0.7:
        # Increment the number of detected faces:
        detected_faces  = 1
        # Get the coordinates of the current detection:
        box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
        (startX, startY, endX, endY) = box.astype("int")

        # Draw the detection and the confidence:
        text = "{:.3f}%".format(confidence * 100)
        y = startY - 10 if startY - 10 > 10 else startY   10
        cv2.rectangle(image, (startX, startY), (endX, endY), (255, 0, 0), 3)
        cv2.putText(image, text, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)

下一个屏幕截图中可以看到face_detection_opencv_dnn.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tcbRVvlO-1681870549417)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/51f8839f-cff6-4788-a0df-5519c7ec6369.png)]

可以看出,以高置信度检测到这三个脸。

使用 Dlib 的人脸检测

您可以使用dlib.get_frontal_face_detector()创建正面检测器,该检测器基于定向梯度直方图HOG)特征和滑动窗口检测方法中的线性分类器。 特别是,HOG 训练器使用基于结构 SVM 的训练算法,该训练算法使训练器可以在每个训练图像中的所有子窗口中进行训练。 此人脸检测器已使用来自带标签的野外数据集中的 3,000 张图像进行了训练。 应当注意的是,该检测器也可以用于发现脸部以外的物体。 您可以查看dlib库中包含的train_object_detector.py脚本,以了解如何轻松训练自己的对象检测器仅使用一些训练图像。 例如,您可以仅使用八个停车标志图像来训练一个出色的停车标志检测器。

face_detection_dlib_hog.py脚本使用上述dlib正面面部检测器检测面部。 第一步是从dlib加载正面检测器:

代码语言:javascript复制
detector = dlib.get_frontal_face_detector()

下一步是执行检测:

代码语言:javascript复制
rects_1 = detector(gray, 0)
rects_2 = detector(gray, 1)

第二个参数表示在执行检测过程之前对图像进行了1上采样,因为图像较大,因此检测器可以检测更多的人脸。 相反,执行时间将增加。 因此,出于表现考虑,应将其考虑在内。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1zRdJoep-1681870549418)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/c74eb9da-cf23-4943-a4ce-64be12bacd5c.png)]

如您所见,如果我们使用原始灰度图像(rects_1 = detector(gray, 0))检测到面部,则只会发现两个面部。 但是,如果我们使用1时间(rects_2 = detector(gray, 1))上采样的灰度图像检测到人脸,则可以正确检测到这三个人脸。

Dlib 库还提供了 CNN 人脸检测器。 您可以使用dlib.cnn_face_detection_model_v1()创建 CNN 人脸检测器。 构造器从文件中加载人脸检测模型。 您可以从这里下载预训练的模型(712 KB)。 创建 CNN 人脸检测器时,应将相应的预训练模型传递给此方法:

代码语言:javascript复制
cnn_face_detector = dlib.cnn_face_detection_model_v1("mmod_human_face_detector.dat")

至此,我们准备使用此检测器识别人脸:

代码语言:javascript复制
rects = cnn_face_detector(img, 0)

该检测器发现了mmod_rectangles对象,该对象是mmod_rectangle对象的列表,并且mmod_rectangle对象具有两个成员变量-dlib.rectangle对象和confidence分数。 因此,为了显示检测结果,对show_detection()函数进行了编码:

代码语言:javascript复制
def show_detection(image, faces):
    """Draws a rectangle over each detected face"""

    # faces contains a list of mmod_rectangle objects
    # The mmod_rectangle object has two member variables, a dlib.rectangle object, and a confidence score
    # Therefore, we iterate over the detected mmod_rectangle objects accessing dlib.rect to draw the rectangle

    for face in faces:
        cv2.rectangle(image, (face.rect.left(), face.rect.top()), (face.rect.right(), face.rect.bottom()), (255, 0, 0), 10)
    return image

show_detection()函数应按以下方式调用:

代码语言:javascript复制
img_faces = show_detection(img.copy(), rects)

完整代码在face_detection_dlib_cnn.py脚本中。 下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MDLKvvQ-1681870549418)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/21ec9ed8-4f88-43da-9579-ceb93eba64e5.png)]

dlib CNN 面部检测器比dlib HOG 面部检测器更加精确,但是运行起来需要更多的计算能力。 例如,对于600 x 400图像,HOG 面部检测器大约需要0.25秒,而 CNN 面部检测器大约需要5秒。 实际上,CNN 面部检测器旨在在 GPU 上执行以获得合理的速度。

如果您具有 GPU,则可以启用 CUDA,这将加快执行速度。 为此,您需要从源代码编译dlib

使用face_recognition的人脸检测

为了使用face_recognition检测人脸,应调用face_locations()函数:

代码语言:javascript复制
rects_1 = face_recognition.face_locations(rgb, 0, "hog")
rects_2 = face_recognition.face_locations(rgb, 1, "hog")

第一个参数是输入(RGB)图像。 第二个参数设置在执行检测过程之前对输入图像进行上采样的次数。 第三个参数确定将使用哪种面部检测模型。

在这种情况下,将使用hog检测模型。 该示例的完整代码可以在face_detection_fr_hog.py脚本中看到。

另外,可以将face_processing配置为使用cnn面部检测器检测面部:

代码语言:javascript复制
rects_1 = face_recognition.face_locations(rgb, 0, "cnn")
rects_2 = face_recognition.face_locations(rgb, 1, "cnn")

您可以看到face_detection_fr_hog.pyface_detection_fr_cnn.py脚本,如果需要更多详细信息,它们分别使用hogcnn面部检测器执行面部识别。

请记住,face_processing库内部使用 HOG 和 CNN dlib人脸检测器。

使用cvlib的人脸检测

为了完整起见,我们在本节中介绍cvlib包,因为它还提供了面部检测算法。 该库是一个简单,高级且易于使用的 Python 开源计算机视觉库。 为了使用cvlib检测人脸,可以使用detect_face()函数,该函数将为所有检测到的人脸返回边界框和相应的置信度:

代码语言:javascript复制
import cvlib as cv
faces, confidences = cv.detect_face(image)

在后台,此函数将 OpenCV DNN 面部检测器与经过预训练的 Caffe 模型一起使用。

有关更多详细信息,请参见face_detection_cvlib_dnn.py脚本。

检测人脸标志

在计算机视觉中,基准面部关键点(也称为人脸标志)的定位通常是许多面部分析方法和算法中的关键步骤。 面部表情识别,头部姿势估计算法和嗜睡检测系统仅是几个示例,它们严重依赖于通过检测地标而提供的面部形状信息。

人脸标志检测算法旨在自动识别图像或视频中人脸标志点的位置。 更具体地,那些关键点或者是描述面部成分的唯一位置的优势点(例如,嘴角或眼睛的角),或者是连接这些围绕面部成分和面部轮廓的优势点的内插点。 形式上,给定表示为I的面部图像,标志检测算法将检测D标志x = {x1, y1, x2, y2, ..., xD, yD},其中xy代表人脸标志的图像坐标。 在本节中,我们将看到如何使用 OpenCV 和 Dlib 来检测人脸标志。

使用 OpenCV 检测人脸标志

OpenCV 人脸标志性 API 称为 Facemark。 它基于三篇不同的论文,提供了三种不同的地标检测实现:

  • FacemarkLBF
  • FacemarkKamezi
  • FacemarkAAM

以下示例显示了如何使用这些算法检测人脸标志:

代码语言:javascript复制
# Import required packages:
import cv2
import numpy as np

# Load image: 
image = cv2.imread("my_image.png",0)

# Find faces:
cas = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
faces = cas.detectMultiScale(image , 1.5, 5)
print("faces", faces)

# At this point, we create landmark detectors and test them:
print("testing LBF")
facemark = cv2.face.createFacemarkLBF()
facemark .loadModel("lbfmodel.yaml")
ok, landmarks = facemark.fit(image , faces)
print ("landmarks LBF", ok, landmarks)

print("testing AAM")
facemark = cv2.face.createFacemarkAAM()
facemark .loadModel("aam.xml")
ok, landmarks = facemark.fit(image , faces)
print ("landmarks AAM", ok, landmarks)

print("testing Kazemi")
facemark = cv2.face.createFacemarkKazemi()
facemark .loadModel("face_landmark_model.dat")
ok, landmarks = facemark.fit(image , faces)
print ("landmarks Kazemi", ok, landmarks)

此示例应使用 OpenCV 提供的三种不同算法来检测人脸标志。 但是,为fit()函数生成的 Python 包装是不正确的。 因此,在编写本文并使用OpenCV 4.0时,此脚本在 Python 中不起作用。

要解决此问题,我们需要修改fit()函数的 C 代码并从源代码安装 OpenCV。 例如,以下是FacemarkLBFImpl::fit()方法的实际代码:

代码语言:javascript复制
// C   code

bool FacemarkLBFImpl::fit( InputArray image, InputArray roi, OutputArrayOfArrays _landmarks )
{
    // FIXIT
    std::vector<Rect> & faces = *(std::vector<Rect> *)roi.getObj();
    if (faces.empty()) return false;

    std::vector<std::vector<Point2f> > & landmarks =
        *(std::vector<std::vector<Point2f> >*) _landmarks.getObj();

    landmarks.resize(faces.size());

    for(unsigned i=0; i<faces.size();i  ){
        params.detectROI = faces[i];
        fitImpl(image.getMat(), landmarks[i]);
    }

    return true;
}

应该使用以下代码对其进行修改:

代码语言:javascript复制
// C   code

bool FacemarkLBFImpl::fit( InputArray image, InputArray roi, OutputArrayOfArrays _landmarks )
{
 Mat roimat = roi.getMat();
 std::vector<Rect> faces = roimat.reshape(4,roimat.rows);
    if (faces.empty()) return false;

    std::vector<std::vector<Point2f> > landmarks(faces.size());

    for (unsigned i=0; i<faces.size();i  ){
        params.detectROI = faces[i];
        fitImpl(image.getMat(), landmarks[i]);
    }

 if (_landmarks.isMatVector()) { // python
 std::vector<Mat> &v = *(std::vector<Mat>*) _landmarks.getObj();
 for (size_t i=0; i<faces.size(); i  )
 v.push_back(Mat(landmarks[i]));
 } else { // c  , java
 std::vector<std::vector<Point2f> > &v = *(std::vector<std::vector<Point2f> >*) _landmarks.getObj();
 v = landmarks;
 }
 return true;
}

这样,为fit()函数生成的 Python 包装器应该是正确的。 应该注意的是,使用这三种算法提供的用于检测人脸标志的 Python 代码是正确的,只有 Python 包装器无法生成正确的代码。 有关此问题的更多信息,请参见以下两个链接:

  • Using Facemark API (Python), Version 4.0.0 - pre : bad alloc error
  • 使用 Facemark API Python

使用 Dlib 检测人脸标志

另一种选择是使用dlib库来检测人脸标志。 在landmarks_detection_dlib.py脚本中,我们使用dlib检测了人脸标志。 更具体地说,我们使用从网络摄像头拍摄的图像使用dlib正面人脸检测进行人脸检测。 我们还提供从测试图像中获取图像的可能性。 下一步是使用形状预测器获得形状:

代码语言:javascript复制
p = "shape_predictor_68_face_landmarks.dat"
predictor = dlib.shape_predictor(p)
shape = predictor(gray, rect)

下一步是将shape转换为numpy数组。 从这个意义上讲,shape是 Dlib full_object_detection对象,它表示图像中对象的位置以及所有部分的位置。 shape_to_np()函数执行以下转换:

代码语言:javascript复制
def shape_to_np(dlib_shape, dtype="int"):
    """Converts dlib shape object to numpy array"""

    # Initialize the list of (x,y) coordinates
    coordinates = np.zeros((dlib_shape.num_parts, 2), dtype=dtype)

    # Loop over all facial landmarks and convert them to a tuple with (x,y) coordinates:
    for i in range(0, dlib_shape.num_parts):
        coordinates[i] = (dlib_shape.part(i).x, dlib_shape.part(i).y)

    # Return the list of (x,y) coordinates:
    return coordinates

最后,我们在图像中绘制了 68 个人脸标志。 为了在图像中绘制标志,我们已经编码了几个函数,这些函数提供了一种灵活的方式来以所需格式绘制所需的标志。 下一个屏幕截图显示了绘制检测到的人脸标志时的不同可能性:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPE9BxSL-1681870549418)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/ea3f35a0-41b8-4656-9c78-5df8e03b171f.png)]

为了从左到右绘制每个图像中的地标,我们执行了以下操作:

  • 第一张图片draw_shape_lines_all(shape, frame)
  • 第二张图片draw_shape_lines_range(shape, frame, JAWLINE_POINTS)
  • 第三张图片draw_shape_points_pos(shape, frame)
  • 第四张图片draw_shape_points_pos_range(shape, frame, LEFT_EYE_POINTS RIGHT_EYE_POINTS NOSE_BRIDGE_POINTS)

应当注意,dlib还提供了检测与两只眼睛和鼻尖位置相对应的5人脸标志的可能性。 因此,如果要使用此形状预测器,则应相应地加载它:

代码语言:javascript复制
p = "shape_predictor_5_face_landmarks.dat"

使用face_recognition检测人脸标志

landmarks_detection_fr.py脚本显示了如何使用face_recognition包检测和绘制人脸标志。

为了检测标志,调用face_recognition.face_landmarks()函数,如下所示:

代码语言:javascript复制
# Detect 68 landmarks:
face_landmarks_list_68 = face_recognition.face_landmarks(rgb)

此函数为图像中的每个脸部返回人脸标志(例如,眼睛和鼻子)的字典。 例如,如果我们打印检测到的地标,则输出如下:

代码语言:javascript复制
[{'chin': [(113, 251), (111, 283), (115, 315), (122, 346), (136, 376), (154, 402), (177, 425), (203, 442), (231, 447), (260, 442), (285, 426), (306, 403), (323, 377), (334, 347), (340, 315), (343, 282), (343, 251)], 'left_eyebrow': [(123, 223), (140, 211), (163, 208), (185, 211), (206, 220)], 'right_eyebrow': [(240, 221), (263, 212), (288, 209), (312, 211), (332, 223)], 'nose_bridge': [(225, 249), (225, 272), (225, 295), (226, 319)], 'nose_tip': [(201, 337), (213, 340), (226, 343), (239, 339), (252, 336)], 'left_eye': [(144, 248), (158, 239), (175, 240), (188, 254), (173, 255), (156, 254)], 'right_eye': [(262, 254), (276, 240), (293, 239), (308, 248), (295, 254), (278, 255)], 'top_lip': [(185, 377), (200, 370), (216, 364), (226, 367), (238, 364), (255, 370), (274, 377), (267, 378), (238, 378), (227, 380), (215, 379), (192, 378)], 'bottom_lip': [(274, 377), (257, 391), (240, 399), (228, 400), (215, 398), (200, 391), (185, 377), (192, 378), (215, 381), (227, 382), (239, 380), (267, 378)]}]

最后一步是绘制检测到的地标:

代码语言:javascript复制
# Draw all detected landmarks:
for face_landmarks in face_landmarks_list_68:
    for facial_feature in face_landmarks.keys():
        for p in face_landmarks[facial_feature]:
            cv2.circle(image_68, p, 2, (0, 255, 0), -1)

需要说明的是,face_recognition.face_landmarks()方法的签名如下:

代码语言:javascript复制
face_landmarks(face_image, face_locations=None, model="large")

因此,默认情况下会检测到 68 个特征点。 如果model="small",将仅检测到 5 个特征点:

代码语言:javascript复制
# Detect 5 landmarks:
face_landmarks_list_5 = face_recognition.face_landmarks(rgb, None, "small")

如果打印face_landmarks_list_5,则会得到以下输出:

代码语言:javascript复制
[{'nose_tip': [(227, 343)], 'left_eye': [(145, 248), (191, 253)], 'right_eye': [(307, 248), (262, 252)]}]

在这种情况下,词典仅包含双眼和鼻尖的面部特征位置。

在以下屏幕截图中可以看到landmarks_detection_fr.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qXVnpOSi-1681870549418)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/79ce6934-2e0a-457a-a6e9-773f6935db45.png)]

在上面的屏幕截图中,您可以看到使用face_recognition包绘制检测到的 68 个和 5 个人脸标志的结果。

人脸追踪

在仅知道目标的初始位置的情况下,对象跟踪会尝试估计整个视频序列中目标的轨迹。 由于多种因素,例如外观变化,遮挡,快速运动,运动模糊和比例变化,此任务确实具有挑战性。

在这种意义上,基于判别相关过滤器DCF)的视觉跟踪器可提供最新的表现。 此外,这些跟踪器的计算效率很高,这在实时应用中至关重要。 实际上,可以在视觉对象跟踪VOT)2014 挑战赛的结果中看到基于 DCF 的跟踪器的最新表现。 在 VOT2014 挑战赛中,排名前三的跟踪器均基于相关性过滤器。 VOT2014 评估了 38 个跟踪器(来自 VOT2014 委员会的 33 个跟踪器和 5 个基线。 因此,DCF 跟踪器是当前基于边界框的跟踪的一种非常流行的选择方法。

Dlib 库实现了基于 DCF 的跟踪器,该跟踪器易于用于对象跟踪。 在本节中,我们将看到如何将此跟踪器用于面部跟踪和跟踪用户选择的任意对象。 在文献中,此方法也称为判别尺度空间跟踪器DSST)。 唯一需要的输入(原始视频除外)是第一帧(目标的初始位置)上的边界框,然后,跟踪器会自动预测目标的轨迹。

使用基于 Dlib DCF 的跟踪器的人脸跟踪

face_tracking_correlation_filters.py脚本中,我们使用 Dlib 正面人脸检测器进行初始化,并使用基于dlib DCF 的跟踪器 DSST 进行人脸跟踪。 为了初始化相关跟踪器,我们执行以下命令:

代码语言:javascript复制
tracker = dlib.correlation_tracker()

这将使用默认值(filter_size = 6num_scale_levels = 5scale_window_size = 23regularizer_space = 0.001nu_space = 0.025regularizer_scale = 0.001nu_scale = 0.025scale_pyramid_alpha = 1.020)初始化跟踪器。 较高的filter_sizenum_scale_levels值可以提高跟踪精度,但是它需要更多的计算能力,从而增加了 CPU 处理量。 filter_size的推荐值为567,推荐的值为num_scale_levels456

要开始跟踪该方法,请使用tracker.start_track()。 在这种情况下,我们执行面部检测。 如果成功,我们将脸部位置传递给此方法,如下所示:

代码语言:javascript复制
if tracking_face is False:
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # Try to detect a face to initialize the tracker:
    rects = detector(gray, 0)
    # Check if we can start tracking (if we detected a face):
    if len(rects) > 0:
        # Start tracking:
        tracker.start_track(frame, rects[0])
        tracking_face = True

这样,对象跟踪器将开始跟踪边界框内的内容,在这种情况下,边界框是检测到的脸部。

另外,为了更新被跟踪对象的位置,调用tracker.update()方法:

代码语言:javascript复制
tracker.update(frame)

此方法更新跟踪器并返回峰-旁瓣比,该比值是衡量跟踪器置信度的指标。 此度量标准的较大值表示高置信度。 此度量标准可用于通过正面人脸检测重新初始化跟踪器。

要获取被跟踪对象的位置,请调用tracker.get_position()方法:

代码语言:javascript复制
pos = tracker.get_position()

此方法返回被跟踪对象的位置。 最后,我们可以绘制脸部的预测位置:

代码语言:javascript复制
cv2.rectangle(frame, (int(pos.left()), int(pos.top())), (int(pos.right()), int(pos.bottom())), (0, 255, 0), 3)

在此脚本中,我们编码了如果按下数字1重新初始化跟踪器的选项。 如果按下此数字,我们将重新初始化跟踪器以尝试检测正面。 为了阐明此脚本的工作方式,包括以下两个屏幕截图。

在第一个屏幕截图中,跟踪算法正在等待,直到执行正面人脸检测以初始化跟踪为止:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vFsRZfuf-1681870549418)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3fa3ba82-f9c6-4a1e-a6c7-2bf0241c4456.png)]

在第二个屏幕截图中,跟踪算法当前正在跟踪先前检测到的面部:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRe1tEud-1681870549419)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/109b9bc9-f98f-453d-9a81-e0bef60d4651.png)]

在上一个屏幕截图中,您可以看到该算法当前正在跟踪检测到的面部。 您还可以看到您还可以按数字1来重新初始化跟踪。

使用基于 Dlib DCF 的跟踪器的对象跟踪

可以修改face_tracking_correlation_filters.py脚本以跟踪任意对象。 在这种情况下,我们将使用鼠标选择要跟踪的对象。 如果按1,该算法将开始跟踪预定义边界框内的对象。 另外,如果我们按2,则预定义的边界框将被清空,跟踪算法将停止,从而允许用户选择另一个边界框。

为了阐明face_tracking_correlation_filters.py脚本的工作方式,我们提供了以下两个屏幕截图。 在第一个中,我们可以看到我们需要选择一个边界框来开始跟踪:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wjo0GFBi-1681870549419)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/385235bf-7d19-4ee8-92d3-6e40ca32a3c0.png)]

在第二篇文章中,我们可以看到算法跟踪对象时任意帧的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ITIMprOa-1681870549419)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/e7f39864-b9d4-4476-9322-ecbf37eddff7.png)]

如您在上一个屏幕截图中所见,该算法正在跟踪边界框内的对象。

人脸识别

随着计算机视觉,机器学习和深度学习的发展,人脸识别已成为热门话题。 人脸识别可广泛应用于各种用途,包括预防犯罪,监视,法医应用,生物识别,以及最近在社交网络中的使用。 自动人脸识别面临各种挑战,例如遮挡,外观变化,表情,老化和比例变化。 在对象识别方面取得成功之后,CNN 已被广泛用于面部识别。

在本章中,我们将看到 OpenCV 提供的与面部识别相关的功能,还将探索一些深度学习方法,这些方法可以轻松地集成到您的计算机视觉项目中,以执行最新的面部识别结果。

使用 OpenCV 的人脸识别

OpenCV 提供了执行面部识别的支持。 实际上,OpenCV 提供了三种不同的实现方式:

  • Eigenfaces
  • Fisherfaces
  • 本地二进制模式直方图LBPH

这些实现以不同的方式执行识别。 但是,您可以通过仅更改识别器的创建方式来使用它们中的任何一个。 更具体地说,要创建这些识别器,需要以下代码:

代码语言:javascript复制
face_recognizer = cv2.face.LBPHFaceRecognizer_create()
face_recognizer = cv2.face.EigenFaceRecognizer_create()
face_recognizer = cv2.face.FisherFaceRecognizer_create()

一旦创建,并且与特定的内部算法无关,OpenCV 将用于执行人脸识别,应使用train()predict()这两个关键方法来进行人脸识别系统的训练和测试 ,并且应注意,我们使用这些方法的方式与创建的识别器无关。

因此,尝试使用三个识别器并为特定任务选择表现最佳的识别器非常容易。 话虽如此,当在通常涉及不同环境和光照条件的野外中识别图像时,LBPH 应该比其他两种方法提供更好的结果。 此外,LBPH 人脸识别器支持update()方法,您可以在其中给定新数据来更新人脸识别器。 对于 Eigenfaces 和 Fisherfaces 方法,此功能是不可能的。

为了训练识别器,应调用train()方法:

代码语言:javascript复制
face_recognizer.train(faces, labels)

cv2.face_FaceRecognizer.train(src, labels)方法训练特定的面部识别器,其中src对应于图像(面部)的训练集,而参数标签为训练集中的每个图像设置相应的标签。

要识别新人脸,应调用predict()方法:

代码语言:javascript复制
label, confidence = face_recognizer.predict(face)

cv2.face_FaceRecognizer.predict(src)方法通过输出预测的标签和关联的置信度来输出(预测)新src图像的识别。

最后,OpenCV 还提供write()read()方法来分别保存创建的模型和加载先前创建的模型。 对于这两种方法,filename参数都设置要保存或加载的模型的名称:

代码语言:javascript复制
cv2.face_FaceRecognizer.write(filename)
cv2.face_FaceRecognizer.read(filename)

如前所述,可以使用update()方法更新 LBPH 人脸识别器:

代码语言:javascript复制
cv2.face_FaceRecognizer.update(src, labels)

在这里,srclabels设置了新的训练示例,这些示例将用于更新 LBPH 识别器。

使用 Dlib 的人脸识别

Dlib 提供了基于深度学习的高质量人脸识别算法。 Dlib 实现了面部识别算法,可提供最先进的准确率。 更具体地说,该模型在野生数据库中带有标签的人脸上的准确率为 99.38%。

该算法的实现基于《用于图像识别的深度残差学习》(2016)中提出的 ResNet-34 网络,该网络使用 300 万张人脸进行了训练。 可以从这里下载创建的模型(21.4 MB)。

该网络以生成用于量化面部的 128 维(128D)描述符的方式进行训练。 使用三元组执行训练步骤。 一个三元组训练示例由三个图像组成。 其中两个对应于同一个人。 网络为每个图像生成 128D 描述符,略微修改神经网络权重,以使与同一个人相对应的两个向量更近,而与另一个人相对应的特征向量更远。 三元组损失函数将其形式化,并尝试将同一个人的两个图像的 128D 描述符推近,而将不同人的两个图像的 128D 描述符推向更远。

对于成千上万的人的数百万个图像,此过程将重复数百万次,最后,它可以为每个人生成 128D 描述符。 因此,由于以下原因,最终的 128D 描述符是良好的编码:

  • 相同人的两个图像的生成的 128D 描述符彼此非常相似。
  • 不同人的两个图像生成的 128D 描述符非常不同。

因此,利用dlib函数,我们可以使用预先训练的模型将人脸映射到 128D 描述符中。 之后,我们可以使用这些特征向量来进行面部识别。

encode_face_dlib.py脚本显示了如何计算用于量化人脸的 128D 描述符。 该过程非常简单,如以下代码所示:

代码语言:javascript复制
# Load image:
image = cv2.imread("jared_1.jpg")

# Convert image from BGR (OpenCV format) to RGB (dlib format):
rgb = image[:, :, ::-1]

# Calculate the encodings for every face of the image:
encodings = face_encodings(rgb)

# Show the first encoding:
print(encodings[0])

您可以猜到,face_encodings()函数为图像中的每个面部返回 128D 描述符:

代码语言:javascript复制
pose_predictor_5_point = dlib.shape_predictor("shape_predictor_5_face_landmarks.dat")
face_encoder = dlib.face_recognition_model_v1("dlib_face_recognition_resnet_model_v1.dat")
detector = dlib.get_frontal_face_detector()

def face_encodings(face_image, number_of_times_to_upsample=1, num_jitters=1):
    """Returns the 128D descriptor for each face in the image"""

    # Detect faces:
    face_locations = detector(face_image, number_of_times_to_upsample)
    # Detected landmarks:
    raw_landmarks = [pose_predictor_5_point(face_image, face_location) for face_location in face_locations]
    # Calculate the face encoding for every detected face using the detected landmarks for each one:
    return [np.array(face_encoder.compute_face_descriptor(face_image, raw_landmark_set, num_jitters)) for
            raw_landmark_set in raw_landmarks]

如您所见,关键是使用检测到的每个标志来计算每个检测到的脸部的脸部编码,并调用dlibface_encoder.compute_face_descriptor()函数。

num_jitters参数设置每个面部随机抖动的次数,并返回每次计算的平均 128D 描述符。 在这种情况下,输出(编码 128D 描述符)如下:

代码语言:javascript复制
[-0.08550473 0.14213498 0.01144615 -0.05947386 -0.05831585 0.01127038 -0.05497809 -0.03466939 0.14322688 -0.1001832 0.17384697 0.02444006 -0.25994921 0.13708787 -0.08945534 0.11796272 -0.25426617 -0.0829383 -0.05489913 -0.10409787 0.07074109 0.05810066 -0.03349853 0.07649824 -0.07817822 -0.29932317 -0.15986916 -0.087205 0.10356752 -0.12659372 0.01795856 -0.01736169 -0.17094864 -0.01318233 -0.00201829 0.0104903 -0.02453734 -0.11754096 0.2014133 0.12671679 -0.0271306 -0.02350519 0.08327188 0.36815098 0.12599576 0.04692561 0.03585262 -0.03999642 0.23675609 -0.28394884 0.11896492 0.11870296 0.20243752 0.2106981 0.03092775 -0.14315812 0.07708532 0.16536239 -0.19648902 0.22793224 0.06825032 -0.00117573 0.00304667 -0.01902146 0.2539638 0.09768397 -0.13558105 -0.15079053 0.11357955 -0.14893037 -0.09028706 0.03625216 -0.13004847 -0.16567475 -0.21958281 0.08687183 0.35941613 0.16637127 -0.08334676 0.02806632 -0.09188357 -0.10760318 0.02889947 0.08376379 -0.11524356 -0.00998984 -0.05582509 0.09372396 0.30287758 -0.01063644 -0.07903813 0.30418509 -0.01998731 0.0752025 -0.00424637 0.07463965 -0.12972119 -0.04034984 -0.08435905 -0.01642537 0.00847361 -0.09549874 -0.07568903 0.06476583 -0.19202243 0.16904426 -0.01247451 0.03941975 -0.01960869 0.02145611 -0.25607404 -0.03039071 0.20248309 -0.25835767 0.21397503 0.19302645 0.07284702 0.07879912 0.06171442 0.02366752 0.06781606 -0.06446165 -0.14713687 -0.0714087 0.11978403 -0.01525984 -0.04687868 0.00167655]

面部被编码后,下一步就是执行识别。

使用使用 128D 描述符计算的某种距离度量可以轻松地计算出识别率。 实际上,如果两个面部描述符向量之间的欧式距离小于0.6,则可以认为它们属于同一个人。 否则,他们来自不同的人。

欧几里德距离可以使用numpy.linalg.norm()来计算。

compare_faces_dlib.py脚本中,我们将四个图像与另一个图像进行比较。 为了比较人脸,我们编写了两个函数:compare_faces()compare_faces_ordered()compare_faces()函数将面部编码列表与候选者进行比较时返回距离以进行检查:

代码语言:javascript复制
def compare_faces(face_encodings, encoding_to_check):
    """Returns the distances when comparing a list of face encodings against a candidate to check"""

    return list(np.linalg.norm(face_encodings - encoding_to_check, axis=1))

compare_faces_ordered()函数在将人脸编码列表与候选者进行比较以进行检查时,返回排序的距离和相应的名称:

代码语言:javascript复制
def compare_faces_ordered(face_encodings, face_names, encoding_to_check):
    """Returns the ordered distances and names when comparing a list of face encodings against a candidate to check"""

    distances = list(np.linalg.norm(face_encodings - encoding_to_check, axis=1))
    return zip(*sorted(zip(distances, face_names)))

因此,将四个图像与另一个图像进行比较的第一步是加载所有图像并转换为 RGB(dlib format):

代码语言:javascript复制
# Load images:
known_image_1 = cv2.imread("jared_1.jpg")
known_image_2 = cv2.imread("jared_2.jpg")
known_image_3 = cv2.imread("jared_3.jpg")
known_image_4 = cv2.imread("obama.jpg")
unknown_image = cv2.imread("jared_4.jpg")

# Convert image from BGR (OpenCV format) to RGB (dlib format):
known_image_1 = known_image_1[:, :, ::-1]
known_image_2 = known_image_2[:, :, ::-1]
known_image_3 = known_image_3[:, :, ::-1]
known_image_4 = known_image_4[:, :, ::-1]
unknown_image = unknown_image[:, :, ::-1]

# Crate names for each loaded image:
names = ["jared_1.jpg", "jared_2.jpg", "jared_3.jpg", "obama.jpg"]

下一步是计算每个图像的编码:

代码语言:javascript复制
# Create the encodings:
known_image_1_encoding = face_encodings(known_image_1)[0]
known_image_2_encoding = face_encodings(known_image_2)[0]
known_image_3_encoding = face_encodings(known_image_3)[0]
known_image_4_encoding = face_encodings(known_image_4)[0]
known_encodings = [known_image_1_encoding, known_image_2_encoding, known_image_3_encoding, known_image_4_encoding]
unknown_encoding = face_encodings(unknown_image)[0]

最后,您可以使用以前的函数比较人脸。 例如,让我们使用compare_faces_ordered()函数:

代码语言:javascript复制
computed_distances_ordered, ordered_names = compare_faces_ordered(known_encodings, names, unknown_encoding)
print(computed_distances_ordered)
print(ordered_names)

这样做将为我们带来以下好处:

代码语言:javascript复制
(0.3913191431497527, 0.39983264838593896, 0.4104153683230741, 0.9053700273411349)
('jared_3.jpg', 'jared_1.jpg', 'jared_2.jpg', 'obama.jpg')

前三个值(0.39131914314975270.399832648385938960.4104153683230741)小于0.6。 这意味着可以从与要检查的图像('jared_4.jpg')相同的人处考虑前三个图像('jared_3.jpg''jared_1.jpg''jared_2.jpg')。 获得的第四值(0.9053700273411349)表示第四张图像('obama.jpg')与要检查的图像不是同一个人。

在下一个屏幕截图中可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nh8MeDRi-1681870549419)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/1d8e0c02-939d-4a42-ab9e-36b33c901b99.png)]

在上一个屏幕截图中,您可以看到可以从同一个人考虑前三张图像(获取的值小于 0.6),而可以从另一个人考虑第四张图像(获取的值大于 0.6)。

使用face_recognition的人脸识别

face_recognition的人脸识别使用dlib函数对人脸进行编码并计算编码人脸的距离。 因此,您无需编码face_encodings()compare_faces()函数,而只需使用它们。

encode_face_fr.py脚本显示了如何创建使用face_recognition.face_encodings()函数的 128D 描述符:

代码语言:javascript复制
# Load image:
image = cv2.imread("jared_1.jpg")

# Convert image from BGR (OpenCV format) to RGB (face_recognition format):
image = image[:, :, ::-1]

# Calculate the encodings for every face of the image:
encodings = face_recognition.face_encodings(image)

# Show the first encoding:
print(encodings[0])

要查看如何使用face_recognition比较人脸,已对compare_faces_fr.py脚本进行了编码。 代码如下:

代码语言:javascript复制
# Load known images (remember that these images are loaded in RGB order):
known_image_1 = face_recognition.load_image_file("jared_1.jpg")
known_image_2 = face_recognition.load_image_file("jared_2.jpg")
known_image_3 = face_recognition.load_image_file("jared_3.jpg")
known_image_4 = face_recognition.load_image_file("obama.jpg")

# Crate names for each loaded image:
names = ["jared_1.jpg", "jared_2.jpg", "jared_3.jpg", "obama.jpg"]

# Load unknown image (this image is going to be compared against all the previous loaded images):
unknown_image = face_recognition.load_image_file("jared_4.jpg")

# Calculate the encodings for every of the images:
known_image_1_encoding = face_recognition.face_encodings(known_image_1)[0]
known_image_2_encoding = face_recognition.face_encodings(known_image_2)[0]
known_image_3_encoding = face_recognition.face_encodings(known_image_3)[0]
known_image_4_encoding = face_recognition.face_encodings(known_image_4)[0]
known_encodings = [known_image_1_encoding, known_image_2_encoding, known_image_3_encoding, known_image_4_encoding]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]

# Compare the faces:
results = face_recognition.compare_faces(known_encodings, unknown_encoding)

# Print the results:
print(results)

获得的结果为[True, True, True, False]。 因此,前三个加载的图像("jared_1.jpg""jared_2.jpg""jared_3.jpg")被视为与未知图像("jared_4.jpg")是同一个人,而第四个加载的图像("obama.jpg")被视为一个不同的人。

总结

在本章中,我们介绍了用于面部检测,检测人脸标志,面部跟踪和面部识别的最新算法和技术。 我们回顾了主要的 Python 库和包提供的面部处理方法。 更具体地说,在面部处理的上下文中引入了 OpenCV,dlibface_processingcvlib。 其中一些经过审查的方法是基于深度学习技术的。

在下一章中,我们将深入探讨深度学习。

问题

  1. 在本章中,有关面部处理的包和库有哪些评论?
  2. 人脸识别和人脸验证之间的主要区别是什么?
  3. cv2.face.getFacesHAAR() OpenCV 函数做什么?
  4. cv2.dnn.blobFromImage()函数有什么作用?
  5. cvlib包提供什么函数来检测人脸?
  6. face_recognition提供什么函数来检测人脸标志?
  7. dlib提供什么函数来初始化相关跟踪器?
  8. dlib提供什么函数来启动相关跟踪器?
  9. dlib提供什么函数来获取被跟踪对象的位置?
  10. 计算 128D 描述符,以dlibimage BGR 图像进行人脸识别。

进一步阅读

以下资源将帮助您更深入地使用 Python 进行面部处理:

  • 《Python 人工智能》,作者 Prateek Joshi(2017)

十二、深度学习简介

如今,深度学习是机器学习中最受欢迎和增长最快的领域。 自 2012 年以来,深度学习已经超越了传统的机器学习应用方法。这就是为什么将许多深度学习架构应用于许多领域(包括计算机视觉)的原因。 深度学习的常见应用包括自动语音识别,图像识别,视觉艺术处理,自然语言处理,推荐系统,生物信息学和图像恢复。 大多数现代深度学习架构都基于人工神经网络,深度学习中的深度指的是架构的层数。

在本章中,将通过研究与传统机器学习方法的差异来向您介绍深度学习,这些差异在第 10 章,“使用 OpenCV 的机器学习”中进行了介绍。 此外,您还将看到一些适用于图像分类和对象检测的常见深度学习架构。 最后,将介绍两个深度学习 Python 库(TensorFlow 和 Keras)。

更具体地说,本章将讨论以下主题:

  • 计算机视觉任务的深度学习概述
  • OpenCV 中的深度学习
  • TensorFlow 库
  • Keras 库

在本章中,将向您介绍 OpenCV 的深度学习领域,以及一些深度学习的 Python 库(TensorFlow 和 Keras)。 在第 13 章,“使用 Python 和 OpenCV 的移动和 Web 计算机视觉”中,您将学习如何创建计算机视觉和深度学习 Web 应用。

技术要求

技术要求在这里列出:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib 包
  • Git 客户端
  • TensorFlow 库(请参阅以下有关如何安装 TensorFlow 的部分)
  • Keras 库(请参阅以下有关如何安装 Keras 的部分)

有关如何安装这些要求的更多详细信息,请参见第 1 章,“设置 OpenCV”。 可以在 Github 中访问《精通 Python OpenCV 4》的 GitHub 存储库,其中包含从本书第一章到最后的所有必要的支持项目文件。

安装 TensorFlow

为了安装 TensorFlow,请使用以下命令:

代码语言:javascript复制
$ pip install tensorflow

要检查安装是否已正确执行,只需打开 Python shell 并尝试导入 TensorFlow 库,如下所示:

代码语言:javascript复制
python
import tensorflow

安装 Keras

为了安装 Keras,请使用以下命令:

代码语言:javascript复制
$ pip install keras

要检查安装是否正确执行,只需打开一个 Python shell 并尝试导入 Keras 库,如下所示:

代码语言:javascript复制
python
import keras

请记住,建议的方法是在虚拟环境中安装包。 请参阅第 1 章,“设置 OpenCV”,以了解如何创建和管理虚拟环境。

计算机视觉任务的深度学习概述

深度学习极大地促进了计算机视觉领域的发展。 在本章的这一部分中,将介绍一些关键概念,以向您介绍深度学习领域。

深度学习特征

与传统的机器学习方法相比,深度学习具有一些关键差异。 此外,在许多计算机视觉任务中,深度学习技术都超越了机器学习,但是应考虑一些关键因素,以便知道何时应用每种技术来完成特定的计算任务。 所有这些注意事项简要总结如下:

  • 与可以在低端机器上运行的机器学习技术相反,深度学习算法需要具有高端基础架构才能正确训练。 实际上,深度学习算法固有地执行了大量计算,而这些计算可以使用 GPU 进行优化。
  • 当对特征自省和工程都缺乏领域的了解时,深度学习技术会胜过其他技术,因为您不必担心特征工程。 特征工程可以定义为将领域知识应用于特征检测器和提取器创建的过程,目的是降低数据的复杂性,从而使传统的机器学习方法能够正确学习。 因此,这些机器学习算法的表现取决于识别和提取特征的准确率。 另一方面,深度学习技术试图从数据中提取高级特征,这使得深度学习比传统的机器学习方法先进得多。 在深度学习中,查找相关特征的任务是算法的一部分,并且通过减少每个问题的特征自省和工程任务来实现自动化。
  • 机器学习和深度学习都能够处理海量数据集。 但是,在处理小型数据集时,机器学习方法更有意义。 从这个意义上说,这两种方法之间的主要区别在于其表现随着数据规模的增加而增加。 例如,当使用小型数据集时,深度学习算法很难在数据中找到模式,并且表现不佳,因为它们需要大量数据来调整其内部参数。 经验法则是,如果数据量很大,则深度学习要胜过其他技术,而当数据集较小时,传统的机器学习算法是可取的。

在下一张图中,我们尝试总结了上述关键点,以便轻松记住它们:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yC19M98U-1681870549420)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/fc2c1ec7-1550-435f-aca4-5a6cf5213ef7.png)]

图中阐述的关键点如下:

  • 计算资源(深度学习–高端机器与机器学习–低端机器)
  • 特征工程(深度学习–同一步骤中的特征提取和分类与机器学习–单独步骤中的特征提取和分类)
  • 数据集大小(深度学习–大/非常大的数据集与机器学习–中/大数据集)

深度学习爆发

深度学习的概念并不是什么新鲜事物,因为 Rina Dechter 于 1986 年以及 Igor Aizenberg 及其同事于 2000 年分别将其引入了机器学习领域和人工神经网络。 但是,直到 2012 年发生深度学习革命时,才出现了一些对研究界具有重大影响的杰出作品。 在计算机视觉方面, AlexNet 架构(作者设计的卷积神经网络的名称)是 ImageNet 大规模视觉识别挑战赛ILSVRC)的获胜者)2012 年的错误率非常低,以巨大的错误率(15.3% 对 26.2%(第二名))击败了所有其他竞争对手。 ImageNet 是一个大型视觉数据库,包含超过 1400 万个带标签的高分辨率图像。 这些图像被人类标记。 ImageNet 包含 20,000 多个类别。 因此,AlexNet 在解决 ILSVRC 2012 方面的 2012 年突破通常被认为是 2010 年代深度学习革命的开始。

用于图像分类的深度学习

继 AlexNet 在此竞赛中获得成功之后,许多其他深度学习架构也已提交 ImageNet 挑战赛,以实现更好的表现。 从这个意义上讲,下一张图展示了提交给 ImageNet 挑战的最相关的深度学习方法的一站式准确率,包括最左侧的 AlexNet(2012)架构,以及迄今为止表现最佳的 Inception-V4(2016) 对:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dh4xPs9Q-1681870549420)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/e9924fac-2310-4f9b-956c-9d819ea96850.png)]

这些深度学习架构的主要方面简要介绍如下,重点介绍了它们引入的关键方面。

此外,如果需要更多详细信息,我们还提供了对每个出版物的引用:

  • AlexNet(2012)
    • 描述:AlexNet 是 LSVRC-2012 的获胜者,它是一种简单但功能强大的网络架构,其中卷积层和池层一个接一个,而顶层则是全连接层。 在将深度学习方法应用于计算机视觉任务时,通常将该架构用作起点。
    • 参考Alex Krizhevsky, Ilya Sutskever, and Geoffrey E Hinton. ImageNet classification with deep convolutional neural networks. In Advances in neural information processing systems, pp. 1097–1105, 2012.
  • VGG-16 和 -19(2014)
    • 描述:VGGNet 由牛津大学的视觉几何组VGG)提出。 通过在整个网络中仅使用3 x 3过滤器,而不使用大型过滤器(例如7 x 711 x 11)。 这项工作的主要贡献在于,它表明网络深度是在卷积神经网络中实现更好的识别或分类精度的关键组成部分。 VGGNet 被认为是对特定任务进行基准测试的良好架构。 但是,它的主要缺点是训练速度非常慢,并且网络架构的权重很大(VGG-16 为 533 MB,VGG-19 为 574 MB)。 VGGNet-19 使用 1.38 亿个参数。
    • 参考Simonyan, K., and Zisserman, A. (2014). Very deep convolutional networks for large-scale image recognition. arXiv preprint arXiv:1409.1556.
  • GoogLeNet/Inception V1(2014)
    • 说明GoogLeNet(也称为 Inception V1)是 LSVRC-2014 的获胜者,其前 5 名错误率达到 6.67%,这非常接近人类水平的表现。 该架构比 VGGNet 更深入。 但是,由于 9 个并行模块(初始模块)的架构是基于几个非常小的卷积的,因此它仅使用 AlexNet 参数数量的十分之一(从 6000 万到仅 400 万个参数),目的是减少参数数量。
    • 参考Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., Anguelov, D., Dumitru, .E, Vincent, .V, and Rabinovich, A. (2015). Going deeper with convolutions.
  • ResNet-18,-34,-50,-101 和 -152(2015)
    • 说明:Microsoft 的残差网络ResNets)是 LSVRC-2015 的获胜者,并且是迄今为止最深的网络,共有 153 个卷积层达到了最高 5 个分类误差为 4.9%(这比人工精度略好)。 此架构包括跳跃连接,也称为门控单元门控循环单元,可实现增量学习更改。 ResNet-34 使用 2180 万个参数,ResNet-50 使用 2560 万个参数,ResNet-101 使用 4450 万个参数,最后,ResNet-152 使用 6020 万个参数。
    • 参考He, K., Zhang, X., Ren, S., and Sun, J. (2016). Deep residual learning for image recognition. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 770-778).
  • Inception V3(2015)
    • 描述:如前所示,初始架构被引入为 GoogLeNet(也称为 Inception V1)。 后来,对该架构进行了修改,以引入批量规范化(Inception-V2)。 Inception V3 架构包括其他分解思想,其目的是在不降低网络效率的情况下减少连接/参数的数量。
    • 参考Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J., and Wojna, Z. (2016). Rethinking the inception architecture for computer vision. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 2818-2826).
  • Inception V4(2016)
    • 说明:从 GoogLeNet 演变而来的 Inception V4。 另外,与 Inception-V3 相比,此架构具有更统一的简化架构和更多的 Inception 模块。 Inception-V4 在 LSVRC 上能够达到 80.2% 的 top-1 精度和 95.2% 的 top-5 精度。
    • 参考Szegedy, C., Ioffe, S., Vanhoucke, V., and Alemi, A. A. (2017, February). Inception-V4, inception-resnet and the impact of residual connections on learning. In AAAI (Vol. 4, p. 12).

用于对象检测的深度学习

对象检测是深度学习中的一个热门话题,它适合于在单个图像中识别和定位多个相关对象。 为了对对象检测算法进行基准测试,通常使用三个数据库。 第一个是 PASCAL 视觉对象分类PASCAL VOC)数据集,其中包括 20 个类别和 10,000 个用于训练和验证的图像,其中包含带有对象的边界框。 ImageNet 自 2013 年以来发布了一个对象检测数据集,它由大约 500,000 张仅用于训练的图像和 200 个类别组成。 最后,上下文中的常见对象COCO)是大规模的对象检测,分割和字幕数据集,在 328,000 张图像中总共有 250 万个标记实例。 有关 COCO 数据集的更多信息,您可以阅读出版物《Microsoft COCO:上下文中的常见对象》(2014)。 为了评估对象检测算法,通常使用平均平均精度mAP),其计算方法是对所有类别和/或所有交并比IoU)阈值计算 mAP,具体取决于比赛。 在二分类中,平均精度AP)指标对应于精度(正预测值)-召回(灵敏度)曲线的摘要,而 IoU 指标是预测框与地面真实框之间的重叠区域。 以下是此示例:

  • PASCAL VOC2007 挑战–仅考虑了一个 IoU 阈值。 对于 PASCAL VOC 挑战,如果 IoU> 0.5,则预测为肯定。 因此,mAP 是对所有 20 个对象类平均的。
  • 在 2017 年 COCO 挑战赛中,对所有 80 个物体类别和所有 10 个 IoU 阈值(从 0.5 到 0.95,步长为 0.05)平均了 mAP。

在 10 个 IoU 阈值(从 0.5 到 0.95,步长为 0.05)上求平均值,而不是仅考虑一个 IoU≥0.5 的阈值,倾向于奖励在精确定位方面更好的模型。

在下表中,您可以看到使用上述三个数据集评估的用于对象检测的最新深度学习算法,其中显示了 PASCAL VOC 和 COCO 数据集上的 mAP 得分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0WxiLz1-1681870549420)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/276707d9-8a1f-4a33-ab02-dc5a62e4175e.png)]

下面包括有关用于对象检测的最新深度学习算法的介绍:

  • R-CNN(2014)
    • 描述基于区域的卷积网络R-CNN)是使用卷积神经网络进行对象检测的首批方法之一,表明与基于类似 HOG 的简单特征的系统相比,卷积神经网络可以提高目标检测表现。 该算法可以分解为以下三个步骤:
      1. 创建一组区域提议
      2. 对每个区域提议执行经过修订版的 AlexNet 的前向传递,以提取特征向量
      3. 潜在的对象通过几个 SVM 分类器进行检测,此外,线性回归器会更改边界框的坐标
    • 参考Girshick, R., Donahue, J., Darrell, T., and Malik, J. (2014). Rich feature hierarchies for accurate object detection and semantic segmentation. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 580-587).
  • Fast R-CNN(2015)
    • 描述基于快速区域的卷积网络Fast R-CNN)方法是对先前方法的一种改进,可以有效地对目标提议进行分类。 此外,Fast R-CNN 采用了多项创新技术来提高训练和测试速度,同时还提高了检测精度。
    • 参考Girshick, R. (2015). Fast r-cnn. In Proceedings of the IEEE international conference on computer vision and pattern recognition (pp. 1440-1448).
  • Faster R-CNN(2015)
    • 说明:更快的 R-CNN 是对快速 R-CNN 的修改,引入了一个区域提议网络RPN),该网络与检测网络共享全图像卷积特征,从而实现几乎免费的区域提议。
    • 参考Ren, S., He, K., Girshick, R., and Sun, J. (2015). Faster R-CNN – Towards real-time object detection with region proposal networks. In Advances in neural information processing systems (pp. 91-99).
  • R-FCN(2016)
    • 描述基于区域的全卷积网络R-FCN)是仅包含卷积层的框架,允许进行完整的反向传播以进行训练和推理,从而获得准确而有效的对象检测。
    • 参考Dai, J., Li, Y., He, K., and Sun, J. (2016). R-FCN: Object Detection via Region-based Fully Convolutional Networks. In Advances in neural information processing systems (pp. 379-387).
  • YOLO(2016)
    • 描述只看一次YOLO)是一种深度学习架构,可在单个步骤中预测边界框和类概率。 与其他深度学习检测器相比,YOLO 会产生更多的定位错误,但是在背景中预测假正例的可能性较小。
    • 参考Redmon, J., Divvala, S., Girshick, R., and Farhadi, A. (2016). You only look once: Unified, Real-Time Object Detection.
  • SSD(2016)
    • 描述单发多盒检测器SSD)是一个深层神经网络,旨在通过端到端卷积神经网络架构的方法,同时预测边界框和类概率。
    • 参考Liu, W., Anguelov, D., Erhan, D., Szegedy, C., Reed, S., Fu, C. Y., and Berg, A. C. (2016, October). SSD: Single Shot Multibox Detector. In European conference on Computer Vision (pp. 21-37). Springer, Cham.
  • YOLO V2(2016)
    • 描述:作者在同一出版物中介绍了 YOLO9000 和 YOLO V2。 YOLO9000 是一种实时对象检测系统,可以检测 9,000 多个对象类别,而 YOLO V2 是 YOLO 的改进版本,致力于在提高准确率的同时仍是快速检测器。
    • 参考Redmon, J., and Farhadi, A. (2017). YOLO9000: Better, Faster, Stronger. arXiv preprint.
  • NASNet(2016)
    • 描述:作者介绍了一种神经网络搜索,这是使用循环神经网络构成神经网络架构的想法。 神经架构搜索网络NASNet)包括学习模型的架构,以优化层数,同时还提高准确率。
    • 参考Zoph, B., and Le, Q. V. (2016). Neural Architecture Search with Reinforcement Learning. arXiv preprint arXiv:1611.01578.
  • Mask R-CNN(2017)
    • 描述基于遮罩区域的卷积网络遮罩 R-CNN)是 Faster R-CNN 模型的另一个扩展,它为边界框检测添加了并行分支,目的是预测对象遮罩。 对象遮罩是按图像中的像素进行分割,从而可以对对象实例进行分割。
    • 参考He, K., Gkioxari, G., Dollár, P., and Girshick, R. (2017, October). Mask r-cnn. In Computer Vision (ICCV), 2017 IEEE International Conference on Computer Vision (pp. 2980-2988). IEEE.

OpenCV 中的深度学习

自 OpenCV 3.1 以来,库中已有深层神经网络DNN)模块,可通过一些流行的深度学习框架进行预训练的深度网络实现前向传递(推理) ,例如 CaffeTensorFlowTorch/PytorchDarknetONNX 格式的模型。 在 OpenCV 3.3 中,该模块已从opencv_contrib存储库升级到主存储库,并已进行了显着加速。 因此,从 OpenCV 3.3 开始,可以在我们的应用中使用经过预训练的网络进行预测,并且在上一节中介绍的许多流行的网络架构都与 OpenCV 3.3 兼容。

在本节中,我们将看到如何将这些架构中的一些应用于对象检测和图像分类,但是在涵盖这一方面之前,应先回顾一下 OpenCV 在 DNN 模块中提供的许多功能。

了解cv2.dnn.blobFromImage()

在第 11 章,“人脸检测,跟踪和识别”中,我们看到了一些涉及深度学习计算的示例。 例如,在face_detection_opencv_dnn.py脚本中,使用了基于深度学习的面部检测器来检测图像中的人脸。 第一步是按以下方式加载预训练的模型:

代码语言:javascript复制
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel")

提醒一下,deploy.prototxt文件定义了模型架构,res10_300x300_ssd_iter_140000_fp16.caffemodel文件包含了实际层的权重。 为了对整个网络执行前向传递以计算输出,网络的输入应为 BLOB。 BLOB 可以看作是经过充分预处理以馈送到网络的图像集合。

此预处理由几个操作组成-调整大小,裁剪,减去平均值,缩放以及交换蓝色和红色通道。

例如,在上述面部检测示例中,我们执行了以下命令:

代码语言:javascript复制
# Load image:
image = cv2.imread("test_face_detection.jpg")

# Create 4-dimensional blob from image:
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104., 117., 123.], False, False)

在这种情况下,这意味着我们要在调整为300 x 300,的 BGR 图像上运行模型,分别对蓝色,绿色和红色通道应用(104, 117, 123)值的平均减法。 下表中对此进行了总结:

| 模型 | 规模 | 尺寸WxH | 均值减法 | 通道顺序 | | — | — | — | — | | OpenCV 人脸检测器 | 1.0 |300 x 300| 104177123 | BGR |

此时,我们可以将 BLOB 设置为输入,并按以下方式获得检测结果:

代码语言:javascript复制
# Set the blob as input and obtain the detections:
net.setInput(blob)
detections = net.forward()

有关更多详细信息,请参见face_detection_opencv_dnn.py脚本。

现在,我们将详细了解cv2.dnn.blobFromImage()cv2.dnn.blobFromImages()函数。 为此,我们首先要看到两个函数的签名,然后我们将看到blob_from_image.pyblob_from_images.py脚本。 这些脚本在理解这些函数时可能会有所帮助。 此外,在这些脚本中,我们还将使用 OpenCV cv2.dnn.imagesFromBlob()函数。

cv2.dnn.blobFromImage()的签名如下:

代码语言:javascript复制
retval=cv2.dnn.blobFromImage(image[, scalefactor[, size[, mean[, swapRB[, crop[, ddepth]]]]]])

此函数从image创建一个二维 BLOB。 此外,它还可以选择将图像调整为size大小,并从中心裁剪输入图像,减去mean值,按scalefactor缩放值,并交换蓝色和红色通道:

  • image:这是要预处理的输入图像。
  • scalefactor:这是image值的乘数。 此值可用于缩放我们的图像。 默认值为1.0,这表示不执行缩放。
  • size:这是输出图像的空间大小。
  • mean:这是从图像中减去平均值的标量。 如果执行均值减法,则在使用swapRB =True时,这些值应为(mean-Rmean-Gmean-B)。
  • swapRB:通过将该标志设置为True,可以使用该标志交换图像中的RB通道。
  • crop:这是一个标志,指示在调整大小后是否将裁切图像。
  • ddepth:输出 BLOB 的深度。 您可以在CV_32FCV_8U之间选择。
  • 如果为crop=False,则在不裁剪的情况下执行图像的调整大小。 否则,如果(crop=True),则首先应用调整大小,然后从中心裁剪图像。
  • 默认值为scalefactor=1.0size = Size()mean = Scalar()swapRB = falsecrop = falseddepth = CV_32F

cv.dnn.blobFromImages()的签名如下:

代码语言:javascript复制
retval=cv.dnn.blobFromImages(images[, scalefactor[, size[, mean[, swapRB[, crop[, ddepth]]]]]])

此函数从多个图像创建一个四维 BLOB。 这样,您可以对整个网络执行前向传递,以一次计算多个图像的输出。 以下代码显示了如何正确使用此函数:

代码语言:javascript复制
# Create a list of images:
images = [image, image2]

# Call cv2.dnn.blobFromImages():
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)

# Set the blob as input and obtain the detections:
net.setInput(blob_images)
detections = net.forward()

至此,我们介绍了cv2.dnn.blobFromImage()cv2.dnn.blobFromImages()函数。 因此,我们准备看blob_from_image.pyblob_from_images.py脚本。

blob_from_image.py脚本中,我们首先加载 BGR 图像,然后使用cv2.dnn.blobFromImage()函数创建一个二维 BLOB。 您可以检查创建的 BLOB 的形状是否为(1, 3, 300, 300)。 然后,我们调用get_image_from_blob()函数,该函数可用于执行逆预处理转换,以便再次获取输入图像。 这样,您将更好地了解此预处理。 get_image_from_blob函数的代码如下:

代码语言:javascript复制
def get_image_from_blob(blob_img, scalefactor, dim, mean, swap_rb, mean_added):
    """Returns image from blob assuming that the blob is from only one image""
    images_from_blob = cv2.dnn.imagesFromBlob(blob_img)
    image_from_blob = np.reshape(images_from_blob[0], dim) / scalefactor
    image_from_blob_mean = np.uint8(image_from_blob)
    image_from_blob = image_from_blob_mean   np.uint8(mean)

    if mean_added is True:
        if swap_rb:
            image_from_blob = image_from_blob[:, :, ::-1]
        return image_from_blob
    else:
        if swap_rb:
            image_from_blob_mean = image_from_blob_mean[:, :, ::-1]
        return image_from_blob_mean

在脚本中,我们利用此函数从 BLOB 获取不同的图像,如以下代码片段所示:

代码语言:javascript复制
# Load image:
image = cv2.imread("face_test.jpg")

# Call cv2.dnn.blobFromImage():
blob_image = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104., 117., 123.], False, False)

# The shape of the blob_image will be (1, 3, 300, 300):
print(blob_image.shape)

# Get different images from the blob:
img_from_blob = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
img_from_blob_swap = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], True, True)
img_from_blob_mean = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], False, False)
img_from_blob_mean_swap = get_image_from_blob(blob_image, 1.0, (300, 300, 3), [104., 117., 123.], True, False)

创建的图像说明如下:

  • img_from_blob图像对应于调整为(300,300)的原始 BGR 图像。
  • img_from_blob_swap图像对应于调整为(300,300)尺寸的原始 BGR 图像,并且蓝色和红色通道已交换。
  • img_from_blob_mean图像对应于调整为(300,300)尺寸的原始 BGR 图像,其中未将具有平均值的标量添加到图像中。
  • img_from_blob_mean_swap图像对应于调整为(300,300)的原始 BGR 图像,其中未将具有平均值的标量添加到该图像,并且已交换了蓝色和红色通道。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTWelmei-1681870549420)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/60a54618-3de9-4e3c-bcd1-965bd8e68eb1.png)]

在上一个屏幕截图中,我们可以看到获得的四个图像(img_from_blobimg_from_blob_swapimg_from_blob_meanimg_from_blob_mean_swap)。

blob_from_images.py脚本中,我们首先加载两个 BGR 图像,并使用cv2.dnn.blobFromImages()函数创建一个二维 BLOB。 您可以检查创建的 BLOB 的形状是否为(2, 3, 300, 300)。 然后,我们调用get_images_from_blob()函数,该函数可用于执行逆预处理转换,以便再次获取输入图像。

get_images_from_blob函数的代码如下:

代码语言:javascript复制
def get_images_from_blob(blob_imgs, scalefactor, dim, mean, swap_rb, mean_added):
    """Returns images from blob"""

    images_from_blob = cv2.dnn.imagesFromBlob(blob_imgs)
    imgs = []

    for image_blob in images_from_blob:
        image_from_blob = np.reshape(image_blob, dim) / scalefactor
        image_from_blob_mean = np.uint8(image_from_blob)
        image_from_blob = image_from_blob_mean   np.uint8(mean)
        if mean_added is True:
            if swap_rb:
                image_from_blob = image_from_blob[:, :, ::-1]
            imgs.append(image_from_blob)
        else:
            if swap_rb:
                image_from_blob_mean = image_from_blob_mean[:, :, ::-1]
            imgs.append(image_from_blob_mean)

    return imgs

如前所示,get_images_from_blob()函数使用 OpenCV cv2.dnn.imagesFromBlob()函数从 BLOB 返回图像。 在脚本中,我们利用此函数从 BLOB 中获取不同的图像,如下所示:

代码语言:javascript复制
# Load images and get the list of images:
image = cv2.imread("face_test.jpg")
image2 = cv2.imread("face_test_2.jpg")
images = [image, image2]

# Call cv2.dnn.blobFromImages():
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)
# The shape of the blob_image will be (2, 3, 300, 300):
print(blob_images.shape)

# Get different images from the blob:
imgs_from_blob = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
imgs_from_blob_swap = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], True, True)
imgs_from_blob_mean = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], False, False)
imgs_from_blob_mean_swap = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], True, False)

在前面的代码中,我们利用get_images_from_blob()函数从 BLOB 获取不同的图像。 创建的图像说明如下:

  • imgs_from_blob图像对应于调整为(300,300)尺寸的原始 BGR 图像。
  • imgs_from_blob_swap图像对应于调整为(300,300)尺寸的原始 BGR 图像,并且蓝色和红色通道已交换。
  • imgs_from_blob_mean图像对应于调整为(300,300)尺寸的原始 BGR 图像,其中带有平均值的标量尚未添加到图像。
  • imgs_from_blob_mean_swap图像对应于调整为(300,300)的原始 BGR 图像,其中未将具有平均值的标量添加到图像中,并且蓝色和红色通道已交换。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5k4cWWed-1681870549420)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/323d11a7-ef39-416d-a935-52a1aa4702f7.png)]

cv2.dnn.blobFromImage()cv2.dnn.blobFromImages()的最后一个考虑因素是crop参数,该参数指示是否裁切图像。 在裁剪的情况下,图像将从中心裁剪,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tqHnqaTA-1681870549421)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/4d786113-f07b-412a-8235-5bf0d16a03a5.png)]

如您所见,裁剪是从图像的中心进行的,由黄线表示。 为了复制 OpenCV 在cv2.dnn.blobFromImage()cv2.dnn.blobFromImages()函数内部执行的裁剪,我们对get_cropped_img()函数进行了如下编码:

代码语言:javascript复制
def get_cropped_img(img):
    """Returns the cropped image"""

    # calculate size of resulting image:
    size = min(img.shape[1], img.shape[0])

    # calculate x1, and y1
    x1 = int(0.5 * (img.shape[1] - size))
    y1 = int(0.5 * (img.shape[0] - size))

    # crop and return the image
    return img[y1:(y1   size), x1:(x1   size)]

如您所见,裁剪图像的大小将基于原始图像的最小尺寸。 因此,在前面的示例中,裁剪后的图像将具有(482, 482)的大小。

blob_from_images_cropping.py脚本中,我们看到了裁剪的效果,并且还在get_cropped_img()函数中复制了裁剪过程:

代码语言:javascript复制
# Load images and get the list of images:
image = cv2.imread("face_test.jpg")
image2 = cv2.imread("face_test_2.jpg")
images = [image, image2]

# To see how cropping works, we are going to perform the cropping formulation that
# both blobFromImage() and blobFromImages() perform applying it to one of the input images:
cropped_img = get_cropped_img(image)
# cv2.imwrite("cropped_img.jpg", cropped_img)

# Call cv2.dnn.blobFromImages():
blob_images = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, False)
blob_blob_images_cropped = cv2.dnn.blobFromImages(images, 1.0, (300, 300), [104., 117., 123.], False, True)

# Get different images from the blob:
imgs_from_blob = get_images_from_blob(blob_images, 1.0, (300, 300, 3), [104., 117., 123.], False, True)
imgs_from_blob_cropped = get_images_from_blob(blob_blob_images_cropped, 1.0, (300, 300, 3), [104., 117., 123.], False, True)

在以下屏幕截图中可以看到blob_from_images_cropping.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rGoeqIQW-1681870549421)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/ccca028d-e5b7-4a5b-abfa-e5d1487bb06d.png)]

可以看到在两个加载的图像中裁剪的效果,并且我们还可以理解保持了宽高比。

OpenCV DNN 人脸检测器的完整示例

接下来,我们将看到如何修改face_detection_opencv_dnn.py脚本(摘自第 11 章,“人脸检测,跟踪和识别”),以便执行以下操作:

  • 当几张图像(可能具有不同的大小)馈送到网络时,计算输出– face_detection_opencv_cnn_images.py脚本
  • cv2.dnn.blobFromImages()函数- face_detection_opencv_cnn_images_crop.py脚本中的crop=True参数馈入网络时,将几张图像(可能具有不同的尺寸)馈送到网络时,计算输出

以下屏幕快照显示了face_detection_opencv_cnn_images.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R04V217z-1681870549421)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/053d9827-6290-4caf-aec5-c3895160ace8.png)]

以下屏幕快照显示了face_detection_opencv_cnn_images_crop.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7GGUxUcO-1681870549421)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/d2bfb9de-0435-4583-a77b-092643fc5c0d.png)]

在上一个屏幕截图中,您可以清楚地看到从中心裁剪图像时的区别。

OpenCV 深度学习分类

本节将介绍如何使用不同的预训练模型进行图像分类的几个示例。 请注意,您可以通过使用net.getPerfProfile()方法获得推断时间,如下所示:

代码语言:javascript复制
# Feed the input blob to the network, perform inference and get the output:
net.setInput(blob)
preds = net.forward()

# Get inference time:
t, _ = net.getPerfProfile()
print('Inference time: %.2f ms' % (t * 1000.0 / cv2.getTickFrequency()))

如您所见,在执行推断后将调用net.getPerfProfile()方法。

net.getPerfProfile()方法返回推理的总时间和层的计时(以滴答为单位)。 这样,您可以使用不同的深度学习架构比较推理时间。

我们将从下一部分介绍的 AlexNet 架构开始,了解主要的深度学习分类架构。

用于图像分类的 AlexNet

image_classification_opencv_alexnet_caffe.py脚本中,通过使用 AlexNet 和 Caffe 预训练模型,使用 OpenCV DNN 模块进行图像分类。 第一步是加载类的名称。 第二步是从磁盘加载序列化的 Caffe 模型。 第三步是加载输入图像进行分类。 第四步是创建大小为(227, 2327)(104, 117, 123)平均减法值的 BLOB。 第五步是将输入 BLOB 馈送到网络,执行推理并获得输出。 第六步是获得概率最高(降序排列)的 10 个索引。 这样,具有最高概率(最高预测)的索引将是第一个。 最后,我们将在图像上绘制与最高预测相关的类和概率。 在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LjFKI0je-1681870549421)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/0e19379e-441b-4bfa-b7f9-ae57b01ed713.png)]

如前面的屏幕快照所示,最高的预测对应于教堂的概率为 0.8325679898。

十大预测如下:

  • 1. label: church, probability: 0.8325679898
  • 2. label: monastery, probability: 0.043678388
  • 3. label: mosque, probability: 0.03827961534
  • 4. label: bell cote, probability: 0.02479489893
  • 5. label: beacon, probability: 0.01249620412
  • 6. label: dome, probability: 0.01223050058
  • 7. label: missile, probability: 0.006323920097
  • 8. label: projectile, probability: 0.005275635514
  • 9. label: palace, probability: 0.004289720673
  • 10. label: castle, probability: 0.003241452388

还应注意,在绘制类别和概率时,我们执行以下操作:

代码语言:javascript复制
text = "label: {} probability: {:.2f}%".format(classes[indexes[0]], preds[0][indexes[0]] * 100)
print(text)
y0, dy = 30, 30
for i, line in enumerate(text.split('n')):
    y = y0   i * dy
    cv2.putText(image, line, (5, y), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)

这样,可以将文本拆分并在图像的不同行中绘制。 例如,如果我们执行以下代码,则文本将分两行绘制:

代码语言:javascript复制
text = "label: {}nprobability: {:.2f}%".format(classes[indexes[0]], preds[0][indexes[0]] * 100)

应当注意,bvlc_alexnet.caffemodel文件未包含在本书的存储库中,因为它超过了 GitHub 的文件大小限制 100.00 MB。 您必须从这里下载。

因此,您必须在运行脚本之前下载bvlc_alexnet.caffemodel文件。

用于图像分类的 GoogLeNet

以与先前脚本类似的方式,在image_classification_opencv_googlenet_caffe.py脚本中使用 GoogLeNet 和 Caffe 预训练模型使用 OpenCV CNN 模块进行图像分类。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pyBaNBzW-1681870549422)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/89583a6f-1a18-42eb-94df-622dd59fc7a0.png)]

如前面的屏幕快照所示,最高的预测对应于一座教堂,其概率为 0.9082632661。

十大预测如下:

  • 1. label: church, probability: 0.9082632661
  • 2. label: bell cote, probability: 0.06350905448
  • 3. label: monastery, probability: 0.02046923898
  • 4. label: dome, probability: 0.002624791814
  • 5. label: mosque, probability: 0.001077500987
  • 6. label: fountain, probability: 0.001011475339
  • 7. label: palace, probability: 0.0007750992081
  • 8. label: castle, probability: 0.0002349214483
  • 9. label: pedestal, probability: 0.0002306570677
  • 10. label: analog clock, probability: 0.0002107089822

用于图像分类的 ResNet

用于图像分类的 ResNet 脚本(image_classification_opencv_restnet_50_caffe.py)将使用带有 Caffe 预训练模型的 ResNet-50 进行图像分类。

在以下屏幕截图中可以看到输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8QrF8lny-1681870549422)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/c0c5f278-fa72-4409-8340-d85a458acb21.png)]

如前面的屏幕快照所示,最高的预测对应于一座教堂的概率为 0.9955400825。

十大预测如下:

  • 1. label: church, probability: 0.9955400825
  • 2. label: dome, probability: 0.002429900225
  • 3. label: bell cote, probability: 0.0007424423238
  • 4. label: monastery, probability: 0.0003768313909
  • 5. label: picket fence, probability: 0.0003282549733
  • 6. label: mosque, probability: 0.000258318265
  • 7. label: mobile home, probability: 0.0001083607058
  • 8. label: stupa, probability: 2.96174203e-05
  • 9. label: palace, probability: 2.621001659e-05
  • 10. label: beacon, probability: 2.02897063e-05

用于图像分类的 SqueezeNet

image_classification_opencv_squeezenet_caffe.py脚本中,我们使用 SqueezeNet 架构执行图像分类,该架构可将 AlexNet 级别的精度降低 50 倍。 在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eaWCu5Q2-1681870549422)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/fa426dba-88e1-4c61-a2b9-d58841e0b756.png)]

如前面的屏幕快照所示,最高的预测对应于一座教堂的概率为 0.9967952371。

在此脚本中,我们使用的是 SqueezeNet v1.1,其计算量比 v1.0 少 2.4 倍,但又不牺牲任何准确率。

十大预测如下:

  • 1. label: church, probability: 0.9967952371
  • 2. label: monastery, probability: 0.001899079769
  • 3. label: bell cote, probability: 0.0006924766349
  • 4. label: mosque, probability: 0.0002616141282
  • 5. label: dome, probability: 0.0001891527208
  • 6. label: palace, probability: 0.0001046952093
  • 7. label: stupa, probability: 8.239243471e-06
  • 8. label: vault, probability: 7.135886335e-06
  • 9. label: triumphal arch, probability: 6.732503152e-06
  • 10. label: cinema, probability: 4.201304819e-06

OpenCV 深度学习对象检测

本节将介绍如何使用不同的预训练模型执行对象检测的几个示例。 对象检测尝试检测图像或视频中预定义类(例如猫,汽车和人)的语义对象实例。

用于对象检测的 MobileNet-SSD

我们将结合使用 MobileNet 架构和 SSD 框架。 MobileNets 可以看作是用于移动视觉应用的高效卷积神经网络。

MobileNet-SSD 在 COCO 数据集上进行了训练,并在 PASCAL VOC 上进行了微调,达到了 72.27% 的 mAP(请参阅汇总 mAP 的表格以了解对象检测算法,以将该指标置于上下文中)。 在 PASCAL VOC 上进行微调时,可以检测到 20 个对象类,如下所示:

  • :人
  • 动物:鸟,猫,牛,狗,马和羊
  • 车辆:飞机,自行车,轮船,公共汽车,汽车,摩托车和火车
  • 室内:瓶子,椅子,餐桌,盆栽,沙发和电视/显示器

object_detection_opencv_mobilenet_caffe.py脚本中,我们使用 OpenCV DNN 模块通过使用 MobileNet-SSD 和 Caffe 预训练模型来执行对象检测。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q7RoohAR-1681870549422)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/15ba0e5f-13b0-431f-94d9-fa672e3ef959.png)]

如上一个屏幕截图所示,所有对象都可以高精度正确检测。

用于对象检测的 YOLO

object_detection_opencv_yolo_darknet.py脚本中,使用 YOLO v3 进行对象检测。 YOLO v3 使用了一些技巧来改善训练并提高表现,其中包括多尺度预测和更好的主干分类器。

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NIGtQ5Cx-1681870549422)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/0a666017-27ad-4445-836d-568bd2ac7f0c.png)]

如上一个屏幕截图所示,可以高度准确地检测到除一个(绵羊)以外的所有物体。 因此,您必须在运行脚本之前下载yolov3.weights文件。

应当注意,yolov3.weights文件未包含在本书的存储库中,因为它超过了 GitHub 的文件大小限制 100.00 MB。 您必须从这里下载。

TensorFlow 库

TensorFlow 是 Google Brain 团队为内部使用而开发的用于机器学习和深度学习的开源软件平台。 随后,TensorFlow 于 2015 年在 Apache 许可下发布。在本节中,我们将看到一些示例,以向您介绍 TensorFlow 库。

TensorFlow 的介绍示例

TensorFlow 库通过将操作链接到计算图中来表示要执行的计算。 创建此计算图后,您可以打开 TensorFlow 会话并执行该计算图以获取结果。 可以在tensorflow_basic_op.py脚本中看到此过程,该脚本执行在计算图中定义的乘法运算,如下所示:

代码语言:javascript复制
# path to the folder that we want to save the logs for Tensorboard
logs_path = "./logs"

# Define placeholders:
X_1 = tf.placeholder(tf.int16, name="X_1")
X_2 = tf.placeholder(tf.int16, name="X_2")

# Define a multiplication operation:
multiply = tf.multiply(X_1, X_2, name="my_multiplication")

在会话中运行图时,将提供占位符的值,如以下代码片段所示:

代码语言:javascript复制
# Start the session and run the operation with different inputs:
with tf.Session() as sess:
    summary_writer = tf.summary.FileWriter(logs_path, sess.graph)

    print("2 x 3 = {}".format(sess.run(multiply, feed_dict={X_1: 2, X_2: 3})))
    print("[2, 3] x [3, 4] = {}".format(sess.run(multiply, feed_dict={X_1: [2, 3], X_2: [3, 4]})))

如您所见,计算图已参数化以访问外部输入,称为占位符。 在同一会话中,我们将使用不同的输入执行两次乘法。 由于计算图是 TensorFlow 的关键点,因此图的可视化可以帮助您使用 TensorBoard 来理解和调试图,TensorBoard 是任何标准 TensorFlow 安装随附的可视化软件。 要使用 TensorBoard 可视化计算图,您需要使用tf.summary.FileWriter()编写程序的日志文件,如前所示。 如果执行此脚本,则会在执行该脚本的相同位置创建logs目录。 要运行 TensorBoard,您应该执行以下代码:

代码语言:javascript复制
$ tensorboard --logdir="./logs"

这将生成一个链接(http://localhost:6006/),供您在浏览器中输入,您将看到 TensorBoard 页面,该页面可以在以下屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNTtwnpT-1681870549423)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/1c45b552-eeb7-46bd-bad0-6b0fde503a29.png)]

您可以看到先前脚本的计算图。 另外,由于 TensorFlow 图可以具有成千上万个节点,因此可以创建范围以简化可视化,并且 TensorBoard 使用此信息来定义图中节点的层次结构。 这个想法显示在tensorflow_basic_ops_scope.py脚本中,其中我们在Operations范围内定义了两个操作(加法和乘法),如下所示:

代码语言:javascript复制
with tf.name_scope('Operations'):
    addition = tf.add(X_1, X_2, name="my_addition")
    multiply = tf.multiply(X_1, X_2, name="my_multiplication")

如果执行脚本并重复前面的步骤,则可以在以下屏幕截图中看到 TensorBoard 中显示的计算图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tg1gLa1h-1681870549423)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/d750ac7b-0496-4ba1-8c27-e6d6da46eef5.png)]

请注意,您还可以在脚本中使用常量(tf.Constant)和变量(tf.Variable)。 tf.Variabletf.placeholder之间的差异在于传递值的时间。 如您在前面的示例中所看到的,使用tf.placeholder不必提供初始值,并且这些值在运行时使用会话内的feed_dict参数指定。 另一方面,如果使用tf.Variable变量,则在声明它时必须提供一个初始值。

占位符只是一个变量,之后将向其分配数据。 在训练/测试算法时,通常使用占位符将训练/测试数据输入到计算图中。

为了简化起见,我们不会在下一个脚本中显示已创建的计算图,但是推荐使用 TensorBoard 来可视化计算图的方法,因为这将有助于您理解(以及验证)执行哪些计算。

TensorFlow 中的线性回归

在接下来的示例中,我们将使用 TensorFlow 执行线性回归,以帮助您了解训练和测试深度学习算法时所需的其他概念。

更具体地说,我们将看到三个脚本。 在每个脚本中,我们将涵盖以下主题:

  • tensorflow_linear_regression_training.py:此脚本生成线性回归模型。
  • tensorflow_linear_regression_testing.py:此脚本加载创建的模型并使用它进行新的预测。
  • tensorflow_save_and_load_using_model_builder.py:此脚本加载创建的模型,并使用SavedModelBuilder()导出模型以进行推断。 此外,此脚本还加载最终模型以做出新的预测。

线性回归是一种非常普遍的统计方法,它使我们能够根据给定的二维样本点集对关系进行建模。 在这种情况下,模型函数如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GqM9lwa2-1681870549423)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/fa7cc8a7-9590-45be-a124-12357c83b63a.png)]

这描述了具有W斜率和y-截距b的线。 因此,目标是找到Wb参数的值,这些值将在某种意义上为二维采样点提供最佳拟合(例如,最小化均方误差)。

在训练线性回归模型(请参阅tensorflow_linear_regression_training.py)时,第一步是生成一些数据,用于训练算法,如下所示:

代码语言:javascript复制
x = np.linspace(0, N, N)
y = 3 * np.linspace(0, N, N)   np.random.uniform(-10, 10, N)

下一步是定义占位符,以便在训练过程中将我们的训练数据输入到优化器中,如下所示:

代码语言:javascript复制
X = tf.placeholder("float", name='X')
Y = tf.placeholder("float", name='Y')

此时,我们为权重和偏差声明两个变量(随机初始化),如下所示:

代码语言:javascript复制
W = tf.Variable(np.random.randn(), name="W")
b = tf.Variable(np.random.randn(), name="b")

下一步是构建线性模型,如下所示:

代码语言:javascript复制
y_model = tf.add(tf.multiply(X, W), b, name="y_model")

我们还定义了成本函数。 在这种情况下,我们将使用均方误差cost函数,如以下代码片段所示:

代码语言:javascript复制
cost = tf.reduce_sum(tf.pow(y_model - Y, 2)) / (2 * N)

现在,我们创建梯度下降优化器,以最小化cost函数,修改Wb变量的值。

传统的优化器称为梯度下降(旨在查找函数最小值的迭代优化算法),如下所示:

代码语言:javascript复制
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

学习率参数控制每次梯度下降算法更新时系数的变化量。 如前所述,梯度下降是一种迭代优化算法,因此,在每次迭代中,根据学习率参数修改参数。

创建模型的最后一步是执行变量的初始化,如下所示:

代码语言:javascript复制
init = tf.global_variables_initializer()

此时,我们可以在一个会话中开始训练过程,如以下代码片段所示:

代码语言:javascript复制
# Start the training procedure inside a TensorFlow Session:
with tf.Session() as sess:
    # Run the initializer:
    sess.run(init)

    # Uncomment if you want to see the created graph
    # summary_writer = tf.summary.FileWriter(logs_path, sess.graph)

    # Iterate over all defined epochs:
    for epoch in range(training_epochs):

        # Feed each training data point into the optimizer:
        for (_x, _y) in zip(x, y):
            sess.run(optimizer, feed_dict={X: _x, Y: _y})

        # Display the results every 'display_step' epochs:
        if (epoch   1) % disp_step == 0:
            # Calculate the actual cost, W and b:
            c = sess.run(cost, feed_dict={X: x, Y: y})
            w_est = sess.run(W)
            b_est = sess.run(b)
            print("Epoch", (epoch   1), ": cost =", c, "W =", w_est, "b =", b_est)

    # Save the final model
    saver.save(sess, './linear_regression')

    # Storing necessary values to be used outside the session
    training_cost = sess.run(cost, feed_dict={X: x, Y: y})
    weight = sess.run(W)
    bias = sess.run(b)

print("Training finished!")

如前面的代码片段所示,一旦会话开始,我们将运行初始化器,然后对所有定义的时期进行迭代以训练线性回归模型。 此外,我们为每个display_step周期打印结果。 最后,训练完成后,我们将保存最终模型。

至此,训练结束,我们可以显示结果了,可以在以下屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kfxdYkx-1681870549423)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b6128011-c18f-4685-996e-42a2279ff6d7.png)]

在上图中,我们可以看到训练数据(左)和与线性回归模型相对应的拟合线(右)。

保存最终模型(saver.save(sess, './linear_regression'))时,将创建四个文件:

  • .meta文件:包含 TensorFlow 图
  • .data文件:包含权重,偏差,梯度和所有其他已保存变量的值
  • .index文件:标识检查点
  • checkpoint文件:记录保存的最新检查点文件

此时,我们可以加载预训练的模型并将其用于预测目的。 这在tensorflow_linear_regression_testing.py脚本中执行。 加载模型时要做的第一件事是从.meta文件中加载图,如下所示:

代码语言:javascript复制
tf.reset_default_graph()
imported_meta = tf.train.import_meta_graph("linear_regression.meta")

第二步是加载变量的值(请注意,这些值仅在会话中存在)。 我们还运行模型以获取Wb的值和新的预测值,如下所示:

代码语言:javascript复制
with tf.Session() as sess:
    imported_meta.restore(sess, './linear_regression')
    # Run the model to get the values of the variables W, b and new prediction values:
    W_estimated = sess.run('W:0')
    b_estimated = sess.run('b:0')
    new_predictions = sess.run(['y_model:0'], {'X:0': new_x})

此时,我们可以显示训练数据,回归线和新获得的预测,可以在以下屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-setqJRLM-1681870549423)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/2ea35e61-e003-42ef-abea-5d49ffe9eb68.png)]

如上一个屏幕截图所示,我们使用了预先训练的模型来进行新的预测(蓝点)。 但是,在生产中提供模型时,我们只希望将模型及其权重很好地打包在一个文件中,以方便存储,版本控制和更新不同模型。 结果将是一个扩展名为.pb的二进制文件,其中包含受训网络的拓扑和权重。 在tensorflow_save_and_load_using_model_builder.py脚本中执行创建此二进制文件的过程以及如何将其用于推理。

在此脚本中,我们对export_model()函数进行了编码,以使用SaveModel导出训练后的模型,如下所示:

代码语言:javascript复制
def export_model():
    """Exports the model"""

    trained_checkpoint_prefix = 'linear_regression'

    loaded_graph = tf.Graph()
    with tf.Session(graph=loaded_graph) as sess:
        sess.run(tf.global_variables_initializer())

        # Restore from checkpoint
        loader = tf.train.import_meta_graph(trained_checkpoint_prefix   '.meta')
        loader.restore(sess, trained_checkpoint_prefix)

        # Add signature:
        ...
        signature_map = {signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature}

        # Export model:
        builder = tf.saved_model.builder.SavedModelBuilder('./my_model')
        builder.add_meta_graph_and_variables(sess, signature_def_map=signature_map,
                                             tags=[tf.saved_model.tag_constants.SERVING])
        builder.save()

这将在my_model文件夹内创建saved_model.pb。 在这一点上,为了验证是否正确生成了导出的模型,我们可以同时导入和使用它,以便进行新的预测,如下所示:

代码语言:javascript复制
with tf.Session(graph=tf.Graph()) as sess:
    tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], './my_model')
    graph = tf.get_default_graph()
    x = graph.get_tensor_by_name('X:0')
    model = graph.get_tensor_by_name('y_model:0')
    print(sess.run(model, {x: new_x}))

调用load函数后,该图将作为默认图被加载。 此外,变量也已加载,因此您可以开始对任何新数据运行推理。 这将输出[153.04472 166.54755 180.05037]数组,它对应于我们的模型生成的预测值。

使用 TensorFlow 的手写数字识别

在此示例中,我们将使用 TensorFlow 对图像进行分类。 更具体地说,我们将创建一个简单的模型(softmax 回归模型),用于使用 MNIST 数据集学习和预测图像中的手写数字。

Softmax 回归是可用于多类分类的逻辑回归的概括。 MNIST 数据集包含各种手写的数字图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qtvUQCyV-1681870549424)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/1d2b0d6a-6ef9-45c8-b350-baf6e49a2747.png)]

mnist_tensorflow_save_model.py脚本创建用于学习和预测图像中手写数字的模型。

主要步骤如下所示。 您可以使用以下代码自动导入此数据集:

代码语言:javascript复制
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets("MNIST/", one_hot=True)

下载的数据集由三部分组成-55,000 行mnist.train训练数据,10,000 行mnist.test测试数据和 5,000 行mnist.validation验证数据。 此外,训练,测试和验证部分还为每个数字包含相应的标签。 例如,训练数据由mnist.train.images(训练数据集图像)和mnist.train.labels(训练数据集标签)组成。 每个图像由28 x 28像素组成,从而形成784元素数组。 one_hot=True选项意味着标签将以这样的方式表示:特定数字的1只有一位。 例如,对于9,相应的标签将为[0 0 0 0 0 0 0 0 0 1]

这项技术称为单热编码,这意味着标签已从单个数字转换为向量,向量的长度等于可能的类数。 这样,除了i元素(其值将是与i类相对应的1之外),向量的所有元素都将设置为零。

在定义占位符时,我们需要匹配其形状和类型,以便将数据输入以下变量:

代码语言:javascript复制
x = tf.placeholder(tf.float32, shape=[None, 784], name='myInput')
y = tf.placeholder(tf.float32, shape=[None, 10], name='Y')

当我们将None分配给占位符时,这意味着可以根据需要为该占位符提供尽可能多的示例。 在这种情况下,x占位符可以输入任何 784 维向量。 因此,该张量的形状为[None, 784 ]。 此外,我们还创建了y占位符,以提供真实标签。 在这种情况下,该张量的形状将为[None, 10]

至此,我们可以开始构建计算图了。 第一步是如下创建Wb变量:

代码语言:javascript复制
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

创建Wb变量并将它们初始化为零,因为 TensorFlow 会在训练时优化这些值。 W的尺寸为[784, 10],因为我们想将其乘以与某个图像表示形式相对应的 784 维数组,以获得 10 维输出向量。

现在,我们可以按以下方式实现我们的模型:

代码语言:javascript复制
output_logits = tf.matmul(x, W)   b
y_pred = tf.nn.softmax(output_logits, name='myOutput')

tf.matmul()用于矩阵乘法,tf.nn.softmax()用于将softmax函数应用于输入张量,这意味着输出已归一化并且可以解释为概率。 在这一点上,我们可以定义损失函数,即创建优化器(在本例中为AdamOptimizer),模型的准确率如下:

代码语言:javascript复制
# Define the loss function, optimizer, and accuracy
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=output_logits), name='loss')
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate, name='Adam-op').minimize(loss)
correct_prediction = tf.equal(tf.argmax(output_logits, 1), tf.argmax(y, 1), name='correct_pred')
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32), name='accuracy')

最后,我们可以训练模型,并使用mnist.validation验证数据对其进行验证,并按如下方式保存模型:

代码语言:javascript复制
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(num_steps):
        # Get a batch of training examples and their corresponding labels.
        x_batch, y_true_batch = data.train.next_batch(batch_size)

        # Put the batch into a dict to be fed into the placeholders
        feed_dict_train = {x: x_batch, y: y_true_batch}
        sess.run(optimizer, feed_dict=feed_dict_train)

    # Validation:
    feed_dict_validation = {x: data.validation.images, y: data.validation.labels}
    loss_test, acc_test = sess.run([loss, accuracy], feed_dict=feed_dict_validation)
    print("Validation loss: {}, Validation accuracy: {}".format(loss_test, acc_test))

    # Save model:
    saved_path_model = saver.save(sess, './softmax_regression_model_mnist')
    print('Model has been saved in {}'.format(saved_path_model))

保存模型后,我们可以使用它来识别图像中的手写数字。 在mnist_save_and_load_model_builder.py脚本中,我们将在my_model文件夹中创建saved_model.pb,并使用该模型对使用 OpenCV 加载图像进行新的预测。 为了保存模型,我们使用了上一节介绍的export_model()函数。 为了做出新的预测,我们使用以下代码:

代码语言:javascript复制
# Load some test images:
test_digit_0 = load_digit("digit_0.png")
test_digit_1 = load_digit("digit_1.png")
test_digit_2 = load_digit("digit_2.png")
test_digit_3 = load_digit("digit_3.png")

with tf.Session(graph=tf.Graph()) as sess:
    tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], './my_model')
    graph = tf.get_default_graph()
    x = graph.get_tensor_by_name('myInput:0')
    model = graph.get_tensor_by_name('myOutput:0')
    output = sess.run(model, {x: [test_digit_0, test_digit_1, test_digit_2, test_digit_3]})
    print("predicted labels: {}".format(np.argmax(output, axis=1)))

在此,test_digit_0test_digit_1test_digit_2test_digit_3是四个加载的图像,每个图像包含一个数字。 要加载每个图像,我们使用load_digit()函数,如下所示:

代码语言:javascript复制
def load_digit(image_name):
    """Loads a digit and pre-process in order to have the proper format"""

    gray = cv2.imread(image_name, cv2.IMREAD_GRAYSCALE)
    gray = cv2.resize(gray, (28, 28))
    flatten = gray.flatten() / 255.0
    return flatten

如您所见,我们必须对每个图像进行预处理,以具有与 MNIST 数据库图像的格式相对应的正确格式。 如果执行此脚本,则将为每个图像获得以下预测类:

代码语言:javascript复制
predicted labels: [0 1 2 3]

Keras 库

Keras 是用 Python 编写的开放源代码,高级神经网络 API(与 Python 2.7-3.6 兼容)。 它能够在 TensorFlow,Microsoft Cognitive Toolkit,Theano 或 PlaidML 之上运行,并且其开发重点是实现快速实验。 在本节中,我们将看到两个示例。 在第一个示例中,我们将看到如何使用与上一节中的 TensorFlow 示例相同的输入数据来解决线性回归问题。 在第二个示例中,我们将使用 MNIST 数据集对一些手写数字进行分类,就像在上一节中使用 TensorFlow 进行的操作一样。 这样,当解决相同类型的问题时,您可以清楚地看到两个库之间的差异。

Keras 中的线性回归

linear_regression_keras_training.py数据集执行线性回归模型的训练。 第一步是创建用于训练/测试算法的数据,如下所示:

代码语言:javascript复制
# Generate random data composed by 50 (N = 50) points:
x = np.linspace(0, N, N)
y = 3 * np.linspace(0, N, N)   np.random.uniform(-10, 10, N)

下一步是创建模型。 为此,我们创建了create_model()函数,如以下代码片段所示:

代码语言:javascript复制
def create_model():
    """Create the model using Sequencial model"""

    # Create a sequential model:
    model = Sequential()
    # All we need is a single connection so we use a Dense layer with linear activation:
    model.add(Dense(input_dim=1, units=1, activation="linear", kernel_initializer="uniform"))
    # Compile the model defining mean squared error(mse) as the loss
    model.compile(optimizer=Adam(lr=0.1), loss='mse')

    # Return the created model
    return model

使用 Keras 时,最简单的模型类型是Sequential模型,该模型可以看作是线性的层栈,并且在此示例中用于创建模型。 此外,对于更复杂的架构,可以使用 Keras 函数式 API,该 API 允许构建任意的层图。 因此,使用Sequential模型,我们通过使用model.add()方法堆叠层来构建模型。 在此示例中,我们使用具有线性激活函数的单个密集全连接层。 接下来,我们可以编译(或配置)将均方误差MSE)定义为损失的模型。 在这种情况下,将使用Adam优化器,并设置学习率0.1

此时,我们现在可以使用model.fit()方法训练提供数据的模型,如下所示:

代码语言:javascript复制
linear_reg_model.fit(x, y, epochs=100, validation_split=0.2, verbose=1)

训练后,我们可以获得wb的值(学习的参数),这些值将用于计算预测,如下所示:

代码语言:javascript复制
w_final, b_final = get_weights(linear_reg_model)

get_weights()函数返回这些参数的值,如下所示:

代码语言:javascript复制
def get_weights(model):
    """Get weights of w and b"""

    w = model.get_weights()[0][0][0]
    b = model.get_weights()[1][0]
    return w, b

在这一点上,我们可以建立以下预测:

代码语言:javascript复制
# Calculate the predictions:
predictions = w_final * x   b_final

我们还可以如下保存模型:

代码语言:javascript复制
linear_reg_model.save_weights("my_model.h5")

在以下屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kN6uNOru-1681870549424)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/ddf59653-c58c-407e-9823-d584607a3337.png)]

如前面的屏幕快照所示,我们既可以看到训练数据(在左侧),也可以看到与线性回归模型相对应的拟合线(在右侧)。

我们可以加载预训练的模型进行预测。 可以在linear_regression_keras_testing.py脚本中看到此示例。 第一步是按以下方式加载权重:

代码语言:javascript复制
linear_reg_model.load_weights('my_model.h5')

使用get_weights()函数,我们可以获得如下学习的参数:

代码语言:javascript复制
m_final, b_final = get_weights(linear_reg_model)

此时,我们获得了训练数据的以下预测,并且还获得了新的预测:

代码语言:javascript复制
predictions = linear_reg_model.predict(x)
new_predictions = linear_reg_model.predict(new_x)

最后一步是显示获得的结果,可以在以下屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KprDgC9d-1681870549424)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/a93deb66-88b8-4d49-bf65-b720c7ad3c64.png)]

如上一个屏幕截图所示,我们使用了预先训练的模型来进行新的预测(蓝点)。

Keras 中的手写数字识别

在此示例中,我们将看到如何使用 Keras 识别手写数字。 mnist_keras_training.py脚本使用四层神经网络创建模型,如以下代码片段所示:

代码语言:javascript复制
def create_model():
    """Create the model using Sequencial model"""

    # Create a sequential model (a simple NN is created) adding a softmax activation at the end with 10 units:
    model = Sequential()
    model.add(Dense(units=128, activation="relu", input_shape=(784,)))
    model.add(Dense(units=128, activation="relu"))
    model.add(Dense(units=128, activation="relu"))
    model.add(Dense(units=10, activation="softmax"))

    # Compile the model using the loss function "categorical_crossentropy" and Stocastic Gradient Descent optimizer:
    model.compile(optimizer=SGD(0.001), loss="categorical_crossentropy", metrics=["accuracy"])

    # Return the created model
    return model

在这种情况下,我们使用categorical_crossentropy损失函数(该损失函数非常适合比较两个概率分布,并使用随机梯度下降SGD)优化器。

要加载 MNIST 数据,我们必须使用以下代码:

代码语言:javascript复制
(train_x, train_y), (test_x, test_y) = mnist.load_data()

此外,我们必须重新调整加载的数据的形状以使其具有适当的形状,如下所示:

代码语言:javascript复制
train_x = train_x.reshape(60000, 784)
test_x = test_x.reshape(10000, 784)
train_y = keras.utils.to_categorical(train_y, 10)
test_y = keras.utils.to_categorical(test_y, 10)

此时,我们可以创建模型,训练模型,保存创建的模型,并获得评估测试数据时获得的准确率,如下所示:

代码语言:javascript复制
# Create the model:
model = create_model()

# Use the created model for training:
model.fit(train_x, train_y, batch_size=32, epochs=10, verbose=1)

# Save the created model:
model.save("mnist-model.h5")

# Get the accuracy when testing:
accuracy = model.evaluate(x=test_x, y=test_y, batch_size=32)

# Show the accuracy:
print("Accuracy: ", accuracy[1])

此时,我们准备使用预先训练的模型来预测图像中的新手写数字。 这是在mnist_keras_predicting.py脚本中执行的,如下所示:

代码语言:javascript复制
# Note: Images should have black background:
def load_digit(image_name):
    """Loads a digit and pre-process in order to have the proper format"""

    gray = cv2.imread(image_name, cv2.IMREAD_GRAYSCALE)
    gray = cv2.resize(gray, (28, 28))
    gray = gray.reshape((1, 784))

    return gray

# Create the model:
model = create_model()

# Load parameters of the model from the saved mode file:
model.load_weights("mnist-model.h5")

# Load some test images:
test_digit_0 = load_digit("digit_0.png")
test_digit_1 = load_digit("digit_1.png")
test_digit_2 = load_digit("digit_2.png")
test_digit_3 = load_digit("digit_3.png")
imgs = np.array([test_digit_0, test_digit_1, test_digit_2, test_digit_3])
imgs = imgs.reshape(4, 784)

# Predict the class of the loaded images
prediction_class = model.predict_classes(imgs)

# Print the predicted classes:
print("Class: ", prediction_class)

如您所见,我们已经加载了四个图像,并且使用了经过训练的模型来预测这些图像的类别。 获得的输出如下:

代码语言:javascript复制
Class:  [0 1 2 3]

总结

在本章中,我们使用一些流行的库(包括 OpenCV,TensorFlow 和 Keras)对深度学习进行了介绍。 在本章的第一部分,我们概述了用于图像分类和对象检测的最新深度学习架构。 在第二部分中,我们研究了 OpenCV 中的深度学习模块,这些模块提供了 DNN 库,该库通过使用一些流行的深度学习框架进行了预训练的深度网络来实现前向传递(推理)。 因此,从 OpenCV 3.3 开始,可以在我们的应用中使用经过预训练的网络进行预测。 在本章的后面,我们对 TensorFlow 进行了介绍,最后,对 Keras 进行了介绍。

在下一章中,我们将对移动和网络计算机视觉进行介绍。 更具体地说,我们将看到如何使用 OpenCV,Keras 和 Flask 创建 Web 计算机视觉以及 Web 深度学习应用,并学习了如何与它们结合以提供 Web 应用机器学习和深度学习功能。

问题

  1. 本章开头所述的机器学习和深度学习之间的三个主要区别是什么?
  2. 哪一年被认为是深度学习的爆炸式增长?
  3. 以下函数执行什么功能? blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104., 117., 123.], False, False)
  4. 以下几行执行什么操作?
代码语言:javascript复制
net.setInput(blob)
preds = net.forward()
  1. TensorFlow 中的占位符是什么?
  2. 在 TensorFlow 中使用saver.save()保存模型时,将创建四个文件?
  3. 单热编码是什么意思?
  4. Keras 中的顺序模型是什么?
  5. Keras 中model.fit()的作用是什么?

进一步阅读

以下参考资料将帮助您深入了解 Python 的深度学习:

  • 《实用卷积神经网络》,作者 Mohit Sewak,Md.Rezaul Karim 和 Pradeep Pujari(2018

第 4 部分:移动和 Web 计算机视觉

在本书的最后一部分中,您将学习如何使用 Flask(这是 BSD 许可下的一个功能强大的小型 Python Web 框架)来创建计算机视觉和深度学习 Web 应用,以构建计算机视觉和深度学习 Web 应用 。 此外,您还将学习如何将 Flask 应用部署到云中。

本章将介绍以下章节:

  • 第 13 章,“使用 Python 和 OpenCV 的移动和 Web 计算机视觉”

十三、使用 Python 和 OpenCV 的移动和 Web 计算机视觉

Web 计算是一个有趣的话题,因为它允许我们利用云计算。 从这个意义上讲,有许多 Python Web 框架可用于部署应用。 这些框架提供了包的集合,使开发人员可以专注于应用的核心逻辑,而不必处理底层细节(例如,协议,套接字或进程和线程管理等)。

在本章中,我们将使用 Flask(这是一个小型且功能强大的 Python Web 框架,已获得 BSD 许可),以建立计算机视觉和深度学习 Web 应用。 此外,我们将了解如何利用云计算将应用部署到云中,而不是在计算机上运行它们。

本章的主要部分如下:

  • Python Web 框架简介
  • Flask 简介
  • 使用 OpenCV 和 Flask 的 Web 计算机视觉应用
  • 使用 Keras 和 Flask 的深度学习 API
  • 将 Flask 应用部署到云上

技术要求

技术要求如下:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib 包
  • Git 客户端
  • Flask(请参阅下一节“安装包中”的“如何安装 Flask”)
  • Keras(请参阅下一节“安装包”中的“如何安装 Keras”)
  • TensorFlow(请参阅下一节“安装包”中的“如何安装 TensorFlow”)
  • requests(请参阅下一节“安装包”中的“如何安装requests”)
  • Pillow(请参阅下一小节“安装包”的安装方法)

有关如何安装这些要求的更多详细信息,请参见第 1 章,“设置 OpenCV”。 可以通过以下 URL 访问《精通 Python OpenCV 4》的 GitHub 存储库,其中包含从本书第一章到最后一章所需的所有支持项目文件。

在接下来的小节中,我们将介绍如何使用pip命令安装必要的包(Flask,Keras,TensorFlow 和请求)。

安装包

让我们快速回顾一下如何安装所需的包:

  • Flask:您可以使用以下命令安装 Flask:
代码语言:javascript复制
$ pip install flask

要检查安装是否正确执行,只需打开 Python shell 并尝试导入 Flask 库:

代码语言:javascript复制
python
import Flask
  • TensorFlow:您可以使用以下命令安装 TensorFlow:
代码语言:javascript复制
$ pip install tensorflow

要检查安装是否正确执行,只需打开一个 Python shell 并尝试导入 TensorFlow 库:

代码语言:javascript复制
python
import tensorflow
  • Keras:您可以使用以下命令安装 Keras:
代码语言:javascript复制
$ pip install keras

要检查安装是否正确执行,只需打开一个 Python shell 并尝试导入 Keras 库:

代码语言:javascript复制
python
import keras
  • requests:您可以使用以下命令安装requests
代码语言:javascript复制
$ pip install requests

要检查安装是否正确执行,只需打开 Python Shell 并尝试导入请求库:

代码语言:javascript复制
python
import requests
  • Pillow:为了安装 Pillow,请使用以下命令:
代码语言:javascript复制
pip install Pillow

要检查安装是否正确执行,只需打开 Python shell 并尝试导入 Pillow 库:

代码语言:javascript复制
python
import PIL

我想提一下,推荐的方法是在虚拟环境中安装包。 请参阅第 1 章,“设置 OpenCV”,以了解有关创建和管理虚拟环境的更多信息。

Python Web 框架简介

Python Web 框架提供了一组包,这些包使开发人员可以专注于应用的核心逻辑,而不必处理底层细节 (例如,协议,套接字或进程以及线程管理等)。 此外,可以将这些框架分为全栈和非全栈框架。 DjangoFlask 是两个流行的 Python 网络框架,我们将在本章稍后讨论:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnjID1zy-1681870549424)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/5a63e63e-fdef-41c0-9135-5f602eb4922a.png)]

全栈框架的完美示例是 Django,这是一个免费的开源全栈 Python 框架, 尝试默认包含所有必需的功能,而不是将它们作为单独的库提供。 Django 使创建 Web 应用更加容易,并且比其他框架所需的时间更少。 它着重于通过遵循不要重复DRY)的原理来实现尽可能的自动化。 如果您有兴趣学习 Django,建议您阅读本教程,该教程是关于编写第一个 Django 应用。

Flask(已获得 BSD 许可)可被视为非全栈框架的完美示例。 实际上,Flask 被认为是一个微框架,它是一个很少或完全不依赖外部库的框架。

Flask 具有以下依赖关系:

  • WSGI 工具包:
    • WSGI 工具库
  • Jinja2:
    • 模板引擎

Django 和 Flask 均可用于开发计算机视觉和深度学习应用。 但是,公认的是,Django 的学习曲线比 Flask 略陡。 此外,Flask 专注于简约和简约。 例如,Flask 的Hello World应用只有几行代码。 此外,还建议将 Flask 用于较小和较不复杂的应用,而 Django 通常用于较大和较复杂的应用。

在本章中,我们将了解如何将 Flask 用于创建计算机视觉和深度学习 Web 应用。

Flask 简介

正如我们提到的,这是 Flask 的Hello World应用,仅包含几行代码。 可以在hello.py脚本中看到,如下所示:

代码语言:javascript复制
# Import required packages:
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

导入所需的包后,我们将创建Flask类的实例,该实例将成为我们的 Web 服务器网关接口WSGI)应用。 route()装饰器用于指示哪个 URL 应该触发hello()函数,该函数将打印消息Hello World!

在 Flask 中,您可以使用route()装饰器将函数绑定到 URL。

使用以下命令执行此脚本:

代码语言:javascript复制
$ python hello.py

您将在控制台中看到此消息,告诉您 Web 服务器已启动:

代码语言:javascript复制
 * Serving Flask app "hello" (lazy loading)
 * Environment: production
 WARNING: Do not use the development server in a production environment.
 Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL C to quit)

此时,您可以打开浏览器并输入http://127.0.0.1:5000/。 这将对我们的服务器执行GET请求,该请求将返回相应的消息,使我们可以在浏览器中看到它。 这些步骤可以总结在下一个屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fpNTphs0-1681870549425)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/65afcc61-2b4f-4a2a-baf8-7805b9592f0e.png)]

如您所见,浏览器显示Hello World!消息。

在前面的示例中,脚本称为hello.py。 确保不要调用您的应用flask.py,因为这可能导致与 Flask 本身发生冲突。

在前面的示例中,只能从我们自己的计算机访问服务器,而不能从网络中的任何其他服务器访问服务器。 为了使服务器公开可用,在运行服务器应用时应添加参数host=0.0.0.0。 可以在hello_external.py脚本中看到:

代码语言:javascript复制
# Import required packages:
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    # Add parameter host='0.0.0.0' to run on your machines IP address:
    app.run(host='0.0.0.0')

这样,我们可以从连接到该网络的任何其他设备执行请求,如下面的屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-324mPps6-1681870549425)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/97836e1e-4f68-42ee-b809-7396f53846c8.png)]

如前所述,可以使用route()装饰器将函数绑定到 URL,如hello_routes_external.py脚本所示:

代码语言:javascript复制
# Import required packages:
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

@app.route("/user")
def hello_user():
    return "User: Hello World!"

if __name__ == "__main__":
    # Add parameter host='0.0.0.0' to run on your machines IP address:
    app.run(host='0.0.0.0')

在下一个屏幕截图中对此进行了说明,我们在其中请求http://192.168.1.101:5000/user URL,并获得User: Hello World!消息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTDmlFAR-1681870549425)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3a99ba0b-461f-4ef3-94cc-e893acc5b01a.png)]

在本节中,我们介绍了一些使用 Flask 创建应用时必须考虑的基本概念。

在下一节中,我们将看到不同的示例,以了解如何使用 OpenCV 和 Flask 创建 Web 计算机视觉应用。

使用 OpenCV 和 Flask 的 Web 计算机视觉应用

在本节中,我们将看到如何使用 OpenCV 和 Flask 创建 Web 计算机视觉应用。 我们将从使用 OpenCV 和 Flask 的等效Hello world应用开始。

OpenCV 和 Flask 的最小示例

对脚本hello_opencv.py进行了编码,以显示如何使用 OpenCV 执行基本的 Web 计算机视觉应用。 该脚本的代码如下所示:

代码语言:javascript复制
# Import required packages:
import cv2
from flask import Flask, request, make_response
import numpy as np
import urllib.request

app = Flask(__name__)

@app.route('/canny', methods=['GET'])
def canny_processing():
    # Get the image:
    with urllib.request.urlopen(request.args.get('url')) as url:
        image_array = np.asarray(bytearray(url.read()), dtype=np.uint8)

    # Convert the image to OpenCV format:
    img_opencv = cv2.imdecode(image_array, -1)

    # Convert image to grayscale:
    gray = cv2.cvtColor(img_opencv, cv2.COLOR_BGR2GRAY)

    # Perform canny edge detection:
    edges = cv2.Canny(gray, 100, 200)

    # Compress the image and store it in the memory buffer:
    retval, buffer = cv2.imencode('.jpg', edges)

    # Build the response:
    response = make_response(buffer.tobytes())
    response.headers['Content-Type'] = 'image/jpeg'

    # Return the response:
    return response

if __name__ == "__main__":
    # Add parameter host='0.0.0.0' to run on your machines IP address:
    app.run(host='0.0.0.0')

可以通过以下步骤来解释先前的代码:

  1. 第一步是导入所需的包。 在此示例中,我们使用route()装饰器将canny_processing()函数绑定到/canny URL。 此外,还需要url参数才能正确执行GET请求。 为了获得该参数,使用了request.args.get()函数。
  2. 下一步是读取该 URL 持有的图像,如下所示:
代码语言:javascript复制
with urllib.request.urlopen(request.args.get('url')) as url:
    image_array = np.asarray(bytearray(url.read()), dtype=np.uint8)

这样,图像将作为数组读取。

  1. 下一步是将图像转换为 OpenCV 格式并执行 Canny 边缘处理,该处理应在相应的灰度图像上执行:
代码语言:javascript复制
# Convert the image to OpenCV format:
img_opencv = cv2.imdecode(image_array, -1)

# Convet image to grayscale:
gray = cv2.cvtColor(img_opencv, cv2.COLOR_BGR2GRAY)

# Perform canny edge detection:
edges = cv2.Canny(gray, 100, 200)
  1. 下一步是压缩图像并将其存储在内存缓冲区中,如下所示:
代码语言:javascript复制
# Compress the image and store it in the memory buffer:
retval, buffer = cv2.imencode('.jpg', edges)
  1. 最后一步是构建响应并将其返回给客户端,如下所示:
代码语言:javascript复制
# Build and return the response:
response = make_response(buffer.tobytes())
response.headers['Content-Type'] = 'image/jpeg'

# Return the response:
return response

如果我们运行脚本($ python hello_opencv.py),服务器将运行,然后,如果我们从客户端(例如我们的手机)执行GET请求,我们将获得处理后的图像,该图像可以在下一个屏幕截图中可以看到。 此外,请考虑到您可能需要禁用防火墙(在 Windows 上)才能执行以下请求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Csyrt5fR-1681870549425)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/dbf8ff98-1319-4766-b34e-5abf4bfcaa02.png)]

如图所示,我们执行了以下GET请求:

代码语言:javascript复制
http://192.168.1.101:5000/canny?url=https://img.yuanmabao.com/zijie/pic/2023/04/27/4q4oifomt2n.jpg

在这里,https://img.yuanmabao.com/zijie/pic/2023/04/27/4q4oifomt2n.jpg是我们要使用 Web 计算视觉应用处理的图像。

使用 OpenCV 的最小人脸 API

在此示例中,我们将看到如何使用 OpenCV 和 Flask 创建 Web Face API。 minimal_face_api项目对 Web 服务器应用进行编码。 main.py脚本负责解析请求并建立对客户端的响应。 该脚本的代码如下:

代码语言:javascript复制
# Import required packages:
from flask import Flask, request, jsonify
import urllib.request
from face_processing import FaceProcessing

# Initialize application and FaceProcessing():
app = Flask(__name__)
fc = FaceProcessing()

@app.errorhandler(400)
def bad_request(e):
    # return also the code error
    return jsonify({"status": "not ok", "message": "this server could not understand your request"}), 400

@app.errorhandler(404)
def not_found(e):
    # return also the code error
    return jsonify({"status": "not found", "message": "route not found"}), 404

@app.errorhandler(500)
def not_found(e):
    # return also the code error
    return jsonify({"status": "internal error", "message": "internal error occurred in server"}), 500

@app.route('/detect', methods=['GET', 'POST', 'PUT'])
def detect_human_faces():
    if request.method == 'GET':
        if request.args.get('url'):
            with urllib.request.urlopen(request.args.get('url')) as url:
                return jsonify({"status": "ok", "result": fc.face_detection(url.read())}), 200
        else:
            return jsonify({"status": "bad request", "message": "Parameter url is not present"}), 400
    elif request.method == 'POST':
        if request.files.get("image"):
            return jsonify({"status": "ok", "result": fc.face_detection(request.files["image"].read())}), 200
        else:
            return jsonify({"status": "bad request", "message": "Parameter image is not present"}), 400
    else:
        return jsonify({"status": "failure", "message": "PUT method not supported for API"}), 405

if __name__ == "__main__":
    # Add parameter host='0.0.0.0' to run on your machines IP address:
    app.run(host='0.0.0.0')

如您所见,我们利用jsonify()函数创建具有application/json MIME 类型的给定参数的 JSON 表示形式。 可以将 JSON 视为信息交换的事实标准,在此示例中,我们将返回 JSON 响应,而不是像在上一个示例中执行的那样返回图像。 您还可以看到,此 API 支持GETPOST请求。 另外,在main.py脚本中,我们还通过使用errorhandler()装饰函数来注册错误处理器。 还记得在将响应返回给客户端时还要设置错误代码。

图像处理是在face_processing.py脚本中执行的,其中FaceProcessing()类被编码为:

代码语言:javascript复制
# Import required packages:
import cv2
import numpy as np
import os

class FaceProcessing(object):
    def __init__(self):
        self.file = os.path.join(os.path.join(os.path.dirname(__file__), "data"), "haarcascade_frontalface_alt.xml")
        self.face_cascade = cv2.CascadeClassifier(self.file)

    def face_detection(self, image):
        # Convert image to OpenCV format:
        image_array = np.asarray(bytearray(image), dtype=np.uint8)
        img_opencv = cv2.imdecode(image_array, -1)
        output = []
        # Detect faces and build output:
        gray = cv2.cvtColor(img_opencv, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(25, 25))
        for face in faces:
            # face.tolist(): returns a copy of the array data as a Python list
            x, y, w, h = face.tolist()
            face = {"box": [x, y, x   w, y   h]}
            output.append(face)
        # Return output:
        return output

face_detection()方法通过使用 OpenCV detectMultiScale()函数执行面部检测。 对于每个检测到的人脸,我们将获取其坐标(x, y, w, h),并通过以适当格式对检测进行编码来构建box

代码语言:javascript复制
face = {"box": [x, y, x   w, y   h]}

最后,我们将编码的人脸检测添加到output中:

代码语言:javascript复制
output.append(face)

将所有检测到的面部添加到输出后,我们将返回它。

要使用此 API,我们可以按照与前面示例相同的方式从浏览器执行GET请求。 此外,由于我们的 API 也支持POST请求,因此我们包含了两个脚本来测试此 API 的功能。 这些脚本同时执行GETPOST请求,以了解如何与上述 Face API 进行交互。 更具体地说,demo_request.py对面部 API 执行几个请求,以获得不同的响应,并查看错误处理的工作方式。

在此脚本中,我们首先使用错误的 URL 执行GET请求:

代码语言:javascript复制
# Import required packages:
import requests

FACE_DETECTION_REST_API_URL = "http://localhost:5000/detect"
FACE_DETECTION_REST_API_URL_WRONG = "http://localhost:5000/process"
IMAGE_PATH = "test_face_processing.jpg"
URL_IMAGE = "https://img.yuanmabao.com/zijie/pic/2023/04/27/4q4oifomt2n.jpg"

# Submit the GET request:
r = requests.get(FACE_DETECTION_REST_API_URL_WRONG)
# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

在这种情况下,我们得到以下信息:

代码语言:javascript复制
status code: 404
 headers: {'Content-Type': 'application/json', 'Content-Length': '51', 'Server': 'Werkzeug/0.14.1 Python/3.6.6', 'Date': 'Sat, 16 Feb 2019 19:20:25 GMT'}
 content: {'message': 'route not found', 'status': 'not found'}

获得的状态码(404)意味着客户端可以与服务器通信,但是服务器找不到请求的内容。 这是因为请求的 URL(http://localhost:5000/process)不正确。

我们执行的第二个请求是正确的GET请求:

代码语言:javascript复制
# Submit the GET request:
payload = {'url': URL_IMAGE}
r = requests.get(FACE_DETECTION_REST_API_URL, params=payload)
# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

在这种情况下,我们得到以下信息:

代码语言:javascript复制
status code: 200
 headers: {'Content-Type': 'application/json', 'Content-Length': '53', 'Server': 'Werkzeug/0.14.1 Python/3.6.6', 'Date': 'Sat, 16 Feb 2019 19:20:31 GMT'}
 content: {'result': [{'box': [213, 200, 391, 378]}], 'status': 'ok'}

状态码(200)指示请求已成功执行。 此外,您还可以看到已检测到与 Lenna 的脸相对应的一张脸。

我们执行的第三个请求也是GET请求,但有效负载丢失:

代码语言:javascript复制
# Submit the GET request:
r = requests.get(FACE_DETECTION_REST_API_URL)
# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

在这种情况下,我们得到的响应如下:

代码语言:javascript复制
status code: 400
 headers: {'Content-Type': 'application/json', 'Content-Length': '66', 'Server': 'Werkzeug/0.14.1 Python/3.6.6', 'Date': 'Sat, 16 Feb 2019 19:20:32 GMT'}
 content: {'message': 'Parameter url is not present', 'status': 'bad request'}

状态码(400)表示请求错误。 如您所见,url参数丢失。

我们执行的第四个请求是带有正确有效负载的POST请求:

代码语言:javascript复制
# Load the image and construct the payload:
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}

# Submit the POST request:
r = requests.post(FACE_DETECTION_REST_API_URL, files=payload)
# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

我们得到以下回应:

代码语言:javascript复制
status code: 200
 headers: {'Content-Type': 'application/json', 'Content-Length': '449', 'Server': 'Werkzeug/0.14.1 Python/3.6.6', 'Date': 'Sat, 16 Feb 2019 19:20:34 GMT'}
 content: {'result': [{'box': [151, 29, 193, 71]}, {'box': [77, 38, 115, 76]}, {'box': [448, 37, 490, 79]}, {'box': [81, 172, 127, 218]}, {'box': [536, 47, 574, 85]}, {'box': [288, 173, 331, 216]}, {'box': [509, 170, 553, 214]}, {'box': [357, 48, 399, 90]}, {'box': [182, 179, 219, 216]}, {'box': [251, 38, 293, 80]}, {'box': [400, 174, 444, 218]}, {'box': [390, 87, 430, 127]}, {'box': [54, 89, 97, 132]}, {'box': [499, 91, 542, 134]}, {'box': [159, 95, 198, 134]}, {'box': [310, 115, 344, 149]}, {'box': [225, 116, 265, 156]}], 'status': 'ok'}

如您所见,检测到许多面部。 这是因为test_face_processing.jpg包含很多人脸。

最终请求是PUT请求:

代码语言:javascript复制
# Submit the PUT request:
r = requests.put(FACE_DETECTION_REST_API_URL, files=payload)
# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

我们得到以下输出:

代码语言:javascript复制
status code: 405
 headers: {'Content-Type': 'application/json', 'Content-Length': '66', 'Server': 'Werkzeug/0.14.1 Python/3.6.6', 'Date': 'Sat, 16 Feb 2019 19:20:35 GMT'}
 content: {'message': 'PUT method not supported for API', 'status': 'failure'}

如您所见,不支持PUT方法。 此面部 API 仅支持GETPOST方法。

如您在前面的响应中看到的那样,当请求成功执行时,我们将检测到的面部作为 JSON 数据。 为了了解如何解析响应并使用它绘制检测到的面部,我们可以对脚本demo_request_drawing.py进行如下编码:

代码语言:javascript复制
# Import required packages:
import cv2
import numpy as np
import requests
from matplotlib import pyplot as plt

def show_img_with_matplotlib(color_img, title, pos):
    """Shows an image using matplotlib capabilities"""

    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(1, 1, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')

FACE_DETECTION_REST_API_URL = "http://localhost:5000/detect"
IMAGE_PATH = "test_face_processing.jpg"

# Load the image and construct the payload:
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}

# Submit the POST request:
r = requests.post(FACE_DETECTION_REST_API_URL, files=payload)

# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

# Get JSON data from the response and get 'result':
json_data = r.json()
result = json_data['result']

# Convert the loaded image to the OpenCV format:
image_array = np.asarray(bytearray(image), dtype=np.uint8)
img_opencv = cv2.imdecode(image_array, -1)

# Draw faces in the OpenCV image:
for face in result:
    left, top, right, bottom = face['box']
    # To draw a rectangle, you need top-left corner and bottom-right corner of rectangle:
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
    # Draw top-left corner and bottom-right corner (checking):
    cv2.circle(img_opencv, (left, top), 5, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 5, (255, 0, 0), -1)

# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(8, 8))
plt.suptitle("Using face detection API", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Show the output image
show_img_with_matplotlib(img_opencv, "face detection", 1)

# Show the Figure:
plt.show()

从上面可以看出,我们首先加载图像并构造有效负载。 然后,我们执行POST请求。 然后,我们从响应中获取 JSON 数据并获得result

代码语言:javascript复制
# Get JSON data from the response and get 'result':
json_data = r.json()
result = json_data['result']

在这一点上,我们可以绘制检测到的人脸,对所有检测到的人脸进行迭代,如下所示:

代码语言:javascript复制
# Draw faces in the OpenCV image:
for face in result:
    left, top, right, bottom = face['box']
    # To draw a rectangle, you need top-left corner and bottom-right corner of rectangle:
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
    # Draw top-left corner and bottom-right corner (checking):
    cv2.circle(img_opencv, (left, top), 5, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 5, (255, 0, 0), -1)

对于每个检测到的脸部,我们绘制一个矩形以及左上角和右下角点。 下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jII5G9a-1681870549426)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/d35f132c-b6b6-407e-9717-00d7be7011fb.png)]

使用 OpenCV 使用 API​​检测到的人脸

如上一个屏幕截图所示,已检测到所有面部。

使用 OpenCV 的深度学习猫检测 API

遵循在上一个示例中使用的相同方法-使用 OpenCV 的最小面部 API,我们将使用 OpenCV 创建深度学习 API。 更具体地说,我们将看到如何创建深度学习猫检测 API。 cat_detection_api项目对 Web 服务器应用进行编码。 main.py脚本负责解析请求并建立对客户端的响应。 该脚本的代码如下:

代码语言:javascript复制
# Import required packages:
from flask import Flask, request, jsonify
import urllib.request
from image_processing import ImageProcessing

app = Flask(__name__)
ip = ImageProcessing()

@app.errorhandler(400)
def bad_request(e):
    # return also the code error
    return jsonify({"status": "not ok", "message": "this server could not understand your request"}), 400

@app.errorhandler(404)
def not_found(e):
    # return also the code error
    return jsonify({"status": "not found", "message": "route not found"}), 404

@app.errorhandler(500)
def not_found(e):
    # return also the code error
    return jsonify({"status": "internal error", "message": "internal error occurred in server"}), 500

@app.route('/catfacedetection', methods=['GET', 'POST', 'PUT'])
def detect_cat_faces():
    if request.method == 'GET':
        if request.args.get('url'):
            with urllib.request.urlopen(request.args.get('url')) as url:
                return jsonify({"status": "ok", "result": ip.cat_face_detection(url.read())}), 200
        else:
            return jsonify({"status": "bad request", "message": "Parameter url is not present"}), 400
    elif request.method == 'POST':
        if request.files.get("image"):
            return jsonify({"status": "ok", "result": ip.cat_face_detection(request.files["image"].read())}), 200
        else:
            return jsonify({"status": "bad request", "message": "Parameter image is not present"}), 400
    else:
        return jsonify({"status": "failure", "message": "PUT method not supported for API"}), 405

@app.route('/catdetection', methods=['GET', 'POST', 'PUT'])
def detect_cats():
    if request.method == 'GET':
        if request.args.get('url'):
            with urllib.request.urlopen(request.args.get('url')) as url:
                return jsonify({"status": "ok", "result": ip.cat_detection(url.read())}), 200
        else:
            return jsonify({"status": "bad request", "message": "Parameter url is not present"}), 400
    elif request.method == 'POST':
        if request.files.get("image"):
            return jsonify({"status": "ok", "result": ip.cat_detection(request.files["image"].read())}), 200
        else:
            return jsonify({"status": "bad request", "message": "Parameter image is not present"}), 400
    else:
        return jsonify({"status": "failure", "message": "PUT method not supported for API"}), 405

if __name__ == "__main__":
    # Add parameter host='0.0.0.0' to run on your machines IP address:
    app.run(host='0.0.0.0')

如您所见,我们利用route()装饰器将detect_cat_faces()函数绑定到/catfacedetection URL,还将detect_cats()函数绑定到/catdetection URL。 另外,我们利用jsonify()函数创建具有application/json MIME 类型的给定参数的 JSON 表示形式。 您还可以看到,此 API 支持GETPOST请求。 此外,在main.py脚本中,我们还通过使用errorhandler()装饰函数来注册错误处理器。 将响应返回给客户端时,请记住还要设置错误代码。

图像处理在image_processing.py脚本中进行,其中ImageProcessing()类被编码。 从这个意义上讲,仅显示cat_face_detection()cat_detection()方法:

代码语言:javascript复制
class ImageProcessing(object):
    def __init__(self):
        ...
        ...

    def cat_face_detection(self, image):
        image_array = np.asarray(bytearray(image), dtype=np.uint8)
        img_opencv = cv2.imdecode(image_array, -1)
        output = []
        gray = cv2.cvtColor(img_opencv, cv2.COLOR_BGR2GRAY)
        cats = self.cat_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(25, 25))
        for cat in cats:
            # face.tolist(): returns a copy of the array data as a Python list
            x, y, w, h = cat.tolist()
            face = {"box": [x, y, x   w, y   h]}
            output.append(face)
        return output

    def cat_detection(self, image):
        image_array = np.asarray(bytearray(image), dtype=np.uint8)
        img_opencv = cv2.imdecode(image_array, -1)
        # Create the blob with a size of (300,300), mean subtraction values (127.5, 127.5, 127.5):
        # and also a scalefactor of 0.007843:
        blob = cv2.dnn.blobFromImage(img_opencv, 0.007843, (300, 300), (127.5, 127.5, 127.5))

        # Feed the input blob to the network, perform inference and ghe the output:
        self.net.setInput(blob)
        detections = self.net.forward()

        # Size of frame resize (300x300)
        dim = 300

        output = []

        # Process all detections:
        for i in range(detections.shape[2]):
            # Get the confidence of the prediction:
            confidence = detections[0, 0, i, 2]

            # Filter predictions by confidence:
            if confidence > 0.1:
                # Get the class label:
                class_id = int(detections[0, 0, i, 1])

                # Get the coordinates of the object location:
                left = int(detections[0, 0, i, 3] * dim)
                top = int(detections[0, 0, i, 4] * dim)
                right = int(detections[0, 0, i, 5] * dim)
                bottom = int(detections[0, 0, i, 6] * dim)

                # Factor for scale to original size of frame
                heightFactor = img_opencv.shape[0] / dim
                widthFactor = img_opencv.shape[1] / dim

                # Scale object detection to frame
                left = int(widthFactor * left)
                top = int(heightFactor * top)
                right = int(widthFactor * right)
                bottom = int(heightFactor * bottom)

                # Check if we have detected a cat:
                if self.classes[class_id] == 'cat':
                    cat = {"box": [left, top, right, bottom]}
                    output.append(cat)
        return output

如此处所示,实现了两种方法。 cat_face_detection()方法使用 OpenCV detectMultiScale()函数执行猫脸检测。

cat_detection()方法使用在 Cafe-SSD 框架中训练的 MobileNet SSD 对象检测执行猫检测,并且可以检测20类。 在此示例中,我们将检测猫。 因此,如果class_id是一只猫,我们将把检测结果添加到输出中。 有关如何处理检测和使用预训练的深度学习模型的更多信息,我们建议第 12 章,“深度学习简介”,该课程侧重于深度学习。 完整代码可在这个页面中找到。

为了测试此 API,可以使用demo_request_drawing.py脚本,如下所示:

代码语言:javascript复制
# Import required packages:
import cv2
import numpy as np
import requests
from matplotlib import pyplot as plt

def show_img_with_matplotlib(color_img, title, pos):
    """Shows an image using matplotlib capabilities"""

    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(1, 1, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')

CAT_FACE_DETECTION_REST_API_URL = "http://localhost:5000/catfacedetection"
CAT_DETECTION_REST_API_URL = "http://localhost:5000/catdetection"
IMAGE_PATH = "cat.jpg"

# Load the image and construct the payload:
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}

# Convert the loaded image to the OpenCV format:
image_array = np.asarray(bytearray(image), dtype=np.uint8)
img_opencv = cv2.imdecode(image_array, -1)

# Submit the POST request:
r = requests.post(CAT_DETECTION_REST_API_URL, files=payload)

# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

# Get JSON data from the response and get 'result':
json_data = r.json()
result = json_data['result']

# Draw cats in the OpenCV image:
for cat in result:
    left, top, right, bottom = cat['box']
    # To draw a rectangle, you need top-left corner and bottom-right corner of rectangle:
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 0), 2)
    # Draw top-left corner and bottom-right corner (checking):
    cv2.circle(img_opencv, (left, top), 10, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 10, (255, 0, 0), -1)

# Submit the POST request:
r = requests.post(CAT_FACE_DETECTION_REST_API_URL, files=payload)

# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

# Get JSON data from the response and get 'result':
json_data = r.json()
result = json_data['result']

# Draw cat faces in the OpenCV image:
for face in result:
    left, top, right, bottom = face['box']
    # To draw a rectangle, you need top-left corner and bottom-right corner of rectangle:
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
    # Draw top-left corner and bottom-right corner (checking):
    cv2.circle(img_opencv, (left, top), 10, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 10, (255, 0, 0), -1)

# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(6, 7))
plt.suptitle("Using cat detection API", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Show the output image
show_img_with_matplotlib(img_opencv, "cat detection", 1)

# Show the Figure:
plt.show()

在先前的脚本中,我们执行两个POST请求,以便同时检测猫的脸部以及cat.jpg图像中的猫。 此外,我们还解析了两个请求的响应并绘制了结果,这可以在此脚本的输出中看到,如以下屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dbwrYBNy-1681870549426)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/ffc794a6-22a6-48f8-8c4f-b2f7749db1d2.png)]

如前面的屏幕快照所示,绘制了猫脸检测和全身猫检测。

使用 Keras 和 Flask 的深度学习 API

在第 12 章,“深度学习简介”中,我们看到了如何同时使用 TensorFlow 和 Keras 创建深度学习应用。 在本节中,我们将看到如何使用 Keras 和 Flask 创建深度学习 API。

更具体地说,我们将看到如何与 Keras 中包含的经过预先训练的深度学习架构一起使用,然后,我们将看到如何使用这些经过预先训练的深度学习架构来创建深度学习 API。

Keras 应用

Keras 应用 ,与 python 2.7-3.6 兼容,并以 MIT 许可分发)是 Keras 深度学习库的应用模块, 为许多流行的架构(例如 VGG16,ResNet50,Xception 和 MobileNet 等)提供深度学习模型定义和预训练权重,这些架构可用于预测,特征提取和微调。

Keras 安装期间会下载模型架构,但是在实例化模型时会自动下载预先训练的权重。 此外,所有这些深度学习架构都与所有后端(TensorFlow,Theano 和 CNTK)兼容。

这些深度学习架构在 ImageNet 数据集上进行了训练和验证,用于将图像分类为1,000类别或类别之一:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mzNkI2p-1681870549426)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/fd1b50b0-fe4b-47eb-81b9-71c249121d58.png)]

在上一个屏幕截图中,您可以在 Keras 应用模块中查看各个模型的文档:

  • Xception:Xception V1 模型,权重在 ImageNet 上进行了预训练
  • VGG16:VGG16 模型,权重在 ImageNet 上进行了预训练
  • VGG19:VGG19 模型,权重在 ImageNet 上进行了预训练
  • ResNet50:ResNet50 模型,权重在 ImageNet 上进行了预训练
  • InceptionV3:InceptionV3 模型,权重在 ImageNet 上进行了预训练
  • InceptionResNetV2:InceptionResNetV2 模型,权重在 ImageNet 上进行了预训练
  • MobileNet:MobileNet 模型,权重在 ImageNet 上进行了预训练
  • MobileNetV2:MobileNetV2 模型,权重在 ImageNet 上进行了预训练
  • DenseNet121:DenseNet121 模型,权重在 ImageNet 上进行了预训练
  • DenseNet169:DenseNet169 模型,权重在 ImageNet 上进行了预训练
  • DenseNet201:DenseNet201 模型,权重在 ImageNet 上进行了预训练
  • NASNetMobile:NASNetMobile 模型,权重在 ImageNet 上进行了预训练
  • NASNetLarge:NASNetLarge 模型,权重在 ImageNet 上进行了预训练

classification_keras_pretrained_imagenet_models.py脚本中,我们展示了如何将这些经过预训练的模型用于预测。

这些预训练的模型还可以用于特征提取(例如,从任意中间层进行特征提取)和微调(例如,在一组新的类上微调预训练的模型)。

接下来可以看到classification_keras_pretrained_imagenet_models.py脚本的关键代码。 应该注意的是,该脚本需要很长时间才能执行。 完整代码可以在这个页面:

代码语言:javascript复制
# Import required packages
...

def preprocessing_image(img_path, target_size, architecture):
    """Image preprocessing to be used for each Deep Learning architecture"""

    # Load image in PIL format
    img = image.load_img(img_path, target_size=target_size)
    # Convert PIL format to numpy array:
    x = image.img_to_array(img)
    # Convert the image/images into batch format:
    x = np.expand_dims(x, axis=0)
    # Pre-process (prepare) the image using the specific architecture:
    x = architecture.preprocess_input(x)
    return x

def put_text(img, model_name, decoded_preds, y_pos):
    """Show the predicted results in the image"""

    cv2.putText(img, "{}: {}, {:.2f}".format(model_name, decoded_preds[0][0][1], decoded_preds[0][0][2]),
                (20, y_pos), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)

# Path of the input image to be classified:
img_path = 'car.jpg'

# Load some available models:
model_inception_v3 = inception_v3.InceptionV3(weights='imagenet')
model_vgg_16 = vgg16.VGG16(weights='imagenet')
model_vgg_19 = vgg19.VGG19(weights='imagenet')
model_resnet_50 = resnet50.ResNet50(weights='imagenet')
model_mobilenet = mobilenet.MobileNet(weights='imagenet')
model_xception = xception.Xception(weights='imagenet')
model_nasnet_mobile = nasnet.NASNetMobile(weights='imagenet')
model_densenet_121 = densenet.DenseNet121(weights='imagenet')

# Prepare the image for the corresponding architecture:
x_inception_v3 = preprocessing_image(img_path, (299, 299), inception_v3)
x_vgg_16 = preprocessing_image(img_path, (224, 224), vgg16)
x_vgg_19 = preprocessing_image(img_path, (224, 224), vgg19)
x_resnet_50 = preprocessing_image(img_path, (224, 224), resnet50)
x_mobilenet = preprocessing_image(img_path, (224, 224), mobilenet)
x_xception = preprocessing_image(img_path, (299, 299), xception)
x_nasnet_mobile = preprocessing_image(img_path, (224, 224), nasnet)
x_densenet_121 = preprocessing_image(img_path, (224, 224), densenet)

# Get the predicted probabilities:
preds_inception_v3 = model_inception_v3.predict(x_inception_v3)
preds_vgg_16 = model_vgg_16.predict(x_vgg_16)
preds_vgg_19 = model_vgg_19.predict(x_vgg_19)
preds_resnet_50 = model_resnet_50.predict(x_resnet_50)
preds_mobilenet = model_mobilenet.predict(x_mobilenet)
preds_xception = model_xception.predict(x_xception)
preds_nasnet_mobile = model_nasnet_mobile.predict(x_nasnet_mobile)
preds_densenet_121 = model_nasnet_mobile.predict(x_densenet_121)

# Print the results (class, description, probability):
print('Predicted InceptionV3:', decode_predictions(preds_inception_v3, top=5)[0])
print('Predicted VGG16:', decode_predictions(preds_vgg_16, top=5)[0])
print('Predicted VGG19:', decode_predictions(preds_vgg_19, top=5)[0])
print('Predicted ResNet50:', decode_predictions(preds_resnet_50, top=5)[0])
print('Predicted MobileNet:', decode_predictions(preds_mobilenet, top=5)[0])
print('Predicted Xception:', decode_predictions(preds_xception, top=5)[0])
print('Predicted NASNetMobile:', decode_predictions(preds_nasnet_mobile, top=5)[0])
print('Predicted DenseNet121:', decode_predictions(preds_densenet_121, top=5)[0])

# Show results:
numpy_image = np.uint8(image.img_to_array(image.load_img(img_path))).copy()
numpy_image = cv2.resize(numpy_image, (500, 500))
numpy_image_res = numpy_image.copy()

put_text(numpy_image_res, "InceptionV3", decode_predictions(preds_inception_v3), 40)
put_text(numpy_image_res, "VGG16", decode_predictions(preds_vgg_16), 65)
put_text(numpy_image_res, "VGG19", decode_predictions(preds_vgg_19), 90)
put_text(numpy_image_res, "ResNet50", decode_predictions(preds_resnet_50), 115)
put_text(numpy_image_res, "MobileNet", decode_predictions(preds_mobilenet), 140)
put_text(numpy_image_res, "Xception", decode_predictions(preds_xception), 165)
put_text(numpy_image_res, "NASNetMobile", decode_predictions(preds_nasnet_mobile), 190)
put_text(numpy_image_res, "DenseNet121", decode_predictions(preds_densenet_121), 215)

第一步是导入所需的包,如下所示:

代码语言:javascript复制
from keras.preprocessing import image
from keras.applications import inception_v3, vgg16, vgg19, resnet50, mobilenet, xception, nasnet, densenet
from keras.applications.imagenet_utils import decode_predictions

第二步是实例化不同的模型架构,如下所示:

代码语言:javascript复制
# Load some available models:
model_inception_v3 = inception_v3.InceptionV3(weights='imagenet')
model_vgg_16 = vgg16.VGG16(weights='imagenet')
model_vgg_19 = vgg19.VGG19(weights='imagenet')
model_resnet_50 = resnet50.ResNet50(weights='imagenet')
model_mobilenet = mobilenet.MobileNet(weights='imagenet')
model_xception = xception.Xception(weights='imagenet')
model_nasnet_mobile = nasnet.NASNetMobile(weights='imagenet')
model_densenet_121 = densenet.DenseNet121(weights='imagenet')

第三步是加载和预处理图像以进行分类。 为此,我们具有preprocessing_image()函数:

代码语言:javascript复制
def preprocessing_image(img_path, target_size, architecture):
    """Image preprocessing to be used for each Deep Learning architecture"""

    # Load image in PIL format
    img = image.load_img(img_path, target_size=target_size)
    # Convert PIL format to numpy array:
    x = image.img_to_array(img)
    # Convert the image/images into batch format:
    x = np.expand_dims(x, axis=0)
    # Pre-process (prepare) the image using the specific architecture:
    x = architecture.preprocess_input(x)
    return x

preprocessing_image()函数的第一步是使用image.load_img()函数加载图像,并指定目标尺寸。 应当注意,Keras 以 PIL 格式(width, height)加载图像,应使用image.img_to_array()函数将其转换为 NumPy 格式(height, width, channels)。 然后,应使用 NumPy 的expand_dims()函数将输入图像转换为三维张量(batchsize, height, width, channels)。 预处理图像的最后一步是对图像进行规范化,这是每种架构所特有的。 这可以通过调用preprocess_input()函数来实现。

我们使用上述preprocessing_image(),如下所示:

代码语言:javascript复制
# Prepare the image for the corresponding architecture:
x_inception_v3 = preprocessing_image(img_path, (299, 299), inception_v3)
x_vgg_16 = preprocessing_image(img_path, (224, 224), vgg16)
x_vgg_19 = preprocessing_image(img_path, (224, 224), vgg19)
x_resnet_50 = preprocessing_image(img_path, (224, 224), resnet50)
x_mobilenet = preprocessing_image(img_path, (224, 224), mobilenet)
x_xception = preprocessing_image(img_path, (299, 299), xception)
x_nasnet_mobile = preprocessing_image(img_path, (224, 224), nasnet)
x_densenet_121 = preprocessing_image(img_path, (224, 224), densenet)

一旦对图像进行了预处理,我们可以使用model.predict()获得分类结果(每个类的预测概率):

代码语言:javascript复制
# Get the predicted probabilities:
preds_inception_v3 = model_inception_v3.predict(x_inception_v3)
preds_vgg_16 = model_vgg_16.predict(x_vgg_16)
preds_vgg_19 = model_vgg_19.predict(x_vgg_19)
preds_resnet_50 = model_resnet_50.predict(x_resnet_50)
preds_mobilenet = model_mobilenet.predict(x_mobilenet)
preds_xception = model_xception.predict(x_xception)
preds_nasnet_mobile = model_nasnet_mobile.predict(x_nasnet_mobile)
preds_densenet_121 = model_nasnet_mobile.predict(x_densenet_121)

可以将预测值解码为元组列表(class IDdescriptionconfidence of prediction):

代码语言:javascript复制
# Print the results (class, description, probability):
print('Predicted InceptionV3:', decode_predictions(preds_inception_v3, top=5)[0])
print('Predicted VGG16:', decode_predictions(preds_vgg_16, top=5)[0])
print('Predicted VGG19:', decode_predictions(preds_vgg_19, top=5)[0])
print('Predicted ResNet50:', decode_predictions(preds_resnet_50, top=5)[0])
print('Predicted MobileNet:', decode_predictions(preds_mobilenet, top=5)[0])
print('Predicted Xception:', decode_predictions(preds_xception, top=5)[0])
print('Predicted NASNetMobile:', decode_predictions(preds_nasnet_mobile, top=5)[0])
print('Predicted DenseNet121:', decode_predictions(preds_densenet_121, top=5)[0])

应该注意的是,我们为批量中的每个图像获得一个元组列表(class IDdescriptionconfidence of prediction)。

在这种情况下,仅一个图像用作输入。 获得的输出如下:

代码语言:javascript复制
Predicted InceptionV3: [('n04285008', 'sports_car', 0.5347126), ('n03459775', 'grille', 0.26265427), ('n03100240', 'convertible', 0.04198084), ('n03770679', 'minivan', 0.030852199), ('n02814533', 'beach_wagon', 0.01985116)]
 Predicted VGG16: [('n03770679', 'minivan', 0.38101497), ('n04285008', 'sports_car', 0.11982699), ('n04037443', 'racer', 0.079280525), ('n02930766', 'cab', 0.063257575), ('n02974003', 'car_wheel', 0.058513235)]
 Predicted VGG19: [('n03770679', 'minivan', 0.23455109), ('n04285008', 'sports_car', 0.22764407), ('n04037443', 'racer', 0.091262065), ('n02930766', 'cab', 0.082842484), ('n02974003', 'car_wheel', 0.07619765)]
 Predicted ResNet50: [('n04285008', 'sports_car', 0.2878513), ('n03770679', 'minivan', 0.27558535), ('n03459775', 'grille', 0.14996652), ('n02974003', 'car_wheel', 0.07796249), ('n04037443', 'racer', 0.050856136)]
 Predicted MobileNet: [('n04285008', 'sports_car', 0.2911019), ('n03770679', 'minivan', 0.24308795), ('n04037443', 'racer', 0.17548184), ('n02814533', 'beach_wagon', 0.12273211), ('n02974003', 'car_wheel', 0.065000646)]
 Predicted Xception: [('n04285008', 'sports_car', 0.3404192), ('n03770679', 'minivan', 0.12870753), ('n03459775', 'grille', 0.11251074), ('n03100240', 'convertible', 0.068289846), ('n03670208', 'limousine', 0.056636304)]
 Predicted NASNetMobile: [('n04285008', 'sports_car', 0.54606944), ('n03100240', 'convertible', 0.2797665), ('n03459775', 'grille', 0.037253976), ('n02974003', 'car_wheel', 0.02682667), ('n02814533', 'beach_wagon', 0.014193514)]
 Predicted DenseNet121: [('n04285008', 'sports_car', 0.65400195), ('n02974003', 'car_wheel', 0.076283), ('n03459775', 'grille', 0.06899618), ('n03100240', 'convertible', 0.058678553), ('n04037443', 'racer', 0.051732656)]

最后,我们使用put_text()函数显示图像中每种架构的获得的结果(最佳预测):

代码语言:javascript复制
put_text(numpy_image_res, "InceptionV3", decode_predictions(preds_inception_v3), 40)
put_text(numpy_image_res, "VGG16", decode_predictions(preds_vgg_16), 65)
put_text(numpy_image_res, "VGG19", decode_predictions(preds_vgg_19), 90)
put_text(numpy_image_res, "ResNet50", decode_predictions(preds_resnet_50), 115)
put_text(numpy_image_res, "MobileNet", decode_predictions(preds_mobilenet), 140)
put_text(numpy_image_res, "Xception", decode_predictions(preds_xception), 165)
put_text(numpy_image_res, "NASNetMobile", decode_predictions(preds_nasnet_mobile), 190)
put_text(numpy_image_res, "DenseNet121", decode_predictions(preds_densenet_121), 215)

put_text()函数代码如下:

代码语言:javascript复制
def put_text(img, model_name, decoded_preds, y_pos):
    """Show the predicted results in the image"""

    cv2.putText(img, "{}: {}, {:.2f}".format(model_name, decoded_preds[0][0][1], decoded_preds[0][0][2]),
                (20, y_pos), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)

put_text()函数中,我们调用cv2.putText()函数在图像中渲染相应的字符串。

下一个屏幕截图中可以看到classification_keras_pretrained_imagenet_models.py脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFVtpP1M-1681870549426)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/a17b4bdf-eac5-4df0-a0b2-fb8ca67ea7b1.png)]

如前面的屏幕快照所示,大多数模型将此图像分类为sport_car; 但是,VGG 模型(VGG16 和 VGG19)将此图像归类为minivan,这可能是由于汽车的高度所致。

如果我们使用另一个输入图像(在这种情况下为cat.jpg)运行脚本,则输出显示在下一个屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMjNElWH-1681870549426)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3a096fe0-bf92-419b-8ed6-b0ff87114d63.png)]

在这种情况下,大多数模型将此图像分类为tabby,这是家猫(虎斑猫)。 Xception 模型将此图像分类为Egyptian_cat

使用 Keras 应用的深度学习 REST API

在上一小节中,我们已经了解了如何使用 Keras 深度学习库的应用模块,为许多流行的架构提供了深度学习模型定义和预训练权重。

在本小节中,我们将了解如何基于这些预先训练的架构之一创建深度学习 REST API。

Keras 深度学习 REST API 是一个名为keras_server.py的文件。 接下来可以看到此脚本的代码:

代码语言:javascript复制
# Import required packages:
from keras.applications import nasnet, NASNetMobile
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
import tensorflow as tf

# Initialize Flask app, Keras model and graph:
app = flask.Flask(__name__)
graph = None
model = None

def load_model():
    # Get default graph:
    global graph
    graph = tf.get_default_graph()
    # Load the pre-trained Keras model(pre-trained on ImageNet):
    global model
    model = NASNetMobile(weights="imagenet")

def preprocessing_image(image, target):
    # Make sure the image mode is RGB:
    if image.mode != "RGB":
        image = image.convert("RGB")

    # Resize the input image:
    image = image.resize(target)
    # Convert PIL format to numpy array:
    image = img_to_array(image)
    # Convert the image/images into batch format:
    image = np.expand_dims(image, axis=0)
    # Pre-process (prepare) the image using the specific architecture:
    image = nasnet.preprocess_input(image)
    # Return the image:
    return image

@app.route("/predict", methods=["POST"])
def predict():
    # Initialize result:
    result = {"success": False}

    if flask.request.method == "POST":
        if flask.request.files.get("image"):
            # Read input image in PIL format:
            image = flask.request.files["image"].read()
            image = Image.open(io.BytesIO(image))

            # Pre-process the image to be classified:
            image = preprocessing_image(image, target=(224, 224))

            # Classify the input image:
            with graph.as_default():
                predictions = model.predict(image)
            results = imagenet_utils.decode_predictions(predictions)
            result["predictions"] = []

            # Add the predictions to the result:
            for (imagenet_id, label, prob) in results[0]:
                r = {"label": label, "probability": float(prob)}
                result["predictions"].append(r)

            # At this point we can say that the request was dispatched successfully:
            result["success"] = True

    # Return result as a JSON response:
    return flask.jsonify(result)

@app.route("/")
def home():
    # Initialize result:
    result = {"success": True}
    # Return result as a JSON response:
    return flask.jsonify(result)

if __name__ == "__main__":
    print("Loading Keras pre-trained model")
    load_model()
    print("Starting")
    app.run()

第一步是导入所需的包,如下所示:

代码语言:javascript复制
# Import required packages:
from keras.applications import nasnet, NASNetMobile
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
import tensorflow as tf

下一步是初始化 Flask 应用,我们的模型和计算图,如下所示:

代码语言:javascript复制
# Initialize Flask app, Keras model and graph:
app = flask.Flask(__name__)
graph = None
model = None

我们定义load_model()函数,该函数负责创建架构并加载所需的权重:

代码语言:javascript复制
def load_model():
    # Get default graph:
    global graph
    graph = tf.get_default_graph()
    # Load the pre-trained Keras model(pre-trained on ImageNet):
    global model
    model = NASNetMobile(weights="imagenet")

可以看出,NASNetMobile 权重已加载。

我们还定义了preprocessing_image()函数,如下所示:

代码语言:javascript复制
def preprocessing_image(image, target):
    # Make sure the image mode is RGB:
    if image.mode != "RGB":
        image = image.convert("RGB")

    # Resize the input image:
    image = image.resize(target)
    # Convert PIL format to numpy array:
    image = img_to_array(image)
    # Convert the image/images into batch format:
    image = np.expand_dims(image, axis=0)
    # Pre-process (prepare) the image using the specific architecture:
    image = nasnet.preprocess_input(image)
    # Return the image:
    return image

此函数准备输入图像-将图像转换为 RGB,调整其大小,将一个或多个图像转换为批量格式,最后使用特定的架构对其进行预处理。

最后,我们使用route()装饰器将predict()函数绑定到/predict URL。 predict()函数处理请求并将预测返回给客户端,如下所示:

代码语言:javascript复制
@app.route("/predict", methods=["POST"])
def predict():
    # Initialize result:
    result = {"success": False}

    if flask.request.method == "POST":
        if flask.request.files.get("image"):
            # Read input image in PIL format:
            image = flask.request.files["image"].read()
            image = Image.open(io.BytesIO(image))

            # Pre-process the image to be classified:
            image = preprocessing_image(image, target=(224, 224))

            # Classify the input image:
            with graph.as_default():
                predictions = model.predict(image)
            results = imagenet_utils.decode_predictions(predictions)
            result["predictions"] = []

            # Add the predictions to the result:
            for (imagenet_id, label, prob) in results[0]:
                r = {"label": label, "probability": float(prob)}
                result["predictions"].append(r)

            # At this point we can say that the request was dispatched successfully:
            result["success"] = True

    # Return result as a JSON response:
    return flask.jsonify(result)

predict()函数中处理图像的第一步是读取 PIL 格式的输入图像。 接下来,我们对图像进行预处理,并将其通过网络以获得预测。 最后,我们将预测添加到结果中,并将结果作为 JSON 响应返回。

以与前面各节相同的方式,我们已经编码了两个脚本,以便对 Keras 深度学习 REST API 执行POST请求。 request_keras_rest_api.py脚本执行POST请求并打印结果。 request_keras_rest_api_drawing.py脚本执行POST请求,打印结果,并创建图像以渲染获得的结果。 为了简化起见,仅显示request_keras_rest_api_drawing.py脚本,如下所示:

代码语言:javascript复制
# Import required packages:
import cv2
import numpy as np
import requests
from matplotlib import pyplot as plt

def show_img_with_matplotlib(color_img, title, pos):
    """Shows an image using matplotlib capabilities"""

    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(1, 1, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')

KERAS_REST_API_URL = "http://localhost:5000/predict"
IMAGE_PATH = "car.jpg"

# Load the image and construct the payload:
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}

# Submit the POST request:
r = requests.post(KERAS_REST_API_URL, files=payload).json()

# Convert the loaded image to the OpenCV format:
image_array = np.asarray(bytearray(image), dtype=np.uint8)
img_opencv = cv2.imdecode(image_array, -1)
img_opencv = cv2.resize(img_opencv, (500, 500))

y_pos = 40

# Show the results:
if r["success"]:
    # Iterate over the predictions
    for (i, result) in enumerate(r["predictions"]):
        # Print the results:
        print("{}. {}: {:.4f}".format(i   1, result["label"], result["probability"]))
        # Render the results in the image:
        cv2.putText(img_opencv, "{}. {}: {:.4f}".format(i   1, result["label"], result["probability"]),
                    (20, y_pos), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)
        y_pos  = 30
else:
    print("Request failed")

# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(8, 6))
plt.suptitle("Using Keras Deep Learning REST API", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Show the output image
show_img_with_matplotlib(img_opencv, "Classification results (NASNetMobile)", 1)

# Show the Figure:
plt.show()

如您所见,我们向 Keras 深度学习 REST API 执行POST请求。 为了显示结果,我们迭代获得的预测。 对于每个预测,我们都将打印结果并将结果呈现在图像中。

下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8I0buw4-1681870549427)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/27162867-0964-4949-aaa9-877887089566.png)]

在上一个屏幕截图中,您可以看到与输入car.jpg图像相对应的顶部5预测。

将 Flask 应用部署到云上

如果您开发了 Flask 应用,则可以在计算机上运行,​​可以通过将其部署到云中轻松地将其公开。 如果要将应用部署到云中,有很多选择(例如,Google App Engine,Microsoft Azure,Heroku 和 Amazon Web Services 等)。 另外,您还可以使用 PythonAnywhere,它是 Python 在线集成开发环境IDE)。 和网络托管环境,可轻松在云中创建和运行 Python 程序。

PythonAnywhere 非常简单,也是托管基于机器学习的 Web 应用的推荐方法。 PythonAnywhere 提供了一些有趣的功能,例如基于 WSGI 的网络托管(例如 Django,Flask 和 Web2py)。

在本节中,我们将看到如何创建 Flask 应用以及如何在 PythonAnywhere 上部署它。

为了向您展示如何使用 PythonAnywhere 将 Flask 应用部署到云中,我们将使用mysite项目的代码。 该代码与本章先前所见的最小面部 API 非常相似(进行了少量修改)。 创建网站后,将对这些修改进行说明:

  1. 第一步是创建一个 PythonAnywhere 帐户。 对于此示例,一个初学者帐户就足够了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7e8GXoft-1681870549427)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/1d9f2c59-519b-405c-af81-115424109d2b.png)]

  1. 注册后,您将可以访问仪表板。 在下一个屏幕截图中可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W32DPkNZ-1681870549427)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/e913294d-dcea-4dce-bfb1-95721e37ef35.png)]

如您所见,我已经创建了用户opencv

  1. 下一步是单击“Web”菜单,然后单击“添加新的 Web 应用”按钮,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5T9e6oaA-1681870549427)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/f9c45040-fcf6-463a-9581-1a8bc06a693e.png)]

  1. 此时,您可以创建新的 Web 应用,如下面的屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DTC2MeOe-1681870549428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/05982c75-8427-44bf-abbe-fad0c5e14a99.png)]

  1. 单击下一步,然后单击 Flask,然后单击最新版本的 Python。 最后,单击“下一步”接受项目路径:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBRPSoCi-1681870549428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/cdf57442-fc8c-4ae8-b51d-35595612c702.png)]

这将创建一个Hello world Flask 应用,如果您访问这里,则可以看到该应用。 在我的情况下,URL 为https://opencv.pythonanywhere.com

  1. 至此,我们准备上传自己的项目。 第一步是单击 Web 菜单的“代码”部分中的“转到目录”,如下面的屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gx4U0Ott-1681870549428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/dd128e83-b04a-4ca2-a4bd-01eab26e933e.png)]

  1. 我们可以使用“上传文件”按钮将文件上传到我们的网站。 我们上传了三个文件,如下所示:
    • flask_app.py
    • face_processing.py
    • haarcascade_frontalface_alt.xml

在下一个屏幕截图中可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y3UQEEaI-1681870549428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/e5281d74-15bc-49b8-8745-076b8c01e1fb.png)]

您可以通过单击下载图标查看这些文件的上载内容。 在这种情况下,您可以在以下 URL 中查看这些文件的内容:

  1. 下一步是设置虚拟环境。 为此,应通过单击此处的“打开 Bash 控制台”来打开 bash 控制台(请参阅上一个屏幕截图)。 打开后,运行以下命令:
代码语言:javascript复制
$ mkvirtualenv --python=/usr/bin/python3.6 my-virtualenv

您将看到提示从更改为(my-virtualenv)。 这意味着虚拟环境已被激活。 此时,我们将安装所有必需的包(flask和opencv-contrib-python):

代码语言:javascript复制
(my-virtualenv)$ pip install flask

(my-virtualenv)$ pip install opencv-contrib-python

您会看到还安装了numpy。 所有这些步骤可以在下一个屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rqhTKCWt-1681870549428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7ef2fe14-ccba-44e6-abd8-46e8f9f6710c.png)]

如果要安装其他包,请不要忘记激活已创建的虚拟环境。 您可以使用以下命令重新激活它:

代码语言:javascript复制
$ workon my-virtualenv
 (my-virtualenv)$
  1. 至此,我们几乎完成了。 最后一步是通过单击菜单中的 Web 选项并重新加载站点来重新加载上载的项目,这可以在下一个屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSQLBIJu-1681870549429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/e79893b3-4216-48d6-92fc-5cb978f01dca.png)]

因此,我们准备好测试上传到 PythonAnywhere 的 face API,可以使用这个页面访问。 您将看到类似以下屏幕截图的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56dZCgFx-1681870549429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/9542e783-4aaf-4881-af4e-3fbcacc806a4.png)]

您可以看到一个 JSON 响应。 之所以获得此 JSON 响应,是因为我们已使用route()装饰器将info_view()函数绑定到 URL /。 与在本章中看到的最小人脸 API 相比,这是我们在本示例中执行的修改之一。 因此,我们修改了flask_app.py脚本,使其包括:

代码语言:javascript复制
@app.route('/', methods=["GET"])
def info_view():
    # List of routes for this API:
    output = {
        'info': 'GET /',
        'detect faces via POST': 'POST /detect',
        'detect faces via GET': 'GET /detect',
    }
    return jsonify(output), 200

这样,当访问这个页面时,我们将获得此 API 的路由列表。 将项目上传到 PythonAnywhere 以便查看一切正常时,这很有用。 第二次(也是最后一次)修改是在face_processing.py脚本中执行的。 在此脚本中,我们更改了haarcascade_frontalface_alt.xml文件的路径,该文件由人脸检测器使用:

代码语言:javascript复制
class FaceProcessing(object):
    def __init__(self):
        self.file = "/home/opencv/mysite/haarcascade_frontalface_alt.xml"
        self.face_cascade = cv2.CascadeClassifier(self.file)

查看文件的路径,该路径与将haarcascade_frontalface_alt.xml文件上传到 PythonAnywhere 时分配的新路径匹配。

该路径应根据用户名(在这种情况下为opencv)进行更改。

与前面的示例相同,我们可以对上传到 PythonAnywhere 的 Face API 执行POST请求。 这是在demo_request.py脚本中执行的:

代码语言:javascript复制
# Import required packages:
import cv2
import numpy as np
import requests
from matplotlib import pyplot as plt

def show_img_with_matplotlib(color_img, title, pos):
    """Shows an image using matplotlib capabilities"""

    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(1, 1, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')

FACE_DETECTION_REST_API_URL = "http://opencv.pythonanywhere.com/detect"
IMAGE_PATH = "test_face_processing.jpg"

# Load the image and construct the payload:
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}

# Submit the POST request:
r = requests.post(FACE_DETECTION_REST_API_URL, files=payload)

# See the response:
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

# Get JSON data from the response and get 'result':
json_data = r.json()
result = json_data['result']

# Convert the loaded image to the OpenCV format:
image_array = np.asarray(bytearray(image), dtype=np.uint8)
img_opencv = cv2.imdecode(image_array, -1)

# Draw faces in the OpenCV image:
for face in result:
    left, top, right, bottom = face['box']
    # To draw a rectangle, you need top-left corner and bottom-right corner of rectangle:
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
    # Draw top-left corner and bottom-right corner (checking):
    cv2.circle(img_opencv, (left, top), 5, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 5, (255, 0, 0), -1)

# Create the dimensions of the figure and set title:
fig = plt.figure(figsize=(8, 6))
plt.suptitle("Using face API", fontsize=14, fontweight='bold')
fig.patch.set_facecolor('silver')

# Show the output image
show_img_with_matplotlib(img_opencv, "face detection", 1)

# Show the Figure:
plt.show()

除了以下几行之外,此脚本中没有任何新内容:

代码语言:javascript复制
FACE_DETECTION_REST_API_URL = "http://opencv.pythonanywhere.com/detect"

请注意,我们正在请求我们的云 API。 下一个屏幕截图中可以看到此脚本的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDRQHPeP-1681870549429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/22331a5a-9ed5-400a-9a57-2a3c73623049.png)]

这样,我们可以确认我们的云 API 已启动并正在运行。

总结

在本书的最后一章中,我们了解了如何使用 Python Web 框架创建 Web 应用,并发现了 Flask 等 Web 框架的潜力。 更具体地说,我们已经使用 OpenCV,Keras 和 Flask 开发了多个 Web 计算机视觉和 Web 深度学习应用,并学习了如何与它们结合以提供 Web 应用机器学习和深度学习功能。 此外,我们还介绍了如何使用提供网络托管功能的 PythonAnywhere 将 Flask 应用部署到云中。 最后,我们还了解了如何执行来自浏览器的请求(例如 GET 和 POST)(GET 请求),以及如何以编程方式(GET 和 POST 请求)使用 OpenCV 和 Flask 创建 Web Face API,以及如何使用 OpenCV 创建深度学习 API。

问题

  • Web 框架存在两种主要类别?
  • Flask 中route()装饰器的目的是什么?
  • 如何运行 Flask 服务器应用以从网络上的任何其他计算机进行访问?
  • jsonify()函数的作用是什么?
  • Flask 中errorhandler()装饰器的目的是什么?
  • 什么是 Keras 应用?
  • 什么是 PythonAnywhere?

进一步阅读

以下参考资料将帮助您更深入地研究 Flask(以及 Django):

  • 《精通 Flask Web 开发:第二版》,作者 Daniel Gaspar 和 Jack Stouffer
  • 《Flask – 构建 Web 应用》
  • 《Flask 示例》
  • 《Python Web 开发:2018 年的 Django VS Flask》,作者 Aaron Lazar

十四、答案

第一章

  1. Python 虚拟环境的主要目的是为 Python 项目创建一个隔离的环境。 这意味着每个项目都可以具有自己的依赖关系,而不管每个其他项目都具有什么依赖关系。 换句话说,它是 Python 的一个隔离工作副本,使您可以在特定项目上工作而不必担心影响其他项目。
  2. pipvirtualenvpipenv,Anaconda 和conda之间的连接如下:
  • pip:Python 包管理器:
    • PyPA 推荐的用于安装 Python 包的工具
    • 您可以使用 PyPI 查找和发布 Python 包:Python 包 索引
  • pyenv:Python 版本管理器:
    • pyenv 让您轻松在多个版本的 Python 之间切换
    • 如果您需要使用其他版本的 Python,pyenv可让您轻松管理
  • virtualenv:Python 环境管理器:
    • virtualenv是用于创建隔离的 Python 环境的工具
    • 要创建virtualenv,只需调用virtualenv ENV,其中ENV是用于放置新虚拟环境的目录
    • 要初始化virtualenv,您需要获取ENV/bin/activate
    • 要停止使用virtualenv,只需致电deactivate
    • 激活virtualenv后,您可以通过运行pip install -r requirements.txt 安装工作区的所有包要求。
  • anaconda:包管理器,环境管理器和其他科学库:
    • Anaconda 包括易于安装的 Python,并更新了 100 多个经过预先构建和测试的科学和分析 Python 包,其中包括 NumPy,Pandas,SciPy,Matplotlib 和 IPython,还可以通过简单的conda install <packagename>提供 620 多个包。
    • conda是 Anaconda 发行版中包含的开源包管理系统和环境 管理系统(提供虚拟环境功能)。 因此,您可以使用conda创建虚拟环境。
    • 尽管conda允许您安装包,但是这些包与 PyPI 包是分开的,因此根据您需要安装的包的类型,您可能仍需要额外使用pip
  1. 笔记本文档是 Jupyter 笔记本应用生成的文档,其中包含计算机代码和富文本元素。 由于代码和文本元素的这种混合,笔记本是将分析描述及其结果结合在一起的理想场所。 此外,可以执行它们以实时执行数据分析。 Jupyter 笔记本 App 是一个服务器客户端应用,它允许通过 Web 浏览器编辑和运行笔记本文档。 Jupyter 是一个缩写,代表它设计的三种语言(Julia,Python 和 R)。它属于 Anaconda 发行版。
  2. 要使用图像,需要的主要包如下:Numpy,opencv,scikit-image,PIL,Pillow,SimpleCV,Mahotas 和 ilastik。 此外,要解决机器学习问题,您还可以使用 Pandas,Scikit-learn,Orange,PyBrain 或 Milk。 最后,如果您的计算机视觉项目涉及深度学习技术,则还可以使用 TensorFlow,Pytorch,Theano 或 Keras。
  3. 要根据本地目录中的requirements.txt文件使用pip安装包,我们应执行pip install -r requirements.txt安装此文件中包含的所有包。 您还可以先创建一个虚拟环境,然后安装所有必需的包:
  • cdrequirements.txt所在的目录
  • 激活您的virtualenv
  • 运行pip install -r requirements.txt
  1. 集成开发环境IDE)是一种软件应用,为计算机程序员提供用于软件开发的全面功能。 IDE 通常包括源代码编辑器,构建自动化工具和调试器。 大多数现代 IDE 具有智能的代码完成功能。 Python IDE 是开始使用 Python 编程的第一件事。 您可以在基本的文本编辑器(如记事本)中开始使用 Python 编程,但是最好使用完整且功能丰富的 Python IDE。

PyCharm 是专业的 Python IDE,有两种形式:

  • 专业:用于 Python 和 Web 开发的全功能 IDE(免费试用)
  • 社区:用于 Python 和科学开发的轻量级 IDE(免费,开源)

它的大多数功能都以社区形式提供,包括智能代码完成,直观的项目导航,即时错误检查和修复,带有 PEP8 检查和智能重构的代码质量,图调试器和测试运行程序。 它还与 IPython 笔记本集成,并支持 Anaconda 以及其他科学包,例如 Matplotlib 和 NumPy。

  1. OpenCV 是在 BSD 许可下发布的。 因此,它对于商业和学术用途都是免费的。 BSD 许可证可以分为三种类型:
  • 两条款 BSD 许可证
  • 三条款 BSD 许可证
  • 四条款 BSD 许可证

OpenCV 使用三节 BSD 许可证。 所有这些子句列出如下:

代码语言:javascript复制
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
代码语言:javascript复制
(1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
代码语言:javascript复制
(2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
代码语言:javascript复制
(3) Neither the name of the [organization] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
代码语言:javascript复制
(4) All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the [organization].

第二章

  1. 共有三个图像处理步骤:
    1. 获取必要的信息以供使用(例如,图像或视频文件等)
    2. 通过应用图像处理技术处理图像
    3. 以所需的方式显示结果(例如,将图像保存到磁盘,显示图像等等)
  2. 处理步骤可以分为三个处理级别:
    1. 低级处理
    2. 中级处理
    3. 高级处理
  3. 灰度图像包含图像的每个像素的值,该值与图像的亮度或灰度级成正比。 此值也称为强度或灰度级。 该值是属于[0, L-1],其中L = 256(对于 8 位图像)。

另一方面,黑白图像为图像的每个像素包含一个只能取两个值的值。 通常,这些值为 0(黑色)和 255(白色)。 在许多情况下,黑白图像是某些图像处理步骤和技术的结果(例如,阈值运算的结果)。

  1. 数字图像是 2D 图像表示形式的有限数字值集,称为像素。 像素是数字图像中可编程颜色的基本单位。

图像分辨率可以看作是图像的细节。 分辨率为800×1200的图像是具有 800 列和 1200 行的网格,包含800×1200 = 960,000像素。

  1. OpenCV 执行以下操作:
  • 加载(读取)图像:cv2.imread()
    • img = cv2.imread('logo.png')
    • gray_img = cv2.imread('logo.png', cv2.IMREAD_GRAYSCALE)
  • 显示图像:cv2.imshow()
    • cv2.imshow('bgr image', img )
  • 等待按键:cv2.waitKey()
    • cv2.waitKey(0)
  • 拆分通道:cv2.split()
    • b, g, r = cv2.split(img)
  • 合并通道:cv2.merge()
    • img = cv2.merge([r, g, b])
  1. $ jupyter notebook
  2. 将获得以下颜色:
  • B = 0, G = 255, R = 255):黄色
  • B = 255, G = 255, R = 0):青色
  • B = 255, G = 0, R = 255):洋红色
  • B = 255, G = 255, R = 255):白色

您可以在这个页面上进一步使用 RGB 颜色图表。

  1. 图像是彩色还是灰度均可通过其尺寸(img.shape)确定。 如本章所述,如果加载了彩色图像,则img.shape的长度将为3。 另一方面,如果加载的图像是灰度图像,则img.shape的长度将为2。 该代码如下所示:
代码语言:javascript复制
# load OpenCV logo image:
img = cv2.imread('logo.png')

# Get the shape of the image:
dimensions = img.shape

# Check the length of dimensions
if len(dimensions) < 3:
 print("grayscale image!")
if len(dimensions) == 3:
 print("color image!")

# Load the same image but in grayscale:
gray_img = cv2.imread('logo.png', cv2.IMREAD_GRAYSCALE)

# Get again the img.shape properties:
dimensions = gray_img.shape

# Check the length of dimensions
if len(dimensions) < 3:
print("grayscale image!")
if len(dimensions) == 3:
print("color image!")

第三章

  1. 列表的第二个元素是脚本的第一个参数,即sys.argv[1]
  2. 代码如下:
代码语言:javascript复制
parser = argparse.ArgumentParser()
parser.add_argument("first_number", help="first number to be added", type=int)
  1. 保存图像的代码如下:
代码语言:javascript复制
cv2.imwrite("image.png", img)
  1. capture对象的创建如下:
代码语言:javascript复制
capture = cv2.VideoCapture(0)
  1. capture对象的创建如下:
代码语言:javascript复制
capture = cv2.VideoCapture(0)
print("CV_CAP_PROP_FRAME_WIDTH: '{}'".format(capture.get(cv2.CAP_PROP_FRAME_WIDTH)))
  1. 读取图像的代码如下:
代码语言:javascript复制
image = cv2.imread("logo.png")
cv2.imwrite("logo_copy.png", gray_image)
  1. 脚本编写如下:
代码语言:javascript复制
"""
Example to introduce how to read a video file backwards and save it
"""

# Import the required packages
import cv2
import argparse

def decode_fourcc(fourcc):
"""Decodes the fourcc value to get the four chars identifying it

"""
fourcc_int = int(fourcc)

# We print the int value of fourcc
print("int value of fourcc: '{}'".format(fourcc_int))

# We can also perform this in one line:
# return "".join([chr((fourcc_int >> 8 * i) & 0xFF) for i in range(4)])

fourcc_decode = ""
for i in range(4):
int_value = fourcc_int >> 8 * i & 0xFF
print("int_value: '{}'".format(int_value))
fourcc_decode  = chr(int_value)
return fourcc_decode

# We first create the ArgumentParser object
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add 'video_path' argument using add_argument() including a help.
parser.add_argument("video_path", help="path to the video file")

# We add 'output_video_path' argument using add_argument() including a help.
parser.add_argument("output_video_path", help="path to the video file to write")

args = parser.parse_args()

# Create a VideoCapture object and read from input file
# If the input is the camera, pass 0 instead of the video file name
capture = cv2.VideoCapture(args.video_path)

# Get some properties of VideoCapture (frame width, frame height and frames per second (fps)):
frame_width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)
frame_height = capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = capture.get(cv2.CAP_PROP_FPS)
codec = decode_fourcc(capture.get(cv2.CAP_PROP_FOURCC))

print("codec: '{}'".format(codec))

# FourCC is a 4-byte code used to specify the video codec and it is platform dependent!
fourcc = cv2.VideoWriter_fourcc(*codec)

# Create VideoWriter object. We use the same properties as the input camera.
# Last argument is False to write the video in grayscale. True otherwise (write the video in color)
out = cv2.VideoWriter(args.output_video_path, fourcc, int(fps), (int(frame_width), int(frame_height)), True)

# Check if camera opened successfully
if capture.isOpened()is False:
print("Error opening video stream or file")

# We get the index of the last frame of the video file
frame_index = capture.get(cv2.CAP_PROP_FRAME_COUNT) - 1
# print("starting in frame: '{}'".format(frame_index))

# Read until video is completed
while capture.isOpened() and frame_index >= 0:

# We set the current frame position
capture.set(cv2.CAP_PROP_POS_FRAMES, frame_index)

# Capture frame-by-frame from the video file:
ret, frame = capture.read()

if ret is True:

# Print current frame number per iteration
# print("CAP_PROP_POS_FRAMES : '{}'".format(capture.get(cv2.CAP_PROP_POS_FRAMES)))

# Get the timestamp of the current frame in milliseconds
# print("CAP_PROP_POS_MSEC : '{}'".format(capture.get(cv2.CAP_PROP_POS_MSEC)))

# Display the resulting frame
cv2.imshow('Original frame', frame)

# Write the frame to the video
out.write(frame)

# Convert the frame to grayscale:
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# Display the grayscale frame
cv2.imshow('Grayscale frame', gray_frame)

frame_index = frame_index - 1
# print("next index to read: '{}'".format(frame_index))

# Press q on keyboard to exit the program:
if cv2.waitKey(25) & 0xFF == ord('q'):
break
# Break the loop
else:
break

# Release everything:
capture.release()
out.release()
cv2.destroyAllWindows()

第四章

  1. 参数厚度可以取正值和负值。 如果该值为正,则表示轮廓的粗细。 负值(例如-1)表示将绘制填充形状。 例如,要绘制填充的椭圆,请注意以下几点:
代码语言:javascript复制
cv2.ellipse(image, (80, 80), (60, 40), 0, 0, 360, colors['red'], -1)

您也可以使用cv2.FILLED

代码语言:javascript复制
cv2.ellipse(image, (80, 80), (60, 40), 0, 0, 360, colors['red'], cv2.FILLED)
  1. lineType参数可以采用三个值(cv2.LINE_4 == 4cv2.LINE_AA == 16cv2.LINE_8 == 8)。 要绘制抗锯齿线,必须使用cv2.LINE_AA
代码语言:javascript复制
cv2.line(image, (0, 0), (20, 20), colors['red'], 1, cv2.LINE_AA)
  1. 对角线是在以下代码的帮助下创建的:
代码语言:javascript复制
cv2.line(image, (0, 0), (512, 512), colors['green'], 3)
  1. 文本展示如下:
代码语言:javascript复制
cv2.putText(image, 'Hello OpenCV', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_4)
  1. 此练习的代码对应于circle_polygon.py脚本。 要获取坐标,可以使用圆的参数方程式(请参见analog_clock_values.py)。 下图显示了此多边形:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZjDQugv-1681870549429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b8ae1785-efec-4617-9c4d-ef397fba85aa.png)]

circle_polygon.py文件的代码如下:

代码语言:javascript复制
"""
Example to show how to draw a circle polygon
"""

# Import required packages:
import cv2
import numpy as np
import matplotlib.pyplot as plt

def show_with_matplotlib(img, title):
    """Shows an image using matplotlib capabilities

    """
    # Convert BGR image to RGB
    img_RGB = img[:, :, ::-1]

    # Show the image using matplotlib:
    plt.imshow(img_RGB)
    plt.title(title)
    plt.show()

# Dictionary containing some colors
colors = {'blue': (255, 0, 0), 'green': (0, 255, 0), 'red': (0, 0, 255), 'yellow': (0, 255, 255),
          'magenta': (255, 0, 255), 'cyan': (255, 255, 0), 'white': (255, 255, 255), 'black': (0, 0, 0),
          'gray': (125, 125, 125), 'rand': np.random.randint(0, high=256, size=(3,)).tolist(),
          'dark_gray': (50, 50, 50), 'light_gray': (220, 220, 220)}

# We create the canvas to draw: 640 x 640 pixels, 3 channels, uint8 (8-bit unsigned integers)
# We set background to black using np.zeros()
image = np.zeros((640, 640, 3), dtype="uint8")

# If you want another background color you can do the following:
# image[:] = colors['light_gray']
image.fill(255)

pts = np.array(
    [(600, 320), (563, 460), (460, 562), (320, 600), (180, 563), (78, 460), (40, 320), (77, 180), (179, 78), (319, 40),
     (459, 77), (562, 179)])

# Reshape to shape (number_vertex, 1, 2)
pts = pts.reshape((-1, 1, 2))

# Call cv2.polylines() to build the polygon:
cv2.polylines(image, [pts], True, colors['green'], 5)

# Show image:
show_with_matplotlib(image, 'polygon with the shape of a circle using 12 points')
  1. 该代码对应于matplotlib_mouse_events_rect.py脚本。

关键是如何捕获双击鼠标左键:

  • 双击:event.dblclick
  • 左键点击:event.button == 1

matplotlib_mouse_events_rect.py文件的代码如下:

代码语言:javascript复制
"""
Example to show how to capture a double left click with matplotlib events to draw a rectangle
"""

# Import required packages:
import cv2
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

# Dictionary containing some colors
colors = {'blue': (255, 0, 0), 'green': (0, 255, 0), 'red': (0, 0, 255), 'yellow': (0, 255, 255),
          'magenta': (255, 0, 255), 'cyan': (255, 255, 0), 'white': (255, 255, 255), 'black': (0, 0, 0),
          'gray': (125, 125, 125), 'rand': np.random.randint(0, high=256, size=(3,)).tolist(),
          'dark_gray': (50, 50, 50), 'light_gray': (220, 220, 220)}

# We create the canvas to draw: 400 x 400 pixels, 3 channels, uint8 (8-bit unsigned integers)
# We set the background to black using np.zeros()
image = np.zeros((400, 400, 3), dtype="uint8")

# If you want another background color you can do the following:
image[:] = colors['light_gray']

def update_img_with_matplotlib():
    """Updates an image using matplotlib capabilities

    """
    # Convert BGR to RGB image format
    img_RGB = image[:, :, ::-1]

    # Display the image:
    plt.imshow(img_RGB)

    # Redraw the Figure because the image has been updated:
    figure.canvas.draw()

# We define the event listener for the 'button_press_event':
def click_mouse_event(event):
    # Check if a double left click is performed:
    if event.dblclick and event.button == 1:
        # (event.xdata, event.ydata) contains the float coordinates of the mouse click event:
        cv2.rectangle(image, (int(round(event.xdata)), int(round(event.ydata))),
                      (int(round(event.xdata))   100, int(round(event.ydata))   50), colors['blue'], cv2.FILLED)
    # Call 'update_image()' method to update the Figure:
    update_img_with_matplotlib()

# We create the Figure:
figure = plt.figure()
figure.add_subplot(111)

# To show the image until a click is performed:
update_img_with_matplotlib()

# 'button_press_event' is a MouseEvent where a mouse botton is click (pressed)
# When this event happens the function 'click_mouse_event' is called:
figure.canvas.mpl_connect('button_press_event', click_mouse_event)

# Display the figure:
plt.show()
  1. 该代码对应于meme_generator_opencv_python.py脚本。 这是一个简单的脚本,在其中加载图像,然后渲染一些文本:
代码语言:javascript复制
"""
Example to show how to draw basic memes with OpenCV
"""

# Import required packages:
import cv2
import numpy as np
import matplotlib.pyplot as plt

def show_with_matplotlib(img, title):
    """Shows an image using matplotlib capabilities

    """
    # Convert BGR image to RGB
    img_RGB = img[:, :, ::-1]

    # Show the image using matplotlib:
    plt.imshow(img_RGB)
    plt.title(title)
    plt.show()

# Dictionary containing some colors
colors = {'blue': (255, 0, 0), 'green': (0, 255, 0), 'red': (0, 0, 255), 'yellow': (0, 255, 255),
          'magenta': (255, 0, 255), 'cyan': (255, 255, 0), 'white': (255, 255, 255), 'black': (0, 0, 0),
          'gray': (125, 125, 125), 'rand': np.random.randint(0, high=256, size=(3,)).tolist(),
          'dark_gray': (50, 50, 50), 'light_gray': (220, 220, 220)}

# We load the image 'lenna.png':
image = cv2.imread("lenna.png")

# Write some text (up)
cv2.putText(image, 'Hello World', (10, 30), cv2.FONT_HERSHEY_TRIPLEX, 0.8, colors['green'], 1, cv2.LINE_AA)

# Write some text (down)
cv2.putText(image, 'Goodbye World', (10, 200), cv2.FONT_HERSHEY_TRIPLEX, 0.8, colors['red'], 1, cv2.LINE_AA)

# Show image:
show_with_matplotlib(image, 'very basic meme generator')

第五章

  1. cv2.split()函数将源多通道图像分割为几个单通道图像 (b, g, r) = cv2.split(image)
  2. cv2.merge()函数将几个单通道图像合并为一个多通道图像image = cv2.merge((b, g, r))
  3. 图像可以平移如下:
代码语言:javascript复制
height, width = image.shape[:2]
M = np.float32([[1, 0, 150], [0, 1, 300]])
dst_image = cv2.warpAffine(image, M, (width, height))
  1. 可以按以下方式旋转图像:
代码语言:javascript复制
height, width = image.shape[:2]
M = cv2.getRotationMatrix2D((width / 2.0, height / 2.0), 30, 1)
dst_image = cv2.warpAffine(image, M, (width, height))
  1. 该图像可以如下构建:
代码语言:javascript复制
kernel = np.ones((5, 5), np.float32) / 25
smooth_image = cv2.filter2D(image, -1, kernel)
  1. 灰度图像如下:
代码语言:javascript复制
M = np.ones(image.shape, dtype="uint8") * 40
added_image = cv2.add(image, M)
  1. COLORMAP_JET可以如下应用:img_COLORMAP_JET = cv2.applyColorMap(gray_img, cv2.COLORMAP_JET)

第六章

  1. 图像直方图是一种反映图像色调分布的直方图。 它绘制每个色调值的频率(像素数)(通常在[0-255]范围内)。
  2. 在 OpenCV 中,我们使用cv2.calcHist()函数来计算图像的直方图。 要使用64位计算灰度图像的直方图,代码如下:
代码语言:javascript复制
 hist = cv2.calcHist([gray_image], [0], None, [64], [0, 256])
  1. 我们首先构建具有与灰度图像gray_image相同形状的图像M,然后为该图像的每个像素设置50值。 然后,我们使用cv2.add()添加两个图像。 最后,使用cv2.calcHist()计算直方图:
代码语言:javascript复制
M = np.ones(gray_image.shape, dtype="uint8") * 50
 added_image = cv2.add(gray_image, M)
 hist_added_image = cv2.calcHist([added_image], [0], None, [256], [0, 256])
  1. 在 BGR 图像中,红色通道是第三通道(index 2):
代码语言:javascript复制
cv2.calcHist([img], [2], None, [256], [0, 256])
  1. OpenCV 提供cv2.calcHist(),numpy 提供np.histogram(),而 matplotlib 提供plt.hist()。 如本章所述,cv2.calcHist()np.histogram()plt.hist()都快。
  2. 我们定义了一个函数get_brightness(),该函数计算给定灰度图像的亮度。 此函数使用 numpy 函数np.mean(),该函数返回数组元素的平均值。 因此,此函数的代码如下:
代码语言:javascript复制
def get_brightness(img):
 """Calculates the brightness of the image"""

 brightness = np.mean(img)
 return brightness

我们已经计算了三个图像的亮度:

代码语言:javascript复制
brightness_1 = get_brightness(gray_image)
brightness_2 = get_brightness(added_image)
brightness_3 = get_brightness(subtracted_image)

该示例的完整代码可以在grayscale_histogram_brightness.py脚本中看到。

  1. 首先,我们必须导入default_timer
代码语言:javascript复制
from timeit import default_timer as timer

然后,我们必须测量两个函数的执行时间:

代码语言:javascript复制
start = timer()
gray_image_eq = cv2.equalizeHist(gray_image)
end = timer()
exec_time_equalizeHist = (end - start) * 1000

start = timer()
gray_image_clahe = clahe.apply(gray_image)
end = timer()
exec_time_CLAHE = (end - start) * 1000

该示例的完整代码可以在comparing_hist_equalization_clahe_time.py.脚本中看到。

第七章

  1. ret, thresh = cv2.threshold(gray_image, 100, 255, cv2.THRESH_BINARY)
  2. thresh = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 2)
  3. ret, th = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU)
  4. ret, th = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY cv2.THRESH_TRIANGLE)
  5. 使用 scikit-image 的大津阈值可应用如下:
代码语言:javascript复制
thresh = threshold_otsu(gray_image)
binary = gray_image > thresh
binary = img_as_ubyte(binary)

请记住,threshold_otsu(gray_image)函数基于大津的二值化算法返回阈值。 然后,使用此值构造二进制图像(dtype= bool),应将其转换为 8 位无符号整数格式(dtype= uint8)以进行适当的可视化。 img_as_ubyte()函数用于此目的。

  1. 可以使用 scikit-image 进行三角形阈值处理:
代码语言:javascript复制
thresh_triangle = threshold_triangle(gray_image)
binary_triangle = gray_image > thresh_triangle
binary_triangle = img_as_ubyte(binary_triangle)
  1. Niblack 使用 scikit-image 的阈值可按以下方式应用:
代码语言:javascript复制
thresh_niblack = threshold_niblack(gray_image, window_size=25, k=0.8)
binary_niblack = gray_image > thresh_niblack
binary_niblack = img_as_ubyte(binary_niblack)

该算法最初是为文本识别而设计的。 有关更多详细信息,请参见出版物《数字图像处理入门》(1986)。

  1. 使用 Scikit-image 和25窗口大小的 Sauvola 阈值可按以下方式应用:
代码语言:javascript复制
thresh_sauvola = threshold_sauvola(gray_image, window_size=25)
binary_sauvola = gray_image > thresh_sauvola
binary_sauvola = img_as_ubyte(binary_sauvola)

值得注意两个要点:

  • Sauvola 是 Niblack 技术的改良版
  • 该算法最初是为文本识别而设计的

有关更多详细信息,请参见出版物《自适应文档图像二值化》(2000)。

  1. 为了获得带有阈值图像的值的数组,我们使用np.arange()。 由于我们需要一个带有10步骤的[60-130]范围内的值的数组,因此以下行对此函数进行了编码:
代码语言:javascript复制
threshold_values = np.arange(start=60, stop=140, step=10)

之后,我们反复应用cv2.threshold()函数和threshold_values中定义的相应阈值:

代码语言:javascript复制
thresholded_images = []
for threshold in threshold_values:
    ret, thresh = cv2.threshold(gray_image, threshold, 255, cv2.THRESH_BINARY)
    thresholded_images.append(thresh)

最后,我们显示thresholded_images数组中包含的阈值图像。 完整的代码可以在thresholding_example_arange.py脚本中看到。

第八章

  1. cv2.findContours()函数在二进制图像(例如,阈值运算后得到的图像)中找到轮廓。
  2. OpenCV 提供的用于压缩轮廓的四个标志如下:
  • cv2.CHAIN_APPROX_NONE
  • cv2.CHAIN_APPROX_SIMPLE
  • cv2.CHAIN_APPROX_TC89_KCOS
  • cv2.CHAIN_APPROX_TC89_L1
  1. cv2.moments()函数计算直到多边形或栅格化形状的三阶的所有矩。
  2. m00给出轮廓的面积。
  3. OpenCV 提供cv2.HuMoments()函数来计算七个胡矩不变量。
  4. cv2.approxPolyDP()函数根据给定的精度返回给定轮廓的轮廓近似值。 此函数使用 Douglas-Peucker 算法。 epsilon参数指定用于在原始曲线与其近似之间建立最大距离的精度。
  5. 可以以更紧凑的方式覆盖contour_functionality.py脚本中定义的extreme_points()函数,如下所示:
代码语言:javascript复制
 def extreme_points_2(contour):
    """Returns extreme points of the contour"""

    extreme_left = tuple(contour[contour[:, :, 0].argmin()][0])
    extreme_right = tuple(contour[contour[:, :, 0].argmax()][0])
    extreme_top = tuple(contour[contour[:, :, 1].argmin()][0])
    extreme_bottom = tuple(contour[contour[:, :, 1].argmax()][0])

    return extreme_left, extreme_right, extreme_top, extreme_bottom
  1. OpenCV 提供cv2.matchShapes()函数,可使用三种比较方法来比较两个轮廓。 所有这些方法都使用胡矩不变量。 三种实现的方法是cv2.CONTOURS_MATCH_I1cv2.CONTOURS_MATCH_I2cv.CONTOURS_MATCH_I3

第九章

  1. 带有 ORB 的已加载图像image中的关键点和计算描述符如下:
代码语言:javascript复制
orb = cv2.ORB()
keypoints = orb.detect(image, None)
keypoints, descriptors = orb.compute(image, keypoints)
  1. 先前检测到的关键点keypoints如下:
代码语言:javascript复制
image_keypoints = cv2.drawKeypoints(image, keypoints, None, color=(255, 0, 255), flags=0)

要绘制检测到的关键点,请使用cv2.drawKeypoints()函数。

  1. BFMatcher对象和先前已计算的描述符,描述符_1和描述符_2的匹配如下创建:
代码语言:javascript复制
bf_matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
bf_matches = bf_matcher.match(descriptors_1, descriptors_2)
  1. 之前已进行匹配的前 20 个匹配如下:
代码语言:javascript复制
bf_matches = sorted(bf_matches, key=lambda x: x.distance)
result = cv2.drawMatches(image_query, keypoints_1, image_scene, keypoints_2, bf_matches[:20], None, matchColor=(255, 255, 0), singlePointColor=(255, 0, 255), flags=0)

要绘制计算出的匹配项,请使用cv2.drawMatches()

  1. 在图像gray_frame中使用 ArUco 进行标记的检测如下:
代码语言:javascript复制
corners, ids, rejected_corners = cv2.aruco.detectMarkers(gray_frame, aruco_dictionary, parameters=parameters)

要检测标记,请使用cv2.aruco.detectMarkers()函数。

  1. 使用 ArUco 时检测到的标记如下:
代码语言:javascript复制
frame = cv2.aruco.drawDetectedMarkers(image=frame, corners=corners, ids=ids, borderColor=(0, 255, 0))

要绘制标记,请使用cv2.aruco.drawDetectedMarkers()函数。

  1. 使用 Aruco 时被拒绝的标记如下:
代码语言:javascript复制
frame = cv2.aruco.drawDetectedMarkers(image=frame, corners=rejected_corners, borderColor=(0, 0, 255))

要绘制标记,还可以使用cv2.aruco.drawDetectedMarkers()函数。

  1. 使用以下代码检测并解码图像中包含的 QR 代码:
代码语言:javascript复制
 data, bbox, rectified_qr_code = qr_code_detector.detectAndDecode(image)

第十章

  1. 在机器学习的上下文中,主要有三种方法和技术:有监督,无监督和半监督机器学习。
  2. 监督学习问题可以进一步分为回归和分类问题。 当输出变量为类别时,将发生分类问题;而当输出变量为实值时,将出现回归问题。 例如,如果我们预测某些地区会下雨的可能性,并分配两个标签(降雨/不下雨),这就是分类问题。 另一方面,如果我们模型的输出是与降雨相关的概率,则这是一个回归问题。
  3. OpenCV 提供cv2.kmeans()函数,实现了 K 均值聚类算法,该算法查找聚类的中心并对聚类周围的输入样本进行分组。 K 均值是可用于无监督学习的最重要的聚类算法之一。
  4. cv2.ml.KNearest_create()方法创建一个空的 KNN 分类器,应使用train()方法对其进行训练,同时提供数据和标签。
  5. cv2.findNearest()方法用于查找邻居。
  6. 要创建空模型,请使用cv2.ml.SVM_create()函数。
  7. 通常,RBF 核是合理的首选。 RBF 核将样本非线性地映射到更高维度的空间中,因此,与线性核不同,RBF 核可以处理类标签和属性之间的关系为非线性的情况。 有关更多详细信息,请参见《支持向量分类的实用指南》(2003)。

第十一章

  1. 我们已经看到了四个库和包:OpenCV 库,还有dlib主页和 PyPI dlib主页,Github dlib主页,PyPI face_recognition主页,Github face_recognition主页和PyPI cvlib主页,Github cvlib主页,cvlib主页 Python 包。
  2. 人脸识别是对象识别的一种特殊情况,其中可以使用从人脸提取的信息从图像或视频中识别或验证人的身份,可以分解为人脸识别和人脸验证:
  • 人脸验证是一个 1:1 匹配的问题,试图回答以下问题:“这是所要求的人吗?” 例如,使用面部解锁手机就是面部验证的一个示例。
  • 人脸识别是一个 1:N 匹配问题,试图回答以下问题:“这个人是谁?” 例如,可以在办公大楼中安装面部识别系统,以识别所有雇员进入办公室时的身份。
  1. cv2.face.getFacesHAAR()函数可用于检测图像中的人脸:
代码语言:javascript复制
retval, faces = cv2.face.getFacesHAAR(img, "haarcascade_frontalface_alt2.xml")

此处,img是 BGR 图像,"haarcascade_frontalface_alt2.xml"是 Haar 级联文件的字符串变量。

  1. cv2.dnn.blobFromImage()函数用于根据输入图像创建一个四维 BLOB。 可选地,此函数执行预处理,这是将 BLOB 输入网络以获得正确结果所必需的。 下一章将更深入地介绍此函数。
  2. cv.detect_face()函数可用于使用cvlib检测面部:
代码语言:javascript复制
import cvlib as cv
faces, confidences = cv.detect_face(image)

在后台,此函数将 OpenCV DNN 面部检测器与经过预训练的 Caffe 模型一起使用。 有关更多详细信息,请参见face_detection_cvlib_dnn.py脚本。

  1. 要检测地标,应调用face_recognition.face_landmarks()函数:
代码语言:javascript复制
face_landmarks_list_68 = face_recognition.face_landmarks(rgb)

此函数为图像中的每个脸部返回人脸标志(例如,眼睛,鼻子等)的字典。 应当注意,face_recognition包适用于 RGB 图像。

  1. 要初始化相关跟踪器,应调用dlib.correlation_tracker()函数:
代码语言:javascript复制
tracker = dlib.correlation_tracker()

这将使用默认值初始化跟踪器。

  1. 要开始跟踪,请使用tracker.start_track()方法,并且需要包含要跟踪的对象的边界框:
代码语言:javascript复制
tracker.start_track(image, rect)

在此,rect是要跟踪的对象的边界框。

  1. 要获取被跟踪对象的位置,请调用tracker.get_position()方法:
代码语言:javascript复制
pos = tracker.get_position()

此方法返回被跟踪对象的位置。

  1. 使用 Dlib 执行 BGR 图像image的人脸识别的 128D 描述符的计算如下:
代码语言:javascript复制
# Convert image from BGR (OpenCV format) to RGB (dlib format):
rgb = image[:, :, ::-1]
# Calculate the encodings for every face of the image:
encodings = face_encodings(rgb)
# Show the first encoding:
print(encodings[0])

第十二章

  1. 机器学习和深度学习之间的三个主要区别如下:
  • 深度学习算法需要具有高端基础架构才能正确训练。 深度学习技术严重依赖高端机器,这与可以在低端机器上运行的传统机器学习技术相反。
  • 当对特征自省和工程都缺乏领域的了解时,深度学习技术会胜过其他技术,因为您不必担心特征工程。
  • 机器学习和深度学习都能够处理海量数据集,但是在处理小型数据集时,机器学习方法更有意义。 经验法则是,如果数据量很大,则深度学习要胜过其他技术,而当数据集较小时,传统的机器学习算法更可取。
  1. 关于计算机视觉,Alex Krizhevsky,Ilya Sutskever 和 Geoff Hinton 发表了《使用深度卷积神经网络的 ImageNet 分类》(2012)。 该出版物也称为 AlexNet ,这是作者设计的卷积神经网络的名称,被认为是计算机视觉中最具影响力的论文之一。 因此,2012 年被认为是深度学习的爆炸式增长。
  2. 此函数从图像创建一个四维 BLOB,这意味着我们要在调整大小为300x300BGR图像上运行模型,并对每个蓝色,绿色和红色通道应用均值(104, 117, 123)的均值减法 , 分别。
  3. 第一行将输入的 BLOB 馈送到网络,而第二行执行推理,当推理完成时,我们得到了预测。
  4. 占位符只是一个变量,稍后我们将为其分配数据。 在训练/测试算法时,通常使用占位符将训练/测试数据输入到计算图中。
  5. 保存最终模型(例如saver.save(sess, './linear_regression'))时,将创建四个文件:
  • .meta文件:包含 TensorFlow 图
  • .data文件:包含权重,偏差,梯度以及所有其他已保存变量的值
  • .index文件:确定检查点
  • checkpoint文件:记录保存的最新检查点文件
  1. 单热编码意味着标签已从单个数字转换为长度等于可能的类数的向量。 这样,除i元素(其值将为 1)之外,向量的所有元素都将设置为零,对应于类别i
  2. 使用 Keras 时,最简单的模型类型是顺序模型,可以将其视为线性的层堆叠,并在本示例中用于创建模型。 此外,对于更复杂的架构,可以使用 Keras 函数式 API,该 API 允许您构建任意的层图。
  3. 此方法可用于训练模型以获取固定数量的周期(数据集上的迭代)。

第十三章

  1. Web 框架可以分为全栈和非全栈框架。 Django 是用于 Python 的全栈 Web 框架,而 Flask 是 Python 的非全栈框架。
  2. 在 Flask 中,您可以使用route()装饰器将函数绑定到 URL。 换句话说,route()装饰器用于向 Flask 指示应触发特定函数的 URL。
  3. 为了使服务器可以公开,在运行服务器应用时应添加host=0.0.0.0参数:
代码语言:javascript复制
if __name__ == "__main__":
    # Add parameter host='0.0.0.0' to run on your machines IP address:
    app.run(host='0.0.0.0')
  1. jsonify()函数用于创建具有application/json mimetype 的给定参数的 JSON 表示形式。 JSON 被认为是信息交换的事实上的标准,因此,将 JSON 数据返回给客户端是一个好主意。
  2. 我们可以通过用errorhandler()装饰函数来注册错误处理器。 例如,请注意以下几点:
代码语言:javascript复制
@app.errorhandler(500)
def not_found(e):
    # return also the code error
    return jsonify({"status": "internal error", "message": "internal error occurred in server"}), 500

如果服务器中发生内部错误,将为客户端提供500错误代码。

  1. Keras 应用 是 Keras 深度学习库的应用模块,它为许多流行的架构(例如 VGG16,ResNet50,Xception 和 MobileNet 等),可用于预测,特征提取和微调。 这些深度学习架构在 ImageNet 数据集上进行了训练和验证,用于将图像分类为1000类别或类别之一。
  2. PythonAnywhere 是 Python 在线集成开发环境(IDE)和 Web 托管环境,可轻松在云中创建和运行 Python 程序。

0 人点赞