PyTorch-现代计算机视觉-九-

113 阅读35分钟

PyTorch 现代计算机视觉(九)

十八、将 OpenCV 实用工具用于图像分析

到目前为止,在前面的章节中,我们已经学习了利用各种技术来执行对象分类、定位和分割,以及生成图像。虽然所有这些技术都利用深度学习来解决任务,但对于相对简单和定义良好的任务,我们可以利用 OpenCV 包中提供的特定功能。例如,如果需要检测的物体总是具有相同背景的相同物体,则我们不需要 YOLO。在图像来自受限环境的情况下,OpenCV 实用程序很有可能在很大程度上帮助解决这个问题。

在本章中,我们将只讨论几个用例,因为要讨论的实用程序实在太多了,以至于需要专门写一本关于 OpenCV 的书。在进行单词检测时,您将了解图像膨胀、腐蚀和提取连接组件周围的轮廓。之后,您将学习 Canny 边缘检测,以识别图像中对象的边缘。此外,您将了解在视频/图像背景中使用绿色屏幕的优势,同时对图像执行逐位运算以识别感兴趣的色彩空间。然后,您将了解一种通过将两幅图像拼接在一起来创建全景视图的技术。最后,您将了解如何利用预先训练的级联过滤器来识别对象,如车牌。

在本章中,我们将了解以下主题:

  • 在图像中的单词周围绘制边框
  • 检测道路图像中的车道
  • 基于颜色检测物体
  • 构建图像的全景视图
  • 检测汽车的牌照

在图像中的单词周围绘制边框

设想一个场景,您正在构建一个模型,该模型根据文档的图像执行单词转录。第一步是识别单词在图像中的位置。主要有两种方法来识别图像中的单词:

  • 使用深度学习技术,如 CRAFT、EAST 等
  • 使用基于 OpenCV 的技术

在这一部分,我们将了解如何在没有利用深度学习的情况下,在干净的图像中识别机器打印的单词。由于背景和前景之间的对比度很高,你不需要像 YOLO 这样的矫枉过正的解决方案来识别单个单词的位置。在这些场景中,使用 OpenCV 将会非常方便,因为我们可以用非常有限的计算资源得到一个解决方案,因此,即使是推理时间也会非常短。唯一的缺点是准确性可能不是 100%,但这也取决于扫描图像的清洁程度。如果扫描保证非常非常清晰,那么你可以期待接近 100%的准确性。

在较高层次上,让我们了解如何识别/隔离图像中的单词:

  1. 将图像转换为灰度,因为颜色不会影响图像中的识别文字。
  2. 稍微放大图像中的内容。膨胀将黑色像素渗色到紧邻的邻域中,从而连接同一个单词的字符之间的黑色像素。这有助于确保属于同一个单词的字符是相连的。但是,不要扩展得太多,否则属于不同相邻单词的字符也会连接起来。
  3. 一旦字符被连接,利用cv2.findContours方法在每个单词周围画一个边界框。

让我们编写前面的策略:

The following code is available as Drawing_bounding_boxes_around_words_in_an_image.ipynb in the Chapter18 folder of this book's GitHub repository - tinyurl.com/mcvp-packt Be sure to copy the URL from the notebook in GitHub to avoid any issue while reproducing the results

  1. 让我们从下载一个示例图像开始:
!wget https://www.dropbox.com/s/3jkwy16m6xdlktb/18_5.JPG
  1. 使用以下代码行查看下载的图像:
import cv2, numpy as np
img = cv2.imread('18_5.JPG')
img1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
import matplotlib.pyplot as plt,cv2
%matplotlib inline
plt.imshow(img1)

上述代码将返回以下输出:

  1. 将输入图像转换为灰度图像:
img_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
  1. 获取原始图像的随机裁剪:
crop = img_gray[250:300,50:100]
plt.imshow(crop,cmap='gray')

上述代码会产生以下输出:

从前面的输出中,我们可以看到有一些像素包含噪声。接下来,我们将消除原始图像中的噪声。

  1. 将输入灰度图像二值化:
_img_gray = np.uint8(img_gray < 200)*255

前面的代码导致值小于 200 的像素的值为0,而亮的像素(像素强度大于 200)的值为255

  1. 查找图像中出现的各种人物的轮廓:
contours,hierarchy=cv2.findContours(_img_gray, \
                   cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

cv2通过创建一组连续的像素作为对象的单个斑点来寻找轮廓。参考下面的截图了解cv2.findContours的工作原理:

  1. 将之前获得的阈值图像转换为具有三个通道,以便我们可以绘制字符周围的彩色边界框:
thresh1 = np.stack([_img_gray]*3,axis=2)
  1. 创建一个空白图像,以便我们可以将相关内容从thresh1复制到新图像中:
thresh2 = np.zeros((thresh1.shape[0],thresh1.shape[1]))
  1. 获取上一步中获得的轮廓,并在提到轮廓的地方绘制一个带有矩形的边界框。此外,将与thresh1图像中的外接矩形相对应的内容复制到thresh2:
for cnt in contours:
    if cv2.contourArea(cnt)>0:
        [x,y,w,h] = cv2.boundingRect(cnt)
        if ((h>5) & (h<100)):
            thresh2[y:(y+h),x:(x+w)] = thresh1[y:(y+h), \
                                       x:(x+w),0].copy()
            cv2.rectangle(thresh1,(x,y),(x+w,y+h),(255,0,0),2)

在前面的代码行中,我们只提取面积大于 5 个像素的轮廓,也只提取边界框高度在 5 到 100 个像素之间的轮廓(这样,我们就排除了太小的边界框,它们很可能是噪声,以及可能包含整个图像的大边界框)。

  1. 绘制结果图像:
fig = plt.figure()
fig.set_size_inches(20,20)
plt.imshow(thresh1)

上述代码获取以下输出:

到目前为止,我们可以在字符周围绘制边框,但是如果我们想在单词周围绘制边框,我们需要将单词中的像素组合成一个连续的单元。接下来,我们将看看如何利用单词膨胀技术在单词周围绘制边界框。

  1. 检查填充的图像,thresh2:
fig = plt.figure()
fig.set_size_inches(20,20)
plt.imshow(thresh2)

生成的图像如下所示:

现在,要解决的问题是如何将不同字符的像素连接成一个,使一个连续的像素集合构成一个单词。

我们使用一种叫做膨胀的技术(使用cv2.dilate),将白色像素渗透到周围的像素中。出血量取决于果仁的大小。如果内核大小是 5,那么白色区域的所有边界向外移动 5 个像素。直观的解释参考下面的截图:

  1. 使用 1 行 2 列的内核大小展开:
dilated = cv2.dilate(thresh2, np.ones((1,2),np.uint8), \
                    iterations=1)

请注意,我们指定了 1 行 2 列(np.ones((1,2),np.uint8))的内核大小,这样相邻的字符很可能会有一些交集。这样,cv2.findContours现在可以包含彼此非常接近的字符。

然而,如果内核大小更大,扩展的单词可能有一些交集,导致组合的单词被捕获在一个边界框中。

  1. 获取放大图像的轮廓:
contours,hierarchy = cv2.findContours(np.uint8(dilated), \
                    cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
  1. 在原始图像上绘制放大图像的轮廓:
for cnt in contours:
    if cv2.contourArea(cnt)>5:
        [x,y,w,h] = cv2.boundingRect(cnt)
        if ((h>5) & (h<100)):
            cv2.rectangle(img1,(x,y),(x+w,y+h),(255,0,0),2)
  1. 用等高线绘制原始图像:
fig = plt.figure()
fig.set_size_inches(20,20)
plt.imshow(img1)

上述代码会产生以下输出:

由此,您可以看到我们获取了一个对应于每个单词的边界框。

要学习的关键方面是我们如何识别像素的集合形成单个连接的单元,以及如果像素的集合没有形成单元,如何使用膨胀来操纵它们。当膨胀出血黑色像素时,有一个类似的函数叫做erode出血白色像素。我们鼓励你进行侵蚀并理解它是如何自己运作的。

到目前为止,我们已经学习了如何在图像中寻找人物(物体)的轮廓。在下一节中,我们将学习识别图像中的线条。

检测道路图像中的车道

想象一下这样一个场景,您必须检测道路图像中的车道。解决这个问题的一种方法是利用深度学习中的语义分割技术。使用 OpenCV 解决这个问题的传统方法之一是使用边缘和线检测器。在本节中,我们将了解边缘检测和线检测如何帮助识别道路图像中的车道。

在这里,我们将概述对该战略的高度理解:

  1. 找出图像中各种物体的边缘。
  2. 识别沿直线且相连的边。
  3. 将识别的线从图像的一端延伸到另一端。

让我们制定我们的策略:

以下代码在本书的 GitHub 知识库的Chapter18文件夹中以detecting_lanes_in_the_image_of_a_road.ipynb的形式提供-【tinyurl.com/mcvp-packt】… GitHub 中的笔记本上复制 URL,以避免在复制结果时出现任何问题

  1. 下载示例图像:
!wget https://www.dropbox.com/s/0n5cs04sb2y98hx/road_image3.JPG
  1. 导入包并检查映像:
!pip install torch_snippets
from torch_snippets import show, read, subplots, cv2, np
IMG = read('road_image3.JPG',1)
img = np.uint8(IMG.copy())

导入的图像如下所示:

图像中有太多的信息,我们只对直线感兴趣。一种快速获取图像边缘的方法是使用 Canny 边缘检测器,当颜色发生剧烈变化时,它会将某些东西识别为边缘。颜色变化在技术上取决于图像中像素的梯度。两个像素的差异越大,像素代表物体边缘的可能性就越高。

  1. 使用cv2.Canny边缘检测技术提取与图像内容相对应的边缘:
blur_img = cv2.blur(img, (5,5))
edges = cv2.Canny(blur_img,150,255)
edges_org = cv2.Canny(img,150,255)
subplots([img,edges_org,blur_img,edges],nc=4, \
        titles=['Original image','Edges of original image', \
        'Blurred image','Edges of blurred image'],sz=15)

在前面的代码中,我们首先使用cv2.blur对原始图像进行模糊处理,我们查看一个 5 x 5 的小块,获取该小块中像素值的平均值,并用每个像素周围像素值的平均值替换中心元素。

当使用cv2.Canny方法计算边缘时,值150255代表对应于边缘的最小和最大可能梯度值。注意,如果一个像素的一边具有某个像素值,而另一边具有与另一边上的像素显著不同的像素值,则该像素是边缘。

原始图像和模糊图像的图像和边缘如下所示:

从前面我们可以看到,当我们对原始图像进行模糊处理时,边缘更符合逻辑。既然边缘已经确定,我们只需要从图像中获取直线。这是使用HoughLines技术完成的。

  1. 使用cv2.HoughLines方法识别长度至少为 100 像素的线条:
lines = cv2.HoughLines(edges,1,np.pi/180,100)

注意,100的参数值指定被识别的线的长度应该至少为 100 个像素。

在这种情况下,获得的线具有 9×1×2 的形状;即图像中有九条线,每条线都有自己离图像左下角的距离和对应的角度(在极坐标中一般称为[rho, theta])。

  1. 画出不太水平的线:
lines = lines[:,0,:]
for rho,theta in lines:
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    x1 = int(x0 + 10000*(-b))
    y1 = int(y0 + 10000*(a))
    x2 = int(x0 - 10000*(-b))
    y2 = int(y0 - 10000*(a))
    if theta < 75*3.141/180 or theta > 105*3.141/180:
        cv2.line(blur_img,(x1,y1),(x2,y2),(255,0,0),1)

show(blur_img,sz=10, grid=True)

上述代码生成以下输出:

总之,我们首先通过执行模糊和边缘检测从图像中过滤掉所有可能的噪声。只有几个像素仍然是车道的可能候选者。接下来,使用HoughLines,我们进一步过滤掉不是至少 100 个像素的直线的候选。虽然在该图像中道路上的车道被相当好地检测到,但是不能保证前面的逻辑在道路的每一幅图像上都有效。作为练习,在一些不同的道路图像上尝试上述过程。在这里,您将体会到深度学习在使用 OpenCV 进行车道检测方面的强大功能,其中模型学习在各种各样的图像上进行准确预测(假设我们在各种各样的图像上训练模型)。

基于颜色检测物体

绿屏是一种经典的视频编辑技术,我们可以让某人看起来好像站在一个完全不同的背景前。这在天气预报中被广泛使用,记者指向移动的云和地图的背景。这项技术的诀窍在于,记者从不穿某种颜色的衣服(比如说绿色),而是站在一个只有绿色的背景前。然后,识别绿色像素将识别什么是背景,并有助于仅替换这些像素处的内容。

在本节中,我们将了解如何利用cv2.inRangecv2.bitwise_and方法来检测任何给定图像中的绿色。

我们将采取的策略如下:

  1. 将图像从 RGB 转换到 HSV 空间。
  2. 指定对应于绿色的 HSV 空间的上限和下限。
  3. 确定有绿色的像素-这将是蒙版。
  4. 在原始图像和掩模图像之间执行bitwise_and操作。

上述策略在代码中实现如下:

The following code is available as Detecting_objects_based_on_color.ipynb in the Chapter18 folder of this book's GitHub repository - tinyurl.com/mcvp-packt Be sure to copy the URL from the notebook in GitHub to avoid any issue while reproducing the results

  1. 获取映像并安装所需的软件包:
!wget https://www.dropbox.com/s/utrkdooh08y9mvm/uno_card.png
!pip install torch_snippets
from torch_snippets import *
import cv2, numpy as np
  1. 读取图像并转换到 HSV ( 色相-饱和度-值)空间。从 RGB 转换到 HSV 空间将使我们从颜色中分离出亮度,这样我们就可以轻松地提取每个像素的颜色信息:
img = read('uno_card.png', 1)
show(img)
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)

以下是 RGB 空间中的图像:

  1. 定义 HSV 空间中绿色的上限和下限:
lower_green = np.array([45,100,100])
upper_green = np.array([80,255,255])
  1. 生成遮罩,仅激活位于定义的上限和下限阈值内的像素。cv2.inRange是检查像素值是否在最小值和最大值之间但在 HSV 标度上的比较操作:
mask = cv2.inRange(hsv, lower_green, upper_green)
  1. 在原始图像和蒙版之间执行cv2.bitwise_and操作以获取结果图像:
res = cv2.bitwise_and(img, img, mask=mask)
subplots([img, mask, res], nc=3, figsize=(10,5), \
        titles=['Original image','Mask on image', \
                'Resulting image'])

原始图像、蒙版和生成的图像如下:

从前面的图中,我们可以看到,该算法忽略了图像中的其余内容,只关注感兴趣的颜色。利用这一点,我们可以扩展逻辑,使用cv2.bitwise_not操作得出一个只不是绿色的前景蒙版,并执行绿屏技术。

总之,我们可以识别图像中的色彩空间,并且如果我们想要将另一个图像投影/覆盖到所识别的绿色屏幕上,我们从另一个图像中选取与原始图像中的绿色像素相对应的像素。

接下来,我们将学习使用关键点检测技术将一幅图像的特征与另一幅图像的特征进行匹配。

构建图像的全景视图

在本节中,我们将学习一种通过组合多幅图像来创建全景视图的技术。

想象一个场景,你正在用相机拍摄一个地方的全景。本质上,你正在拍摄多张照片,在后端,算法将图像中的公共元素映射到一张图像中(从最左边移动到最右边)。

为了执行图像的拼接,我们将利用cv2中可用的球体 ( 快速定向和旋转简要)方法。深入了解这些算法如何工作的细节超出了本书的范围——我们鼓励您浏览文档和论文,可从opencv-python-tutro als . readthedocs . io/en/latest/py _ tutorials/py _ feature 2d/py _ orb/py _ orb . html获得。

在高层次上,该方法识别在查询图像 ( image1)中的关键点,然后如果关键点匹配,则将它们与在另一个训练图像 ( image2)中识别的关键点相关联。

我们将采用以下策略来执行图像拼接:

  1. 计算关键点并在两幅图像中提取它们。
  2. 使用蛮力方法识别两幅图像中的共同特征。
  3. 利用cv2.findHomoGraphy方法来转换训练图像,以匹配查询图像的方向。
  4. 最后,我们利用cv2.warpperspective方法获取一个看起来像标准视图的视图。

现在,我们将使用以下代码实现上述策略:

The following code is available as Building_a_panoramic_view_of_images.ipynb in the Chapter18 folder of the book's GitHub repository - tinyurl.com/mcvp-packt Be sure to copy the URL from the notebook in GitHub to avoid any issue while reproducing the results

  1. 获取图像并导入相关包:
!pip install torch_snippets
from torch_snippets import *
!wget https://www.dropbox.com/s/mfg1codtc2rue84/g1.png
!wget https://www.dropbox.com/s/4yhui8s1xjndavm/g2.png
  1. 加载查询和训练图像,并将其转换为灰度图像:
queryImg = read('g1.png', 1)
queryImg_gray = read('g1.png')

trainImg = read('g2.png', 1)
trainImg_gray = read('g2.png')

subplots([trainImg, queryImg], nc=2, figsize=(10,5), \
        titles = ['Query image', \
    'Training image (Image to be stitched to Query image)'])

查询和训练图像如下所示:

  1. 使用 ORB 特征检测器提取两幅图像中的关键点和特征:
# Fetch the keypoints and features corresponding to the images
descriptor = cv2.ORB_create()
kpsA, featuresA = descriptor.detectAndCompute(trainImg_gray, \
                                                None)
kpsB, featuresB = descriptor.detectAndCompute(queryImg_gray, \
                                                None)
# Draw the keypoints obtained on images
img_kpsA = cv2.drawKeypoints(trainImg_gray,kpsA,None, \
                            color=(0,255,0))
img_kpsB = cv2.drawKeypoints(queryImg_gray,kpsB,None, \
                            color=(0,255,0))
subplots([img_kpsB, img_kpsA], nc=2, figsize=(10,5), \
            titles=['Query image with keypoints', \
                'Training image with keypoints'])

两个图像中提取的关键点的绘图如下:

ORB 或任何其他特征检测器分两步工作:

  1. 首先,它识别两幅图像中有趣的关键点。标准的关键点检测器之一是 Harris 角点检测器,它可以识别线的交点,以判断某个东西是否是尖角。
  2. 第二,将来自两个图像的所有关键点对相互比较,以查看关键点附近的图像块周围是否有高相关性。如果匹配度很高,这意味着两个关键点都指向图像中的同一个位置。

要深入了解 ORB,请参考 ORB:筛选或冲浪的高效替代方案(【ieeexplore.ieee.org/document/61…

  1. 使用cv2.BFMatcher方法找到两幅图像特征的最佳匹配:
bf = cv2.BFMatcher(cv2.NORM_HAMMING)
best_matches = bf.match(featuresA,featuresB)
matches = sorted(best_matches, key = lambda x:x.distance)

匹配的输出是一个DMatch对象的列表。DMatch对象具有以下属性:

  • DMatch.distance:描述符之间的距离。越低越好
  • DMatch.trainIdx:训练描述符中描述符的索引
  • DMatch.queryIdx:查询描述符中描述符的索引
  • DMatch.imgIdx:列车图像索引

请注意,我们已经根据两个图像特征之间的距离对它们的匹配进行了排序。

  1. 使用以下代码绘制匹配项:
img3 = cv2.drawMatches(trainImg,kpsA,queryImg,kpsB, \
                        matches[:100],None, \
        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
show(img3)

上述代码会产生以下输出:

现在,我们需要找到正确的平移、旋转和缩放设置,将第二幅图像叠加在第一幅图像之上。这组变换作为单应矩阵获得。

  1. 获取对应于两幅图像的单应性:
kpsA = np.float32([kp.pt for kp in kpsA])
kpsB = np.float32([kp.pt for kp in kpsB])
ptsA = np.float32([kpsA[m.queryIdx] for m in matches])
ptsB = np.float32([kpsB[m.trainIdx] for m in matches])

(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC,4)

注意,我们只考虑被识别为两幅图像之间匹配的那些点。此外,通过执行单应性,我们得到了一个矩阵H,它能够使用下面的等式将ptsA与其在ptsB中的相关点进行变换:

  1. 执行图像拼接:

给定H矩阵,您可以使用cv2.warpPerspective函数进行实际的平移、旋转和缩放。做完这些后,在trainImg上,我们将在上面叠加queryImg,我们就有了我们的全景图像!

width = trainImg.shape[1] + queryImg.shape[1]
height = trainImg.shape[0] + queryImg.shape[0]

result = cv2.warpPerspective(trainImg, H, (width, height))
result[0:queryImg.shape[0], 0:queryImg.shape[1]] = queryImg

_x = np.nonzero(result.sum(0).sum(-1) == 0)[0][0]
_y = np.nonzero(result.sum(1).sum(-1) == 0)[0][0]

show(result[:_y,:_x])

上述操作会产生以下输出:

从前面的描述中,我们可以看到,我们已经使用被检测到在两幅图像之间具有匹配的关键点成功地组合了两幅图像。本节的关键在于,有几种关键点匹配技术可以识别两幅不同图像中的两个局部特征是否相同。

一旦确定了共同的关键点,我们就利用单应性来确定要执行的转换。最后,我们执行转换,通过利用cv2.warpperspective技术将两幅图像对齐,并将两幅图像缝合在一起。除了图像拼接之外,这种技术管道(关键点识别、识别两个图像之间的匹配关键点、识别要执行的变换以及执行变换)在诸如图像配准之类的应用中非常有用,其中一个图像需要叠加在另一个图像之上。

接下来,我们将了解在识别汽车牌照位置时如何利用预先训练好的级联分类器。

检测汽车的牌照

想象一个场景,我们要求您在汽车图像中识别车牌的位置。我们在关于目标检测的章节中学到的一种方法是提出基于锚盒的技术来识别车牌的位置。这将要求我们在利用模型之前,在几百张图像上训练模型。

但是,有一个级联分类器,它是一个预先训练好的文件,我们可以用它来识别汽车图像中车牌的位置。如果一个分类器由几个更简单的分类器(阶段)组成,那么它就是一个级联分类器,这些分类器随后被应用于感兴趣的区域,直到在某个阶段,候选区域被拒绝或者所有阶段都通过。这些类似于我们到目前为止已经学会如何使用的卷积核。这不是一个从其他内核学习内核的深度神经网络,而是一个内核列表,这些内核已经被识别为当它们的所有分类都被投票时给出良好的分类分数。

例如,一个人脸层叠可以有多达 6000 个内核来处理人脸的某个部分。其中一些内核可能看起来像这样:

这些级联也被称为哈尔级联。

有了这个高层次的理解,让我们用粉笔画出我们在利用预先训练的级联分类器来识别汽车图像中的车牌位置时将采用的策略:

  1. 导入相关的级联。
  2. 将图像转换为灰度图像。
  3. 指定图像中感兴趣对象的最小和最大比例。
  4. 获取来自级联分类器的区域建议。
  5. 围绕区域方案绘制边界框。

让我们用代码实现前面的策略:

The following code is available as Detecting_the_number_plate_of_a_car.ipynb in the Chapter18 folder of this book's GitHub repository - tinyurl.com/mcvp-packt Be sure to copy the URL from the notebook in GitHub to avoid any issue while reproducing the results

  1. 获取车牌识别级联:
!wget https://raw.githubusercontent.com/zeusees/HyperLPR/master/model/cascade.xml
  1. 获取图像:
!wget https://www.dropbox.com/s/4hbem2kxzqcwo0y/car1.jpg
  1. 加载图像和级联分类器:
!pip install torch_snippets
from torch_snippets import *
plate_cascade = cv2.CascadeClassifier('cascade.xml')
image = read("car1.jpg", 1)
  1. 将图像转换为灰度并绘制:
image_gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
  1. 利用级联检测多个秤的车牌:
plates = plate_cascade.detectMultiScale(image_gray, 1.08, \
                                    2, minSize=(40, 40), \
                                    maxSize=(1000, 100))

plate_cascade.detectMultiScale将返回与级联核高度卷积匹配的所有可能的矩形区域,这有助于识别图像中车牌的位置。此外,我们正在指定宽度和高度的最小和最大尺寸。

  1. 循环通过板区域建议(板)并获取比区域建议稍大的区域:
image2 = image.astype('uint8')
for (x, y, w, h) in plates:
    print(x,y,w,h)
    x -= w * 0.14
    w += w * 0.75
    y -= h * 0.15
    h += h * 0.3
    cv2.rectangle(image2, (int(x), int(y)), \
                (int(x + w), int(y + h)), (0, 255, 0), 10)
show(image2, grid=True)

上述代码生成以下输出:

从前面的截图可以看出,预先训练好的级联分类器能够准确识别出车牌的位置。类似于道路车道检测练习,即使在车牌检测的情况下,我们也可能会遇到我们的策略在不同的图像集上不起作用的情况。我们鼓励您在不同的自定义图像上尝试上述步骤。

摘要

在这一章中,我们学习了利用一些基于 OpenCV 的技术来识别轮廓、边缘和线条,并跟踪彩色物体。虽然我们在本章中讨论了一些用例,但是这些技术在各种用例中有更广泛的应用。然后,我们学习了在拼接两幅彼此相关的图像时,使用关键点和特征提取技术来识别两幅图像之间的相似性。最后,我们学习了级联分类器,并利用预先训练好的分类器,只需很少的开发工作就能获得最佳解决方案,还能实时生成预测。

总的来说,通过这一章,我们希望表明,并不是所有的问题都需要神经网络,特别是在受约束的环境中,我们可以使用大量的历史知识和技术来快速解决这些问题。在 OpenCV 无法解决的地方,我们已经深入研究了神经网络。

图像很迷人。存储它们是人类最早的努力之一,也是获取内容的最有力的方式之一。在 21 世纪,捕捉图像变得很容易,这带来了许多问题,不管有没有人的干预,这些问题都可以得到解决。我们已经使用 PyTorch 完成了一些最常见也是最现代的任务——图像分类、目标检测、图像分割、图像嵌入、图像生成、处理生成的图像、用很少的数据点进行训练、将计算机视觉与 NLP 技术相结合以及强化学习。我们从头开始讲述了各种算法的工作细节。我们还学习了如何用公式表达一个问题,获取数据,创建网络,从训练好的模型中进行推断,以及如何训练和验证它们。我们了解了如何挑选代码库/预训练模型,并为我们的任务定制它们,最后,我们学习了如何部署我们的模型。

我们希望你已经掌握了处理图像的技能,就像处理图像是你的第二天性一样,并解决你自己感兴趣的任务。

最重要的是,我们希望这对你来说是一个快乐的旅程,你喜欢读这本书,就像我们喜欢写它一样!

十九、附录

第一章-人工神经网络基础

  1. 神经网络中的各层是什么? 输入、隐藏和输出层

  2. 前馈传播的输出是什么? 帮助计算损失值的预测

  3. 连续因变量的损失函数与二元因变量以及分类因变量的损失函数有何不同?MSE 是连续因变量的常用损失函数,二元因变量的常用交叉熵。分类交叉熵用于分类因变量。

  4. 什么是随机梯度下降? 这是一个减少损失的过程,通过向梯度递减的方向调整权重

  5. 反向传播练习做什么?它使用链式法则计算所有权重相对于损失的梯度

  6. 在反向传播期间,跨层的所有权重的权重更新是如何发生的?使用公式 dW = W–alpha *(dW/dL)来实现

  7. 在训练神经网络的每个时期内,神经网络的所有功能是什么? 对于一个时期中的每一批,执行正向推进- >、反向推进- >、更新权重- >对下一批重复,直到所有时期结束

  8. 为什么在 GPU 上训练网络比在 CPU 上训练更快?在 GPU 硬件上可以并行执行更多矩阵运算

  9. 学习率如何影响神经网络的训练?过高的学习率会使权重爆炸,过低的学习率根本不会改变权重

  10. 学习率参数的典型值是多少?1e-2 至 1e-5

第二章- PyTorch 基础知识

  1. 训练时为什么要把整数输入转换成浮点值? nn.Linear(以及几乎所有的火炬层)只接受浮动作为输入
  2. 重塑张量物体的各种方法有哪些? 重塑、查看
  3. 为什么张量对象比 NumPy 数组的计算速度更快?在 GPU 上并行运行的能力仅在张量对象上可用
  4. 神经网络类中的 init 神奇函数是由什么构成的? 调用super().__init__()并指定神经网络的层数
  5. 为什么我们在执行反向传播之前执行零梯度? 确保冲洗掉之前计算的梯度
  6. 数据集类由哪些神奇的函数构成? __len____getitem__
  7. 我们如何对新的数据点做出预测? 通过在张量上调用模型,就好像它是一个函数-模型(x)
  8. 我们如何获取神经网络的中间层值? 通过创建自定义方法
  9. Sequential方法如何帮助简化神经网络架构的定义? 我们可以通过连接一系列层来避免创建__init__forward方法

第三章-使用 PyTorch 构建深度神经网络

  1. 如果输入值未在输入数据集中进行缩放,会出现什么问题? 将权重调整到最佳值需要更长时间,因为输入值在未缩放时变化很大
  2. 在训练神经网络时,如果背景具有白色像素颜色,而内容具有黑色像素颜色,会出现什么问题?神经网络必须学会忽略大多数不太有用的白色内容
  3. 批量大小对模型的训练时间、给定数量的时期的准确性有什么影响? 批量越大,收敛所需的时间越长,达到高精度所需的迭代次数也越多
  4. 训练结束时,输入值范围对权重分布有什么影响? 如果输入值没有缩放到某个范围,某些权重会有助于过度拟合
  5. 批处理规范化如何帮助提高准确性? 正如我们缩放输入以更好地收敛人工神经网络是多么重要一样,批量标准化缩放激活以更好地收敛其下一层
  6. 我们如何知道一个模型是否过度适合训练数据?当验证损失是常数或随着更多的时期而不断增加,而训练损失随着时期的增加而不断减少时
  7. 正则化如何帮助避免过度拟合?正则化技术有助于模型在受限环境中训练,从而迫使人工神经网络以更少偏差的方式调整其权重
  8. L1 正则化和 L2 正则化有何不同? L1 =权重绝对值之和,L2 =除典型损失之外的损失值加上权重平方之和
  9. 辍学如何有助于减少过度拟合?通过减少人工神经网络中的一些连接,我们迫使网络从更少的数据中学习。这迫使模型一般化。

第四章-介绍卷积神经网络

  1. 为什么在使用传统神经网络时,对翻译图像的预测很低?所有图像都位于原始数据集中的中心,因此人工神经网络只学习位于中心的图像的任务。
  2. 卷积是怎么做的? 卷积是两个矩阵之间的乘法。
  3. 如何确定过滤器中的最佳重量值? 通过反向传播。
  4. 卷积和汇集的结合如何帮助解决图像转换的问题? 虽然卷积给出了重要的图像特征,但是汇集提取了图像片中最显著的特征。这使得池化在邻近区域上是一个健壮的操作,即,即使某些东西被平移了几个像素,池化仍将返回预期的输出。
  5. 更接近输入层的层中的过滤器学习什么?像边缘这样的低级特征。
  6. 池化有助于构建模型的功能是什么? 它通过减少特征图大小来减少输入大小,并使模型平移不变。
  7. 为什么我们不能获取输入图像,就像我们在 FashionMNIST 数据集上所做的那样展平,并为真实世界的图像训练一个模型?如果图像尺寸非常大,那么连接两个层的参数数量将会以百万计。
  8. 数据增强如何帮助改善图像翻译? 数据增强创建了被平移了几个像素的图像副本。因此,即使图像中的对象偏离中心,模型也被迫学习正确的类。
  9. 在什么场景下我们利用collate_fn进行数据加载? 当我们需要执行批处理级别的转换时,这在__getitem__中很难/很慢地执行。
  10. 改变训练数据点的数量对验证数据集的分类准确性有什么影响? 一般来说,数据集越大,模型精度就越好。

第五章-图像分类的迁移学习

  1. VGG 和 ResNet 预培训架构的培训内容是什么? Imagenet 数据集中的图像。
  2. 为什么 VGG11 的精度不如 VGG16?与 VGG16 相比,VGG11 的层数较少。
  3. VGG11 中的数字 11 代表什么? 11 层。
  4. 残余网络中的残余是什么? 除了层的变换,层还返回输入。
  5. 残余网络的优势是什么?它有助于避免渐变消失,也有助于增加模型深度。
  6. 有哪些各种流行的预训模型?VGG,雷斯网,盗梦空间,AlexNet。
  7. 在迁移学习过程中,为什么要使用与预训练模型的训练中使用的相同的均值和标准差来标准化图像? 模型被训练成使得它们期望输入图像用特定的平均值和标准偏差来归一化。
  8. 为什么我们要冻结模型中的某些参数? 我们冻结,以便参数在反向传播期间不会更新。他们没有更新,因为他们已经训练有素。
  9. 我们如何知道预训练模型中存在的各种模块? print(model)
  10. 我们如何训练一个能同时预测分类值和数值的模型? 通过使用多个预测头,并使用每个预测头的单独损失进行训练。
  11. 如果我们执行与我们在年龄和性别估计一节中所写的相同的代码,为什么年龄和性别预测代码可能不总是对您感兴趣的图像起作用? 与训练数据不具有相似分布的图像可能会产生意想不到的结果。
  12. 我们如何进一步提高我们在面部关键点预测部分中编写的面部关键点识别模型的准确性?我们可以在训练过程中添加颜色和几何增强。

第六章-图像分类的实际应用

  1. 类激活图是如何获得的? 参考生成凸轮一节中提供的 8 个步骤

  2. 批量规范化和数据扩充在训练模型时有什么帮助?它们有助于减少过度拟合

  3. CNN 模型过度拟合的常见原因是什么? 无批量标准化、数据扩充、丢失

  4. CNN 模型在数据科学家端处理训练和验证数据,但在现实世界中不处理的各种场景有哪些?真实世界的数据可能与用于训练和验证模型的数据具有不同的分布。此外,模型可能会过度拟合训练数据

  5. 我们利用 OpenCV 包的各种场景是什么? 在受限环境中工作时,以及推断速度更重要时

第七章-目标检测的基础

  1. 区域提议技术如何生成提议?它识别颜色、纹理、大小和形状相似的区域。

  2. 如果一个图像中有多个对象,如何计算 IoU? IoU 是使用交集/并集度量为每个对象计算的

  3. 为什么 R-CNN 生成预测需要很长时间?因为有多少提议,我们就创建多少正向传播

  4. 为什么快速 R-CNN 比 R-CNN 快?对于所有提案,从 VGG 主干提取特征图是常见的。与快速 RCNN 相比,这减少了几乎 90%的计算

  5. 投资回报池是如何工作的? 所有的selectivesearch作物都经过自适应池内核,因此最终输出的大小相同

  6. 在预测边界框校正时,获取要素地图后没有多个图层会有什么影响?你可能没有注意到模型没有学会准确预测边界框

  7. 为什么在计算总体损失时,我们必须给回归损失分配较高的权重?分类损失是交叉熵,其通常为 log(n)量级,导致输出可能具有高范围。然而,边界框回归损失介于 0 和 1 之间。因此,回归损失必须按比例增加。

  8. 非最大抑制是如何工作的?通过组合相同类别且具有高 iou 的盒子,我们消除了冗余的包围盒预测。

第八章-高级目标检测

  1. 为什么快速 R-CNN 比快速 R-CNN 更快? 使用selectivesearch技术,我们不需要每次都输入大量不必要的提议。相反,更快的 R-CNN 使用区域提议网络自动找到他们。
  2. 与更快的 R-CNN 相比,YOLO 和 SSD 的速度如何?我们不需要依赖新的提案网络。该网络一次就能直接找到这些建议。
  3. 是什么让 YOLO 和 SSD 单次检测算法?网络一次性预测所有的提议和预测
  4. 客观分和类分有什么区别?对象性标识一个对象是否存在。但是类分数预测了对象非零的锚盒的类

第九章-图像分割

  1. 向上扩展对 U-Net 架构有何帮助? 放大有助于增加特征地图的大小,以便最终输出与输入大小相同。
  2. U-Net 中为什么需要全卷积网络? 因为输出也是图像,并且很难使用线性层来预测图像成形张量。
  3. 在 Mask R-CNN 中,RoI Align 如何改进 RoI pooling? RoI Align 采用预测建议的偏移来精确对齐特征图。
  4. U-Net 和 Mask R-CNN 在分割方面的主要区别是什么?U-Net 是完全卷积的,具有单个端到端网络,而 Mask R-CNN 使用诸如 Backbone、RPN 等迷你网络来完成不同的任务。Mask R-CNN 能够识别和分离同类型的几个对象,而 U-Net 只能识别(但不能分离成单个实例)。
  5. 什么是实例分段?如果在同一个图像中有同一个类的不同对象,那么每个这样的对象被称为一个实例。应用图像分割在像素级别上分别预测所有实例被称为实例分割。

第十一章-自编码器和图像处理

  1. autoencoder 中的编码器是什么? 将图像转换成矢量表示的较小的神经网络。

  2. autoencoder 针对什么损失函数进行优化? 像素级均方误差,直接比较预测与输入。

  3. 自编码器如何帮助分组相似的图像? 相似的图像会返回相似的编码,更容易聚类。

  4. 卷积自编码器什么时候有用? 当输入是图像时。

  5. 如果我们从普通/卷积自编码器获得的嵌入向量空间中随机采样,为什么会得到不直观的图像? 编码中的数值范围不受限制,因此正确的输出高度依赖于正确的数值范围。一般来说,随机抽样假设平均值为 0,标准差为 1。

  6. 变分自编码器优化的损失函数是什么? 像素级 MSE 和 KL-来自编码器的平均值和标准偏差分布的散度。

  7. 变分自编码器如何克服普通/卷积自编码器生成新图像的限制? 通过将预测编码限制为正态分布,所有编码都落在均值-0 和标准偏差 1 的区域内,这很容易采样。

  8. 在对抗性攻击中,为什么我们修改输入图像像素而不是权重值?在对抗性攻击中,我们无法控制神经网络。

  9. 在神经类型转移中,我们优化的损失是什么? 生成图像与原始图像的感知(VGG)损失,以及来自生成图像和风格图像的格拉姆矩阵的风格损失。

  10. 为什么我们在计算样式和内容损失时考虑不同图层的激活而不考虑原始图像? 使用更多中间层可确保生成的图像保留图像的更精细细节。此外,使用更多损耗会使梯度上升更加稳定。

  11. 为什么我们在计算风格损失时考虑的是克矩阵损失而不是图像之间的差异?Gram matrix 给出了图像风格的指示,即纹理、形状和颜色是如何排列的,并且将忽略实际内容。这就是为什么它更方便风格的损失。

  12. 为什么我们在建立模型的时候会扭曲图像,从而产生深度假像?扭曲图像有助于规则化。此外,它有助于生成所需数量的图像。

第十二章-使用 GANs 生成图像

  1. 如果生成器和鉴别器模型的学习率很高会怎么样?根据经验,观察到模型稳定性较低。
  2. 在生成器和鉴别器训练有素的场景中,给定图像是真实的概率是多少? 0.5。
  3. 为什么我们在生成图像时使用 convtranspose2d?我们不能使用线性图层来放大/生成图像。
  4. 为什么我们在条件 GANs 中嵌入的大小比类的数量大?使用更多的参数给予模型更多的自由度来学习每一类的重要特征。
  5. 我们如何生成有胡子的男人的图像? 用有条件的 GAN。就像我们有男性和女性的形象,我们可以有胡子的男性和其他类似的类,而训练模型。
  6. 为什么我们在发生器的最后一层激活 Tanh,而不是 ReLU 或 Sigmoid? 归一化图像的像素范围是[-1,1],因此我们使用 Tanh
  7. 为什么我们得到了真实的图像,即使我们没有对生成的数据去归一化? 即使像素值不在[0,255]之间,相对值也足以让make_grid实用程序去归一化输入
  8. 如果我们在训练 GAN 之前不裁剪对应于图像的面部,会发生什么?如果有太多的背景,GAN 会得到关于什么是脸什么不是脸的错误信号,因此它可能会专注于生成更真实的背景
  9. 为什么训练发生器更新时鉴频器的权重不更新(因为generator_train_step函数涉及鉴频器网络)? 这是一个循序渐进的过程。当更新生成器时,我们假设鉴别器能够做到最好。
  10. 为什么我们在训练鉴别器时得到真实和虚假图像的损失,而在训练生成器时只得到虚假图像的损失?因为无论发生器创造了什么,都只是虚假的图像。

第十三章-处理图像的高级 GANs

  1. 为什么我们需要 Pix2Pix GAN,而像 U-Net 这样的监督学习算法可以从轮廓中生成图像? U-net 在训练时只使用像素级损失。我们需要 pix2pix,因为 U-net 生成图像时不会损失真实感。
  2. 为什么我们需要在 CycleGAN 中针对 3 种不同的损失函数进行优化? 在 CycleGAN 的第 7 点中提供了答案。
  3. 进步中的技巧如何帮助建立风格?ProgressiveGAN 帮助网络一次学习几个上采样层,以便当图像必须增加尺寸时,负责生成当前尺寸图像的网络是最佳的。
  4. 我们如何识别对应于给定自定义图像的潜在向量? 通过调整随机产生的噪声,使得产生的图像和感兴趣的图像之间的 MSE 损失尽可能最小。

第十四章-用最少的数据点进行训练

  1. 预训练的词向量是如何获得的? 来自现有数据库,如 GLOVE 或 word2vec
  2. 零拍学习中我们如何从一个图像特征嵌入映射到单词嵌入? 通过创建一个合适的神经网络,该网络返回一个与单词嵌入形状相同的向量,并用 mse-loss 进行训练(将预测与实际单词嵌入进行比较)
  3. 暹罗网为什么这么叫?因为我们总是产生两个输出并相互比较,以确保一致性。暹罗代表双胞胎。
  4. 暹罗网是怎么得出两幅图像的相似度的? 损失函数迫使网络预测,如果图像相似,输出具有较小的距离。

第十五章-结合计算机视觉和自然语言处理技术

  1. 为什么 CNN 和 RNN 在图像字幕中结合在一起?CNN 需要捕捉图像特征,而 RNN 需要创建语言输出。
  2. 为什么图像字幕中提供了开始和结束标记,而手写转录中没有?CTC 丢失不需要这种令牌,而且,在 OCR 中,我们一次生成所有时间步的令牌。
  3. 为什么在手写转录中利用 CTC 丢失功能?我们无法描绘图像中的时间步长。CTC 负责将关键图像特征与时间步长对齐。
  4. 转换器如何帮助目标检测?通过将锚盒视为转换器解码器的嵌入输入,DETR 学习动态锚盒,从而帮助目标检测。

第十六章-结合计算机视觉和强化学习

  1. 给定状态的值是如何计算的?通过计算该状态下的预期回报

  2. Q 表是如何填充的? 通过计算所有状态的期望报酬

  3. 为什么我们在国家行动价值计算中要有一个贴现因子?由于不确定性,我们不确定未来会如何发展。因此,我们通过贴现的方式来降低未来奖励的权重

  4. 勘探开发战略的必要性是什么?只有开发会使模型停滞不前并变得可预测,因此模型应该能够探索并发现看不见的步骤,这些步骤甚至比模型已经知道的更有价值。

  5. 深度 Q 学习的需求是什么?我们让神经网络学习可能的奖励系统,而不需要昂贵的算法,这些算法可能需要太多时间或要求整个环境的可见性。

  6. 如何使用深度 Q 学习计算给定状态动作组合的值?它只是神经网络的输出。输入是状态,网络为给定状态下的每一个行为预测一个期望的回报。

  7. 在卡拉环境下可能会有哪些动作? 加速--1,0,1 转向--1,0,1