Machine Learning Mastery OpenCV 教程(二)
OpenCV 中的 k-Means 聚类及其在颜色量化中的应用
原文:
machinelearningmastery.com/k-means-clustering-in-opencv-and-application-for-color-quantization/
k-means 聚类算法是一种无监督机器学习技术,旨在将相似的数据分组到不同的集群中,以揭示数据中的模式,这些模式可能在肉眼观察下并不明显。
这可能是数据聚类中最广为人知的算法,并已在 OpenCV 库中实现。
在本教程中,您将学习如何应用 OpenCV 的 k-means 聚类算法进行图像的颜色量化。
完成本教程后,您将了解:
-
了解数据聚类在机器学习中的含义。
-
在 OpenCV 中将 k-means 聚类算法应用于包含不同数据集群的简单二维数据集。
-
如何在 OpenCV 中应用 k-means 聚类算法进行图像的颜色量化。
通过我的书籍 《OpenCV 中的机器学习》 来启动您的项目。它提供了自学教程和可运行的代码。
让我们开始吧。
使用 OpenCV 进行颜色量化的 k-Means 聚类
图片由 Billy Huynh 提供,保留部分权利。
教程概述
本教程分为三部分,它们是:
-
聚类作为一种无监督机器学习任务
-
在 OpenCV 中发现 k-Means 聚类
-
使用 k-Means 进行颜色量化
聚类作为一种无监督机器学习任务
聚类分析是一种无监督学习技术。
它涉及将数据自动分组到不同的组(或集群)中,其中每个集群中的数据相似,但与其他集群的数据不同。它旨在揭示数据中的模式,这些模式在聚类之前可能并不明显。
有许多不同的聚类算法,如 本教程 所述,其中 k-means 聚类是最广为人知的算法之一。
k-means 聚类算法处理无标签的数据点。它试图将数据点分配到 k 个集群中,每个数据点属于离其最近的集群中心的集群,而每个集群的中心被视为属于该集群的数据点的均值。该算法要求用户提供 k 的值作为输入,因此需要事先知道或根据数据调整这个值。
在 OpenCV 中发现 k-Means 聚类
在深入更复杂的任务之前,我们首先考虑将 k-means 聚类应用于包含不同数据集群的简单二维数据集。
为此,我们将生成一个由 100 个数据点(由n_samples指定)组成的数据集,这些数据点平均分配到 5 个高斯聚类(由centers标识),标准差设置为 1.5(由cluster_std确定)。为了能够复制结果,我们还定义一个random_state的值,我们将其设置为 10:
Python
# Generating a dataset of 2D data points and their ground truth labels
x, y_true = make_blobs(n_samples=100, centers=5, cluster_std=1.5, random_state=10)
# Plotting the dataset
scatter(x[:, 0], x[:, 1])
show()
上面的代码应生成以下数据点的图表:
由 5 个高斯聚类组成的数据集的散点图
想要开始使用 OpenCV 进行机器学习吗?
现在就参加我的免费电子邮件速成课程(附样例代码)。
点击注册并获取课程的免费 PDF 电子书版本。
如果我们查看这个图表,我们可能已经可以直观地区分一个聚类与另一个聚类,这意味着这应该是一个足够简单的任务,适合 k-means 聚类算法。
在 OpenCV 中,k-means 算法不属于ml模块,但可以直接调用。为了能够使用它,我们需要为其输入参数指定值,如下所示:
-
输入,未标记的
data。 -
所需的聚类数量
K。 -
终止标准
TERM_CRITERIA_EPS和TERM_CRITERIA_MAX_ITER,分别定义了所需的准确性和最大迭代次数,当达到这些标准时,算法迭代应停止。 -
attempts的数量,表示算法将执行的次数,每次使用不同的初始标记以寻找最佳聚类紧凑度。 -
聚类中心的初始化方式,无论是随机的、用户提供的,还是通过诸如 kmeans++ 的中心初始化方法,如参数
flags所指定的。
OpenCV 中的 k-means 聚类算法返回:
-
每个聚类的
compactness,计算为每个数据点到其对应聚类中心的平方距离之和。较小的紧凑度值表明数据点分布得更接近其对应的聚类中心,因此聚类更紧凑。 -
预测的聚类标签
y_pred,将每个输入数据点与其对应的聚类关联起来。 -
每个数据点聚类的
centers坐标。
现在我们将 k-means 聚类算法应用于之前生成的数据集。请注意,我们将输入数据转换为float32类型,这是 OpenCV 中kmeans()函数所期望的:
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 input data
compactness, y_pred, centers = kmeans(data=x.astype(float32), K=5, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
# Plot the data clusters, each having a different color, together with the corresponding cluster centers
scatter(x[:, 0], x[:, 1], c=y_pred)
scatter(centers[:, 0], centers[:, 1], c='red')
show()
上面的代码生成了以下图表,其中每个数据点根据其分配的聚类进行着色,聚类中心用红色标记:
使用 k-means 聚类识别聚类的数据集的散点图
完整的代码列表如下:
Python
from cv2 import kmeans, TERM_CRITERIA_MAX_ITER, TERM_CRITERIA_EPS, KMEANS_RANDOM_CENTERS
from numpy import float32
from matplotlib.pyplot import scatter, show
from sklearn.datasets import make_blobs
# Generate a dataset of 2D data points and their ground truth labels
x, y_true = make_blobs(n_samples=100, centers=5, cluster_std=1.5, random_state=10)
# Plot the dataset
scatter(x[:, 0], x[:, 1])
show()
# 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 input data
compactness, y_pred, centers = kmeans(data=x.astype(float32), K=5, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
# Plot the data clusters, each having a different colour, together with the corresponding cluster centers
scatter(x[:, 0], x[:, 1], c=y_pred)
scatter(centers[:, 0], centers[:, 1], c='red')
show()
使用 k-means 的颜色量化
k-means 聚类的一种应用是图像的颜色量化。
颜色量化指的是减少图像表示中使用的不同颜色数量的过程。
颜色量化对于在只能显示有限颜色数量的设备上显示多色图像至关重要,这通常是由于内存限制,并且能够高效地压缩某些类型的图像。
颜色量化,2023。**
在这种情况下,我们将提供给 k-means 聚类算法的数据点是每个图像像素的 RGB 值。正如我们将看到的,我们将以 数组的形式提供这些值,其中 表示图像中的像素数量。
让我们在这张图片上尝试 k-means 聚类算法,我将其命名为 bricks.jpg:
图像中突出的主色是红色、橙色、黄色、绿色和蓝色。然而,许多阴影和光斑为主色引入了额外的色调和颜色。
我们将首先使用 OpenCV 的 imread 函数读取图像。
请记住,OpenCV 以 BGR 而非 RGB 顺序加载此图像。在将其输入 k-means 聚类算法之前,不需要将其转换为 RGB,因为后者会根据像素值的顺序仍然分组相似的颜色。然而,由于我们使用 Matplotlib 来显示图像,因此我们会将其转换为 RGB,以便稍后可以正确显示量化结果:
Python
# Read image
img = imread('Images/bricks.jpg')
# Convert it from BGR to RGB
img_RGB = cvtColor(img, COLOR_BGR2RGB)
正如我们之前提到的,下一步涉及将图像重塑为 数组,然后我们可以应用 k-means 聚类算法,将结果数组值分配到多个聚类中,这些聚类对应于我们上面提到的主色数。
在下面的代码片段中,我还包括了一行代码,用于打印图像中总像素数的唯一 RGB 像素值。我们发现,从 14,155,776 像素中,我们有 338,742 个唯一 RGB 值,这个数字相当可观:
Python
# Reshape image to an Mx3 array
img_data = img_RGB.reshape(-1, 3)
# Find the number of unique RGB values
print(len(unique(img_data, axis=0)), 'unique RGB values out of', img_data.shape[0], 'pixels')
# 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 pixel values
compactness, labels, centers = kmeans(data=img_data.astype(float32), K=5, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
此时,我们将应用聚类中心的实际 RGB 值到预测的像素标签上,并将结果数组重塑为原始图像的形状,然后显示它:
Python
# Apply the RGB values of the cluster centers to all pixel labels
colours = centers[labels].reshape(-1, 3)
# Find the number of unique RGB values
print(len(unique(colours, axis=0)), 'unique RGB values out of', img_data.shape[0], 'pixels')
# Reshape array to the original image shape
img_colours = colours.reshape(img_RGB.shape)
# Display the quantized image
imshow(img_colours.astype(uint8))
show()
再次打印量化图像中的唯一 RGB 值,我们发现这些值已经减少到我们为 k-means 算法指定的聚类数量:
Python
5 unique RGB values out of 14155776 pixels
如果我们查看颜色量化图像,会发现黄色和橙色砖块的像素被分组到同一个簇中,这可能是由于它们的 RGB 值相似。相比之下,其中一个簇则聚合了阴影区域的像素:
使用 5 个簇的 k-means 聚类进行颜色量化图像
现在尝试更改指定 k-means 聚类算法簇数的值,并调查其对量化结果的影响。
完整的代码清单如下:
Python
from cv2 import kmeans, TERM_CRITERIA_MAX_ITER, TERM_CRITERIA_EPS, KMEANS_RANDOM_CENTERS, imread, cvtColor, COLOR_BGR2RGB
from numpy import float32, uint8, unique
from matplotlib.pyplot import show, imshow
# Read image
img = imread('Images/bricks.jpg')
# Convert it from BGR to RGB
img_RGB = cvtColor(img, COLOR_BGR2RGB)
# Reshape image to an Mx3 array
img_data = img_RGB.reshape(-1, 3)
# Find the number of unique RGB values
print(len(unique(img_data, axis=0)), 'unique RGB values out of', img_data.shape[0], 'pixels')
# 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 pixel values
compactness, labels, centers = kmeans(data=img_data.astype(float32), K=5, bestLabels=None, criteria=criteria, attempts=10, flags=KMEANS_RANDOM_CENTERS)
# Apply the RGB values of the cluster centers to all pixel labels
colours = centers[labels].reshape(-1, 3)
# Find the number of unique RGB values
print(len(unique(colours, axis=0)), 'unique RGB values out of', img_data.shape[0], 'pixels')
# Reshape array to the original image shape
img_colours = colours.reshape(img_RGB.shape)
# Display the quantized image
imshow(img_colours.astype(uint8))
show()
进一步阅读
本节提供了更多资源,如果你想深入了解该主题。
书籍
- 《OpenCV 机器学习》,2017 年。
网站
-
《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 聚类算法进行图像的颜色量化。
具体来说,你学到了:
-
机器学习中的数据聚类是什么。
-
在 OpenCV 中应用 k-means 聚类算法于一个包含不同数据簇的简单二维数据集。
-
如何在 OpenCV 中应用 k-means 聚类算法进行图像的颜色量化。
你有任何问题吗?
在下面的评论中提问,我会尽力回答。
使用 OpenCV 的图像分类逻辑回归
原文:
machinelearningmastery.com/logistic-regression-for-image-classification-using-opencv/
在一个之前的教程,我们探讨了逻辑回归作为一个简单但流行的二分类机器学习算法,并在 OpenCV 库中实现了它。
到目前为止,我们已经看到逻辑回归如何应用于我们自己生成的自定义二分类数据集。
在本教程中,你将学习标准的逻辑回归算法,如何从本质上设计用于二分类问题,并修改为适应多分类问题,通过将其应用于图像分类任务。
完成本教程后,你将了解:
-
逻辑回归算法的几个重要特性。
-
如何将逻辑回归算法修改以解决多分类问题。
-
如何将逻辑回归应用于图像分类问题。
快速启动你的项目,参考我的书籍《OpenCV 中的机器学习》。它提供了自学教程和可运行的代码。
让我们开始吧。
使用 OpenCV 的图像分类逻辑回归
图片由David Marcu提供,版权所有。
教程概述
本教程分为三个部分,分别是:
-
回顾逻辑回归的定义
-
修改逻辑回归以解决多分类问题
-
将逻辑回归应用于多分类问题
回顾逻辑回归的定义
在一个之前的教程,我们开始探索 OpenCV 对逻辑回归算法的实现。到目前为止,我们已将其应用于我们生成的自定义二分类数据集,该数据集由聚集在两个簇中的二维点组成。
根据 Jason Brownlee 的逻辑回归教程,我们还回顾了逻辑回归的重要点。我们已经看到逻辑回归与线性回归紧密相关,因为它们都涉及特征的线性组合生成实值输出。然而,逻辑回归通过应用逻辑(或 sigmoid)函数扩展了这一过程。因此得名。它是将实值输出映射到[0, 1]范围内的概率值。这个概率值如果超过 0.5 的阈值则被分类为默认类别;否则,被分类为非默认类别。这使得逻辑回归本质上是一种二分类方法。
逻辑回归模型由与输入数据中的特征数量相等的系数以及一个额外的偏置值表示。这些系数和偏置值在训练过程中通过梯度下降或最大似然估计(MLE)技术进行学习。
为多类别分类问题修改逻辑回归
如前一节所述,标准逻辑回归方法仅适用于两类问题,因为逻辑函数及其阈值处理将特征的线性组合的实值输出映射为类别 0 或类别 1。
因此,针对多类别分类问题(或涉及两个以上类别的问题)的逻辑回归需要对标准算法进行修改。
实现这一点的一种技术是将多类别分类问题拆分为多个二分类(或两类)子问题。然后可以将标准逻辑回归方法应用于每个子问题。这就是 OpenCV 实现多类别逻辑回归的方法:
… 逻辑回归支持二分类和多分类(对于多分类,它创建多个二分类分类器)。
这种类型的技术称为一对一方法,它涉及为数据集中每对唯一的类别训练一个独立的二分类器。在预测时,这些二分类器中的每一个都会对其所训练的两个类别中的一个投票,获得最多投票的类别被认为是预测类别。
还有其他技术可以通过逻辑回归实现多类别分类,例如通过一对其余方法。你可以在这些教程中找到更多信息 [1, 2]。
将逻辑回归应用于多类别分类问题
为此,我们将使用OpenCV 中的数字数据集,尽管我们开发的代码也可以应用于其他多类数据集。
我们的第一步是加载 OpenCV 数字图像,将其分割成包含 0 到 9 的手写数字的许多子图像,并创建相应的真实标签,以便稍后量化训练的逻辑回归模型的准确性。对于这个特定的示例,我们将 80%的数据集图像分配给训练集,其余 20%的图像分配给测试集:
Python
# 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)
接下来,我们将遵循类似于前一个教程中的过程,我们在一个两类数据集上训练并测试了逻辑回归算法,改变了一些参数以适应更大的多类数据集。
第一阶段,再次是创建逻辑回归模型本身:
Python
# Create an empty logistic regression model
lr_digits = ml.LogisticRegression_create()
我们可以再次确认 OpenCV 将批量梯度下降作为默认训练方法(由值 0 表示),然后将其更改为迷你批量梯度下降方法,指定迷你批次大小:
Python
# Check the default training method
print('Training Method:', lr_digits.getTrainMethod())
# Set the training method to Mini-Batch Gradient Descent and the size of the mini-batch
lr_digits.setTrainMethod(ml.LogisticRegression_MINI_BATCH)
lr_digits.setMiniBatchSize(400)
不同的迷你批次大小肯定会影响模型的训练和预测准确性。
在这个示例中,我们对迷你批次大小的选择基于一种启发式方法,为了实用性,我们尝试了几种迷你批次大小,并确定了一个结果足够高的预测准确度的值(稍后我们将看到)。然而,你应该采取更系统的方法,以便对迷你批次大小做出更明智的决策,以便在计算成本和预测准确性之间提供更好的折衷。
接下来,我们将定义在选择的训练算法终止前我们希望运行的迭代次数:
Python
# Set the number of iterations
lr.setIterations(10)
我们现在准备在训练数据上训练逻辑回归模型:
Python
# Train the logistic regressor on the set of training data
lr_digits.train(digits_train_imgs.astype(float32), ml.ROW_SAMPLE, digits_train_labels.astype(float32))
在我们的前一个教程中,我们打印出了学习到的系数,以了解如何定义最佳分离我们所用的两类样本的模型。
这次我们不会打印出学习到的系数,主要是因为系数太多了,因为我们现在处理的是高维输入数据。
我们将选择打印出学习到的系数数量(而不是系数本身)以及输入特征的数量,以便能够比较这两者:
Python
# Print the number of learned coefficients, and the number of input features
print('Number of coefficients:', len(lr_digits.get_learnt_thetas()[0]))
print('Number of input features:', len(digits_train_imgs[0, :]))
Python
Number of coefficients: 401
Number of input features: 400
确实,我们发现系数值的数量与输入特征一样多,加上一个额外的偏差值,正如我们预期的那样(我们处理的是像素的图像,我们使用像素值本身作为输入特征,因此每张图像 400 个特征)。
我们可以通过在数据集的测试部分尝试这个模型来测试它对目标类别标签的预测效果:
Python
# Predict the target labels of the testing data
_, y_pred = lr_digits.predict(digits_test_imgs.astype(float32))
# Compute and print the achieved accuracy
accuracy = (sum(y_pred[:, 0] == digits_test_labels[:, 0]) / digits_test_labels.size) * 100
print('Accuracy:', accuracy, '%')
Python
Accuracy: 88.8 %
最后一步,让我们生成并绘制一个 混淆 矩阵,以更深入地了解哪些数字被互相混淆:
Python
# Generate and plot confusion matrix
cm = confusion_matrix(digits_test_labels, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
disp.plot()
show()
混淆矩阵
通过这种方式,我们可以看到,性能最低的类别是 5 和 2,它们大多被误认为是 8。
完整的代码清单如下:
Python
from cv2 import ml
from sklearn.datasets import make_blobs
from sklearn import model_selection as ms
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from numpy import float32
from matplotlib.pyplot import show
from digits_dataset import split_images, split_data
# 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)
# Create an empty logistic regression model
lr_digits = ml.LogisticRegression_create()
# Check the default training method
print('Training Method:', lr_digits.getTrainMethod())
# Set the training method to Mini-Batch Gradient Descent and the size of the mini-batch
lr_digits.setTrainMethod(ml.LogisticRegression_MINI_BATCH)
lr_digits.setMiniBatchSize(400)
# Set the number of iterations
lr_digits.setIterations(10)
# Train the logistic regressor on the set of training data
lr_digits.train(digits_train_imgs.astype(float32), ml.ROW_SAMPLE, digits_train_labels.astype(float32))
# Print the number of learned coefficients, and the number of input features
print('Number of coefficients:', len(lr_digits.get_learnt_thetas()[0]))
print('Number of input features:', len(digits_train_imgs[0, :]))
# Predict the target labels of the testing data
_, y_pred = lr_digits.predict(digits_test_imgs.astype(float32))
# Compute and print the achieved accuracy
accuracy = (sum(y_pred[:, 0] == digits_test_labels[:, 0]) / digits_test_labels.size) * 100
print('Accuracy:', accuracy, '%')
# Generate and plot confusion matrix
cm = confusion_matrix(digits_test_labels, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
disp.plot()
show()
在本教程中,我们将固有设计用于二分类的逻辑回归方法应用于多分类问题。我们使用像素值作为表示每张图像的输入特征,获得了 88.8% 的分类准确率。
那么,测试在从图像中提取的 HOG 描述符上训练逻辑回归算法是否会提高准确率怎么样?
想要开始使用 OpenCV 进行机器学习吗?
现在就参加我的免费电子邮件速成课程(包括示例代码)。
点击注册并获得课程的免费 PDF 电子书版本。
进一步阅读
本节提供了更多资源,如果你想深入了解这个主题。
书籍
-
OpenCV 的机器学习,2017 年。
-
掌握 OpenCV 4 的 Python,2019 年。
网站
总结
在本教程中,你学会了如何将固有设计用于二分类的标准逻辑回归算法,修改为适应多分类问题,并将其应用于图像分类任务。
具体来说,你学到了:
-
逻辑回归算法的几个重要特性。
-
逻辑回归算法如何修改以适应多分类问题。
-
如何将逻辑回归应用于图像分类问题。
你有任何问题吗?
在下面的评论中提出你的问题,我会尽力回答。
OpenCV 中的逻辑回归
原文:
machinelearningmastery.com/logistic-regression-in-opencv/
逻辑回归是一种简单但受欢迎的机器学习算法,用于二分类问题,其核心使用逻辑函数或 sigmoid 函数。它也已在 OpenCV 库中实现。
在本教程中,你将学习如何应用 OpenCV 的逻辑回归算法,首先从我们自己生成的自定义二分类数据集开始。然后我们将在随后的教程中将这些技能应用于特定的图像分类应用。
完成本教程后,你将了解到:
-
逻辑回归算法的一些最重要特征。
-
如何在 OpenCV 中对自定义数据集使用逻辑回归算法。
通过我的书籍 Machine Learning in OpenCV 启动你的项目。它提供了自学教程和可运行的代码。
让我们开始吧。
OpenCV 中的逻辑回归
照片由 Fabio Santaniello Bruun 提供。保留所有权利。
教程概述
本教程分为两个部分;它们是:
-
逻辑回归的提醒
-
在 OpenCV 中探索逻辑回归
逻辑回归的提醒
关于逻辑回归的话题已经在 Jason Brownlee 的这些教程中讲解得很好了 [1, 2, 3],但让我们先从回顾一些最重要的点开始:
- 逻辑回归得名于其核心所使用的函数,即逻辑函数(也称为 sigmoid 函数)。
尽管名称中包含回归*一词,逻辑回归实际上是一种用于二分类的方法,或者更简单地说,是解决两类值问题的技术。
逻辑回归可以被视为线性回归的扩展,因为它通过使用逻辑函数将特征的线性组合的实际值输出映射(或压缩)为范围在 [0, 1] 内的概率值。*
*** 在双类情景中,逻辑回归方法对默认类别的概率进行建模。举个简单的例子,假设我们试图根据花瓣数来区分类别 A 和 B,并且我们将默认类别定为 A。那么,对于一个未见过的输入 X,逻辑回归模型会给出 X 属于默认类别 A 的概率:
**
- 如果输入 X 的概率 P(X) > 0.5,则 X 被分类为默认类别 A。否则,它被分类为非默认类别 B。
*** 逻辑回归模型由一组称为系数(或权重)的参数表示,这些参数通过训练数据学习得来。这些系数在训练过程中会被迭代调整,以最小化模型预测与实际类别标签之间的误差。
*** 系数值可以通过梯度下降法或最大似然估计(MLE)技术在训练过程中进行估计。
**## 探索 OpenCV 中的逻辑回归
在深入更复杂的问题之前,让我们从一个简单的二分类任务开始。
正如我们在相关教程中通过其他机器学习算法(如 SVM 算法)所做的那样,我们将生成一个包含 100 个数据点(由n_samples指定)的数据集,这些数据点均分为两个高斯簇(由centers指定),标准差设置为 5(由cluster_std指定)。为了能够复制结果,我们将再次利用random_state参数,并将其设置为 15:
Python
# Generate a dataset of 2D data points and their ground truth labels
x, y_true = make_blobs(n_samples=100, centers=2, cluster_std=5, random_state=15)
# Plot the dataset
scatter(x[:, 0], x[:, 1], c=y_true)
show()
上面的代码应生成以下数据点的图示。你可能会注意到,我们将颜色值设置为真实标签,以便区分属于两个不同类别的数据点:
属于两种不同类别的数据点
下一步是将数据集分为训练集和测试集,其中前者用于训练逻辑回归模型,后者用于测试:
Python
# Split the data into training and testing sets
x_train, x_test, y_train, y_test = ms.train_test_split(x, y_true, test_size=0.2, random_state=10)
# Plot the training and testing datasets
fig, (ax1, ax2) = subplots(1, 2)
ax1.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
ax1.set_title('Training data')
ax2.scatter(x_test[:, 0], x_test[:, 1], c=y_test)
ax2.set_title('Testing data')
show()
将数据点拆分为训练集和测试集
上面的图像表明,两个类别在训练和测试数据中明显可分。因此,我们期望这个二分类问题对于训练过的线性回归模型来说是一个简单的任务。让我们在 OpenCV 中创建并训练一个逻辑回归模型,以最终查看它在数据集测试部分的表现。
第一步是创建逻辑回归模型本身:
Python
# Create an empty logistic regression model
lr = ml.LogisticRegression_create()
在下一步中,我们将选择训练方法,以便在训练过程中更新模型的系数。OpenCV 的实现允许我们在批量梯度下降方法和迷你批量梯度下降方法之间进行选择。
如果选择了批量梯度下降方法,模型的系数将在每次梯度下降算法迭代中使用整个训练数据集进行更新。如果我们处理的是非常大的数据集,那么这种更新模型系数的方法可能会非常耗费计算资源。
想开始使用 OpenCV 进行机器学习吗?
立即参加我的免费电子邮件速成课程(包含示例代码)。
点击注册并获得课程的免费 PDF 电子书版本。
更新模型系数的更实际方法,特别是在处理大型数据集时,是选择迷你批量梯度下降方法,该方法将训练数据划分为较小的批量(称为迷你批量,因此得名),并通过逐个处理迷你批量来更新模型系数。
我们可以通过使用以下代码行检查 OpenCV 默认的训练方法:
Python
# Check the default training method
print(‘Training Method:’, lr.getTrainMethod())
Python
Training Method: 0
返回值 0 表示 OpenCV 中的批量梯度下降方法。如果我们想将其更改为迷你批量梯度下降方法,可以将ml.LogisticRegression_MINI_BATCH传递给setTrainMethod函数,然后设置迷你批量的大小:
Python
# Set the training method to Mini-Batch Gradient Descent and the size of the mini-batch
lr.setTrainMethod(ml.LogisticRegression_MINI_BATCH)
lr.setMiniBatchSize(5)
将迷你批量大小设置为 5 意味着训练数据将被分成每个包含 5 个数据点的迷你批量,模型的系数将在处理完每个迷你批量后迭代更新。如果我们将迷你批量的大小设置为训练数据集中样本的总数,这实际上将导致批量梯度下降操作,因为每次迭代时都会一次性处理整个训练数据批量。
接下来,我们将定义在算法终止之前希望运行所选训练算法的迭代次数:
Python
# Set the number of iterations
lr.setIterations(10)
我们现在可以在训练数据上训练逻辑回归模型:
Python
# Train the logistic regressor on the set of training data
lr.train(x_train.astype(float32), ml.ROW_SAMPLE, y_train.astype(float32))
如前所述,训练过程旨在迭代地调整逻辑回归模型的系数,以最小化模型预测与实际类别标签之间的误差。
我们输入模型的每个训练样本包括两个特征值,分别表示为和。这意味着我们期望生成的模型由两个系数(每个输入特征一个)和一个定义偏差(或截距)的额外系数定义。
然后可以按照以下方式定义返回模型的概率值:
其中 和 表示模型系数, 为偏差, 为应用于特征线性组合真实值的逻辑(或 sigmoid)函数。
让我们打印出学习到的系数值,看看是否能获取到我们期望的数量:
Python
# Print the learned coefficients
print(lr.get_learnt_thetas())
Python
[[-0.02413555 -0.34612912 0.08475047]]
我们发现我们按预期检索到了三个值,这意味着我们可以通过以下方式定义最佳模型,以区分我们正在处理的两类样本:
我们可以通过将特征值 和 插入上述模型,将一个新的、未见过的数据点分配到两个类别之一。如果模型返回的概率值 > 0.5,我们可以将其视为对类别 0(默认类别)的预测。否则,它就是对类别 1 的预测。
让我们继续尝试在数据集的测试部分上测试这个模型,以查看它对目标类别标签的预测效果:
Python
# Predict the target labels of the testing data
_, y_pred = lr.predict(x_test.astype(float32))
# Compute and print the achieved accuracy
accuracy = (sum(y_pred[:, 0].astype(int) == y_test) / y_test.size) * 100
print('Accuracy:', accuracy, ‘%')
Python
Accuracy: 95.0 %
我们可以绘制真实值与预测类别的图表,并打印出真实值和预测类别标签,以调查任何误分类情况:
Python
# Plot the ground truth and predicted class labels
fig, (ax1, ax2) = subplots(1, 2)
ax1.scatter(x_test[:, 0], x_test[:, 1], c=y_test)
ax1.set_title('Ground Truth Testing data')
ax2.scatter(x_test[:, 0], x_test[:, 1], c=y_pred)
ax2.set_title('Predicted Testing data’)
show()
# Print the ground truth and predicted class labels of the testing data
print('Ground truth class labels:', y_test, '\n',
'Predicted class labels:', y_pred[:, 0].astype(int))
Python
Ground truth class labels: [1 1 1 1 1 0 0 1 1 1 0 0 1 0 0 1 1 1 1 0]
Predicted class labels: [1 1 1 1 1 0 0 1 0 1 0 0 1 0 0 1 1 1 1 0]
测试数据点属于真实类别和预测类别,其中红色圆圈突出显示了一个误分类的数据点
通过这种方式,我们可以看到一个样本在真实数据中原本属于类别 1,但在模型预测中被误分类为类别 0。
整个代码清单如下:
Python
from cv2 import ml
from sklearn.datasets import make_blobs
from sklearn import model_selection as ms
from numpy import float32
from matplotlib.pyplot import scatter, show, subplots
# Generate a dataset of 2D data points and their ground truth labels
x, y_true = make_blobs(n_samples=100, centers=2, cluster_std=5, random_state=15)
# Plot the dataset
scatter(x[:, 0], x[:, 1], c=y_true)
show()
# Split the data into training and testing sets
x_train, x_test, y_train, y_test = ms.train_test_split(x, y_true, test_size=0.2, random_state=10)
# Plot the training and testing datasets
fig, (ax1, ax2) = subplots(1, 2)
ax1.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
ax1.set_title('Training data')
ax2.scatter(x_test[:, 0], x_test[:, 1], c=y_test)
ax2.set_title('Testing data')
show()
# Create an empty logistic regression model
lr = ml.LogisticRegression_create()
# Check the default training method
print('Training Method:', lr.getTrainMethod())
# Set the training method to Mini-Batch Gradient Descent and the size of the mini-batch
lr.setTrainMethod(ml.LogisticRegression_MINI_BATCH)
lr.setMiniBatchSize(5)
# Set the number of iterations
lr.setIterations(10)
# Train the logistic regressor on the set of training data
lr.train(x_train.astype(float32), ml.ROW_SAMPLE, y_train.astype(float32))
# Print the learned coefficients
print(lr.get_learnt_thetas())
# Predict the target labels of the testing data
_, y_pred = lr.predict(x_test.astype(float32))
# Compute and print the achieved accuracy
accuracy = (sum(y_pred[:, 0].astype(int) == y_test) / y_test.size) * 100
print('Accuracy:', accuracy, '%')
# Plot the ground truth and predicted class labels
fig, (ax1, ax2) = subplots(1, 2)
ax1.scatter(x_test[:, 0], x_test[:, 1], c=y_test)
ax1.set_title('Ground truth testing data')
ax2.scatter(x_test[:, 0], x_test[:, 1], c=y_pred)
ax2.set_title('Predicted testing data')
show()
# Print the ground truth and predicted class labels of the testing data
print('Ground truth class labels:', y_test, '\n',
'Predicted class labels:', y_pred[:, 0].astype(int))
在本教程中,我们考虑了为在 OpenCV 中实现的逻辑回归模型设置两个特定训练参数的值。这些参数定义了使用的训练方法以及训练过程中我们希望运行所选择训练算法的迭代次数。
然而,这些并不是可以为逻辑回归方法设置的唯一参数值。其他参数,如学习率和正则化类型,也可以修改以实现更好的训练准确度。因此,我们建议你探索这些参数,调查不同值如何影响模型的训练和预测准确性。
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解,可以参考这些资源。
书籍
-
《OpenCV 机器学习》,2017 年。
-
《用 Python 掌握 OpenCV 4》,2019 年。
网站
总结
在本教程中,你学习了如何应用 OpenCV 的逻辑回归算法,从我们生成的自定义二分类数据集开始。
具体来说,你学到了:
-
逻辑回归算法的几个最重要特征。
-
如何在 OpenCV 中使用逻辑回归算法处理自定义数据集。
你有任何问题吗?
在下方评论中提问,我会尽力回答。**************
OpenCV 中的机器学习(7 天迷你课程)
原文:
machinelearningmastery.com/machine-learning-in-opencv-7-day-mini-course/
机器学习是处理许多任务的一个惊人工具。OpenCV 是一个用于图像处理的优秀库。如果我们能将它们结合起来,那就太好了。
在这个 7 部分的速成课程中,你将通过示例学习如何利用机器学习和 OpenCV 的图像处理 API 实现一些目标。这个迷你课程是针对那些已经对 Python 编程感到舒适、了解机器学习基本概念并有一定图像处理背景的从业人员设计的。让我们开始吧。
OpenCV 中的机器学习(7 天迷你课程)
图片由 Nomadic Julien 提供。保留部分权利。
这个迷你课程适合谁?
在我们开始之前,让我们确保你在正确的地方。下面的列表提供了一些关于此课程设计对象的通用指南。如果你不完全符合这些要点,不要惊慌,你可能只需要在某个领域稍作复习以跟上课程进度。
-
会写一点代码的开发者。这意味着你能够使用 Python 完成任务,并且知道如何在工作站上设置生态系统(这是一个先决条件)。这并不意味着你是一个代码天才,但意味着你不怕安装软件包和编写脚本。
-
知道一点机器学习的开发者。这意味着你了解一些常见的机器学习算法,如回归或神经网络。这并不意味着你是机器学习博士,仅仅是你知道这些领域的标志性知识或知道在哪里查找它们。
-
了解一点图像处理的开发者。这意味着你知道如何读取图像文件,如何操作像素,以及如何裁剪子图像。最好使用 OpenCV。这并不意味着你是图像处理专家,但你理解数字图像是像素数组。
这个迷你课程不是关于机器学习、OpenCV 或数字图像处理的教科书。而是一个项目指南,逐步带你从一个具有最低限度知识的开发者成长为一个可以自信使用 OpenCV 中的机器学习的开发者。
迷你课程概述
这个迷你课程分为 7 部分。
每一课的设计时间大约是 30 分钟。你可能会更快完成某些部分,也可能选择深入研究并花费更多时间。
你可以根据自己的节奏完成每个部分。一个舒适的时间表可能是每隔一天完成一节课,共七天。强烈推荐。
接下来的 7 节课中你将涵盖的主题如下:
-
第 1 课:OpenCV 介绍
-
第 2 课:使用 OpenCV 读取和显示图像
-
第 3 课:寻找圆形
-
课程 4:提取子图像
-
课程 5:匹配硬币
-
课程 6:构建硬币分类器
-
课程 7:在 OpenCV 中使用 DNN 模块
这将会非常有趣。
不过你需要做一些工作,包括阅读、研究和编程。你想学习机器学习和计算机视觉,对吗?
在评论中发布你的结果;我会为你加油!
坚持下去;不要放弃。
课程 01:OpenCV 简介
OpenCV 是一个流行的开源图像处理库。它在 Python、C++、Java 和 Matlab 中都有 API 绑定。它提供了数千个函数,并实现了许多先进的图像处理算法。如果你使用 Python,OpenCV 的一个常见替代品是 PIL(Python Imaging Library,或其继任者 Pillow)。与 PIL 相比,OpenCV 提供了更丰富的功能集,并且通常速度更快,因为它是用 C++ 实现的。
这个迷你课程是为了在 OpenCV 中应用机器学习。在本课程的后续课程中,你还需要 TensorFlow/Keras 和 Python 中的 tf2onnx 库。
在这节课中,你的目标是安装 OpenCV。
对于基础的 Python 环境,你可以使用 pip 安装软件包。要使用 pip 安装 OpenCV、TensorFlow 和 tf2onnx,你可以使用:
sudo pip install opencv-python tensorflow tf2onnx
OpenCV 在 PyPI 中被称为 opencv-python 包,但它仅包含“免费”算法和主要模块。还有一个名为 opencv-contrib-python 的包,其中包含了“额外”模块。这些额外的模块较不稳定且未经充分测试。如果你更愿意安装后者,你应该使用以下命令:
sudo pip install opencv-contrib-python tensorflow tf2onnx
然而,如果你使用 Anaconda 或 miniconda 环境,软件包的名称仅为 opencv,你可以使用 conda install 命令进行安装。
要检查你的 OpenCV 安装是否正常工作,你可以简单地运行一个小脚本并检查其版本:
import cv2
print(cv2.version.opencv_version)
想了解更多关于 OpenCV 的信息,你可以从其 在线文档 开始。
你的任务
重复上述代码,以确保你已正确安装 OpenCV。你能通过在代码中添加几行来打印 TensorFlow 模块的版本吗?
在下一课中,你将使用 OpenCV 读取和显示图像。
课程 02:使用 OpenCV 读取和显示图像
OpenCV,即开源计算机视觉库,是一个强大的图像处理和计算机视觉任务工具。但在深入复杂算法之前,让我们先掌握基础:读取和显示图像。
使用 OpenCV 读取图像是使用 cv2.imread() 函数。它接受图像文件的路径并返回一个 NumPy 数组。这个数组通常是三维的,形状为高度×宽度×通道,每个元素是无符号 8 位整数。在 OpenCV 中,“通道”通常是 BGR(蓝-绿-红)。但如果你更愿意以灰度图像加载,你可以添加一个额外的参数,如:
import cv2
image = cv2.imread("path/filename.jpg", cv2.IMREAD_GRAYSCALE)
print(image.shape)
上面的代码将打印数组的维度。虽然我们通常将图像描述为宽度×高度,但数组维度描述为高度×宽度。如果图像以灰度形式读取,则只有一个通道,因此输出将是一个二维数组。
如果你移除了第二个参数,使其仅为 cv2.imread("path/filename.jpg"),则数组的形状应为高度×宽度×3,表示 BGR 的三个通道。
要显示图像,可以使用 OpenCV 的 cv2.imshow() 函数。这将创建一个窗口来显示图像。但是,除非你要求 OpenCV 等待你与窗口互动,否则此窗口将不会显示。通常,你可以使用:
...
cv2.imshow("My image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imshow() 将窗口标题作为第一个参数。要显示的图像应为 BGR 通道顺序。cv2.waitKey() 函数将等待你按键的时间为函数参数中指定的毫秒数。如果为零,它将无限期等待。按键将以整数形式返回其代码点,在这种情况下,你可以忽略它。作为一个好的做法,你应该在程序结束前关闭窗口。
将所有内容组合起来:
import cv2
image = cv2.imread("path/filename.jpg", cv2.IMREAD_GRAYSCALE)
cv2.imshow("My image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
你的任务
修改上面的代码,将路径指向你磁盘中的一张图像并试一下。你如何修改上面的代码以等待 Esc 键被按下,但忽略所有其他按键?(提示:Esc 键的代码点是 27)
在下一课中,你将看到如何在图像中寻找模式。
课程 03:寻找圆形
由于数字图像表示为矩阵,你可以设计你的算法并检查图像的每个像素以识别图像中是否存在某种模式。多年来,发明了许多巧妙的算法,你可以在任何数字图像处理教科书中学习其中的一些。
在这个迷你课程中,你将解决一个简单的问题:给定一张包含许多硬币的图像,识别并计数某种特定类型的硬币。硬币是圆形的。要识别图像中的圆形,一种有前景的算法是使用霍夫圆变换。
霍夫变换是一种利用图像的梯度信息的算法。因此,它作用于灰度图像而不是彩色图像。要将彩色图像转换为灰度图像,你可以使用 OpenCV 的 cv2.cvtColor() 函数。由于霍夫变换基于梯度信息,它对图像噪声非常敏感。应用高斯模糊是一个常见的预处理步骤,用于减少霍夫变换的噪声。在代码中,对于你读取的 BGR 图像,你可以应用以下操作:
...
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
这里使用 的内核应用高斯模糊。根据图像中的噪声水平,你可以使用较小或较大的内核。
霍夫圆变换用于从图像中寻找圆,使用以下函数:
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
有很多参数。第一个参数是灰度图像,第二个参数是要使用的算法。其余的参数如下:
-
dp:图像分辨率与累加器分辨率的比例。通常使用 1.0 到 2.0。 -
minDist:检测到的圆心之间的最小距离。值越小,假阳性越多。 -
param1这是 Canny 边缘检测器的阈值 -
param2:当使用算法cv2.HOUGH_GRADIENT时,这是累加器阈值。值越小,假阳性越多。 -
minRadius和maxRadius:检测的最小和最大圆半径
从 cv2.HoughCircles() 函数返回的值是一个 NumPy 数组,表示为包含中心坐标和半径的行。
让我们尝试 一个示例图像:
硬币
下载图像,保存为 coins-1.jpg,并运行以下代码:
import cv2
import numpy as np
image_path = "coins-1.jpg"
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT,
dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
if circles is not None:
for c in np.uint16(np.round(circles[0])):
cv2.circle(img, (c[0], c[1]), c[2], (255,0,0), 10)
cv2.circle(img, (c[0], c[1]), 2, (0,0,255), 20)
cv2.imshow("Circles", img)
cv2.waitKey()
cv2.destroyAllWindows()
检测到的圆用蓝色表示,圆心用红色标出
上面的代码首先将检测到的圆数据四舍五入并转换为整数。然后在原始图像上绘制这些圆。从上面的示例中,你可以看到霍夫圆变换如何帮助你找到图像中的硬币。
进一步阅读
-
P. E. Hart. “霍夫变换的发明”。IEEE 信号处理杂志,26(6),2009 年 11 月,第 18–22 页。DOI: 10.1109/msp.2009.934181。
-
R. O. Duda 和 P. E. Hart. “使用霍夫变换检测图像中的直线和曲线”。通讯 ACM,15,1 月 11–15。DOI: 10.1145/361237.361242。
你的任务
检测对你提供给 cv2.HoughCircles() 函数的参数非常敏感。尝试修改参数并查看结果。你也可以尝试为不同的图片找到最佳参数,尤其是不同光照条件或不同分辨率的图片。
在下一节中,你将看到如何根据检测到的圆从图像中提取硬币。
第 04 课:提取子图像
使用 OpenCV 读取的图像是一个形状为高度×宽度×通道的 NumPy 数组。要提取图像的一部分,你可以简单地使用 NumPy 切片语法。例如,从 BGR 彩色图像中,你可以用以下代码提取红色通道:
red = img[:, :, 2]
因此,要提取图像的一部分,你可以使用
subimg = img[y0:y1, x0:x1]
这样你会得到较大图像的一个矩形部分。请记住,在矩阵中,你首先从上到下计算垂直元素(像素),然后从左到右计算水平元素。因此,你应在切片语法中首先描述 -坐标范围。
让我们修改前一节中的代码,提取我们找到的每个硬币:
import cv2
import numpy as np
image_path = "coins-1.jpg"
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
for c in np.uint16(np.round(circles[0])):
x, y, r = c
subimg = img[y-r:y+r, x-r:x+r]
cv2.imshow("Coin", subimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
这段代码为霍夫变换找到的每个圆提取一个正方形子图像。然后在窗口中显示该子图像,并等待你按键后再显示下一个。
OpenCV 窗口显示检测到的硬币
你的任务
运行此代码。你会发现每个检测到的圆圈可能大小不同,提取的子图像也是如此。在显示它们之前,你如何将子图像调整为一致的大小?
在下一课中,你将学习如何将提取的子图像与参考图像进行比较。
课时 05:匹配硬币
我们的任务是从图像中识别并计数便士硬币。你可以在维基百科上找到美国便士的图像。
以此为参考图像,如何将识别出的硬币与参考图像进行比较?这比听起来更复杂。使用过的硬币可能会生锈、暗淡或有划痕。图片中的硬币可能会旋转。比较像素并判断它们是否为同一硬币并不容易。
更好的方法是使用关键点匹配算法。OpenCV 中有几种关键点算法。我们来尝试使用 ORB,它是 OpenCV 团队的一个发明。从上述链接下载参考图像为penny.png,你可以使用以下代码提取关键点和关键点描述符:
import cv2
reference_path = "penny.png"
sample = cv2.imread(reference_path)
orb = cv2.ORB_create(nfeatures=500)
kp, ref_desc = orb.detectAndCompute(sample, None)
元组kp是关键点对象,但不像ref_desc数组那样重要,后者是关键点描述符。这是一个形状为的 NumPy 数组,其中为检测到的关键点数。每个关键点的 ORB 描述符是一个 32 个整数的向量。
如果你从另一张图像中获取描述符,你可以将其与已有的描述符进行比较。你不应该期望描述符完全匹配。相反,你可以应用Lowe 比率测试来决定关键点是否匹配:
...
bf = cv2.BFMatcher()
kp, desc = orb.detectAndCompute(coin_gray, None)
matches = bf.knnMatch(ref_desc, desc, k=2)
count = len([1 for m, n in matches if m.distance < 0.80*n.distance])
在这里,你使用cv2.BFMatcher()的蛮力匹配器运行 kNN 算法,以获取每个参考关键点到候选图像中关键点的两个最近邻。然后比较向量距离(距离较短表示匹配较好)。Lowe 比率测试用于判断匹配是否足够好。你可以尝试不同于 0.8 的常数。我们计算良好匹配的数量,如果找到足够的良好匹配,我们就认为硬币被识别出来了。
以下是完整的代码,我们将使用 Matplotlib 显示并识别每个找到的硬币。由于 Matplotlib 期望的是 RGB 通道的彩色图像而不是 BGR 通道,你需要在使用plt.imshow()显示图像之前,使用cv2.cvtColor()将图像转换为 RGB。
import math
import cv2
import numpy as np
import matplotlib.pyplot as plt
image_path = "coins-1.jpg"
reference_path = "penny.png"
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
sample = cv2.imread(reference_path)
orb = cv2.ORB_create(nfeatures=500)
bf = cv2.BFMatcher()
kp, ref_desc = orb.detectAndCompute(sample, None)
plt.figure(2)
N = len(circles[0])
rows = math.ceil(N / 4)
for i, c in enumerate(np.uint16(np.around(circles[0]))):
x, y, r = c
coin = img[y-r:y+r, x-r:x+r]
coin_gray = cv2.cvtColor(coin, cv2.COLOR_BGR2GRAY)
kp, desc = orb.detectAndCompute(coin_gray, None)
matches = bf.knnMatch(ref_desc, desc, k=2)
count = len([1 for m, n in matches if m.distance < 0.80*n.distance])
plt.subplot(rows, 4, i+1)
plt.imshow(cv2.cvtColor(coin, cv2.COLOR_BGR2RGB))
plt.title(f"{count}")
plt.axis('off')
plt.show()
检测到的硬币和匹配的关键点数量
你可以看到,匹配的关键点数量无法提供一个明确的度量来帮助识别便士硬币与其他硬币。你能想到其他可能有用的算法吗?
进一步阅读
你的任务
上面的代码使用了 ORB 关键点。你也可以尝试使用 SIFT 关键点。如何修改上面的代码?你会看到关键点匹配数量的变化吗?另外,由于 ORB 特征是向量。你能构建一个逻辑回归分类器来识别好的关键点,从而不需要依赖参考图像吗?
在下一节课中,你将处理一个更好的硬币识别器。
课程 06:构建一个硬币分类器
提供一枚硬币的图像并确定它是否为美国便士硬币,对人类来说很容易,但对计算机来说却不那么简单。已知进行这种分类的最佳方法是使用机器学习。你应该首先从图像中提取特征向量,然后运行机器学习算法作为分类器,以判断它是否匹配。
决定使用哪种特征本身就是一个困难的问题。但是如果你使用卷积神经网络,你可以让机器学习算法自动找出特征。然而,训练神经网络需要数据。幸运的是,你不需要很多数据。让我们看看你如何构建一个。
首先,你可以在这里查看一些硬币的图片:
-
machinelearningmastery.com/wp-content/uploads/2024/01/coins-2.jpg -
machinelearningmastery.com/wp-content/uploads/2024/01/coins-3.jpg
要提取的硬币图片作为数据集用于训练神经网络
将它们保存为coins-2.jpg和coins-3.jpg,然后使用霍夫圆变换提取硬币的图像,并将它们保存到一个名为dataset的目录中:
import cv2
import numpy as np
idx = 1
for image_path in ["coins-2.jpg", "coins-3.jpg"]:
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT,
dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
for c in np.uint16(np.around(circles[0])):
x, y, r = c
cv2.imwrite(f"dataset/{idx}.jpg", img[y-r:y+r, x-r:x+r])
idx += 1
图像数量不多。你可以手动将每张图像标记为便士硬币(正样本)或非便士硬币(负样本)。一种方法是将正样本移动到子目录dataset/pos中,将负样本移动到dataset/neg中。为了方便你,你可以在这里找到标记过的图像的压缩文件副本。
使用这些,我们来构建一个卷积神经网络来解决这个二分类问题,使用 Keras 和 TensorFlow。
在数据准备阶段,你将读取每个正样本和负样本图像。为了简化卷积神经网络,你将输入大小固定为像素,通过先调整图像大小来实现。为了增加数据集的变化性,你可以将每张图像旋转 90 度、180 度和 270 度,并将其添加到数据集中(因为图像样本都是正方形的,这很简单)。然后,你可以利用 scikit-learn 中的train_test_split()函数将数据集分为训练集和测试集,比例为 7:3。
为了创建模型,你可以使用多个 Conv2D 层和 MaxPooling 的分类架构,然后在输出层后跟 Dense 层。请注意这是一个二分类模型。因此,在最终输出层,你应该使用 sigmoid 激活函数。
在训练时,你可以简单地使用大量的迭代(例如,epochs=200)并设置早期停止,以避免担心过拟合。你应该监控在测试集上评估的损失,以确保不会出现过拟合。在代码中,你可以这样训练模型并将其保存为penny.h5:
import glob
import cv2
import numpy as np
from sklearn.model_selection import train_test_split
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.layers import Conv2D, Dense, MaxPooling2D, Flatten
from tensorflow.keras.models import Sequential
images = []
labels = []
for filename in glob.glob("dataset/pos/*"):
img = cv2.imread(filename)
img = cv2.resize(img, (256,256))
images.append(img)
labels.append(1)
for _ in range(3):
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
images.append(img)
labels.append(1)
for filename in glob.glob("dataset/neg/*"):
img = cv2.imread(filename)
img = cv2.resize(img, (256,256))
images.append(img)
labels.append(0)
for _ in range(3):
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
images.append(img)
labels.append(0)
images = np.array(images)
labels = np.array(labels)
X_train, X_test, y_train, y_test = train_test_split(images, labels, test_size=0.3)
model = Sequential([
Conv2D(16, (5,5), input_shape=(256,256,3), padding="same", activation="relu"),
MaxPooling2D((2,2)),
Conv2D(32, (5,5), activation="relu"),
MaxPooling2D((2,2)),
Conv2D(64, (5,5), activation="relu"),
MaxPooling2D((2,2)),
Conv2D(128, (5,5), activation="relu"),
Flatten(),
Dense(256, activation="relu"),
Dense(1, activation="sigmoid")
])
# Training
earlystopping = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True)
model.compile(loss="binary_crossentropy", optimizer="adagrad", metrics=["accuracy"])
model.fit(X_train, y_train, validation_data=(X_test, y_test),
epochs=200, batch_size=32, callbacks=[earlystopping])
model.save("penny.h5")
观察其输出,你应该很容易看到准确率在几次迭代后超过 90%。
你的任务
运行上述代码并创建一个训练好的模型penny.h5,你将在下一课中使用它。
你可以修改模型设计,看看是否能提高准确率。你可以尝试的一些想法包括:使用不同数量的 Conv2D-MaxPooling 层、不同的层大小(例如 16-32-64-128),或使用 ReLU 以外的激活函数。
在下一课中,你将把你在 Keras 中创建的模型转换为 OpenCV 使用的格式。
第 07 课:在 OpenCV 中使用 DNN 模块
假设你在上一课中已经构建了一个卷积神经网络,你现在可以将其与 OpenCV 一起使用。如果你首先将其转换为 ONNX 格式,OpenCV 会更容易使用你的模型。为此,你需要 Python 的 tf2onnx 模块。安装完成后,你可以使用以下命令转换模型:
python -m tf2onnx.convert --keras penny.h5 --output penny.onnx
由于你将 Keras 模型保存为penny.h5,此命令将创建文件penny.onnx。
使用 ONNX 模型文件,你现在可以使用 OpenCV 的cv2.dnn模块。使用方法如下:
import cv2
net = cv2.dnn.readNetFromONNX("penny.onnx")
net.setInput(blob)
output = float(net.forward())
也就是说,你使用 OpenCV 创建一个神经网络对象,分配输入,并使用forward()运行模型以获取输出,这个输出是 0 到 1 之间的浮点值,具体取决于你设计模型的方式。作为神经网络的惯例,即使你只提供一个输入样本,输入也是批处理的。因此,在将图像发送到神经网络之前,你应该为图像添加一个批处理维度。
现在让我们看看如何实现硬币计数的目标。你可以从修改第 05 课的代码开始,如下所示:
import math
import cv2
import numpy as np
import matplotlib.pyplot as plt
image_path = "coins-1.jpg"
model_path = "penny.onnx"
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT,
dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
plt.figure(2)
N = len(circles[0])
rows = math.ceil(N / 4)
net = cv2.dnn.readNetFromONNX("penny2.onnx")
for i, c in enumerate(np.uint16(np.around(circles[0]))):
x, y, r = c
coin = img[y-r:y+r, x-r:x+r]
coin = cv2.resize(coin, (256,256))
blob = coin[np.newaxis, ...]
net.setInput(blob)
score = float(net.forward())
plt.subplot(rows, 4, i+1)
plt.imshow(cv2.cvtColor(coin, cv2.COLOR_BGR2RGB))
plt.title(f"{score:.2f}")
plt.axis('off')
plt.show()
你使用了卷积神经网络来读取 sigmoidal 输出,而不是使用 ORB 并计算匹配的好关键点。输出如下:
神经网络检测到的硬币及其匹配得分
你可以看到这个模型的效果相当好。所有硬币都被识别,得分接近 1。负样本效果不如预期(可能是因为我们提供的负样本不够)。让我们使用 0.9 作为得分截断值,并重写程序以进行计数:
import math
import cv2
import numpy as np
import matplotlib.pyplot as plt
image_path = "coins-1.jpg"
model_path = "penny.onnx"
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 1)
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, dp=1, minDist=100,
param1=80, param2=60, minRadius=90, maxRadius=150)
positive = 0
negative = 0
net = cv2.dnn.readNetFromONNX(model_path)
for i, c in enumerate(np.uint16(np.around(circles[0]))):
x, y, r = c
coin = img[y-r:y+r, x-r:x+r]
coin = cv2.resize(coin, (256,256))
blob = coin[np.newaxis, ...]
net.setInput(blob)
score = float(net.forward())
if score >= 0.9:
positive += 1
else:
negative += 1
print(f"{positive} out of {positive+negative} coins identified are pennies")
你的任务
运行上述代码进行测试。你可以尝试使用另一张图片,如下所示:
你能修改上面的代码,通过不断从你的摄像头读取图像来报告计数吗?
这是最后一节课。
完结!(看看你走了多远)
你完成了,干得好!
花点时间回顾一下你走了多远。
-
你发现了 OpenCV 作为一个机器学习库,除了其图像处理能力之外。
-
你利用 OpenCV 提取图像特征作为数值向量,这是任何机器学习算法的基础。
-
你构建了一个神经网络模型,并将其转换为与 OpenCV 兼容。
-
最后,你构建了一个硬币计数程序。虽然不完美,但它展示了如何将 OpenCV 与机器学习结合起来。
不要轻视这一点,你在短时间内取得了长足的进步。这只是你计算机视觉和机器学习之旅的开始。继续练习和提升你的技能。
总结
你在这个迷你课程中的表现如何?
你喜欢这个速成课程吗?
你有任何问题吗?有没有什么难点?
告诉我。请在下方留言。
使用 OpenCV 进行图像分割的常规贝叶斯分类器
原文:
machinelearningmastery.com/normal-bayes-classifier-for-image-segmentation-using-opencv/
朴素贝叶斯算法是一种简单而强大的监督学习技术。其高斯变体已在 OpenCV 库中实现。
在本教程中,你将学习如何应用 OpenCV 的常规贝叶斯算法,首先是在一个自定义的二维数据集上,然后用于图像分割。
完成本教程后,你将知道:
-
应用贝叶斯定理到机器学习中的几个最重要的要点。
-
如何在 OpenCV 中对自定义数据集使用常规贝叶斯算法。
-
如何使用常规贝叶斯算法在 OpenCV 中进行图像分割。
**通过我的书 Machine Learning in OpenCV 启动你的项目。它提供了 自学教程 和 实用代码。
让我们开始吧。
使用 OpenCV 进行图像分割的常规贝叶斯分类器
图片由 Fabian Irsara 提供,部分权利保留。
教程概述
本教程分为三部分,它们是:
-
机器学习中应用贝叶斯定理的提醒
-
发现 OpenCV 中的贝叶斯分类
-
使用常规贝叶斯分类器进行图像分割
机器学习中应用贝叶斯定理的提醒
这个教程由 Jason Brownlee 编写,深入解释了机器学习中的贝叶斯定理,所以让我们先从复习他教程中的一些最重要的要点开始:
贝叶斯定理在机器学习中非常有用,因为它提供了一个统计模型来形成数据与假设之间的关系。
*** 贝叶斯定理表示为 ,该定理指出给定假设为真的概率(记作 ,也称为后验概率)可以通过以下方式计算:
*** * 观察到数据的概率给定假设(记作 ,也称为似然)。
*** **假设为真而独立于数据的概率(记作 $P(h)$,也称为*先验概率*)。***** **观察到数据独立于假设的概率(记作 $P(D)$,也称为*证据*)。******
******* 贝叶斯定理假设构成输入数据 的每个变量(或特征)都依赖于所有其他变量(或特征)。
*** 在数据分类的背景下,可以将贝叶斯定理应用于计算给定数据样本的类别标签的条件概率问题:,其中类别标签现在替代了假设。证据 是一个常数,可以省略。
*** 如上所述问题的公式化,估计 的可能性可能会很困难,因为这要求数据样本的数量足够大,以包含每个类别的所有可能变量(或特征)组合。这种情况很少见,尤其是对于具有许多变量的高维数据。
*** 上述公式可以简化为所谓的朴素贝叶斯,其中每个输入变量被单独处理:
*** 朴素贝叶斯估计将公式从依赖条件概率模型更改为独立条件概率模型,其中输入变量(或特征)现在被假定为独立。这一假设在现实世界的数据中很少成立,因此得名朴素。
**## 在 OpenCV 中发现贝叶斯分类
假设我们处理的输入数据是连续的。在这种情况下,可以使用连续概率分布来建模,例如高斯(或正态)分布,其中每个类别的数据由其均值和标准差建模。
OpenCV 中实现的贝叶斯分类器是普通贝叶斯分类器(也常被称为高斯朴素贝叶斯),它假设来自每个类别的输入特征是正态分布的。
这个简单的分类模型假设来自每个类别的特征向量是正态分布的(尽管不一定是独立分布的)。
– OpenCV,机器学习概述,2023。
要发现如何在 OpenCV 中使用普通贝叶斯分类器,让我们从在一个简单的二维数据集上测试它开始,就像我们在之前的教程中做过的那样。
想开始使用 OpenCV 进行机器学习吗?
现在立即参加我的免费电子邮件速成课程(包括示例代码)。
点击注册并获取课程的免费 PDF 电子书版本。
为此,让我们生成一个包含 100 个数据点的 dataset(由 n_samples 指定),这些数据点被均等地划分为 2 个高斯簇(由 centers 标识),标准差设为 1.5(由 cluster_std 指定)。我们还定义一个 random_state 值,以便能够复制结果:
Python
# Generating a dataset of 2D data points and their ground truth labels
x, y_true = make_blobs(n_samples=100, centers=2, cluster_std=1.5, random_state=15)
# Plotting the dataset
scatter(x[:, 0], x[:, 1], c=y_true)
show()
上述代码应生成以下数据点的图:
包含 2 个高斯簇的数据集的散点图
然后,我们将拆分数据集,将 80%的数据分配到训练集,其余 20%分配到测试集:
Python
# Split the data into training and testing sets
x_train, x_test, y_train, y_test = ms.train_test_split(x, y_true, test_size=0.2, random_state=10)
接下来,我们将创建标准贝叶斯分类器,并在将数据类型转换为 32 位浮点数后,对数据集进行训练和测试:
Python
# Create a new Normal Bayes Classifier
norm_bayes = ml.NormalBayesClassifier_create()
# Train the classifier on the training data
norm_bayes.train(x_train.astype(float32), ml.ROW_SAMPLE, y_train)
# Generate a prediction from the trained classifier
ret, y_pred, y_probs = norm_bayes.predictProb(x_test.astype(float32))
通过使用predictProb方法,我们将获得每个输入向量的预测类别(每个向量存储在输入到标准贝叶斯分类器的数组的每一行)和输出概率。
在上述代码中,预测的类别存储在y_pred中,而y_probs是一个与类别数(此情况下为两个)相同列数的数组,保存每个输入向量属于每个考虑类别的概率值。分类器返回的每个输入向量的输出概率值的总和应该是 1。然而,这不一定是情况,因为分类器返回的概率值没有通过证据 进行标准化,如前节所述,我们已从分母中移除了。
相反,报告的是一种可能性,这基本上是条件概率方程的分子,即 p(C) p(M | C)。分母 p(M)不需要计算。
– OpenCV 的机器学习,2017 年。
尽管无论值是否被标准化,通过识别具有最高概率值的类别,可以找到每个输入向量的类别预测。
目前为止的代码列表如下:
Python
from sklearn.datasets import make_blobs
from sklearn import model_selection as ms
from numpy import float32
from matplotlib.pyplot import scatter, show
from cv2 import ml
# Generate a dataset of 2D data points and their ground truth labels
x, y_true = make_blobs(n_samples=100, centers=2, cluster_std=1.5, random_state=15)
# Plot the dataset
scatter(x[:, 0], x[:, 1], c=y_true)
show()
# Split the data into training and testing sets
x_train, x_test, y_train, y_test = ms.train_test_split(x, y_true, test_size=0.2, random_state=10)
# Create a new Normal Bayes Classifier
norm_bayes = ml.NormalBayesClassifier_create()
# Train the classifier on the training data
norm_bayes.train(x_train.astype(float32), ml.ROW_SAMPLE, y_train)
# Generate a prediction from the trained classifier
ret, y_pred, y_probs = norm_bayes.predictProb(x_test.astype(float32))
# Plot the class predictions
scatter(x_test[:, 0], x_test[:, 1], c=y_pred)
show()
我们可以看到,在这个简单数据集上训练的标准贝叶斯分类器生成的类别预测是正确的:
对测试样本生成的预测的散点图
使用标准贝叶斯分类器的图像分割
贝叶斯分类器在许多应用中被广泛使用,其中包括皮肤分割,它将图像中的皮肤像素与非皮肤像素分开。
我们可以调整上述代码以对图像中的皮肤像素进行分割。为此,我们将使用皮肤分割数据集,该数据集包含 50,859 个皮肤样本和 194,198 个非皮肤样本,用于训练标准贝叶斯分类器。数据集以 BGR 顺序呈现像素值及其对应的类别标签。
在加载数据集后,我们将把 BGR 像素值转换为 HSV(表示色调、饱和度和值),然后使用色调值来训练普通贝叶斯分类器。色调在图像分割任务中通常优于 RGB,因为它代表了未修改的真实颜色,比 RGB 更不容易受到光照变化的影响。在 HSV 颜色模型中,色调值是径向排列的,范围从 0 到 360 度:
Python
from cv2 import ml,
from numpy import loadtxt, float32
from matplotlib.colors import rgb_to_hsv
# Load data from text file
data = loadtxt("Data/Skin_NonSkin.txt", dtype=int)
# Select the BGR values from the loaded data
BGR = data[:, :3]
# Convert to RGB by swapping the array columns
RGB = BGR.copy()
RGB[:, [2, 0]] = RGB[:, [0, 2]]
# Convert RGB values to HSV
HSV = rgb_to_hsv(RGB.reshape(RGB.shape[0], -1, 3) / 255)
HSV = HSV.reshape(RGB.shape[0], 3)
# Select only the hue values
hue = HSV[:, 0] * 360
# Select the labels from the loaded data
labels = data[:, -1]
# Create a new Normal Bayes Classifier
norm_bayes = ml.NormalBayesClassifier_create()
# Train the classifier on the hue values
norm_bayes.train(hue.astype(float32), ml.ROW_SAMPLE, labels)
注意 1:OpenCV 库提供了 cvtColor 方法来进行颜色空间转换,如 本教程 所示,但 cvtColor 方法要求源图像保持原始形状作为输入。而 Matplotlib 中的 rgb_to_hsv 方法则接受形状为 (…, 3) 的 NumPy 数组作为输入,其中数组值需要在 0 到 1 的范围内进行归一化。我们在这里使用后者,因为我们的训练数据由单独的像素组成,而不是以三通道图像的常见形式结构化的。
注意 2:普通贝叶斯分类器假设待建模的数据遵循高斯分布。虽然这不是一个严格的要求,但如果数据分布不同,分类器的性能可能会下降。我们可以通过绘制直方图来检查我们处理的数据的分布。例如,如果我们以皮肤像素的色调值为例,我们发现高斯曲线可以描述它们的分布:
Python
from numpy import histogram
from matplotlib.pyplot import bar, title, xlabel, ylabel, show
# Choose the skin-labelled hue values
skin = x[labels == 1]
# Compute their histogram
hist, bin_edges = histogram(skin, range=[0, 360], bins=360)
# Display the computed histogram
bar(bin_edges[:-1], hist, width=4)
xlabel('Hue')
ylabel('Frequency')
title('Histogram of the hue values of skin pixels')
show()
检查数据的分布
一旦普通贝叶斯分类器经过训练,我们可以在一张图像上进行测试(我们可以考虑 这张示例图像 进行测试):
Python
from cv2 import imread
from matplotlib.pyplot import show, imshow
# Load a test image
face_img = imread("Images/face.jpg")
# Reshape the image into a three-column array
face_BGR = face_img.reshape(-1, 3)
# Convert to RGB by swapping the array columns
face_RGB = face_BGR.copy()
face_RGB[:, [2, 0]] = face_RGB[:, [0, 2]]
# Convert from RGB to HSV
face_HSV = rgb_to_hsv(face_RGB.reshape(face_RGB.shape[0], -1, 3) / 255)
face_HSV = face_HSV.reshape(face_RGB.shape[0], 3)
# Select only the hue values
face_hue = face_HSV[:, 0] * 360
# Display the hue image
imshow(face_hue.reshape(face_img.shape[0], face_img.shape[1]))
show()
# Generate a prediction from the trained classifier
ret, labels_pred, output_probs = norm_bayes.predictProb(face_hue.astype(float32))
# Reshape array into the input image size and choose the skin-labelled pixels
skin_mask = labels_pred.reshape(face_img.shape[0], face_img.shape[1], 1) == 1
# Display the segmented image
imshow(skin_mask, cmap='gray')
show()
结果分割掩码显示了被标记为皮肤(类别标签为 1)的像素。
通过定性分析结果,我们可以看到大多数皮肤像素已被正确标记为皮肤。我们还可以看到一些头发丝(因此是非皮肤像素)被错误地标记为皮肤。如果我们查看它们的色调值,可能会发现这些值与皮肤区域的色调值非常相似,因此导致了错误标记。此外,我们还可以注意到使用色调值的有效性,这些值在面部区域的光照或阴影下仍相对恒定,与原始 RGB 图像中的表现一致:
原始图像(左);色调值(中);分割后的皮肤像素(右)
你能想到更多的测试方法来尝试普通贝叶斯分类器吗?
进一步阅读
本节提供了更多资源,如果你想深入了解这个主题。
书籍
-
OpenCV 机器学习, 2017.
-
Python 与 OpenCV 4 实战, 2019.
总结
在本教程中,你学习了如何应用 OpenCV 的正态贝叶斯算法,首先在自定义的二维数据集上,然后用于图像分割。
具体来说,你学到了:
-
应用贝叶斯定理到机器学习中的几个最重要的要点。
-
如何在 OpenCV 中使用正态贝叶斯算法处理自定义数据集。
-
如何在 OpenCV 中使用正态贝叶斯算法来进行图像分割。
你有任何问题吗?
在下方评论区留言你的问题,我会尽力回答。
OpenCV 中的图像特征提取:边缘和角点
在计算机视觉和图像处理的世界里,从图像中提取有意义的特征是非常重要的。这些特征作为各种下游任务的重要输入,如目标检测和分类。找出这些特征有多种方式。最简单的方法是计数像素。但在 OpenCV 中,有许多例程可以帮助你从图像中提取特征。在这篇文章中,你将看到 OpenCV 如何帮助发现一些高级特征。
完成本教程后,你将了解:
-
角点和边缘可以从图像中提取
-
在 OpenCV 中提取角点和边缘的常见算法有哪些
通过我的书 《OpenCV 中的机器学习》 来启动你的项目。它提供了自学教程和可运行代码。
OpenCV 中的图像特征提取:边缘和角点
图片由Michael Barth提供,版权所有。
概述
本文分为三部分;它们是:
-
理解图像特征提取
-
OpenCV 中的 Canny 边缘检测
-
OpenCV 中的 Harris 角点检测
先决条件
对于本教程,我们假设你已经熟悉:
理解图像特征提取
图像特征提取涉及到识别和表示图像中的独特结构。读取图像的像素当然是一种方法,但这属于低级特征。图像的高级特征可以是边缘、角点,甚至是更复杂的纹理和形状。
特征是图像的特征属性。通过这些独特的特征,你可以区分不同的图像。这是计算机视觉中的第一步。通过提取这些特征,你可以创建比单纯的像素更紧凑和有意义的表示。这有助于进一步的分析和处理。
在接下来的内容中,你将学习到两个基本但非常常见的特征提取算法。这两种算法都以 numpy 数组格式返回基于像素的分类。
OpenCV 中的 Canny 边缘检测
多年来,已经开发了许多图像特征提取算法。这些算法不是机器学习模型,而是更接近于确定性算法。这些算法各自针对特定的特征。
OpenCV 提供了一整套丰富的工具和函数用于图像特征提取。我们首先从 Canny 边缘检测开始。
在图像中寻找边缘可能是最简单的特征提取。其目标是识别哪个像素在边缘上。边缘定义为像素强度的梯度。换句话说,如果有突兀的颜色变化,则认为它是一个边缘。但其中还有更多细节,因此噪声被排除。
让我们考虑以下图像,并将其保存为 image.jpg 在本地目录中:
一个寻找和说明边缘的示例如下:
Python
import cv2
import numpy as np
# Load the image
img = cv2.imread('image.jpg')
# Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Detect edges using Canny method
edges = cv2.Canny(gray, 150, 300)
# Display the image with corners
img[edges == 255] = (255,0,0)
cv2.imshow('Canny Edges', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上面,图像被转换为灰度图像,然后调用了 cv2.Canny() 函数。许多特征提取算法需要灰度图像,因为它们通常设计为在单一颜色通道上工作。
cv2.Canny() 函数的参数需要两个数值,分别用于最小和最大阈值。这些阈值用于滞后阈值处理以将像素合并成边缘。最大阈值越高,结果中仅保留更强的边缘。然而,最小阈值越高,你将会看到更多的“断裂边缘”。
此函数返回一个与图像像素维度匹配的 numpy 数组,其值要么为 0(不在边缘上),要么为 255(在边缘上)。上面的代码将这些像素着色为蓝色。结果如下:
Canny 边缘检测的结果
原始照片由 Gleren Meneghin 提供,部分版权保留。
你应该能看到上面的蓝色线条标记了门和窗户,并且勾勒出了每一块砖。你可以调整这两个阈值以查看不同的结果。
OpenCV 中的 Harris 角点检测
Harris 角点检测是一种用于识别强度显著变化的方法,这些变化通常对应于图像中物体的角点。OpenCV 提供了该技术的简单高效实现,使我们能够检测角点,这些角点作为图像分析和匹配的显著特征。
从图像中提取角点可以分为三个步骤:
-
将图像转换为灰度图像,因为 Harris 角点检测算法仅在单一颜色通道上工作
-
运行
cv2.cornerHarris(image, blockSize, ksize, k)并获取每个像素的分数 -
通过比较分数与图像最大值,确定哪个像素在角落处
cornerHarris() 函数的参数包括邻域大小 blockSize 和核大小 ksize。这两个值都是小的正整数,但后者必须是奇数。最后一个参数 k 是一个正浮点值,用于控制角点检测的敏感度。该值过大可能会导致算法将角点误认为边缘。你可能需要对其值进行实验。
一个示例代码,运行 Harris 角点检测在上述相同图像上:
import cv2
import numpy as np
# Load the image
img = cv2.imread('image.jpg')
# Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Detect corners using the Harris method
dst = cv2.cornerHarris(gray, 3, 5, 0.1)
# Create a boolean bitmap of corner positions
corners = dst > 0.05 * dst.max()
# Find the coordinates from the boolean bitmap
coord = np.argwhere(corners)
# Draw circles on the coordinates to mark the corners
for y, x in coord:
cv2.circle(img, (x,y), 3, (0,0,255), -1)
# Display the image with corners
cv2.imshow('Harris Corners', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
生成的图像如下:
Harris 角点检测的结果
原始照片由 Gleren Meneghin提供,保留部分版权。
红点是通过 cv2.circle() 函数在上面的 for 循环中绘制的。它们仅用于说明。关键思想是,算法为图像的每个像素提供一个分数,以表示该像素被认为是角点、边缘还是“平坦”的(即,两者都不是)。你需要通过将分数与整个图像中的最大值进行比较来控制结论的敏感度,具体见
corners = dst > 0.05 * dst.max()
结果是一个布尔型 numpy 数组 corners,它随后通过 np.argwhere() 函数被转换为坐标数组。
从上面的图像可以看出,Harris 角点检测并不完美,但如果角点足够明显,它是可以被检测到的。
想要开始使用 OpenCV 进行机器学习?
现在就报名参加我的免费电子邮件速成课程(包含示例代码)。
点击注册,还可获得课程的免费 PDF 电子书版本。
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解,可以参考。
书籍
- 掌握 OpenCV 4 与 Python,2019。
网站
-
OpenCV,
opencv.org/ -
OpenCV 特征检测与描述,
docs.opencv.org/4.x/db/d27/tutorial_py_table_of_contents_feature2d.html -
OpenCV Canny 边缘检测,
docs.opencv.org/4.x/da/d22/tutorial_py_canny.html
总结
在本教程中,你学习了如何在图像上应用 OpenCV 的 Canny 边缘检测和 Harris 角点检测算法。
具体来说,你学到了:
-
这些是基于像素的算法,它们将每个像素分类为边缘或非边缘、角点或非角点。
-
如何使用 OpenCV 函数将这些算法应用于图像并解读结果
如果你有任何问题,请在下面的评论中提出。
如何在 OpenCV 中使用 HOG 训练一个对象检测引擎
在上一篇文章中,你看到 OpenCV 可以使用称为方向梯度直方图(HOG)的技术从图像中提取特征。简而言之,这就是将图像的一个“补丁”转换为数值向量。如果设置得当,这个向量可以识别该补丁中的关键特征。虽然你可以使用 HOG 比较图像的相似性,但一个实际的应用是将其作为分类器的输入,从而在图像中检测对象。
在这篇文章中,你将学习如何使用 HOG 创建一个分类器。具体来说,你将学习:
-
如何为分类器训练准备输入数据
-
如何运行训练并保存模型以便在 OpenCV 中重复使用
用我的书《OpenCV 中的机器学习》来启动你的项目。它提供了自学教程和有效代码。
如何在 OpenCV 中使用 HOG 训练一个对象检测引擎
图片由Neil Thomas拍摄。保留某些权利。
概述
本文分为三个部分;它们是:
-
HOG 用于分类
-
准备数据
-
使用 HOG 特征训练分类器
HOG 用于分类
在上一篇文章中,你了解到 HOG 是一种从图像区域生成特征向量的技术。很可能,这个区域中的对象决定了特征向量的大部分。
对象检测是找出较大图像中某个特定对象的位置。通常,目标是找到一个矩形边界框,使得对象紧紧地位于该框内。
使用 HOG 进行对象检测并不困难:你只需从图像中随机绘制多个边界框。然后,你可以使用 HOG 查找边界框的特征向量,并将其与目标对象预期的特征向量进行比较。
然而,你需要注意多个细节:首先,HOG 有多个参数,包括窗口、块和单元的大小。这也决定了边界框的大小和长宽比。如果你的边界框有不同的大小,可能需要调整大小。其次,HOG 对旋转敏感。因此,如果图像倾斜,从 HOG 获得的特征向量可能对对象检测没有用。
最后,即使所有边界框都识别了相同的对象,每个边界框生成的 HOG 向量也会有所不同。你需要一种聪明的方法来判断对象是否被检测到,这通常是一个机器学习模型。
有几种模型可以用来比较候选边界框的 HOG。在这篇文章中,你将使用支持向量机 (SVM)。OpenCV 有一个内置的人员检测器,它也是作为 SVM 实现的。
准备数据
让我们考虑猫检测的任务。对于一张有猫的图像,你需要在猫的脸周围绘制一个方框。你将使用 OpenCV 构建一个 SVM 来完成这个任务。
和所有机器学习项目一样,第一步是获取数据集。你可以从 Oxford-IIIT Pet Dataset 获得包含猫图像的数据集,地址为:
这是一个 800MB 的数据集,在计算机视觉数据集的标准下属于小型数据集。图像以 Pascal VOC 格式进行标注。简而言之,每张图像都有一个对应的 XML 文件,格式如下:
XHTML
<?xml version="1.0"?>
<annotation>
<folder>OXIIIT</folder>
<filename>Abyssinian_100.jpg</filename>
<source>
<database>OXFORD-IIIT Pet Dataset</database>
<annotation>OXIIIT</annotation>
<image>flickr</image>
</source>
<size>
<width>394</width>
<height>500</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>cat</name>
<pose>Frontal</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<bndbox>
<xmin>151</xmin>
<ymin>71</ymin>
<xmax>335</xmax>
<ymax>267</ymax>
</bndbox>
<difficult>0</difficult>
</object>
</annotation>
XML 文件告诉你它所指的图像文件,以及它包含的对象,边界框在 <bndbox></bndbox> 标签之间。
有一些 Python 库可以用来处理 Pascal VOC XML 文件。但对于像这样的简单情况,你可以直接使用 Python 的内置 XML 解析器。下面是一个函数,给定 XML 文件名,它读取内容并返回一个 Python 字典,告诉你所有包含的对象以及相应的边界框:
import xml.etree.ElementTree as ET
def read_voc_xml(xmlfile: str) -> dict:
root = ET.parse(xmlfile).getroot()
boxes = {"filename": root.find("filename").text,
"objects": []}
for box in root.iter('object'):
bb = box.find('bndbox')
obj = {
"name": box.find('name').text,
"xmin": int(bb.find("xmin").text),
"ymin": int(bb.find("ymin").text),
"xmax": int(bb.find("xmax").text),
"ymax": int(bb.find("ymax").text),
}
boxes["objects"].append(obj)
return boxes
上述函数返回的字典示例如下:
{'filename': 'yorkshire_terrier_160.jpg',
'objects': [{'name': 'dog', 'xmax': 290, 'xmin': 97, 'ymax': 245, 'ymin': 18}]}
在这个数据集中,每张图像中只有一个对象(猫或狗)。边界框以像素坐标指定。使用你上面获得的文件名,你可以使用 OpenCV 读取图像。图像是一个 numpy 数组,因此你可以使用数组切片提取部分内容。如下所示:
img = cv2.imread(path)
portion = img[ymin:ymax, xmin:xmax]
让我们专注于训练分类器的目标。首先,你需要设计 HOG 计算的参数。我们考虑一个长度适中的向量,即,
-
窗口大小: (64,64)
-
块大小: (32, 32)
-
块步幅: (16, 16)
-
单元格大小: (16, 16)
-
直方图箱数: 9
换句话说,你将考虑图像上一个 64×64 像素的方形窗口,单元格大小为 16×16 像素。每个块有 2×2 个单元格。
因为窗口是方形的,你不想改变图像的宽高比,所以你需要将数据集中的边界框调整为方形大小。之后,你应该裁剪调整后的边界框,将其调整为 64×64 像素,并将其保存为正样本。你还需要负样本用于训练。既然你想做一个猫检测器,你可以使用狗的图像作为负样本。你希望负样本覆盖图像的背景。你可以简单地随机裁剪这些图像中的一个方形区域,并调整为 64×64 像素作为负样本,而不是遵循边界框。
下面是如何从数据集中收集 1000 个正样本和负样本的代码。假设你已经下载了数据集并将两个 tar 文件解压到 oxford-iiit-pet 目录中:
def make_square(xmin, xmax, ymin, ymax):
"""Shrink the bounding box to square shape"""
xcenter = (xmax + xmin) // 2
ycenter = (ymax + ymin) // 2
halfdim = min(xmax-xmin, ymax-ymin) // 2
xmin, xmax = xcenter-halfdim, xcenter+halfdim
ymin, ymax = ycenter-halfdim, ycenter+halfdim
return xmin, xmax, ymin, ymax
# Define HOG parameters
winSize = (64, 64)
blockSize = (32, 32)
blockStride = (16, 16)
cellSize = (16, 16)
nbins = 9
num_samples = 1000
base_path = pathlib.Path("oxford-iiit-pet")
img_src = base_path / "images"
ann_src = base_path / "annotations" / "xmls"
# collect samples by cropping the images from dataset
positive = []
negative = []
# collect positive samples
for xmlfile in ann_src.glob("*.xml"):
# load xml
ann = read_voc_xml(str(xmlfile))
# use only cat photos
if ann["objects"][0]["name"] != "cat":
continue
# adjust the bounding box to square
box = ann["objects"][0]
xmin, xmax, ymin, ymax = box["xmin"], box["xmax"], box["ymin"], box["ymax"]
xmin, xmax, ymin, ymax = make_square(xmin, xmax, ymin, ymax)
# crop a positive sample
img = cv2.imread(str(img_src / ann["filename"]))
sample = img[ymin:ymax, xmin:xmax]
sample = cv2.resize(sample, winSize)
positive.append(sample)
if len(positive) > num_samples:
break
# collect negative samples
for xmlfile in ann_src.glob("*.xml"):
# load xml
ann = read_voc_xml(str(xmlfile))
# use only dog photos
if ann["objects"][0]["name"] == "cat":
continue
# random bounding box: at least the target size to avoid scaling up
height, width = img.shape[:2]
boxsize = random.randint(winSize[0], min(height, width))
x = random.randint(0, width-boxsize)
y = random.randint(0, height-boxsize)
sample = img[y:y+boxsize, x:x+boxsize]
sample = cv2.resize(sample, winSize)
negative.append(sample)
if len(negative) > num_samples:
break
使用 HOG 特征训练分类器
OpenCV 在 cv2.ml 中提供了一个 SVM 模块,它的工作方式类似于 scikit-learn。实质上,你只需做以下操作来进行训练:
svm = cv2.ml.SVM_create()
svm.setType(cv2.ml.SVM_C_SVC)
svm.setKernel(cv2.ml.SVM_RBF)
svm.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 10000, 1e-8))
svm.train(data, cv2.ml.ROW_SAMPLE, labels)
svm.save('svm_model.yml')
你首先用 cv2.ml.SVM_create() 创建一个 SVM 对象。然后你配置 SVM,因为它有许多变体。在上面的代码中,你使用了 SVM_C_SVC 作为类型,因为它是一个 C-Support Vector Classifier(允许不完美分离的 SVM 分类器)。你使用了径向基函数核(SVM_RBF),因为它通常效果较好。如果任务比较简单,你也可以选择使用更简单的线性核(SVM_LINEAR)。SVM 还有许多其他参数。例如,如果使用 RBF 核,你可以使用 svm.setGamma() 设置 gamma 值,既然你使用了 C-SVC,你可以使用 svm.setC() 设置参数 C 的值。在上面的代码中,你将所有参数留给了 OpenCV 的默认设置。
SVM 的训练需要一个终止条件。在上面的代码中,你使用了 svm.setTermCritera() 来使训练在 10000 次迭代或损失函数低于 时停止,以较早出现者为准。所有操作完成后,你只需将数据和标签传递给训练程序。
训练数据以 numpy 数组的形式呈现。你将其设置为数组中的每一行代表一个样本。所需的标签仅是整数标签,0 或 1。由于你正在用 SVM 训练一个 HOG 分类器,你需要将样本转换为 HOG 特征。使用 OpenCV 进行这一操作并不困难。以下是如何创建 numpy 数组的步骤,基于你收集的正负样本:
images = positive + negative
labels = ([1] * len(positive)) + ([0] * len(negative))
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins)
data = []
for img in images:
features = hog.compute(img)
data.append(features.flatten())
data = np.array(data, dtype=np.float32)
labels = np.array(labels, dtype=np.int32)
从数据收集到训练的完整代码如下:
import pathlib
import random
import xml.etree.ElementTree as ET
import cv2
import numpy as np
def read_voc_xml(xmlfile):
"""read the Pascal VOC XML"""
root = ET.parse(xmlfile).getroot()
boxes = {"filename": root.find("filename").text,
"objects": []}
for box in root.iter('object'):
bb = box.find('bndbox')
obj = {
"name": box.find('name').text,
"xmin": int(bb.find("xmin").text),
"ymin": int(bb.find("ymin").text),
"xmax": int(bb.find("xmax").text),
"ymax": int(bb.find("ymax").text),
}
boxes["objects"].append(obj)
return boxes
def make_square(xmin, xmax, ymin, ymax):
"""Shrink the bounding box to square shape"""
xcenter = (xmax + xmin) // 2
ycenter = (ymax + ymin) // 2
halfdim = min(xmax-xmin, ymax-ymin) // 2
xmin, xmax = xcenter-halfdim, xcenter+halfdim
ymin, ymax = ycenter-halfdim, ycenter+halfdim
return xmin, xmax, ymin, ymax
# Define HOG parameters
winSize = (64, 64)
blockSize = (32, 32)
blockStride = (16, 16)
cellSize = (16, 16)
nbins = 9
num_samples = 1000
# Load your dataset and corresponding bounding box annotations
base_path = pathlib.Path("oxford-iiit-pet")
img_src = base_path / "images"
ann_src = base_path / "annotations" / "xmls"
# collect samples by cropping the images from dataset
positive = []
negative = []
# collect positive samples
for xmlfile in ann_src.glob("*.xml"):
# load xml
ann = read_voc_xml(str(xmlfile))
# use only cat photos
if ann["objects"][0]["name"] != "cat":
continue
# adjust the bounding box to square
box = ann["objects"][0]
xmin, xmax, ymin, ymax = box["xmin"], box["xmax"], box["ymin"], box["ymax"]
xmin, xmax, ymin, ymax = make_square(xmin, xmax, ymin, ymax)
# crop a positive sample
img = cv2.imread(str(img_src / ann["filename"]))
sample = img[ymin:ymax, xmin:xmax]
sample = cv2.resize(sample, winSize)
positive.append(sample)
if len(positive) > num_samples:
break
# collect negative samples
for xmlfile in ann_src.glob("*.xml"):
# load xml
ann = read_voc_xml(str(xmlfile))
# use only dog photos
if ann["objects"][0]["name"] == "cat":
continue
# random bounding box: at least the target size to avoid scaling up
height, width = img.shape[:2]
boxsize = random.randint(winSize[0], min(height, width))
x = random.randint(0, width-boxsize)
y = random.randint(0, height-boxsize)
sample = img[y:y+boxsize, x:x+boxsize]
assert tuple(sample.shape[:2]) == (boxsize, boxsize)
sample = cv2.resize(sample, winSize)
negative.append(sample)
if len(negative) > num_samples:
break
images = positive + negative
labels = ([1] * len(positive)) + ([0] * len(negative))
# Create the HOG descriptor and the HOG from each image
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins)
data = []
for img in images:
features = hog.compute(img)
data.append(features.flatten())
# Convert data and labels to numpy arrays
data = np.array(data, dtype=np.float32)
labels = np.array(labels, dtype=np.int32)
# Train the SVM
svm = cv2.ml.SVM_create()
svm.setType(cv2.ml.SVM_C_SVC)
svm.setKernel(cv2.ml.SVM_RBF)
svm.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
100000,
1e-8))
svm.train(data, cv2.ml.ROW_SAMPLE, labels)
# Save the SVM model
svm.save('svm_model.yml')
print(svm.getSupportVectors())
上面代码的最后一行是打印训练好的 SVM 的支持向量。这个操作是可选的,因为你已经将模型保存到 svm_model.yml 文件中。
下面是如何使用训练好的模型:首先,你创建一个 HOG 对象,一个 SVM 对象,然后将 SVM 对象分配给 HOG 作为检测器。当你有一张图像时,你使用 HOG 的 detectMultiScale() 方法来寻找检测到的对象的位置。这个函数会多次重新缩放图像,使得你为 HOG 设置的窗口大小不需要完全匹配对象的大小。这非常有用,因为你无法知道图像中目标对象的大小。由于 SVM 是在特定的 HOG 特征配置上训练的,你必须使用与训练中相同的参数(窗口、块、单元和直方图)来创建 HOG 对象。检测函数的输出将是多个边界框,但你可以根据评分简单地选择最佳匹配的框。
代码如下:
winSize = (64, 64)
blockSize = (32, 32)
blockStride = (16, 16)
cellSize = (16, 16)
nbins = 9
svm = cv2.ml.SVM_load('svm_model.yml')
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins)
hog.setSVMDetector(svm.getSupportVectors()[0])
locations, scores = hog.detectMultiScale(img)
x, y, w, h = locations[np.argmax(scores.flatten())]
cv2.rectangle(img, (x, y), (x + w, y + h), (255,0,0), 5)
假设训练好的 SVM 已经保存到文件 svm_model.yml 中。你可以直接使用保存的文件创建 SVM 对象。hog.detectMultiScale() 的输出 scores 是一个 N×1 的 numpy 数组。因此,你应该将其展平成一个向量并找到最大值。数组 locations 中的对应边界框是匹配度最高的。该函数返回的边界框是以左上角的坐标以及宽度和高度表示的。上述代码中的最后一行是直接在图像上标注这样的框。
你确实可以在相同的数据集上运行此代码。完整代码如下,其中原始边界框和 SVM 检测到的边界框都绘制在图像上并通过 OpenCV 显示:
import pathlib
import xml.etree.ElementTree as ET
import cv2
import numpy as np
def read_voc_xml(xmlfile: str) -> dict:
"""read the Pascal VOC XML and return (filename, object name, bounding box)
where bounding box is a vector of (xmin, ymin, xmax, ymax). The pixel
coordinates are 1-based.
"""
root = ET.parse(xmlfile).getroot()
boxes = {"filename": root.find("filename").text,
"objects": []
}
for box in root.iter('object'):
bb = box.find('bndbox')
obj = {
"name": box.find('name').text,
"xmin": int(bb.find("xmin").text),
"ymin": int(bb.find("ymin").text),
"xmax": int(bb.find("xmax").text),
"ymax": int(bb.find("ymax").text),
}
boxes["objects"].append(obj)
return boxes
# load the SVM
winSize = (64, 64)
blockSize = (32, 32)
blockStride = (16, 16)
cellSize = (16, 16)
nbins = 9
svm = cv2.ml.SVM_load('svm_model.yml')
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins)
hog.setSVMDetector(svm.getSupportVectors()[0])
base_path = pathlib.Path("oxford-iiit-pet")
img_src = base_path / "images"
ann_src = base_path / "annotations" / "xmls"
for xmlfile in ann_src.glob("*.xml"):
# load xml
ann = read_voc_xml(str(xmlfile))
# annotate
img = cv2.imread(str(img_src / ann["filename"]))
bbox = ann["objects"][0]
start_point = (bbox["xmin"], bbox["ymin"])
end_point = (bbox["xmax"], bbox["ymax"])
annotated_img = cv2.rectangle(img, start_point, end_point, (0,0,255), 2)
# detect and draw
locations, scores = hog.detectMultiScale(img)
x, y, w, h = locations[np.argmax(scores.flatten())]
cv2.rectangle(img, (x, y), (x + w, y + h), (255,0,0), 5)
cv2.imshow(f"{ann['filename']}: {ann['objects'][0]['name']}", annotated_img)
key = cv2.waitKey(0)
cv2.destroyAllWindows()
if key == ord('q'):
break
OpenCV 将从数据集中逐个显示带注释的图像。数据集中的边界框为红色,而 SVM 报告的边界框为蓝色。请注意,这是一个猫检测器,因此如果是狗的图像,理想情况下不应检测到任何东西。然而,带有 HOG 特征的 SVM 在这方面表现不是很好。
数据集中的边界框(红色)与训练模型的检测输出(蓝色)相比。
实际上,这个检测器的准确性不是很高。上面的例子中,检测到的边界框与猫的脸相差很大。尽管如此,这仍然不是一个糟糕的模型。你可以通过调整模型参数(如 C 和 gamma)以及提供更好的训练数据来提高 SVM 的质量。
想要开始使用 OpenCV 进行机器学习吗?
立即领取我的免费电子邮件速成课程(附带示例代码)。
点击注册并获取课程的免费 PDF 电子书版本。
进一步阅读
本节提供了更多有关此主题的资源,如果你想深入了解。
书籍
- 掌握 OpenCV 4 和 Python,2019 年。
网站
-
StackOverflow: OpenCV HOG 特征解释
-
OpenCV: 支持向量机简介
摘要
在这篇文章中,你学习了如何使用 OpenCV 库完全训练一个带有 HOG 特征的 SVM 进行物体检测。特别是,你学到了:
-
如何为训练准备数据,因为 SVM 只接受特征作为 numpy 数组
-
如何在 OpenCV 中保存和加载 SVM
-
如何将 SVM 对象附加到 OpenCV 中的 HOG 对象以进行多尺度检测