大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
个人主页-Sonhhxg_柒的博客_CSDN博客
欢迎各位→点赞 + 收藏⭐️ + 留言
系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟
文章目录
在图像中的单词周围绘制边界框
检测道路图像中的车道
根据颜色检测物体
构建图像的全景视图
检测汽车的车牌
概括
到目前为止,在前面的章节中,我们已经了解了如何利用各种技术来执行对象分类、定位和分割,以及生成图像。虽然所有这些技术都利用深度学习来解决任务,但对于相对简单且定义明确的任务,我们可以利用 OpenCV 包中提供的特定功能。例如,如果需要检测的对象始终是具有相同背景的同一对象,我们就不需要 YOLO。在图像来自受限环境的情况下,OpenCV 实用程序之一很有可能在很大程度上帮助解决问题。
我们将在本章中仅介绍几个用例,因为要涵盖的实用程序太多,因此需要单独出版一本专注于 OpenCV 的书。在进行单词检测时,您将了解图像膨胀、腐蚀和提取连接组件周围的轮廓。之后,您将了解 Canny 边缘检测以识别图像中对象的边缘。此外,您将了解在视频/图像背景中使用绿屏的优势,同时对图像执行按位运算以识别感兴趣的色彩空间。然后,您将了解一种通过将两幅图像拼接在一起来帮助创建全景视图的技术。最后,您将了解如何利用预先训练的级联过滤器来识别车牌等对象。
在本章中,我们将学习以下主题:
想象一个场景,您正在构建一个模型,该模型从文档图像执行单词转录。第一步是识别图像中单词的位置。首先,有两种方法可以识别图像中的单词:
在本节中,我们将了解如何在不利用深度学习的情况下在干净的图像中识别机器打印的单词。由于背景和前景之间的对比度很高,因此您不需要像 YOLO 这样的矫枉过正的解决方案来识别单个单词的位置。在这些场景中使用 OpenCV 将特别方便,因为我们可以在计算资源非常有限的情况下获得解决方案,因此,即使推理时间 也会非常短。唯一的缺点是准确率可能不是 100%,但这也取决于扫描图像的清洁程度。如果保证扫描非常非常清晰,那么您可以期望接近 100% 的准确度。
在高层次上,让我们了解如何识别/隔离图像中的单词:
让我们编写前面的策略:
1.让我们从下载示例图像开始:
!wget https://www.dropbox.com/s/3jkwy16m6xdlktb/18_5.JPG
2.使用以下代码行查看下载的图像:
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)
前面的代码将返回以下输出:
3.将输入图像转换为灰度图像:
img_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
4.获取原始图像的随机裁剪:
crop = img_gray[250:300,50:100]
plt.imshow(crop,cmap='gray')
前面的代码产生以下输出:
从前面的输出中,我们可以看到有几个像素包含噪声。接下来,我们将去除原始图像中存在的噪声。
5.二值化输入灰度图像:
_img_gray = np.uint8(img_gray < 200)*255
前面的代码导致值小于 200 的像素的值为0,而明亮的像素(像素强度大于 200)的值为255。
6.找出图像中出现的各种字符的轮廓:
contours,hierarchy=cv2.findContours(_img_gray, \
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2通过将一组连续像素集合创建为对象的单个斑点来查找轮廓。请参阅以下屏幕截图以了解其cv2.findContours工作原理:
7.将之前获得的阈值图像转换为具有三个通道,以便我们可以在字符周围绘制彩色边界框:
thresh1 = np.stack([_img_gray]*3,axis=2)
8.创建一个空白图像,以便我们可以将相关内容复制thresh1到新图像中:
thresh2 = np.zeros((thresh1.shape[0],thresh1.shape[1]))
9.获取在上一步中获得的轮廓,并在提到轮廓的地方绘制一个带有矩形的边界框。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 像素之间的那些轮廓(这样,我们消除了那些太小,可能是噪声,大边界框可能包含整个图像)。
10.绘制结果图像:
fig = plt.figure()
fig.set_size_inches(20,20)
plt.imshow(thresh1)
前面的代码获取以下输出:
到目前为止,我们可以在字符周围绘制边界框,但是如果我们想在单词周围绘制框,我们需要将单词中的像素组合成一个连续的单元。接下来,我们将看看利用单词膨胀技术在单词周围绘制边界框。
11.检查填充的图像,thresh2:
fig = plt.figure()
fig.set_size_inches(20,20)
plt.imshow(thresh2)
生成的图像如下所示:
现在,要解决的问题是如何将不同字符的像素连接成一个,使一个连续的像素集合构成一个词。
我们使用一种称为膨胀(使用cv2.dilate)的技术,它将白色像素渗入周围的像素中。出血量由内核大小决定。如果内核大小为 5,则白色区域的所有边界向外移动 5 个像素。请参阅以下屏幕截图以获得直观的解释:
12.以 1 行 2 列的内核大小进行扩张:
dilated = cv2.dilate(thresh2, np.ones((1,2),np.uint8), \
iterations=1)
请注意,我们将内核大小指定为 1 行 2 列 ( np.ones((1,2),np.uint8)),以便相邻字符很可能有一些交集。这样,cv2.findContours现在可以包含彼此非常接近的字符。
但是,如果内核大小更大,膨胀的词可能会有一些交集,导致组合词被捕获在一个边界框中。
13.获取膨胀图像的轮廓:
contours,hierarchy = cv2.findContours(np.uint8(dilated), \
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
14.在原始图像上绘制扩张图像的轮廓:
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)
15.用轮廓绘制原始图像:
fig = plt.figure()
fig.set_size_inches(20,20)
plt.imshow(img1)
前面的代码产生以下输出:
从中可以看出,我们获取了每个单词对应的边界框。
学习的关键方面是我们如何识别像素集合形成一个单一的连接单元,如果像素集合没有形成一个单元,如何使用膨胀来操纵它们。虽然膨胀会渗出黑色像素,但有一个类似的函数称为erode会渗出白色像素。我们鼓励您自己执行腐蚀并了解它是如何工作的。
到目前为止,我们已经了解了如何在图像中的字符(对象)周围寻找轮廓。在下一节中,我们将学习如何识别图像中的线条。
想象一个场景,您必须检测道路图像中的车道。解决这个问题的一种方法是利用深度学习中的语义分割技术。使用 OpenCV 解决此问题的传统方法之一是使用边缘和线检测器。在本节中,我们将了解边缘检测和线检测如何帮助识别道路图像中的车道。
在这里,我们将概述对该策略的高级理解:
让我们编写我们的策略:
1.下载示例图像:
!wget https://www.dropbox.com/s/0n5cs04sb2y98hx/road_image3.JPG
2.导入包并检查图像:
!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 边缘检测器,当颜色发生剧烈变化时,它会将某物识别为边缘。颜色变化在技术上取决于图像内像素的梯度。两个像素的差异越大,像素代表物体边缘的可能性就越高。
3.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,值150和255表示与边缘对应的最小和最大可能梯度值。请注意,如果像素的一侧具有某个像素值,而另一侧的像素值与另一侧的像素有很大不同,则该像素就是边缘。
原始图像和模糊图像的图像和边缘如下所示:
从前面我们可以看出,当我们对原始图像进行模糊处理时,边缘更符合逻辑。现在边缘已被识别,我们只需要从图像中获取直线边缘。这是使用该HoughLines技术完成的。
4.使用以下方法识别长度至少为 100 像素的线条cv2.HoughLines:
lines = cv2.HoughLines(edges,1,np.pi/180,100)
请注意,参数值100指定标识线的长度应至少为 100 像素。
在这种情况下,获得的线条具有 9 x 1 x 2 的形状;也就是说,图像中有九条线,每条线都有自己与图像左下角的距离和相应的角度(通常称为[rho, theta]极坐标)。
5.绘制水平较低的线:
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.inRange和cv2.bitwise_and方法来检测任何给定图像中的绿色。
我们将采取的策略如下:
上述策略在代码中实现如下:
1.获取映像并安装所需的软件包:
!wget https://www.dropbox.com/s/utrkdooh08y9mvm/uno_card.png
!pip install torch_snippets
from torch_snippets import *
import cv2, numpy as np
2.读取图像并将其转换为HSV(Hue-Saturation-Value)空间。从 RGB 转换为 HSV 空间将使我们将亮度与颜色分离,以便我们可以轻松提取每个像素的颜色信息:
img = read('uno_card.png', 1)
show(img)
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
这是RGB空间中的图像:
3.定义 HSV 空间中绿色的上限和下限:
lower_green = np.array([45,100,100])
upper_green = np.array([80,255,255])
4.生成掩码,它仅激活落在定义的上限和下限阈值内的像素。cv2.inRange是检查像素值是否在最小值和最大值之间但在 HSV 尺度上的比较操作:
mask = cv2.inRange(hsv, lower_green, upper_green)
5.在原始图像和掩码之间执行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
总之,我们可以识别图像中的颜色空间,如果我们想将另一张图像投影/叠加到识别的绿色屏幕上,我们从另一张图像中选择与原始图像中的绿色像素相对应的像素。
接下来,我们将学习如何使用关键点检测技术将一张图像的特征匹配到另一张图像。
在本节中,我们将学习通过组合多个图像来帮助创建全景视图的一种技术。
想象一个场景,您正在使用相机捕捉一个地方的全景。本质上,您正在拍摄多张照片,并且在后端,该算法将图像中存在的常见元素(从最左侧移动到最右侧)映射到单个图像中。
为了执行图像拼接,我们将利用. 深入了解这些算法如何工作的细节超出了本书的范围——我们鼓励您阅读https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/上提供的文档和论文py_feature2d/py_orb/py_orb.html。cv2
在高层次上,该方法识别查询图像中的关键点( ),然后如果关键点匹配image1,则将它们与另一个训练图像中识别的关键点( ) 相关联。image2
我们将采用的图像拼接策略如下:
现在,我们将使用以下代码实现上述策略:
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
2.加载查询和训练图像并将它们转换为灰度图像:
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)'])
查询和训练图像如下所示:
3.使用 ORB 特征检测器提取两个图像中的关键点和特征:
# 获取图像对应的关键点和特征
descriptor = cv2.ORB_create()
kpsA, featuresA = descriptor.detectAndCompute(trainImg_gray, \
None)
kpsB, featuresB = descriptor.detectAndCompute(queryImg_gray, \
None)
# 绘制在图像上获得的关键点
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 或任何其他特征检测器分两步工作:
要深入了解 ORB,请参阅 ORB:SIFT 或 SURF 的有效替代方案( ORB: An efficient alternative to SIFT or SURF | IEEE Conference Publication | IEEE Xplore )。
4.使用以下方法在两幅图像的特征中找到最佳匹配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属性:
请注意,我们已经根据距离对两个图像特征之间的匹配进行了排序。
5.使用以下代码绘制匹配:
img3 = cv2.drawMatches(trainImg,kpsA,queryImg,kpsB, \
matches[:100],None, \
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
show(img3)
前面的代码产生以下输出:
现在,我们需要找到正确的平移、旋转和缩放集合,以将第二张图像叠加在第一张图像之上。这组变换作为单应矩阵获得。
6.获取两个图像对应的单应性:
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
7.执行图像拼接:
给定矩阵,您可以使用函数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技术将两个图像对齐并将两个图像拼接在一起。除了图像拼接之外,他的技术流水线(关键点识别、识别两幅图像之间的匹配关键点、识别要执行的变换以及执行变换)在图像配准等应用中非常有用,其中一个图像需要叠加在另一个之上。
接下来,我们将学习在识别汽车车牌位置时利用预训练的级联分类器。
想象一个场景,我们要求您识别汽车图像中车牌的位置。我们在关于对象检测的章节中学习了如何做到这一点的一种方法是提出基于锚框的技术来识别车牌的位置。这将要求我们在利用模型之前在几百张图像上训练模型。
但是,有一个级联分类器作为预训练文件很容易获得,我们可以使用它来识别汽车图像中车牌的位置。一个分类器是一个级联分类器,如果它由几个更简单的分类器(阶段)组成,这些分类器随后应用于感兴趣的区域,直到某个阶段,候选区域被拒绝或所有阶段都通过。这些类似于到目前为止我们已经学会如何使用的卷积核。这是一个内核列表,而不是一个从其他内核学习内核的深度神经网络,而是一个内核列表,当所有分类都被投票支持时,这些内核已被确定为给出良好的分类分数。
例如,一个面部级联可以有多达 6,000 个内核来处理面部的某些部分。其中一些内核可能如下所示:
这些级联也称为 Haar Cascade。
有了这种高层次的理解,让我们来总结一下我们将采用的策略,即利用预先训练的级联分类器来识别汽车图像中车牌的位置:
让我们在代码中实现上述策略:
1.取车牌识别级联:
!wget https://raw.githubusercontent.com/zeusees/HyperLPR/master/model/cascade.xml
2.获取图像:
!wget https://www.dropbox.com/s/4hbem2kxzqcwo0y/car1.jpg
3.加载图像和级联分类器:
!pip install torch_snippets
from torch_snippets import *
plate_cascade = cv2.CascadeClassifier('cascade.xml')
image = read("car1.jpg", 1)
4.将图像转换为灰度并绘制它:
image_gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
5.利用级联检测多尺度车牌:
plates = plate_cascade.detectMultiScale(image_gray, 1.08, \
2, minSize=(40, 40), \
maxSize=(1000, 100))
plate_cascade.detectMultiScale 将返回与级联内核具有高度卷积匹配的所有可能的矩形区域——这有助于识别图像中车牌的位置。此外,我们指定宽度和高度的最小和最大尺寸。
6.循环遍历板块区域提案(板块)并获取比区域提案大一点的区域:
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 技术相结合,和强化学习。我们从头开始介绍了各种算法的工作细节。我们还学习了如何制定问题、捕获数据、创建网络以及从训练后的模型中进行推断,并学习了如何训练和验证它们。我们了解了如何获取代码库/预训练模型并为我们的任务定制它们,最后,我们了解了如何部署我们的模型。
我们希望您已经掌握了处理图像的技能,就像它的第二天性一样,并解决了您自己感兴趣的任务。