使用 Haar 级联进行对象检测
译文:
machinelearningmastery.com/using-haar-cascade-for-object-detection/
在深度学习革命重新定义计算机视觉之前,Haar 特征和 Haar 级联是你必须了解的对象检测工具。即便在今天,它们仍然是非常有用的对象检测器,因为它们轻量化。在这篇文章中,你将了解 Haar 级联及其如何检测物体。完成本篇文章后,你将会知道:
-
什么是 Haar 特征
-
Haar 级联如何利用 Haar 特征来检测物体
-
OpenCV 中的一些预定义 Haar 级联对象检测器
启动你的项目,请参考我的书籍《OpenCV 中的机器学习》。它提供了自学教程和工作代码。
使用 Haar 级联进行对象检测
图片由亚历山大·希普提供。保留部分权利。
概述
本教程分为两个部分,它们是:
-
什么是 Haar 特征和 Haar 级联?
-
OpenCV 中的 Haar 级联
什么是 Haar 特征和 Haar 级联?
自 2001 年保罗·维奥拉和迈克尔·琼斯开发了这一技术以来,Haar 特征和 Haar 级联已经彻底改变了对象检测。它们已成为各种应用中的重要组成部分,从面部识别到实时对象检测。
Haar 特征从图像中的矩形区域提取。特征值基于像素强度。通常,它是通过滑动窗口计算的,窗口中的区域被划分为两个或更多矩形区域。Haar 特征是这些区域之间像素强度总和的差异。
认为物体的存在会扭曲像素强度的变化。例如,背景通常是均匀的模式,而前景物体将不适合这种背景。通过检查相邻矩形区域之间的像素强度,应该能注意到差异。因此,它表明了物体的存在。
为了提高计算效率,Haar 特征中的矩形区域通常与图像的边缘平行,而不是倾斜的。然而,我们可以使用多种尺寸和形状的矩形来捕捉物体的不同特征和尺度变化。因此,Haar 特征的关键优势在于它们能够表示三种模式:
-
边缘:由于我们如何定向矩形区域而呈现的垂直或水平边缘。它们对于识别不同图像区域之间的边界非常有用。
-
线条:图像中的对角边缘。它们对于识别物体中的线条和轮廓非常有用。
-
中心环绕特征:这检测矩形区域中心与周围区域之间的强度变化。这对于识别具有明显形状或模式的对象很有用。
Haar 级联通过层次结构组合多个 Haar 特征来构建分类器。级联将检测过程分解为多个阶段,每个阶段包含一组特征,而不是用每个 Haar 特征分析整个图像。
Haar 级联的关键思想是,整个图像中只有少量像素与关注的对象相关。因此,尽快丢弃图像中无关的部分至关重要。在检测过程中,Haar 级联以不同的尺度和位置扫描图像,以消除无关区域。使用 AdaBoost 算法训练的级联结构实现了高效的分层特征评估,减少了计算负担并加快了检测速度。
OpenCV 中的 Haar Cascade
Haar 级联是一种算法,但首先,你需要训练一个 Haar 级联分类器,然后才能将其用作对象检测器。
在 OpenCV 中,以下是预训练的 Haar 级联分类器(你可以从 github.com/opencv/opencv/tree/4.x/data/haarcascades
下载模型文件):
-
人脸
-
眼睛检测
-
人体的全身、上半身或下半身
-
车辆牌照
预训练分类器以 XML 文件的形式存储。你可以从 GitHub 链接 找到内置分类器的文件名。要创建分类器,你必须提供此 XML 文件的路径。如果你使用的是 OpenCV 自带的分类器,可以使用以下语法:
# Load the Haar cascade for face detection
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
通常,照片有多个颜色通道(如红色、绿色和蓝色)。Haar 级联仅依赖于像素强度。因此,你应该提供单通道图像,如灰度图像。
想要开始使用 OpenCV 进行机器学习吗?
现在就参加我的免费电子邮件速成课程(含示例代码)。
点击注册并获得免费的 PDF 电子书版本课程。
使用 Haar 级联分类器来检测对象就是使用 detectMultiScale()
方法。它接受以下参数:
-
image
:这是你想要进行对象检测的输入图像。它应该是灰度格式,或者对于 HSV 通道格式的图像是“V”通道。 -
scaleFactor
:此参数弥补了不同距离的对象在摄像头中出现不同尺寸的事实。它控制每个图像尺度下图像大小的缩小程度。它必须严格大于 1. 较低的scaleFactor
增加检测时间,但也增加了检测的可能性。典型值范围从 1.01 到 1.3。 -
minNeighbors
:这个参数指定了每个候选对象应拥有多少个邻居以保留它。较高的值会导致检测到的对象较少,但质量较高。较低的值可能会导致更多的检测结果,但可能会有误检。这是精准度与召回率之间的权衡。 -
minSize
:这个参数设置了对象的最小尺寸。小于此尺寸的对象将被忽略。它是一个形式为(width, height)
的元组。
让我们通过一个例子来试试。你可以在以下网址下载一张街景照片:
一张用于面部检测的 Haar 级联照片。
照片由JACQUELINE BRANDWAYN提供。保留部分权利。
本示例使用了分辨率为 1920×1080 的中等尺寸。如果你的分辨率不同,你可能需要特别调整detectMultiScale()
中的参数,尤其是minSize
。
让我们创建一个面部检测器,并找到行人面部的位置。分类器是使用与 OpenCV 一起提供的预训练模型haarcascade_frontalface_default.xml
创建的。模型文件位于cv2.data.haarcascades
指向的路径中。然后我们可以用它来将面部检测为边界框:
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=4, minSize=(20, 20))
随意调整你的参数。为了说明结果,你可以使用 OpenCV 的函数在原始图像上绘制。
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
请注意,cv2.rectangle()
函数要求提供矩形框对角线的坐标,而detectMultiScale()
的输出提供的是左上角的坐标以及宽度和高度。上述函数在每个检测到的面部上绘制了两像素宽的蓝色框。请注意,在 OpenCV 中,图像以 BGR 通道顺序呈现。因此,像素颜色(255, 0, 0)
代表蓝色。
结果如下:
Haar 级联检测到的面部
你可以看到有一些误检,但总体来说,结果还是相当不错的。你可以调整上述参数,查看结果的变化。使用 Haar 级联的对象检测器的质量取决于它训练得如何,以生成你从 XML 文件中读取的模型。
完整代码如下:
import cv2
import sys
# Photo https://unsplash.com/photos/people-walking-on-sidewalk-during-daytime-GBkAx9qUeus
# Jacqueline Brandwayn
filename = 'jacqueline-brandwayn-GBkAx9qUeus-unsplash.jpg'
#filename = 'people2.jpg'
# Load the Haar cascade for face detection
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
# Read the input image
img = cv2.imread(filename)
# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Perform face detection
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=4, minSize=(20, 20))
# Draw rectangles around the detected faces
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
# Display the result
cv2.imshow('Face Detection', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
进一步阅读
本节提供了更多相关资源,供你深入了解。
书籍
-
精通 OpenCV 4 与 Python,2019。
-
数字图像处理,2017
论文
-
Paul Viola 和 Michael J. Jones. 稳健的实时人脸检测。《计算机视觉国际期刊》,57(2):137–154,2004 年。
-
Rainer Lienhart 和 Jochen Maydt. 一组扩展的 Haar-like 特征用于快速对象检测。见于《国际图像处理会议论文集》,2002 年,第 1 卷,第 1-900 页。IEEE,2002 年。
网站
-
维基百科上的 Haar 特征:
en.wikipedia.org/wiki/Haar-like_feature
-
OpenCV,
opencv.org/
-
OpenCV Cascade Classifier,
docs.opencv.org/4.x/db/d28/tutorial_cascade_classifier.html
总结
在本教程中,你学习了如何使用 OpenCV 的 Haar 级联分类器。
具体来说,你学到了:
-
什么是 Haar 特征和 Haar 级联,它如何识别对象
-
如何使用内置的 Haar 级联分类器通过 OpenCV 检测照片中的对象
使用 OpenCV 提取梯度直方图
除了由 SIFT、SURF 和 ORB 生成的特征描述符外,如在之前的帖子中所示,方向梯度直方图(HOG)是另一种使用 OpenCV 可以获取的特征描述符。HOG 是计算机视觉和图像处理中广泛使用的强大特征描述符,用于对象检测和识别任务。它捕捉图像中梯度方向的分布,并提供了一个强大的表示,不受光照和阴影变化的影响。
在本帖子中,您将学习关于 HOG 的内容。具体来说,您将了解:
-
HOG 是什么,它与图像有何关系
-
如何在 OpenCV 中计算它
用我的书OpenCV 中的机器学习开启您的项目。它提供了自学教程和可运行的代码。
让我们开始吧!
使用 OpenCV 提取梯度直方图
照片由Alexas_Fotos提供。部分权利保留。
概述
本帖子分为两部分;它们是:
-
理解 HOG
-
在 OpenCV 中计算 HOG
-
使用 HOG 进行人员检测
理解 HOG
HOG 算法背后的概念是计算图像局部区域中梯度方向的分布。HOG 在图像上操作一个窗口,即图像上固定像素大小的区域。一个窗口被分为小的空间区域,称为块,而块进一步被划分为多个单元。HOG 计算每个单元内的梯度幅度和方向,并创建梯度方向的直方图。然后将同一块内的直方图串联起来。
梯度衡量像素的颜色强度与其邻域的比较。它的变化越剧烈,幅度越高。方向告诉我们哪个方向是最陡的梯度。通常,这适用于单通道图像(即灰度图像),每个像素可以有自己的梯度。HOG 收集一个块内的所有梯度,并将它们放入一个直方图中。
HOG 制作直方图的巧妙之处在于,直方图中的箱由角度决定,但值在最接近的箱之间插值。例如,如果箱分配值为 0、20、40,而梯度角度为 30 时,值为 10,则在 20 和 40 的箱中添加了值 5。这样,HOG 能够有效地捕捉图像中物体的纹理和形状。
HOG 特别适用于检测具有明显纹理和模式的对象,使其成为行人检测和其他形式对象识别任务的热门选择。凭借捕捉梯度方向分布的能力,HOG 提供了对光照条件和阴影变化不敏感的强大表示。
在 OpenCV 中计算 HOG
OpenCV 提供了一种直接的方法来计算 HOG 描述符,使得开发人员和研究人员都能轻松访问。让我们来看一个在 OpenCV 中计算 HOG 的基本示例:
import cv2
# Load the image and convert to grayscale
img = cv2.imread('image.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# define each block as 4x4 cells of 64x64 pixels each
cell_size = (128, 128) # h x w in pixels
block_size = (4, 4) # h x w in cells
win_size = (8, 6) # h x w in cells
nbins = 9 # number of orientation bins
img_size = img.shape[:2] # h x w in pixels
# create a HOG object
hog = cv2.HOGDescriptor(
_winSize=(win_size[1] * cell_size[1],
win_size[0] * cell_size[0]),
_blockSize=(block_size[1] * cell_size[1],
block_size[0] * cell_size[0]),
_blockStride=(cell_size[1], cell_size[0]),
_cellSize=(cell_size[1], cell_size[0]),
_nbins=nbins
)
n_cells = (img_size[0] // cell_size[0], img_size[1] // cell_size[1])
# find features as a 1xN vector, then reshape into spatial hierarchy
hog_feats = hog.compute(img)
hog_feats = hog_feats.reshape(
n_cells[1] - win_size[1] + 1,
n_cells[0] - win_size[0] + 1,
win_size[1] - block_size[1] + 1,
win_size[0] - block_size[0] + 1,
block_size[1],
block_size[0],
nbins)
print(hog_feats.shape)
HOG 一次计算一个窗口的特征。一个窗口中有多个块。在一个块中,有多个“单元格”。见下图:
假设整个图像是一个窗口。一个窗口被划分为多个单元格(绿色网格),几个单元格组合成一个块(红色和蓝色框)。一个窗口中有许多重叠的块,但所有块的大小相同。
每个单元格的大小是固定的。在上面的示例中,你使用了 64×64 像素的单元格。每个块有相同数量的单元格。在上面的示例中,你使用了 4×4 单元格的块。此外,窗口中的单元格数量也是相等的;你使用了 8×6 单元格。然而,在计算 HOG 时,我们并没有将图像划分为块或窗口。相反,
-
视窗口为图像上的滑动窗口,其中滑动窗口的步幅大小是一个单元格的大小,即每次滑动一个单元格
-
我们将窗口划分为固定大小的单元格
-
我们设置第二个滑动窗口以匹配块大小并扫描窗口。它每次滑动一个单元格
-
在一个块内,HOG 是从每个单元格中计算的
返回的 HOG 是整个图像的一个向量。在上述代码中,你重新塑造它以清晰地显示窗口、块、单元格和直方图箱的层次结构。例如,hog_feats[i][j]
对应于窗口(以 numpy 切片语法表示):
img[n_cells[1]*i : n_cells[1]*i+(n_cells[1]*win_size[1]),
n_cells[0]*j : n_cells[0]*j+(n_cells[0]*win_size[0])]
或者,相当于窗口的左上角是单元格(i,j)。
滑动窗口是一种在目标检测中常用的技术,因为你不能确定一个特定对象是否完全位于一个网格单元中。将单元格做得更小,但窗口更大,是捕捉对象的比仅看到部分对象更好的方法。然而,有一个限制:窗口较大的对象会被遗漏。此外,过小的对象可能会被窗口中的其他元素所掩盖。
通常,你会有一些与 HOG 相关的下游任务,例如在 HOG 特征上运行 SVM 分类器进行对象检测。在这种情况下,你可能想将 HOG 输出重新塑造成整个块的向量,而不是像上面那样按单元格的层次结构。
使用 HOG 进行人群检测
上述代码中的特征提取技术对于您想要获取其他用途的原始特征向量很有用。但对于一些常见任务,OpenCV 提供了预训练的机器学习模型,您可以轻松使用它们。
我们来考虑以下 URL 的照片(保存为 people.jpg
):
使用 HOG 检测人的示例照片。
图片由 Chris Dickens 提供。保留所有权利。
这是一个人们过马路的照片。OpenCV 中的 HOG 有一个经过训练的“人检测器”,其窗口大小为 64×128 像素。用它来检测照片中的人非常简单:
import cv2
# Load the image and convert it to grayscale
img = cv2.imread('people.jpg')
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
# Detect people in the image
locations, confidence = hog.detectMultiScale(img)
# Draw rectangles around the detected people
for (x, y, w, h) in locations:
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 5)
# Display the image with detected people
cv2.imshow('People', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上面,您使用了cv2.HOGDescriptor_getDefaultPeopleDetector()
的参数创建了一个 HOG 描述符,它将初始化一个 SVM 分类器来检测特定对象,在这种情况下是人。
您可以在图像上调用描述符并使用hog.detectMultiScale(img)
运行 SVM,该函数返回每个检测到的对象的边界框。虽然窗口大小是固定的,但此检测函数会在多个尺度下调整图像大小,以找到最佳检测结果。即便如此,返回的边界框仍然不够紧凑。上面的代码还通过在图像上标记边界框来标注检测到的人。您可以通过检测器报告的置信度分数进一步过滤结果。一些过滤算法,如非极大值抑制,可能适用,但这里不予讨论。以下是输出:
使用 OpenCV 中的 HOG 生成的边界框
您可以看到这些检测器只能在全身可见的情况下找到人。输出中包含假阳性(未检测到的人)和假阴性(未检测到的人)。在拥挤的场景中计算所有人可能会很具有挑战性。但这是一个很好的开始,展示了如何使用 OpenCV 轻松完成任务。
不幸的是,OpenCV 除了人脸检测器之外,没有其他检测器。但您可以使用 HOG 作为特征向量来训练自己的 SVM 或其他模型。使机器学习模型更为便捷是从图像中提取特征向量的关键点。
想要开始使用 OpenCV 进行机器学习吗?
立即参加我的免费电子邮件速成课程(包含示例代码)。
点击注册并获取课程的免费 PDF 电子书版本。
进一步阅读
本节提供了更多关于该主题的资源,供您深入了解。
书籍
- 精通 OpenCV 4 与 Python,2019 年。
网站
-
OpenCV,
opencv.org/
-
StackOverflow: OpenCV HOG 特征解释:
stackoverflow.com/questions/44972099/opencv-hog-features-explanation
总结
在本教程中,你学习了如何在 OpenCV 中使用 HOG 来基于滑动窗口提取特征向量。这是一种有效的特征检测方法,有助于对象检测。
具体来说,你学会了:
-
如何从图像中提取 HOG 特征
-
如何使用 OpenCV 内置的 HOG 人物检测器
如果你有任何问题,请在下方留言。
使用 OpenCV 的 k-最近邻分类
machinelearningmastery.com/k-nearest-neighbors-classification-using-opencv/
OpenCV 库有一个模块实现了用于机器学习应用的 k-最近邻算法。
在本教程中,你将学习如何应用 OpenCV 的 k-最近邻算法来分类手写数字。
完成本教程后,你将了解:
-
k-最近邻算法的几个最重要特征。
-
如何在 OpenCV 中使用 k-最近邻算法进行图像分类。
用我的书《OpenCV 中的机器学习》 启动你的项目。它提供了自学教程和可工作的代码。
让我们开始吧。
使用 OpenCV 的 k-最近邻分类
图片由Gleren Meneghin提供,部分版权保留。
教程概述
本教程分为两个部分;它们是:
-
k-最近邻算法如何工作的提醒
-
使用 k-最近邻算法进行 OpenCV 中的图像分类
前提条件
对于本教程,我们假设你已经熟悉:
k-最近邻算法如何工作的提醒
k-最近邻(kNN)算法已经在Jason Brownlee 的这篇教程中讲解得很好,但让我们先回顾一下他教程中一些最重要的点:
- kNN 算法不涉及任何学习。它只是存储和使用整个训练数据集作为其模型表示。因此,kNN 也被称为懒惰学习算法。
*** 由于整个训练数据集被存储,因此保持其整理良好、经常用新数据更新,并尽可能避免异常值是有意义的。
*** 新的实例通过根据选择的距离度量在整个训练数据集中搜索最相似的实例来预测。距离度量的选择通常基于数据的特性。
*** 如果 kNN 用于解决回归问题,则通常使用 k 个最相似实例的均值或中位数来生成预测。
*** 如果 kNN 用于解决分类问题,则可以从 k 个最相似实例中频率最高的类别生成预测。
*** 可以通过尝试不同的值来调整 k 的值,并查看哪种值最适合问题。
*** kNN 算法的计算成本随着训练数据集的大小增加而增加。kNN 算法在输入数据的维度增加时也会遇到困难。
**## 在 OpenCV 中使用 k-最近邻进行图像分类
在本教程中,我们将考虑对手写数字进行分类的应用。
在之前的教程中,我们看到 OpenCV 提供了图像 digits.png,该图像由 5,000 个 20 × 20 20\times 20 20×20 像素的子图像组成,每个子图像展示了从 0 到 9 的手写数字。
我们还看到如何将数据集图像转换为特征向量表示,然后再输入到机器学习算法中。
我们将把 OpenCV 的数字数据集拆分为训练集和测试集,将其转换为特征向量,然后使用这些特征向量来训练和测试一个 kNN 分类器以分类手写数字。
注意:我们之前提到 kNN 算法不涉及任何训练/学习,但我们将参考一个训练数据集,以区分用于模型表示的图像和稍后用于测试的图像。
我们首先加载 OpenCV 的数字图像,将其拆分为训练集和测试集,并使用方向梯度直方图(HOG)技术将其转换为特征向量:
Python
from cv2 import imshow, waitKey
from digits_dataset import split_images, split_data
from feature_extraction import hog_descriptors
# Load the full training image
img, sub_imgs = split_images('Images/digits.png', 20)
# Check that the correct image has been loaded
imshow('Training image', img)
waitKey(0)
# Check that the sub-images have been correctly split
imshow('Sub-image', sub_imgs[0, 0, :, :].reshape(20, 20))
waitKey(0)
# Split the dataset into training and testing
train_imgs, train_labels, test_imgs, test_labels = split_data(20, sub_imgs, 0.5)
# Convert the training and testing images into feature vectors using the HOG technique
train_hog = hog_descriptors(train_imgs)
test_hog = hog_descriptors(test_imgs)
接下来,我们将初始化一个 kNN 分类器:
Python
from cv2 import ml
knn = ml.KNearest_create()
然后在数据集的训练分割上“训练”它。对于数据集的训练分割,我们可以使用图像像素本身的强度值(类型转换为 32 位浮点值,以符合函数的预期输入):
Python
knn.train(float32(train_imgs), ml.ROW_SAMPLE, train_labels)
或者使用 HOG 技术生成的特征向量。在前一节中,我们提到 kNN 算法在处理高维数据时会遇到困难。使用 HOG 技术生成更紧凑的图像数据表示有助于缓解这个问题:
Python
knn.train(train_hog, ml.ROW_SAMPLE, train_labels)
我们继续这个教程,利用 HOG 特征向量。
训练好的 kNN 分类器现在可以在数据集的测试分割上进行测试,然后通过计算与真实值匹配的正确预测的百分比来计算其准确性。暂时将 k
的值经验性地设置为 3:
Python
from numpy import sum
k = 3
ret, result, neighbours, dist = knn.findNearest(test_hog, k)
accuracy = (sum(result == test_labels) / test_labels.size) * 100
然而,正如我们在前一节中提到的,通常做法是通过尝试不同的 k 值来调整,并查看哪种值最适合当前问题。我们还可以尝试使用不同的比例值拆分数据集,以观察它们对预测准确性的影响。
为此,我们将把 kNN 分类器代码放入一个嵌套的 for
循环中,其中外部循环迭代不同的比率值,而内部循环迭代不同的 k 值。在内部循环中,我们还将填充一个字典,记录计算出的准确度值,以便稍后使用 Matplotlib 绘制它们。
我们将包括的最后一个细节是检查我们是否正确加载了图像并将其正确拆分为子图像。为此,我们将使用 OpenCV 的 imshow
方法来显示图像,然后使用输入为零的 waitKey
方法来停止并等待键盘事件:
Python
from cv2 import imshow, waitKey, ml
from numpy import sum
from matplotlib.pyplot import plot, show, title, xlabel, ylabel, legend
from digits_dataset import split_images, split_data
from feature_extraction import hog_descriptors
# Load the full training image
img, sub_imgs = split_images('Images/digits.png', 20)
# Check that the correct image has been loaded
imshow('Training image', img)
waitKey(0)
# Check that the sub-images have been correctly split
imshow('Sub-image', sub_imgs[0, 0, :, :].reshape(20, 20))
waitKey(0)
# Define different training-testing splits
ratio = [0.5, 0.7, 0.9]
for i in ratio:
# Split the dataset into training and testing
train_imgs, train_labels, test_imgs, test_labels = split_data(20, sub_imgs, i)
# Convert the training and testing images into feature vectors using the HOG technique
train_hog = hog_descriptors(train_imgs)
test_hog = hog_descriptors(test_imgs)
# Initiate a kNN classifier and train it on the training data
knn = ml.KNearest_create()
knn.train(train_hog, ml.ROW_SAMPLE, train_labels)
# Initiate a dictionary to hold the ratio and accuracy values
accuracy_dict = {}
# Populate the dictionary with the keys corresponding to the values of 'k'
keys = range(3, 16)
for k in keys:
# Test the kNN classifier on the testing data
ret, result, neighbours, dist = knn.findNearest(test_hog, k)
# Compute the accuracy and print it
accuracy = (sum(result == test_labels) / test_labels.size) * 100
print("Accuracy: {0:.2f}%, Training: {1:.0f}%, k: {2}".format(accuracy, i*100, k))
# Populate the dictionary with the values corresponding to the accuracy
accuracy_dict[k] = accuracy
# Plot the accuracy values against the value of 'k'
plot(accuracy_dict.keys(), accuracy_dict.values(), marker='o', label=str(i * 100) + '%')
title('Accuracy of the k-nearest neighbors model')
xlabel('k')
ylabel('Accuracy')
legend(loc='upper right')
show()
绘制不同比率值和不同 k 值下计算出的预测准确度,可以更好地了解这些不同值对特定应用中预测准确度的影响:
不同训练数据拆分和不同 ‘k’ 值下的预测准确度线图
尝试使用不同的图像描述符,并调整所选算法的不同参数,然后将数据输入到 kNN 算法中,并调查你更改所导致的 kNN 输出。
想要开始使用 OpenCV 进行机器学习吗?
现在立即报名参加我的免费邮件速成课程(附带示例代码)。
点击注册并获取课程的免费 PDF 电子书版本。
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解。
书籍
- 掌握 OpenCV 4 与 Python,2019 年。
网站
-
OpenCV,
opencv.org/
-
OpenCV KNearest 类,
docs.opencv.org/4.7.0/dd/de1/classcv_1_1ml_1_1KNearest.html
总结
在本教程中,你学会了如何应用 OpenCV 的 k-最近邻算法来分类手写数字。
具体来说,你学到了:
-
k-最近邻算法的一些重要特性。
-
如何在 OpenCV 中使用 k-最近邻算法进行图像分类。
你有任何问题吗?
在下面的评论中提出你的问题,我会尽力回答。**************
温和介绍 OpenCV:开源计算机视觉与机器学习库
如果你对图像和视频处理感兴趣,并希望在计算机视觉应用中引入机器学习,那么 OpenCV 是你需要获取的库。
OpenCV 是一个庞大的开源库,可以与多种编程语言接口,包括 Python,并被许多个人和商业实体广泛使用。
在本教程中,你将熟悉 OpenCV 库及其重要性。
完成本教程后,你将知道:
-
OpenCV 库是什么。
-
它的用途是什么,谁在使用它。
-
如何在 Python 中安装和导入 OpenCV。
通过我的书籍 《OpenCV 中的机器学习》 来启动你的项目。它提供了自学教程和有效的代码。
让我们开始吧。
温和介绍 OpenCV:开源计算机视觉与机器学习库
照片由Greg Rakozy拍摄,部分权利保留。
教程概览
本教程分为四部分,它们是:
-
OpenCV 是什么?
-
OpenCV 的用途是什么?
-
谁在使用 OpenCV?
-
如何在 Python 中安装和导入 OpenCV?
OpenCV 是什么?
OpenCV 代表开源计算机视觉 库;顾名思义,它是一个开源的计算机视觉和机器学习软件库。
它拥有 Apache 2.0 许可证,允许用户使用、修改和分发软件。这使得商业实体特别愿意在其产品中使用此库。
OpenCV 库是用 C++原生编写的,支持 Windows、Linux、Android 和 MacOS,并提供 C++、Python、Java 和 MATLAB 接口。
它主要针对实时计算机视觉应用。
OpenCV 的用途是什么?
OpenCV 是一个庞大的库,包含超过 2500 个优化的算法,可以用于许多不同的计算机视觉应用,如:
- 人脸检测和识别。
*** **物体识别。 ***** **物体跟踪。 ***** **图像配准与拼接。 ***** **增强现实。 **********
******以及许多其他内容。
在这一系列教程中,你将发现 OpenCV 库在将机器学习应用于计算机视觉应用中的具体作用。
OpenCV 库中实现的一些流行机器学习算法包括:
- K 最近邻
*** 支持向量机*** 决策树****
******以及包括 TensorFlow 和 PyTorch 在内的多个深度学习框架的支持。
谁在使用 OpenCV**?**
OpenCV 网站估计库的下载量超过 1800 万次,用户社区由超过 4.7 万名用户组成。
许多知名公司也在使用 OpenCV 库。
OpenCV 网站提到了一些知名公司,如 Google、Yahoo、Microsoft、Intel 和 Toyota,它们在工作中使用该库。
这些公司使用 OpenCV 库的应用范围也非常广泛:
OpenCV 的应用范围包括从拼接街景图像、检测以色列监控视频中的入侵、监控中国矿山设备、帮助 Willow Garage 的机器人导航和拾取物体、检测欧洲游泳池溺水事故、在西班牙和纽约运行互动艺术、检查土耳其跑道上的碎片、检查全球工厂中产品的标签到日本的快速人脸检测。
– OpenCV,2022 年。
这显示了 OpenCV 库的广泛应用。
OpenCV 在 Python 中如何安装和导入**?**
如前所述,OpenCV 库是用 C++ 编写的,但其功能仍可以从 Python 中调用。
这是通过绑定生成器创建 C++ 和 Python 之间的桥梁实现的。
可以通过以下单行命令从 Python 包索引(PyPi)安装 OpenCV 库:
Python
pip install opencv-python
导入 OpenCV 以利用其功能,就像调用以下命令一样简单:
Python
import cv2
在我们浏览库的过程中,我们将频繁使用 import
命令。
我们将从最基础的开始,学习如何将图像和视频读取为 NumPy 数组,显示它们,访问其像素值,以及在颜色空间之间转换。
想要开始使用 OpenCV 进行机器学习吗?
立即参加我的免费电子邮件速成课程(附示例代码)。
点击注册并获得课程的免费 PDF 电子书版本。
那么,让我们开始吧!
进一步阅读
如果你想深入了解本主题,这一部分提供了更多资源。
书籍
- 掌握 OpenCV 4 和 Python,2019 年。
网站
- OpenCV,
opencv.org/
总结
在本教程中,你将熟悉 OpenCV 库以及它的重要性。
具体来说,你学到了:
-
什么是 OpenCV 库。
-
它用于什么,谁在使用它。
-
如何在 Python 中安装和导入 OpenCV。
你有任何问题吗?
在下面的评论中提出你的问题,我会尽力回答。************
如何使用 OpenCV 读取和显示视频
原文:
machinelearningmastery.com/how-to-read-and-display-videos-using-opencv/
数字视频是数字图像的近亲,因为它们由许多数字图像快速连续地显示组成,以创建运动视觉数据的效果。
OpenCV 库提供了几种处理视频的方法,如从不同来源读取视频数据和访问其多个属性。
在本教程中,你将熟悉处理视频时最基本的 OpenCV 操作。
完成本教程后,你将了解到:
-
数字视频如何作为数字图像的近亲进行制定。
-
如何从摄像头读取组成视频的图像帧。
-
如何从保存的视频文件中读取组成视频的图像帧。
启动你的项目,使用我的书籍 《OpenCV 中的机器学习》。它提供了自学教程和工作代码。
让我们开始吧。
使用 OpenCV 读取和显示视频
照片由 Thomas William 拍摄,版权所有。
教程概述
本教程分为三个部分,它们是:
-
视频是如何制定的?
-
从摄像头读取和显示图像帧
-
从视频文件中读取和显示图像帧
视频是如何制定的?
我们已经看到,数字图像由像素组成,每个像素由其图像空间中的空间坐标和其强度或灰度级值来表征。
我们还提到,包含单一通道的灰度图像可以由 2D 函数 I(x, y) 描述,其中 x 和 y 表示上述空间坐标,I 在任何图像位置 (x, y) 的值表示像素强度。
一个 RGB 图像可以通过这三种 2D 函数描述,I*[R](x, y), I*[G](x, y), 和 I*[B]*(x, y),分别对应其红色、绿色和蓝色通道。
在描述数字视频时,我们将添加一个额外的维度,即t,表示时间。这样做的原因是数字视频由数字图像在一段时间内快速连续地显示组成。在视频的上下文中,我们将这些图像称为图像帧。图像帧连续显示的速率称为帧率,以每秒帧数(FPS)来测量。
因此,如果我们需要从某个特定时间点 t 中的灰度视频中挑选一帧图像,我们将通过函数 I(x, y, t) 来描述它,这现在包含了时间维度。
同样地,如果我们需要从特定时间实例t的RGB视频中选择图像帧,我们将通过三个函数来描述它:I*[R](x, y, t), I*[G](x, y, t), 和 I*[B]*(x, y, t),分别对应其红、绿和蓝通道。
我们的公式告诉我们,数字视频中包含的数据是时间依赖的,这意味着数据随时间变化。
简单来说,这意味着在时间实例t处具有坐标(x, y)的像素的强度值可能与另一个时间实例(t + 1)处的强度值不同。这种强度值的变化可能来自于被记录的物理场景的变化,但也可能来自视频数据中的噪声(例如来自相机传感器本身)。
从相机读取和显示图像帧
要从连接到计算机的相机或存储在硬盘上的视频文件中读取图像帧,我们的第一步是创建一个VideoCapture
对象来处理。所需的参数是int
类型的索引值,对应于要读取的相机,或者是视频文件的名称。
让我们首先从相机中抓取图像帧开始。
如果您的计算机内置或连接了网络摄像头,则可以通过值0
来索引它。如果您有其他连接的相机希望读取,则可以使用值1
、2
等,具体取决于可用的相机数量。
Python
from cv2 import VideoCapture
capture = VideoCapture(0)
在尝试读取和显示图像帧之前,检查是否成功建立了与相机的连接是明智的。可以使用capture.isOpened()
方法来实现这一目的,如果连接未能建立则返回False
:
Python
if not capture.isOpened():
print("Error establishing connection")
如果相机成功连接,我们可以通过使用capture.read()
方法来读取图像帧,如下所示:
Python
ret, frame = capture.read()
该方法返回frame
中的下一个图像帧,以及一个布尔值ret
,如果成功抓取到图像帧则为True
,反之为False
,例如相机已断开连接时可能返回空图像。
以与静态图像相同的方式显示抓取的图像框架,使用imshow
方法:
Python
from cv2 import imshow
if ret:
imshow('Displaying image frames from a webcam', frame)
请记住,在使用 OpenCV 时,每个图像帧都以 BGR 颜色格式读取。
在完整的代码列表中,我们将把上述代码放入一个while
循环中,该循环将继续从相机中抓取图像帧,直到用户终止它。为了让用户终止while
循环,我们将包含以下两行代码:
Python
from cv2 import waitKey
if waitKey(25) == 27:
break
这里,waitKey
函数停止并等待指定的毫秒数以响应键盘事件。它返回按键的代码,如果在指定时间内没有生成键盘事件,则返回-1。在我们的特定情况下,我们指定了 25 毫秒的时间窗口,并检查 ASCII 码27
,这对应于按下Esc
键。当按下Esc
键时,while
循环通过break
命令终止。
我们将要包括的最后几行代码用于停止视频捕捉,释放内存,并关闭用于图像显示的窗口:
Python
from cv2 import destroyAllWindows
capture.release()
destroyAllWindows()
在某些笔记本电脑上,当使用视频捕捉时,你会看到内置摄像头旁边的小 LED 灯亮起。你需要停止视频捕捉才能关闭该 LED 灯。即使你的程序正在从摄像头读取,也没有关系。你必须在其他程序可以使用你的摄像头之前也停止视频捕捉。
完整的代码列表如下:
Python
from cv2 import VideoCapture, imshow, waitKey, destroyAllWindows
# Create video capture object
capture = VideoCapture(0)
# Check that a camera connection has been established
if not capture.isOpened():
print("Error establishing connection")
while capture.isOpened():
# Read an image frame
ret, frame = capture.read()
# If an image frame has been grabbed, display it
if ret:
imshow('Displaying image frames from a webcam', frame)
# If the Esc key is pressed, terminate the while loop
if waitKey(25) == 27:
break
# Release the video capture and close the display window
capture.release()
destroyAllWindows()
想开始使用 OpenCV 进行机器学习吗?
立即参加我的免费电子邮件速成课程(附示例代码)。
点击注册并获取课程的免费 PDF 电子书版本。
从视频文件读取和显示图像帧
另一种方法是从存储在硬盘上的视频文件中读取图像帧。OpenCV 支持许多视频格式。为此,我们将修改我们的代码以指定视频文件的路径,而不是摄像头的索引。
我下载了这个视频,将其重命名为Iceland.mp4,并将其保存到名为Videos的本地文件夹中。
我可以从显示在本地驱动器上的视频属性中看到,该视频包括 1920 x 1080 像素的图像帧,并且以 25 帧每秒的帧率运行。
为了读取该视频的图像帧,我们将修改以下代码行如下:
Python
capture = VideoCapture('Videos/Iceland.mp4')
还可以获取捕捉对象的多个属性,例如图像帧的宽度和高度以及帧率:
Python
from cv2 import CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FPS
frame_width = capture.get(CAP_PROP_FRAME_WIDTH)
frame_height = capture.get(CAP_PROP_FRAME_HEIGHT)
fps = capture.get(CAP_PROP_FPS)
完整的代码列表如下:
Python
from cv2 import (VideoCapture, imshow, waitKey, destroyAllWindows,
CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FPS)
# Create video capture object
capture = VideoCapture('Videos/Iceland2.mp4')
# Check that a camera connection has been established
if not capture.isOpened():
print("Error opening video file")
else:
# Get video properties and print them
frame_width = capture.get(CAP_PROP_FRAME_WIDTH)
frame_height = capture.get(CAP_PROP_FRAME_HEIGHT)
fps = capture.get(CAP_PROP_FPS)
print("Image frame width: ", int(frame_width))
print("Image frame height: ", int(frame_height))
print("Frame rate: ", int(fps))
while capture.isOpened():
# Read an image frame
ret, frame = capture.read()
# If an image frame has been grabbed, display it
if ret:
imshow('Displaying image frames from video file', frame)
# If the Esc key is pressed, terminate the while loop
if waitKey(25) == 27:
break
# Release the video capture and close the display window
capture.release()
destroyAllWindows()
视频具有时间维度。但在 OpenCV 中,你一次处理一个帧。这可以使视频处理与图像处理保持一致,从而使你可以将一种技术从一个应用到另一个。
我们可以在while
循环内部包括其他代码行,以处理每个图像帧,这些图像帧在capture.read()
方法抓取后获得。一个例子是将每个 BGR 图像帧转换为灰度图像,为此我们可以使用与我们用于转换静态图像相同的cvtColor
方法。
Python
from cv2 import COLOR_BGR2GRAY
frame = cvtColor(frame, COLOR_BGR2GRAY)
你能想到对图像帧应用哪些其他变换吗?
进一步阅读
本节提供了更多关于此主题的资源,如果你想深入了解。
书籍
- 掌握 OpenCV 4 与 Python,2019 年。
网站
- OpenCV,
opencv.org/
总结
在本教程中,你将熟悉处理视频时必需的最基本的 OpenCV 操作。
具体来说,你学到了:
-
数字视频如何作为数字图像的近亲进行编制。
-
如何从摄像机中读取构成视频的图像帧。
-
如何从保存的视频文件中读取构成视频的图像帧。
你有任何问题吗?
在下面的评论中提出你的问题,我会尽力回答。
如何在 OpenCV 中读取、写入、显示图像以及转换颜色空间
原文:
machinelearningmastery.com/how-to-read-write-display-images-in-opencv-and-converting-color-spaces/
在处理图像时,一些最基本的操作包括从磁盘读取图像、显示图像、访问其像素值以及在颜色空间之间转换。
本教程解释了这些基本操作,从描述数字图像如何通过其空间坐标和强度值来构建开始。
在本教程中,你将熟悉在处理图像时至关重要的最基本的 OpenCV 操作。
完成本教程后,你将了解到:
-
数字图像如何通过其空间坐标和强度值来构建。
-
如何在 OpenCV 中读取和显示图像。
-
如何访问图像的像素值。
-
图像如何从一种颜色空间转换到另一种颜色空间。
启动你的项目,可以参考我的书籍 Machine Learning in OpenCV。它提供了自学教程和可运行的代码。
让我们开始吧。
使用 OpenCV 读取和显示图像,以及在颜色空间之间转换
图片由 Andrew Ridley 提供,部分权利保留。
教程概述
本教程分为三个部分,它们是:
-
图像的构建
-
在 OpenCV 中读取和显示图像
-
颜色空间转换
图像的构建
数字图像由像素组成,每个像素由其空间坐标和强度或灰度级值来表征。
本质上,图像可以通过一个二维函数 I(x, y) 来描述,其中 x 和 y 表示上述空间坐标,I 在任何图像位置 (x, y) 的值表示像素强度。在数字图像中,空间坐标以及强度值都是有限的、离散的量。
我们刚刚描述的数字图像类型称为灰度图像,这是因为它由一个单一的通道组成,像素值仅包含强度信息。像素强度通常由范围为 [0, 255] 的整数值表示,这意味着每个像素可以取 256 个离散值中的任何一个。
另一方面,RGB 图像由三个通道组成,即红色、绿色和蓝色。
RGB 颜色模型并不是唯一存在的模型,但它可能是许多计算机视觉应用中最常用的。它是一个加色模型,指的是通过混合(或添加)不同颜色源的光谱来创建颜色的过程。
由于 RGB 图像由三个通道组成,因此我们需要三个函数来描述它:I*[R](x, y), I*[G](x, y) 和 I*[B]*(x, y),分别对应红色、绿色和蓝色通道。因此,在 RGB 图像中,每个像素值由三个强度值的三元组表示。
在 OpenCV 中读取和显示图像
首先,导入 Python 中 OpenCV 库的imread
方法:
Python
from cv2 import imread
然后继续读取一张 RGB 图像。为此,我下载了这张图片并将其保存到磁盘,文件名为Dog.jpg,保存在一个名为Images的文件夹中。
Python
img = imread('Images/Dog.jpg')
imread
方法返回一个包含图像像素值的 NumPy 数组img
。我们可以如下检查数组的数据类型和维度:
Python
print('Datatype:', img.dtype, '\nDimensions:', img.shape)
Python
Datatype: uint8
Dimensions: (4000, 6000, 3)
返回的信息告诉我们数组的数据类型是 uint8,这意味着我们处理的是 8 位无符号整数值。这表示图像中每个通道的像素可以取任意 2⁸ = 256 个值,范围从 0 到 255。这与我们上面审查的图像格式完全一致。我们还了解到数组的维度是 4000 × 6000 × 3,分别对应图像的行数、列数和通道数。
该图像是一个 3 维的 NumPy 数组。因此,你可以使用 NumPy 语法来操作这个数组。
现在尝试访问位于图像左上角的第一个像素的值。请记住,Python 中的数组是从零开始索引的,因此该像素的坐标是(0, 0)。
Python
print(img[0, 0])
Python
[173 186 232]
从输出中可以看到,如预期的那样,每个像素携带三个值,每个值对应图像中的三个通道之一。我们将在下一部分发现这三个值分别对应哪个特定通道。
注意:一个重要的点是,如果imread
方法无法加载输入图像(因为提供的图像名称不存在或其路径无效),它不会自行生成错误,而是返回一个NoneType
对象。因此,可以在继续运行进一步的代码之前,包含以下检查,以确保img
值的有效性:
Python
if img is not None:
...
接下来,我们将使用 Matplotlib 包和 OpenCV 的 imshow
方法显示图像。后者的第一个参数是包含图像的窗口的名称,第二个参数是要显示的图像。我们还将在图像显示后调用 OpenCV 的 waitkey
函数,该函数会等待指定的毫秒数的键盘事件。如果输入值为 0,则 waitkey
函数将无限等待,允许我们看到显示的窗口,直到生成键盘事件。
- 使用 Matplotlib:
**Python
import matplotlib.pyplot as plt
plt.imshow(img)
plt.title('Displaying image using Matplotlib')
plt.show()
使用 Matplotlib 显示 BGR 图像。
- 使用 OpenCV:
**Python
from cv2 import imshow, waitKey
imshow('Displaying image using OpenCV', img)
waitKey(0)
使用 OpenCV 显示 BGR 图像。
如果你对 Matplotlib 生成的输出感到惊讶并想知道发生了什么原因,这主要是因为 OpenCV 以 BGR 而不是 RGB 顺序读取和显示图像。
OpenCV 的初始开发者选择了 BGR 颜色格式(而不是 RGB 格式),因为当时 BGR 颜色格式在软件提供商和相机制造商中非常流行。
掌握 OpenCV 4 与 Python,2019 年。
另一方面,Matplotlib 使用 RGB 颜色格式,因此需要先将 BGR 图像转换为 RGB 才能正确显示。
想开始使用 OpenCV 进行机器学习吗?
现在就获取我的免费电子邮件速成课程(包含示例代码)。
点击注册并获取免费的 PDF 电子书版本课程。
使用 OpenCV,你还可以将 NumPy 数组写入文件中,如下所示:
from cv2 import imwrite
imwrite("output.jpg", img)
当你使用 OpenCV 的 imwrite()
函数写入图像时,必须确保 NumPy 数组的格式符合 OpenCV 的要求,即它是一个具有 BGR 通道顺序的 uint8 类型的 3 维数组,格式为行 × 列 × 通道。*
颜色空间之间的转换
从一种颜色空间转换到另一种颜色空间可以通过 OpenCV 的 cvtColor
方法实现,该方法将源图像作为输入参数,并使用颜色空间转换代码。
为了在 BGR 和 RGB 颜色空间之间转换,我们可以使用以下代码:
Python
from cv2 import cvtColor, COLOR_BGR2RGB
img_rgb = cvtColor(img, COLOR_BGR2RGB)
如果我们需要重新尝试使用 Matplotlib 显示图像,我们现在可能会看到它正确显示:
Python
plt.imshow(img_rgb)
plt.show()
将 BGR 图像转换为 RGB 并使用 Matplotlib 显示它。
如果我们还需要访问新转换的 RGB 图像的第一个像素的值:
Python
print(img_rgb[0, 0])
Python
[232 186 173]
比较这些值与我们之前为 BGR 图像打印的值 [173 186 232],我们可能会注意到第一个和第三个值现在已交换。这告诉我们的是,值的顺序与图像通道的顺序相对应。
BGR 转 RGB 并不是通过这种方法实现的唯一颜色转换。事实上,还有许多颜色空间转换代码可供选择,例如 COLOR_RGB2HSV
,它在 RGB 和 HSV(色相、饱和度、明度)颜色空间之间进行转换。
另一个常见的转换是将 RGB 转换为灰度图像,正如我们之前提到的,得到的结果应该是一个单通道图像。我们来试一下:
Python
from cv2 import COLOR_RGB2GRAY
img_gray = cvtColor(img_rgb, COLOR_RGB2GRAY)
imshow(‘Grayscale Image', img_gray)
waitKey(0)
如何将 RGB 图像转换为灰度图像并使用 OpenCV 显示它。
转换似乎已成功完成,但我们也来尝试访问坐标(0, 0)处第一个像素的值:
Python
print(img_gray[0, 0])
Python
198
正如预期的那样,只打印出一个对应于像素强度值的单一数字。
值得注意的是,这并不是将图像转换为灰度图像的唯一方法。实际上,如果我们要处理的应用程序只需要使用灰度图像(而不是 RGB 图像),我们也可以选择直接以灰度图像的形式读取图像:
Python
from cv2 import IMREAD_GRAYSCALE
img_gray = imread('Images/Dog.jpg', IMREAD_GRAYSCALE)
imshow(‘Grayscale Image', img_gray)
waitKey(0)
注意:这里的 OpenCV 文档警告说,使用 IMREAD_GRAYSCALE
会利用编解码器的内部灰度转换(如果可用),这可能会导致与 cvtColor()
的输出不同。
imread
方法还支持其他几种标志值,其中两个是 IMREAD_COLOR
和 IMREAD_UNCHANGED
。IMREAD_COLOR
标志是默认选项,它将图像转换为 BGR 颜色,忽略任何透明度。而 IMREAD_UNCHANGED
则读取可能包含 alpha 通道的图像。
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解。
书籍
- 掌握 OpenCV 4 与 Python,2019 年。
网站
-
OpenCV,
opencv.org/
-
OpenCV 颜色转换代码,
docs.opencv.org/4.x/d8/d01/group__imgproc__color__conversions.html#func-members
总结
在本教程中,你熟悉了在处理图像时必不可少的最基本的 OpenCV 操作。
具体来说,你学到了:
-
数字图像如何从空间坐标和强度值的角度进行构造。
-
如何在 OpenCV 中读取和显示图像。
-
如何访问图像的像素值。
-
图像如何从一种颜色空间转换到另一种颜色空间。
你有任何问题吗?
请在下面的评论中提出你的问题,我将尽力回答。*****
如何使用 OpenCV 转换图像并创建视频
原文:
machinelearningmastery.com/how-to-transform-images-and-create-video-with-opencv/
当你使用 OpenCV 时,你最常处理的是图像。然而,你可能会发现从多个图像创建动画是有用的。展示快速连续的图像可能会给你不同的见解,或者通过引入时间轴让你更容易可视化你的工作。
在这篇文章中,你将看到如何在 OpenCV 中创建视频剪辑。作为示例,你还将学习一些基本的图像处理技术来创建这些图像。特别是,你将学习:
-
如何将图像作为 numpy 数组进行操作
-
如何使用 OpenCV 函数操作图像
-
如何在 OpenCV 中创建视频文件
快速启动你的项目,请参阅我的书籍 《OpenCV 中的机器学习》。它提供了自学教程和可用代码。
让我们开始吧。
如何使用 OpenCV 转换图像并创建视频
图片由 KAL VISUALS 提供。一些权利保留。
概述
本文分为两个部分;它们是:
-
Ken Burns 效果
-
编写视频
Ken Burns 效果
你将通过参考其他帖子创建大量图像。也许这是为了可视化你机器学习项目的一些进展,或者展示计算机视觉技术如何操作你的图像。为了简化问题,你将对输入图像进行最简单的操作:裁剪。
本文的任务是创建Ken Burns 效果。这是一种以电影制作人 Ken Burns 命名的平移和缩放技术:
Ken Burns 效果不是在屏幕上显示一张大的静态照片,而是裁剪到一个细节,然后在图像上平移。
— 维基百科,“Ken Burns 效果”
让我们看看如何使用 OpenCV 在 Python 代码中创建 Ken Burns 效果。我们从一张图像开始,例如下面这张可以从维基百科下载的鸟类图片:
一张Buthraupis montana cucullata的图片。照片由 Charles J. Sharp 提供。(CC-BY-SA)
这张图片的分辨率是 4563×3042 像素。用 OpenCV 打开这张图片很简单:
import cv2
imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
img = cv2.imread(imgfile, cv2.IMREAD_COLOR)
cv2.imshow("bird", img)
cv2.waitKey(0)
OpenCV 读取的图像 img
实际上是一个形状为 (3042, 4563, 3) 的 numpy 数组,数据类型为 uint8
(8 位无符号整数),因为这是一个彩色图像,每个像素以 BGR 值在 0 到 255 之间表示。
Ken Burns 效果是缩放和平移。视频中的每一帧都是原始图像的裁剪(然后放大以填充屏幕)。给定一个 numpy 数组来裁剪图像是很简单的,因为 numpy 已经为你提供了切片语法:
cropped = img[y0:y1, x0:x1]
图像是一个三维 numpy 数组。前两个维度分别表示高度和宽度(与设置矩阵坐标的方式相同)。因此,你可以使用 numpy 切片语法在垂直方向上获取像素 y 0 y_0 y0 到 y 1 y_1 y1,在水平方向上获取像素 x 0 x_0 x0 到 x 1 x_1 x1(请记住,在矩阵中,坐标是从上到下和从左到右编号的)。
裁剪一张图片意味着将尺寸为 W × H W\times H W×H 的图片裁剪成一个更小的尺寸 W ’ × H ’ W’\times H’ W’×H’。为了制作视频,你需要创建固定尺寸的帧。裁剪后的尺寸 W ’ × H ’ W’\times H’ W’×H’ 需要调整大小。此外,为了避免失真,裁剪后的图像还需要保持预定义的长宽比。
要调整图像大小,你可以定义一个新的 numpy 数组,然后逐一计算并填充像素值。有很多方法可以计算像素值,例如使用线性插值或简单地复制最近的像素。如果你尝试实现调整大小操作,你会发现它并不困难,但仍然相当繁琐。因此,更简单的方法是使用 OpenCV 的原生函数,如下所示:
resized = cv2.resize(cropped, dsize=target_dim, interpolation=cv2.INTER_LINEAR)
函数 cv2.resize()
接受一张图像和目标尺寸(以像素宽度和高度的元组形式),并返回一个新的 numpy 数组。你可以指定调整大小的算法。上面的代码使用线性插值,在大多数情况下效果很好。
这些基本上是你可以在 OpenCV 中操作图像的所有方法,即:
-
直接操作 numpy 数组。这对于你想在像素级别进行操作的简单任务效果很好。
-
使用 OpenCV 函数。这更适用于复杂任务,在这些任务中,你需要考虑整个图像,或者操作每个像素效率过低。
利用这些,你可以创建你的 Ken Burns 动画。流程如下:
-
给定一张图像(最好是高分辨率的),你需要通过指定起始和结束的焦点坐标来定义平移。你还需要定义起始和结束的缩放比例。
-
你有一个预定义的视频时长和帧率(FPS)。视频中的总帧数是时长乘以 FPS。
-
对于每一帧,计算裁剪坐标。然后将裁剪后的图像调整到视频的目标分辨率。
-
准备好所有帧后,你可以写入视频文件。
让我们从常量开始:假设我们要创建一个两秒钟的 720p 视频(分辨率为 1280×720),以 25 FPS 制作(这虽然较低,但视觉效果可接受)。平移将从图像的左侧 40% 和顶部 60% 处的中心开始,并在图像的左侧 50% 和顶部 50% 处的中心结束。缩放将从原始图像的 70% 开始,然后缩放到 100%。
imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
video_dim = (1280, 720)
fps = 25
duration = 2.0
start_center = (0.4, 0.6)
end_center = (0.5, 0.5)
start_scale = 0.7
end_scale = 1.0
你需要多次裁剪图像以创建帧(准确地说,有 2×25=50 帧)。因此,创建一个裁剪函数是有益的:
def crop(img, x, y, w, h):
x0, y0 = max(0, x-w//2), max(0, y-h//2)
x1, y1 = x0+w, y0+h
return img[y0:y1, x0:x1]
这个裁剪函数接受一个图像、像素坐标中的初步中心位置,以及宽度和高度(以像素为单位)。裁剪会确保不会超出图像边界,因此使用了两个 max()
函数。裁剪是通过 numpy 切片语法完成的。
如果你认为当前时间点在整个持续时间的 α \alpha α% 位置,你可以使用仿射变换来计算确切的缩放级别和全景的位置。就全景中心的相对位置(以原始宽度和高度的百分比计算)而言,仿射变换给出
rx = end_center[0]*alpha + start_center[0]*(1-alpha)
ry = end_center[1]*alpha + start_center[1]*(1-alpha)
其中 alpha
在 0 和 1 之间。同样,缩放级别为
scale = end_scale*alpha + start_scale*(1-alpha)
给定原始图像的大小和缩放比例,你可以通过乘法计算裁剪图像的大小。但由于图像的纵横比可能与视频不同,你应该调整裁剪尺寸以适应视频的纵横比。假设图像的 numpy 数组是 img
,而上述计算的缩放级别是 scale
,裁剪后的大小可以计算为:
orig_shape = img.shape[:2]
if orig_shape[1]/orig_shape[0] > video_dim[0]/video_dim[1]:
h = int(orig_shape[0]*scale)
w = int(h * video_dim[0] / video_dim[1])
else:
w = int(orig_shape[1]*scale)
h = int(w * video_dim[1] / video_dim[0])
上述是为了比较图像和视频之间的纵横比(宽度除以高度),并使用缩放级别来计算更有限的边缘,并根据目标纵横比计算另一边缘。
一旦你知道需要多少帧,你可以使用 for 循环来创建每一帧,每一帧具有不同的仿射参数 alpha
,这些参数可以通过 numpy 函数 linspace()
获得。完整代码如下:
import cv2
import numpy as np
imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
video_dim = (1280, 720)
fps = 25
duration = 2.0
start_center = (0.4, 0.6)
end_center = (0.5, 0.5)
start_scale = 0.7
end_scale = 1.0
img = cv2.imread(imgfile, cv2.IMREAD_COLOR)
orig_shape = img.shape[:2]
def crop(img, x, y, w, h):
x0, y0 = max(0, x-w//2), max(0, y-h//2)
x1, y1 = x0+w, y0+h
return img[y0:y1, x0:x1]
num_frames = int(fps * duration)
frames = []
for alpha in np.linspace(0, 1, num_frames):
rx = end_center[0]*alpha + start_center[0]*(1-alpha)
ry = end_center[1]*alpha + start_center[1]*(1-alpha)
x = int(orig_shape[1]*rx)
y = int(orig_shape[0]*ry)
scale = end_scale*alpha + start_scale*(1-alpha)
# determined how to crop based on the aspect ratio of width/height
if orig_shape[1]/orig_shape[0] > video_dim[0]/video_dim[1]:
h = int(orig_shape[0]*scale)
w = int(h * video_dim[0] / video_dim[1])
else:
w = int(orig_shape[1]*scale)
h = int(w * video_dim[1] / video_dim[0])
# crop, scale to video size, and save the frame
cropped = crop(img, x, y, w, h)
scaled = cv2.resize(cropped, dsize=video_dim, interpolation=cv2.INTER_LINEAR)
frames.append(scaled)
# write to MP4 file
vidwriter = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, video_dim)
for frame in frames:
vidwriter.write(frame)
vidwriter.release()
最后的几行是如何使用 OpenCV 写入视频。你创建一个具有指定 FPS 和分辨率的 VideoWriter
对象。然后你逐帧写入视频,最后释放对象以关闭写入的文件。
创建的视频类似于 这个。预览如下:
https://machinelearningmastery.com/wp-content/uploads/2023/10/output.webm?_=1
machinelearningmastery.com/wp-content/uploads/2023/10/output.webm
创建视频的预览。查看此内容需要支持的浏览器。
写入视频
从上一节的示例中,你可以看到我们如何创建一个 VideoWriter
对象:
vidwriter = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, video_dim)
与图像文件(如 JPEG 或 PNG)的写入方式不同,OpenCV 创建的视频格式不是从文件名中推断的。它是第二个参数来指定视频格式,即 FourCC,这是一个由四个字符组成的代码。你可以从以下网址的列表中找到 FourCC 代码和对应的视频格式:
然而,并非所有 FourCC 代码都可以使用。这是因为 OpenCV 使用 FFmpeg 工具来创建视频。你可以使用以下命令找到支持的视频格式列表:
ffmpeg -codecs
确保ffmpeg
命令与 OpenCV 使用的相同。另外请注意,上述命令的输出仅告诉你 ffmpeg 支持的格式,而不是相应的 FourCC 代码。你需要在其他地方查找代码,例如从上述 URL 中。
要检查是否可以使用特定的 FourCC 代码,你必须试用并查看 OpenCV 是否抛出异常:
try:
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter('temp.mkv', fourcc, 30, (640, 480))
assert writer.isOpened()
print("Supported")
except:
print("Not supported")
想要开始使用 OpenCV 进行机器学习吗?
现在就来参加我的免费电子邮件速成课程(包含示例代码)。
点击注册,还可以获得课程的免费 PDF 电子书版本。
总结
在这篇文章中,你学习了如何在 OpenCV 中创建视频。创建的视频由一系列帧(即无音频)组成。每一帧都是固定大小的图像。例如,你学习了如何对图片应用 Ken Burns 效果,其中你特别应用了:
-
使用 numpy 切片语法裁剪图像的技巧
-
使用 OpenCV 函数调整图像大小的技巧
-
使用仿射变换来计算缩放和平移的参数,并创建视频帧。
最后,你使用VideoWriter
对象将帧写入视频文件中。
练习机器学习的图像数据集在 OpenCV 中
原文:
machinelearningmastery.com/image-datasets-for-practicing-machine-learning-in-opencv/
在你开始机器学习之旅的最初阶段,公开可用的数据集减轻了自己创建数据集的担忧,让你能够专注于学习使用机器学习算法。如果数据集大小适中,不需要过多的预处理,可以更快地练习使用算法,然后再转向更具挑战性的问题,这也会很有帮助。
我们将查看的两个数据集是 OpenCV 提供的较简单的数字数据集和更具挑战性但广泛使用的 CIFAR-10 数据集。在我们探索 OpenCV 的机器学习算法时,我们将使用这两个数据集中的任何一个。
在本教程中,你将学习如何下载和提取 OpenCV 数字和 CIFAR-10 数据集,以练习在 OpenCV 中进行机器学习。
完成本教程后,你将了解:
-
如何下载和提取 OpenCV 数字数据集。
-
如何下载和提取 CIFAR-10 数据集,而不依赖其他 Python 包(如 TensorFlow)。
启动你的项目,可以参考我的书籍 《OpenCV 中的机器学习》。它提供了自学教程和可运行的代码。
让我们开始吧。
练习机器学习的图像数据集在 OpenCV 中
图片由 OC Gonzalez 拍摄,版权所有。
教程概览
本教程分为三个部分;它们是:
-
数字数据集
-
CIFAR-10 数据集
-
加载数据集
数字数据集
OpenCV 提供的图像,digits.png,由 20$\times$20 像素的子图像组成,每个子图像展示了从 0 到 9 的一个数字,可以拆分成数据集。总的来说,digits 图像包含 5,000 个手写数字。
OpenCV 提供的数字数据集不一定代表更复杂数据集所面临的现实挑战,主要因为其图像内容变化非常有限。然而,它的简单性和易用性将允许我们以较低的预处理和计算成本快速测试几种机器学习算法。
为了能够从完整的数字图像中提取数据集,我们的第一步是将其拆分成构成它的多个子图像。为此,我们来创建以下 split_images
函数:
Python
from cv2 import imread, IMREAD_GRAYSCALE
from numpy import hsplit, vsplit, array
def split_images(img_name, img_size):
# Load the full image from the specified file
img = imread(img_name, IMREAD_GRAYSCALE)
# Find the number of sub-images on each row and column according to their size
num_rows = img.shape[0] / img_size
num_cols = img.shape[1] / img_size
# Split the full image horizontally and vertically into sub-images
sub_imgs = [hsplit(row, num_cols) for row in vsplit(img, num_rows)]
return img, array(sub_imgs)
split_images
函数接受全图像的路径以及子图像的像素大小作为输入。由于我们处理的是正方形子图像,我们将其大小用一个维度表示,该维度等于 20。
该函数随后应用 OpenCV 的 imread
方法将图像的灰度版本加载到 NumPy 数组中。然后,使用 hsplit
和 vsplit
方法分别对 NumPy 数组进行水平和垂直切割。
split_images
函数返回的子图像数组大小为 (50, 100, 20, 20)。
一旦我们提取了子图像数组,我们将把它分割成训练集和测试集。我们还需要为这两个数据分割创建真实标签,以便在训练过程中使用并评估测试结果。
以下 split_data
函数用于这些目的:
Python
from numpy import float32, arange, repeat, newaxis
def split_data(img_size, sub_imgs, ratio):
# Compute the partition between the training and testing data
partition = int(sub_imgs.shape[1] * ratio)
# Split dataset into training and testing sets
train = sub_imgs[:, :partition, :, :]
test = sub_imgs[:, partition:sub_imgs.shape[1], :, :]
# Flatten each image into a one-dimensional vector
train_imgs = train.reshape(-1, img_size ** 2)
test_imgs = test.reshape(-1, img_size ** 2)
# Create the ground truth labels
labels = arange(10)
train_labels = repeat(labels, train_imgs.shape[0] / labels.shape[0])[:, newaxis]
test_labels = repeat(labels, test_imgs.shape[0] / labels.shape[0])[:, newaxis]
return train_imgs, train_labels, test_imgs, test_labels
split_data
函数以子图像数组和数据集训练部分的分割比例作为输入。然后,该函数计算 partition
值,将子图像数组沿其列分割成训练集和测试集。这个 partition
值随后用于将第一组列分配给训练数据,将剩余的列分配给测试数据。
为了在 digits.png 图像上可视化这种分割,效果如下所示:
将子图像分割为训练数据集和测试数据集
你还会注意到,我们将每个 20$\times 20 的子图像展平为一个长度为 400 像素的一维向量,使得在包含训练和测试图像的数组中,每一行现在存储的是展平后的 20 20 的子图像展平为一个长度为 400 像素的一维向量,使得在包含训练和测试图像的数组中,每一行现在存储的是展平后的 20 20的子图像展平为一个长度为400像素的一维向量,使得在包含训练和测试图像的数组中,每一行现在存储的是展平后的20/times$20 像素图像。
split_data
函数的最后一部分创建了值在 0 到 9 之间的真实标签,并根据我们拥有的训练和测试图像的数量重复这些值。
CIFAR-10 数据集
CIFAR-10 数据集并未随 OpenCV 提供,但我们将考虑它,因为它比 OpenCV 的数字数据集更好地代表了现实世界的挑战。
CIFAR-10 数据集总共包含 60,000 张 32$\times$32 RGB 图像。它包括属于 10 个不同类别的各种图像,如飞机、猫和船。数据集文件已经分割成 5 个 pickle 文件,其中包含 1,000 张训练图像及其标签,另一个文件包含 1,000 张测试图像及其标签。
让我们继续从 这个链接 下载 CIFAR-10 数据集用于 Python (注意:不使用 TensorFlow/Keras 的原因是展示如何在不依赖额外 Python 包的情况下工作)。请注意你在硬盘上保存并解压数据集的路径。
以下代码加载数据集文件,并返回训练和测试图像以及标签:
Python
from pickle import load
from numpy import array, newaxis
def load_images(path):
# Create empty lists to store the images and labels
imgs = []
labels = []
# Iterate over the dataset's files
for batch in range(5):
# Specify the path to the training data
train_path_batch = path + 'data_batch_' + str(batch + 1)
# Extract the training images and labels from the dataset files
train_imgs_batch, train_labels_batch = extract_data(train_path_batch)
# Store the training images
imgs.append(train_imgs_batch)
train_imgs = array(imgs).reshape(-1, 3072)
# Store the training labels
labels.append(train_labels_batch)
train_labels = array(labels).reshape(-1, 1)
# Specify the path to the testing data
test_path_batch = path + 'test_batch'
# Extract the testing images and labels from the dataset files
test_imgs, test_labels = extract_data(test_path_batch)
test_labels = array(test_labels)[:, newaxis]
return train_imgs, train_labels, test_imgs, test_labels
def extract_data(path):
# Open pickle file and return a dictionary
with open(path, 'rb') as fo:
dict = load(fo, encoding='bytes')
# Extract the dictionary values
dict_values = list(dict.values())
# Extract the images and labels
imgs = dict_values[2]
labels = dict_values[1]
return imgs, labels
需要记住的是,使用更大且更多样化的数据集(如 CIFAR-10)进行不同模型的测试相比于使用较简单的数据集(如 digits 数据集)的妥协在于,前者的训练可能会更耗时。
加载数据集
让我们试着调用我们之前创建的函数。
我将属于 digits 数据集的代码与属于 CIFAR-10 数据集的代码分开成两个不同的 Python 脚本,分别命名为 digits_dataset.py
和 cifar_dataset.py
:
Python
from digits_dataset import split_images, split_data
from cifar_dataset import load_images
# Load the digits image
img, sub_imgs = split_images('Images/digits.png', 20)
# Obtain training and testing datasets from the digits image
digits_train_imgs, digits_train_labels, digits_test_imgs, digits_test_labels = split_data(20, sub_imgs, 0.8)
# Obtain training and testing datasets from the CIFAR-10 dataset
cifar_train_imgs, cifar_train_labels, cifar_test_imgs, cifar_test_labels = load_images('Images/cifar-10-batches-py/')
注意:不要忘记将上面代码中的路径更改为你保存数据文件的位置。
在接下来的教程中,我们将看到如何使用不同的机器学习技术处理这些数据集,首先了解如何将数据集图像转换为特征向量,作为使用它们进行机器学习的预处理步骤之一。
想要开始使用 OpenCV 进行机器学习吗?
现在就参加我的免费电子邮件速成课程(包括示例代码)。
点击注册,并获得课程的免费 PDF Ebook 版本。
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解。
书籍
- Mastering OpenCV 4 with Python,2019 年。
网站
- OpenCV,
opencv.org/
总结
在本教程中,你学习了如何下载和提取 OpenCV digits 和 CIFAR-10 数据集,以便在 OpenCV 中练习机器学习。
具体来说,你学到了:
-
如何下载和提取 OpenCV digits 数据集。
-
如何下载和提取 CIFAR-10 数据集,而无需依赖其他 Python 包(如 TensorFlow)。
你有任何问题吗?
在下面的评论中提出你的问题,我会尽力回答。
使用 OpenCV 进行机器学习的图像向量表示
原文:
machinelearningmastery.com/image-vector-representation-for-machine-learning-using-opencv/
在将图像输入机器学习算法之前,常见的预处理步骤之一是将其转换为特征向量。正如我们在本教程中将看到的,将图像转换为特征向量有几个优势,使其更加高效。
在将图像转换为特征向量的不同技术中,方向梯度直方图和词袋模型是与不同机器学习算法结合使用的两种最流行的技术。
在本教程中,你将发现方向梯度直方图(HOG)和词袋模型(BoW)技术用于图像向量表示。
完成本教程后,你将了解:
-
使用方向梯度直方图和词袋模型技术进行图像向量表示的优势是什么?
-
如何在 OpenCV 中使用方向梯度直方图技术。
-
如何在 OpenCV 中使用词袋模型技术。
通过我的书 《OpenCV 中的机器学习》 启动你的项目。它提供了自学教程和工作代码。
让我们开始吧。
使用 OpenCV 进行机器学习的图像向量表示
摄影:John Fowler,版权所有。
教程概述
本教程分为四个部分;它们是:
-
使用 HOG 或 BoW 进行图像向量表示的优势是什么?
-
方向梯度直方图技术
-
词袋模型技术
-
将技术付诸实践
使用 HOG 或 BoW 进行图像向量表示的优势是什么?
在处理机器学习算法时,图像数据通常会经历一个数据预处理步骤,以使机器学习算法能够处理它。
在 OpenCV 中,例如,ml 模块要求图像数据以等长特征向量的形式输入到机器学习算法中。
每个训练样本是一个值向量(在计算机视觉中有时称为特征向量)。通常所有向量都有相同数量的组件(特征);OpenCV 的 ml 模块假设如此。
– OpenCV,2023。
一种组织图像数据的方法是将其展平为一维向量,其中向量的长度等于图像中的像素数。例如,一个 20 × 20 20\times 20 20×20的像素图像会生成一个长度为 400 像素的一维向量。这个一维向量作为特征集输入到机器学习算法中,其中每个像素的强度值代表每个特征。
然而,虽然这是我们可以创建的最简单特征集,但它不是最有效的,特别是在处理较大的图像时,这会导致生成的输入特征过多,从而无法有效处理。
这可以显著影响在具有许多输入特征的数据上训练的机器学习算法的性能,通常被称为“维度诅咒。”
机器学习中的降维介绍,2020 年。
我们希望减少表示每个图像的输入特征数量,以便机器学习算法能够更好地对输入数据进行泛化。用更技术的话来说,进行降维是理想的,这将图像数据从高维空间转换到低维空间。
一种方法是应用特征提取和表示技术,例如方向梯度直方图(HOG)或词袋模型(BoW),以更紧凑的方式表示图像,从而减少特征集中的冗余并减少处理它的计算需求。
将图像数据转换为特征向量的另一个优点是,图像的向量表示变得对光照、尺度或视角的变化更加鲁棒。
想开始使用 OpenCV 进行机器学习吗?
立即参加我的免费电子邮件速成课程(附样例代码)。
点击注册并获得免费的 PDF 电子书版本课程。
在接下来的部分中,我们将探讨使用 HOG 和 BoW 技术进行图像向量表示。
方向梯度直方图技术
HOG 是一种特征提取技术,旨在通过边缘方向的分布来表示图像空间内物体的局部形状和外观。
简而言之,当 HOG 技术应用于图像时,会执行以下步骤:
-
- 使用例如 Prewitt 算子的图像梯度计算图像的水平和垂直方向的梯度。然后计算图像中每个像素的梯度的幅度和方向。
-
- 将图像划分为固定大小的不重叠单元格,并计算每个单元格的梯度直方图。每个图像单元格的直方图表示更紧凑且对噪声更具鲁棒性。单元格大小通常根据我们要捕捉的图像特征的大小进行设置。
-
- 将直方图在单元格块上拼接成一维特征向量并进行归一化。这使得描述符对光照变化更加鲁棒。
-
- 最后,将所有归一化的特征向量拼接在一起,表示单元格块,从而获得整个图像的最终特征向量表示。
OpenCV 中的 HOG 实现接受几个输入参数,这些参数对应于上述步骤,包括:
-
-
窗口大小(winSize)对应于要检测的最小对象大小。
-
单元格大小(cellSize)通常捕捉感兴趣的图像特征的大小。
-
块大小(blockSize)解决了光照变化的问题。
-
块步幅(blockStride)控制相邻块的重叠程度。
-
直方图的箱子数量(nbins)用于捕捉 0 到 180 度之间的梯度。
-
让我们创建一个函数 hog_descriptors()
,使用 HOG 技术计算一组图像的特征向量:
Python
def hog_descriptors(imgs):
# Create a list to store the HOG feature vectors
hog_features = []
# Set parameter values for the HOG descriptor based on the image data in use
winSize = (20, 20)
blockSize = (10, 10)
blockStride = (5, 5)
cellSize = (10, 10)
nbins = 9
# Set the remaining parameters to their default values
derivAperture = 1
winSigma = -1.
histogramNormType = 0
L2HysThreshold = 0.2
gammaCorrection = False
nlevels = 64
# Create a HOG descriptor
hog = HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins, derivAperture, winSigma,
histogramNormType, L2HysThreshold, gammaCorrection, nlevels)
# Compute HOG descriptors for the input images and append the feature vectors to the list
for img in imgs:
hist = hog.compute(img.reshape(20, 20).astype(uint8))
hog_features.append(hist)
return array(hog_features)
注意:重要的是要注意,这里图像的重新形状对应于稍后将在本教程中使用的图像数据集。如果使用不同的数据集,请不要忘记相应调整此部分代码。
词袋模型技术
BoW 技术已在 这个教程 中介绍,应用于使用机器学习算法建模文本。
尽管如此,这项技术也可以应用于计算机视觉,在这种情况下,图像被视为可以提取特征的视觉词。因此,当应用于计算机视觉时,BoW 技术通常被称为视觉词袋技术。
简而言之,当将 BoW 技术应用于图像时,执行以下步骤:
-
- 使用诸如尺度不变特征变换(SIFT)或加速稳健特征(SURF)等算法从图像中提取特征描述符。理想情况下,提取的特征应对强度、尺度、旋转和仿射变换不变。
-
- 从特征描述符中生成代码字,其中每个代码字代表相似的图像块。生成这些代码字的一种方法是使用 k-means 聚类将相似的描述符聚集成簇,簇的中心代表视觉词,而簇的数量则代表词汇大小。
-
- 将特征描述符映射到词汇表中最近的簇,本质上是为每个特征描述符分配一个代码字。
-
- 将代码字分箱到直方图中,并使用此直方图作为图像的特征向量表示。
让我们创建一个函数 bow_descriptors()
,该函数使用 SIFT 对一组图像应用 BoW 技术:
Python
def bow_descriptors(imgs):
# Create a SIFT descriptor
sift = SIFT_create()
# Create a BoW descriptor
# The number of clusters equal to 50 (analogous to the vocabulary size) has been chosen empirically
bow_trainer = BOWKMeansTrainer(50)
bow_extractor = BOWImgDescriptorExtractor(sift, BFMatcher(NORM_L2))
for img in imgs:
# Reshape each RGB image and convert it to grayscale
img = reshape(img, (32, 32, 3), 'F')
img = cvtColor(img, COLOR_RGB2GRAY).transpose()
# Extract the SIFT descriptors
_, descriptors = sift.detectAndCompute(img, None)
# Add the SIFT descriptors to the BoW vocabulary trainer
if descriptors is not None:
bow_trainer.add(descriptors)
# Perform k-means clustering and return the vocabulary
voc = bow_trainer.cluster()
# Assign the vocabulary to the BoW descriptor extractor
bow_extractor.setVocabulary(voc)
# Create a list to store the BoW feature vectors
bow_features = []
for img in imgs:
# Reshape each RGB image and convert it to grayscale
img = reshape(img, (32, 32, 3), 'F')
img = cvtColor(img, COLOR_RGB2GRAY).transpose()
# Compute the BoW feature vector
hist = bow_extractor.compute(img, sift.detect(img))
# Append the feature vectors to the list
if hist is not None:
bow_features.append(hist[0])
return array(bow_features)
注意:需要注意的是,这里图像的重塑方式对应于本教程后面将使用的图像数据集。如果你使用不同的数据集,请不要忘记相应调整这部分代码。
测试技术
并不一定存在适用于所有情况的最佳技术,选择用于图像数据的技术通常需要进行控制实验。
在本教程中,作为一个示例,我们将对 OpenCV 附带的数字数据集应用 HOG 技术,对 CIFAR-10 数据集中的图像应用 BoW 技术。在本教程中,我们将仅考虑这两个数据集中的一个子集,以减少所需的处理时间。然而,相同的代码可以很容易地扩展到完整的数据集。
我们将从加载我们将使用的数据集开始。回顾一下,我们在本教程中已经看到如何从每个数据集中提取图像。digits_dataset
和 cifar_dataset
是我创建的 Python 脚本,分别包含加载数字和 CIFAR-10 数据集的代码:
Python
from digits_dataset import split_images, split_data
from cifar_dataset import load_images
# Load the digits image
img, sub_imgs = split_images('Images/digits.png', 20)
# Obtain a dataset from the digits image
digits_imgs, _, _, _ = split_data(20, sub_imgs, 0.8)
# Load a batch of images from the CIFAR dataset
cifar_imgs = load_images('Images/cifar-10-batches-py/data_batch_1')
# Consider only a subset of images
digits_subset = digits_imgs[0:100, :]
cifar_subset = cifar_imgs[0:100, :]
然后,我们可以将数据集传递给我们在本教程中创建的 hog_descriptors()
和 bow_descriptors()
函数:
Python
digits_hog = hog_descriptors(digits_subset)
print('Size of HOG feature vectors:', digits_hog.shape)
cifar_bow = bow_descriptors(cifar_subset)
print('Size of BoW feature vectors:', cifar_bow.shape)
完整的代码列表如下:
Python
from cv2 import (imshow, waitKey, HOGDescriptor, SIFT_create, BOWKMeansTrainer,
BOWImgDescriptorExtractor, BFMatcher, NORM_L2, cvtColor, COLOR_RGB2GRAY)
from digits_dataset import split_images, split_data
from cifar_dataset import load_images
from numpy import uint8, array, reshape
# Load the digits image
img, sub_imgs = split_images('Images/digits.png', 20)
# Obtain a dataset from the digits image
digits_imgs, _, _, _ = split_data(20, sub_imgs, 0.8)
# Load a batch of images from the CIFAR dataset
cifar_imgs = load_images('Images/cifar-10-batches-py/data_batch_1')
# Consider only a subset of images
digits_subset = digits_imgs[0:100, :]
cifar_subset = cifar_imgs[0:100, :]
def hog_descriptors(imgs):
# Create a list to store the HOG feature vectors
hog_features = []
# Set parameter values for the HOG descriptor based on the image data in use
winSize = (20, 20)
blockSize = (10, 10)
blockStride = (5, 5)
cellSize = (10, 10)
nbins = 9
# Set the remaining parameters to their default values
derivAperture = 1
winSigma = -1.
histogramNormType = 0
L2HysThreshold = 0.2
gammaCorrection = False
nlevels = 64
# Create a HOG descriptor
hog = HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins, derivAperture, winSigma,
histogramNormType, L2HysThreshold, gammaCorrection, nlevels)
# Compute HOG descriptors for the input images and append the feature vectors to the list
for img in imgs:
hist = hog.compute(img.reshape(20, 20).astype(uint8))
hog_features.append(hist)
return array(hog_features)
def bow_descriptors(imgs):
# Create a SIFT descriptor
sift = SIFT_create()
# Create a BoW descriptor
# The number of clusters equal to 50 (analogous to the vocabulary size) has been chosen empirically
bow_trainer = BOWKMeansTrainer(50)
bow_extractor = BOWImgDescriptorExtractor(sift, BFMatcher(NORM_L2))
for img in imgs:
# Reshape each RGB image and convert it to grayscale
img = reshape(img, (32, 32, 3), 'F')
img = cvtColor(img, COLOR_RGB2GRAY).transpose()
# Extract the SIFT descriptors
_, descriptors = sift.detectAndCompute(img, None)
# Add the SIFT descriptors to the BoW vocabulary trainer
if descriptors is not None:
bow_trainer.add(descriptors)
# Perform k-means clustering and return the vocabulary
voc = bow_trainer.cluster()
# Assign the vocabulary to the BoW descriptor extractor
bow_extractor.setVocabulary(voc)
# Create a list to store the BoW feature vectors
bow_features = []
for img in imgs:
# Reshape each RGB image and convert it to grayscale
img = reshape(img, (32, 32, 3), 'F')
img = cvtColor(img, COLOR_RGB2GRAY).transpose()
# Compute the BoW feature vector
hist = bow_extractor.compute(img, sift.detect(img))
# Append the feature vectors to the list
if hist is not None:
bow_features.append(hist[0])
return array(bow_features)
digits_hog = hog_descriptors(digits_subset)
print('Size of HOG feature vectors:', digits_hog.shape)
cifar_bow = bow_descriptors(cifar_subset)
print('Size of BoW feature vectors:', cifar_bow.shape)
上面的代码返回以下输出:
Python
Size of HOG feature vectors: (100, 81)
Size of BoW feature vectors: (100, 50)
根据我们选择的参数值,我们可能会看到 HOG 技术为每个图像返回大小为 1 × 81 1\times 81 1×81 的特征向量。这意味着每个图像现在由 81 维空间中的点表示。另一方面,BoW 技术为每个图像返回大小为 1 × 50 1\times 50 1×50 的向量,其中向量长度由选择的 k-means 聚类数量决定,这也类似于词汇表大小。
因此,我们可以看到,通过应用 HOG 和 BoW 技术,我们不仅仅是将每个图像展平成一维向量,而是以更紧凑的方式表示了每个图像。
我们的下一步是看看如何利用这些数据使用不同的机器学习算法。
进一步阅读
如果你想深入了解,本节提供了更多相关资源。
书籍
- 使用 Python 3 学习 OpenCV 4 计算机视觉,2020 年。
网站
-
OpenCV,
opencv.org/
-
使用 OpenCV 解释的方向梯度直方图,
learnopencv.com/histogram-of-oriented-gradients/
-
计算机视觉中的词袋模型,
en.wikipedia.org/wiki/Bag-of-words_model_in_computer_vision
总结
在本教程中,你将了解方向梯度直方图和词袋技术在图像向量表示中的应用。
具体来说,你学到了:
-
使用方向梯度直方图和词袋技术进行图像向量表示的优势是什么?
-
如何在 OpenCV 中使用方向梯度直方图技术。
-
如何在 OpenCV 中使用词袋技术。
你有任何问题吗?
在下方的评论中提问,我会尽力回答。
使用 OpenCV 进行图像分类的 k-Means 聚类
原文:
machinelearningmastery.com/k-means-clustering-for-image-classification-using-opencv/
在 之前的教程中,我们探讨了如何使用 k-means 聚类算法作为一种无监督机器学习技术,旨在将相似数据分组到不同的簇中,从而揭示数据中的模式。
到目前为止,我们已经看到如何将 k-means 聚类算法应用于一个包含不同簇的简单二维数据集,以及图像颜色量化的问题。
在本教程中,你将学习如何应用 OpenCV 的 k-means 聚类算法进行图像分类。
完成本教程后,你将会了解:
-
为什么 k-means 聚类可以应用于图像分类。
-
将 k-means 聚类算法应用于 OpenCV 中的数字数据集,以进行图像分类。
-
如何减少由于倾斜造成的数字变异,以提高 k-means 聚类算法在图像分类中的准确性。
启动你的项目,请参考我的书籍 《OpenCV 中的机器学习》。它提供了自学教程和实用代码。
让我们开始吧。
使用 OpenCV 进行图像分类的 k-Means 聚类
图片由 Jeremy Thomas 提供,部分权利保留。
教程概述
本教程分为两个部分,它们是:
-
k-Means 聚类作为一种无监督机器学习技术的回顾
-
应用 k-Means 聚类于图像分类
k-Means 聚类作为一种无监督机器学习技术的回顾
在 之前的教程中,我们介绍了 k-means 聚类作为一种无监督学习技术。
我们已经看到该技术涉及自动将数据分组到不同的组(或簇)中,其中每个簇中的数据彼此相似,但与其他簇中的数据不同。其目标是揭示数据中的模式,这些模式在聚类之前可能并不明显。
我们已经将 k-means 聚类算法应用于一个包含五个簇的简单二维数据集,以标记每个簇中的数据点,并随后应用于颜色量化任务,在该任务中,我们使用此算法来减少表示图像的不同颜色数量。
在本教程中,我们将再次利用 k-means 聚类在数据中揭示隐藏结构的能力,将其应用于图像分类任务。
对于这样的任务,我们将使用在之前的教程中介绍的 OpenCV 数字数据集,我们将尝试以无监督的方式(即不使用实际标签信息)对类似手写数字的图像进行分组。
将 k-Means 聚类应用于图像分类
我们首先需要加载 OpenCV 数字图像,将其分成许多包含从 0 到 9 的手写数字的子图像,并创建相应的实际标签,这将使我们能够量化 k-means 聚类算法的性能:
Python
# Load the digits image and divide it into sub-images
img, sub_imgs = split_images('Images/digits.png', 20)
# Create the ground truth labels
imgs, labels_true, _, _ = split_data(20, sub_imgs, 1.0)
返回的imgs
数组包含 5000 个子图像,以平铺的一维向量形式组织,每个图像由 400 个像素组成:
Python
# Check the shape of the 'imgs' array
print(imgs.shape)
Python
(5000, 400)
k-means 算法可以使用与我们在颜色量化示例中使用的输入参数相同的输入参数,唯一的例外是我们需要将imgs
数组作为输入数据,并将K
簇的值设置为 10(即我们可用的数字数量):
Python
# Specify the algorithm's termination criteria
criteria = (TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 10, 1.0)
# Run the k-means clustering algorithm on the image data
compactness, clusters, centers = kmeans(data=imgs.astype(float32), K=10, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
kmeans
函数返回一个centers
数组,该数组应包含每个簇的代表性图像。返回的centers
数组形状为 10$\times
400
,这意味着我们需要先将其重塑为
20
400,这意味着我们需要先将其重塑为 20
400,这意味着我们需要先将其重塑为20\times$20 像素的图像,然后再进行可视化:
Python
# Reshape array into 20x20 images
imgs_centers = centers.reshape(-1, 20, 20)
# Visualise the cluster centers
fig, ax = subplots(2, 5)
for i, center in zip(ax.flat, imgs_centers):
i.imshow(center)
show()
簇中心的代表性图像如下:
k-means 算法找到的簇中心的代表性图像
k-means 算法生成的簇中心确实类似于 OpenCV 数字数据集中包含的手写数字,这一点非常值得注意。
你可能还会注意到,簇中心的顺序不一定按照 0 到 9 的数字顺序。这是因为 k-means 算法可以将相似的数据聚类在一起,但没有顺序的概念。然而,这在将预测标签与实际标签进行比较时也会带来问题。这是因为实际标签是根据图像中的数字生成的。然而,k-means 算法生成的簇标签不一定遵循相同的约定。为了解决这个问题,我们需要重新排序簇标签:
Python
# Found cluster labels
labels = array([2, 0, 7, 5, 1, 4, 6, 9, 3, 8])
labels_pred = zeros(labels_true.shape, dtype='int')
# Re-order the cluster labels
for i in range(10):
mask = clusters.ravel() == i
labels_pred[mask] = labels[i]
现在我们准备计算算法的准确性,方法是找到与实际标签对应的预测标签的百分比:
Python
# Calculate the algorithm's accuracy
accuracy = (sum(labels_true == labels_pred) / labels_true.size) * 100
# Print the accuracy
print("Accuracy: {0:.2f}%".format(accuracy[0]))
Python
Accuracy: 54.80%
到目前为止的完整代码列表如下:
Python
from cv2 import kmeans, TERM_CRITERIA_MAX_ITER, TERM_CRITERIA_EPS, KMEANS_RANDOM_CENTERS
from numpy import float32, array, zeros
from matplotlib.pyplot import show, imshow, subplots
from digits_dataset import split_images, split_data
# Load the digits image and divide it into sub-images
img, sub_imgs = split_images('Images/digits.png', 20)
# Create the ground truth labels
imgs, labels_true, _, _ = split_data(20, sub_imgs, 1.0)
# Check the shape of the 'imgs' array
print(imgs.shape)
# Specify the algorithm's termination criteria
criteria = (TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 10, 1.0)
# Run the k-means clustering algorithm on the image data
compactness, clusters, centers = kmeans(data=imgs.astype(float32), K=10, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
# Reshape array into 20x20 images
imgs_centers = centers.reshape(-1, 20, 20)
# Visualise the cluster centers
fig, ax = subplots(2, 5)
for i, center in zip(ax.flat, imgs_centers):
i.imshow(center)
show()
# Cluster labels
labels = array([2, 0, 7, 5, 1, 4, 6, 9, 3, 8])
labels_pred = zeros(labels_true.shape, dtype='int')
# Re-order the cluster labels
for i in range(10):
mask = clusters.ravel() == i
labels_pred[mask] = labels[i]
# Calculate the algorithm's accuracy
accuracy = (sum(labels_true == labels_pred) / labels_true.size) * 100
现在,让我们打印出混淆矩阵,以深入了解哪些数字被误认为了其他数字:
Python
from sklearn.metrics import confusion_matrix
# Print confusion matrix
print(confusion_matrix(labels_true, labels_pred))
Python
[[399 0 2 28 2 23 27 0 13 6]
[ 0 351 0 1 0 1 0 0 1 146]
[ 4 57 315 26 3 14 4 4 38 35]
[ 2 4 3 241 9 12 3 1 141 84]
[ 0 8 58 0 261 27 3 93 0 50]
[ 3 4 0 150 27 190 12 1 53 60]
[ 6 13 83 4 0 13 349 0 0 32]
[ 0 22 3 1 178 10 0 228 0 58]
[ 0 15 16 85 15 18 3 8 260 80]
[ 2 4 23 7 228 8 1 161 1 65]]
混淆矩阵需要按如下方式解读:
解读混淆矩阵
对角线上的值表示正确预测的数字数量,而非对角线上的值表示每个数字的误分类情况。我们可以看到表现最好的数字是0,其对角线值最高且误分类很少。表现最差的数字是9,因为它与许多其他数字(主要是 4)有最多的误分类。我们还可以看到,7 大多数被误认为 4,而8 则大多数被误认为3。
这些结果并不令人惊讶,因为如果我们查看数据集中的数字,可能会发现几个不同数字的曲线和倾斜使它们彼此相似。为了研究减少数字变化的效果,我们引入一个函数deskew_image()
,该函数基于从图像矩计算出的倾斜度对图像应用仿射变换:
Python
from cv2 import (kmeans, TERM_CRITERIA_MAX_ITER, TERM_CRITERIA_EPS, KMEANS_RANDOM_CENTERS, moments, warpAffine, INTER_CUBIC, WARP_INVERSE_MAP)
from numpy import float32, array, zeros
from matplotlib.pyplot import show, imshow, subplots
from digits_dataset import split_images, split_data
from sklearn.metrics import confusion_matrix
# Load the digits image and divide it into sub-images
img, sub_imgs = split_images('Images/digits.png', 20)
# Create the ground truth labels
imgs, labels_true, _, _ = split_data(20, sub_imgs, 1.0)
# De-skew all dataset images
imgs_deskewed = zeros(imgs.shape)
for i in range(imgs_deskewed.shape[0]):
new = deskew_image(imgs[i, :].reshape(20, 20))
imgs_deskewed[i, :] = new.reshape(1, -1)
# Specify the algorithm's termination criteria
criteria = (TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 10, 1.0)
# Run the k-means clustering algorithm on the de-skewed image data
compactness, clusters, centers = kmeans(data=imgs_deskewed.astype(float32), K=10, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
# Reshape array into 20x20 images
imgs_centers = centers.reshape(-1, 20, 20)
# Visualise the cluster centers
fig, ax = subplots(2, 5)
for i, center in zip(ax.flat, imgs_centers):
i.imshow(center)
show()
# Cluster labels
labels = array([9, 5, 6, 4, 2, 3, 7, 8, 1, 0])
labels_pred = zeros(labels_true.shape, dtype='int')
# Re-order the cluster labels
for i in range(10):
mask = clusters.ravel() == i
labels_pred[mask] = labels[i]
# Calculate the algorithm's accuracy
accuracy = (sum(labels_true == labels_pred) / labels_true.size) * 100
# Print the accuracy
print("Accuracy: {0:.2f}%".format(accuracy[0]))
# Print confusion matrix
print(confusion_matrix(labels_true, labels_pred))
def deskew_image(img):
# Calculate the image moments
img_moments = moments(img)
# Moment m02 indicates how much the pixel intensities are spread out along the vertical axis
if abs(img_moments['mu02']) > 1e-2:
# Calculate the image skew
img_skew = (img_moments['mu11'] / img_moments['mu02'])
# Generate the transformation matrix
# (We are here tweaking slightly the approximation of vertical translation due to skew by making use of a
# scaling factor of 0.6, because we empirically found that this value worked better for this application)
m = float32([[1, img_skew, -0.6 * img.shape[0] * img_skew], [0, 1, 0]])
# Apply the transformation matrix to the image
img_deskew = warpAffine(src=img, M=m, dsize=img.shape, flags=INTER_CUBIC | WARP_INVERSE_MAP)
else:
# If the vertical spread of pixel intensities is small, return a copy of the original image
img_deskew = img.copy()
return img_deskew
Python
Accuracy: 70.92%
[[400 1 5 1 2 58 27 1 1 4]
[ 0 490 1 1 0 1 2 0 1 4]
[ 5 27 379 28 10 2 3 4 30 12]
[ 1 27 7 360 7 44 2 9 31 12]
[ 1 29 3 0 225 0 13 1 0 228]
[ 5 12 1 14 24 270 11 0 7 156]
[ 8 40 6 0 6 8 431 0 0 1]
[ 0 39 2 0 48 0 0 377 4 30]
[ 2 32 3 21 8 77 2 0 332 23]
[ 5 13 1 5 158 5 2 28 1 282]]
去偏斜函数对某些数字具有如下效果:
第一列展示了原始数据集图像,而第二列展示了修正倾斜后的图像。
值得注意的是,当减少数字的倾斜度时,准确率上升至 70.92%,而簇中心变得更能代表数据集中的数字:
k-均值算法找到的簇中心的代表性图像
这个结果显示,倾斜是导致我们在未进行修正时准确率损失的重要因素。
你能想到其他可能引入的预处理步骤来提高准确率吗?
想开始学习使用 OpenCV 进行机器学习吗?
现在就参加我的免费电子邮件速成课程(包含示例代码)。
点击注册,还可以免费获得课程的 PDF 电子书版本。
进一步阅读
本节提供了更多相关资源,如果你想深入了解的话。
书籍
-
Machine Learning for OpenCV,2017 年。
-
Mastering OpenCV 4 with Python,2019 年。
网站
-
10 种 Python 聚类算法,
machinelearningmastery.com/clustering-algorithms-with-python/
-
OpenCV 中的 K-Means 聚类,
docs.opencv.org/3.4/d1/d5c/tutorial_py_kmeans_opencv.html
-
k-means 聚类,
en.wikipedia.org/wiki/K-means_clustering
总结
在本教程中,你学习了如何应用 OpenCV 的 k-means 聚类算法进行图像分类。
具体来说,你学习了:
-
为什么 k-means 聚类可以应用于图像分类。
-
将 k-means 聚类算法应用于 OpenCV 中的数字数据集进行图像分类。
-
如何减少由于倾斜造成的数字变异,以提高 k-means 聚类算法在图像分类中的准确性。
你有什么问题吗?
在下面的评论中提问,我会尽力回答。