【精选优质专栏推荐】
- 《AI 技术前沿》 —— 紧跟 AI 最新趋势与应用
- 《网络安全新手快速入门(附漏洞挖掘案例)》 —— 零基础安全入门必看
- 《BurpSuite 入门教程(附实战图文)》 —— 渗透测试必备工具详解
- 《网安渗透工具使用教程(全)》 —— 一站式工具手册
- 《CTF 新手入门实战教程》 —— 从题目讲解到实战技巧
- 《前后端项目开发(新手必知必会)》 —— 实战驱动快速上手
每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

前言
支持向量机(Support Vector Machine, SVM)算法是 OpenCV 库中实现的最流行的监督式机器学习技术之一。
在本教程中,你将学习如何使用 OpenCV 的支持向量机算法来解决图像分类与检测问题。
完成本教程后,你将掌握:
-
支持向量机的若干重要特性。
-
如何将支持向量机应用于图像分类与检测问题。
本教程分为三部分:
- 回顾支持向量机的工作原理
- 将 SVM 算法应用于图像分类
- 使用 SVM 算法进行图像检测
回顾支持向量机的工作原理
SVM 通过计算一个决策边界来将数据点分成不同的类别,该边界最大化了与每个类别中最近数据点(称为支持向量)的间距。通过调整参数 C,可以放宽最大化间距的约束,该参数控制了最大化间距与减少训练数据误分类之间的权衡。
SVM 算法可以使用不同的核函数,这取决于输入数据是否线性可分。对于非线性可分的数据,可以使用非线性核函数将数据映射到更高维空间,使其在该空间中线性可分。这相当于在原始输入空间中找到一个非线性的决策边界。
将 SVM 算法应用于图像分类
在本任务中,我们将使用 OpenCV 的数字(digits)数据集,但所编写的代码同样可以用于其他数据集。
第一步是加载 OpenCV 的数字图像,将其分割成包含手写数字 0–9 的多个子图像,并创建相应的真实标签(ground truth labels),以便后续评估训练好的 SVM 分类器的准确率。在本例中,我们将 80% 的数据分配给训练集,其余 20% 分配给测试集:
# 加载数字图像
img, sub_imgs = split_images('Images/digits.png', 20)
# 从数字图像中获取训练集和测试集
digits_train_imgs, digits_train_labels, digits_test_imgs, digits_test_labels = split_data(20, sub_imgs, 0.8)
接下来,我们在 OpenCV 中创建一个使用 RBF 核的 SVM。与前一教程类似,我们需要设置多个与 SVM 类型及核函数相关的参数。同时,还需要定义终止条件,以便在 SVM 优化迭代过程中达到指定精度后停止:
# 创建一个新的 SVM
svm_digits = ml.SVM_create()
# 将 SVM 的核函数设置为 RBF
svm_digits.setKernel(ml.SVM_RBF)
svm_digits.setType(ml.SVM_C_SVC)
svm_digits.setGamma(0.5)
svm_digits.setC(12)
svm_digits.setTermCriteria((TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 100, 1e-6))
在训练和测试 SVM 之前,我们不会直接使用原始图像数据,而是先将每张图像转换为其 HOG 描述符。
如前文所述,HOG 技术旨在通过利用图像的局部形状与外观,获得一种更紧凑的表示。使用 HOG 描述符进行分类器训练,能够在区分类别时提升判别能力,同时降低计算成本:
# 将图像数据转换为 HOG 特征描述符
digits_train_hog = hog_descriptors(digits_train_imgs)
digits_test_hog = hog_descriptors(digits_test_imgs)
最后,我们使用 HOG 描述符训练 SVM,并对测试数据进行标签预测,然后计算分类器的准确率:
# 预测测试数据的标签
_, digits_test_pred = svm_digits.predict(digits_test_hog.astype(float32))
# 计算并输出模型的准确率
accuracy_digits = (sum(digits_test_pred.astype(int) == digits_test_labels) / digits_test_labels.size) * 100
print('Accuracy:', accuracy_digits[0], '%')
输出:
Accuracy: 97.1 %
在本例中,参数 C 和 gamma 的取值是经验设定的。然而,建议使用如网格搜索(grid search)算法之类的调参技术,以探索更优的超参数组合,从而进一步提升分类器的准确率。
完整代码如下:
from cv2 import ml, TERM_CRITERIA_MAX_ITER, TERM_CRITERIA_EPS
from numpy import float32
from digits_dataset import split_images, split_data
from feature_extraction import hog_descriptors
# 加载数字图像
img, sub_imgs = split_images('Images/digits.png', 20)
# 从数字图像中获取训练集和测试集
digits_train_imgs, digits_train_labels, digits_test_imgs, digits_test_labels = split_data(20, sub_imgs, 0.8)
# 创建一个新的 SVM
svm_digits = ml.SVM_create()
# 将 SVM 核设置为 RBF
svm_digits.setKernel(ml.SVM_RBF)
svm_digits.setType(ml.SVM_C_SVC)
svm_digits.setGamma(0.5)
svm_digits.setC(12)
svm_digits.setTermCriteria((TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 100, 1e-6))
# 将图像数据转换为 HOG 特征描述符
digits_train_hog = hog_descriptors(digits_train_imgs)
digits_test_hog = hog_descriptors(digits_test_imgs)
# 使用训练数据集训练 SVM
svm_digits.train(digits_train_hog.astype(float32), ml.ROW_SAMPLE, digits_train_labels)
# 预测测试数据的标签
_, digits_test_pred = svm_digits.predict(digits_test_hog.astype(float32))
# 计算并输出准确率
accuracy_digits = (sum(digits_test_pred.astype(int) == digits_test_labels) / digits_test_labels.size) * 100
print('Accuracy:', accuracy_digits[0], '%')
使用 SVM 算法进行图像检测
我们可以将上述的图像分类思想扩展到图像检测。图像检测是指在图像中识别并定位感兴趣的目标。
可以通过在较大图像(以下简称“测试图像”)的不同位置重复前面章节中的分类操作来实现。
在本示例中,我们将创建一个由 OpenCV 数字数据集中随机选取的子图像拼接而成的图像拼贴,然后尝试检测其中目标数字的出现位置。
首先创建测试图像。方法是从整个数据集中每隔一定间隔随机选择 25 个子图像,打乱顺序后将它们拼接为一张 100×100 像素的图像:
# 加载数字图像
img, sub_imgs = split_images('Images/digits.png', 20)
# 从数字图像中获取训练集和测试集
digits_train_imgs, _, digits_test_imgs, _ = split_data(20, sub_imgs, 0.8)
# 创建一个空列表用于存储随机数
rand_nums = []
# 设置随机数种子以确保可重复性
seed(10)
# 从测试集中随机选择25个数字
for i in range(0, digits_test_imgs.shape[0], int(digits_test_imgs.shape[0] / 25)):
# 生成一个随机整数
rand = randint(i, int(digits_test_imgs.shape[0] / 25) + i - 1)
# 将其添加到列表中
rand_nums.append(rand)
# 打乱生成的随机整数顺序
shuffle(rand_nums)
# 读取与随机整数对应的图像数据
rand_test_imgs = digits_test_imgs[rand_nums, :]
# 初始化一个数组来保存测试图像
test_img = zeros((100, 100), dtype=uint8)
# 初始化子图计数器
img_count = 0
# 遍历测试图像
for i in range(0, test_img.shape[0], 20):
for j in range(0, test_img.shape[1], 20):
# 使用选定的数字填充测试图像
test_img[i:i + 20, j:j + 20] = rand_test_imgs[img_count].reshape(20, 20)
# 递增子图计数器
img_count += 1
# 显示测试图像
imshow(test_img, cmap='gray')
show()
生成的测试图像如下所示:

接下来,我们将像上一节一样训练一个新创建的 SVM。不过,由于现在处理的是检测问题,真实标签(ground truth labels)不再表示图像中的数字,而是区分训练集中正样本与负样本。
例如,假设我们希望检测测试图像中出现的两个数字 0。那么,在训练集中包含数字 0 的图像被视为正样本,并标记为类别标签 1;其余数字的图像被视为负样本,标记为类别标签 0。
生成标签后,我们即可创建并训练 SVM 模型:
# 为正样本和负样本生成标签
digits_train_labels = ones((digits_train_imgs.shape[0], 1), dtype=int)
digits_train_labels[int(digits_train_labels.shape[0] / 10):digits_train_labels.shape[0], :] = 0
# 创建一个新的 SVM
svm_digits = ml.SVM_create()
# 将 SVM 核函数设置为 RBF
svm_digits.setKernel(ml.SVM_RBF)
svm_digits.setType(ml.SVM_C_SVC)
svm_digits.setGamma(0.5)
svm_digits.setC(12)
svm_digits.setTermCriteria((TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 100, 1e-6))
# 将训练图像转换为 HOG 特征描述符
digits_train_hog = hog_descriptors(digits_train_imgs)
# 使用训练数据集训练 SVM
svm_digits.train(digits_train_hog, ml.ROW_SAMPLE, digits_train_labels)
我们将在前面的代码基础上增加最后一段代码,执行以下操作:
-
以预设步幅(stride)遍历测试图像。
-
在每次迭代中,从测试图像中裁剪出与数字子图像大小相同的图像块(20×20 像素)。
-
提取每个图像块的 HOG 描述符。
-
将这些 HOG 描述符输入训练好的 SVM,得到标签预测结果。
-
当检测到匹配时,保存对应的图像块坐标。
-
在原始测试图像上绘制检测结果的边界框。
# 创建一个空列表来存储匹配补丁的坐标
positive_patches = []
# 定义滑动的步长
stride = 5
# 遍历测试图像
for i in range(0, test_img.shape[0] - 20 + stride, stride):
for j in range(0, test_img.shape[1] - 20 + stride, stride):
# 从测试图像中裁剪一个小块
patch = test_img[i:i + 20, j:j + 20].reshape(1, 400)
# 将图像块转换为HOG特征描述符
patch_hog = hog_descriptors(patch)
# 预测图像块的目标标签
_, patch_pred = svm_digits.predict(patch_hog.astype(float32))
# 如果检测到匹配项,保存其坐标值
if patch_pred == 1:
positive_patches.append((i, j))
# 将列表转换为数组
positive_patches = array(positive_patches)
# 遍历匹配坐标并绘制其边界框
for i in range(positive_patches.shape[0]):
rectangle(test_img, (positive_patches[i, 1], positive_patches[i, 0]),
(positive_patches[i, 1] + 20, positive_patches[i, 0] + 20), 255, 1)
# 显示测试图像
imshow(test_img, cmap='gray')
show()
完整代码如下:
from cv2 import ml, TERM_CRITERIA_MAX_ITER, TERM_CRITERIA_EPS, rectangle
from numpy import float32, zeros, ones, uint8, array
from matplotlib.pyplot import imshow, show
from digits_dataset import split_images, split_data
from feature_extraction import hog_descriptors
from random import randint, seed, shuffle
# 加载数字图像
img, sub_imgs = split_images('Images/digits.png', 20)
# 从数字图像中获取训练集和测试集
digits_train_imgs, _, digits_test_imgs, _ = split_data(20, sub_imgs, 0.8)
# 创建一个空列表来存储随机数
rand_nums = []
# 设置随机数种子以确保可重复性
seed(10)
# 从测试集中随机选择25个数字
for i in range(0, digits_test_imgs.shape[0], int(digits_test_imgs.shape[0] / 25)):
# 生成一个随机整数
rand = randint(i, int(digits_test_imgs.shape[0] / 25) + i - 1)
# 将其添加到列表中
rand_nums.append(rand)
# 打乱生成的随机整数顺序
shuffle(rand_nums)
# 读取与随机整数对应的图像数据
rand_test_imgs = digits_test_imgs[rand_nums, :]
# 初始化一个数组来保存测试图像
test_img = zeros((100, 100), dtype=uint8)
# 启动子图计数器
img_count = 0
# 遍历测试图像
for i in range(0, test_img.shape[0], 20):
for j in range(0, test_img.shape[1], 20):
# 将选定的数字填充到测试图像中
test_img[i:i + 20, j:j + 20] = rand_test_imgs[img_count].reshape(20, 20)
# 递增子图计数器
img_count += 1
# 显示测试图像
imshow(test_img, cmap='gray')
show()
# 为正负样本生成标签
digits_train_labels = ones((digits_train_imgs.shape[0], 1), dtype=int)
digits_train_labels[int(digits_train_labels.shape[0] / 10):digits_train_labels.shape[0], :] = 0
# 创建一个新的SVM
svm_digits = ml.SVM_create()
# 将SVM核设置为RBF
svm_digits.setKernel(ml.SVM_RBF)
svm_digits.setType(ml.SVM_C_SVC)
svm_digits.setGamma(0.5)
svm_digits.setC(12)
svm_digits.setTermCriteria((TERM_CRITERIA_MAX_ITER + TERM_CRITERIA_EPS, 100, 1e-6))
# 将训练图像转换为HOG特征描述符
digits_train_hog = hog_descriptors(digits_train_imgs)
# 使用训练数据集训练SVM
svm_digits.train(digits_train_hog, ml.ROW_SAMPLE, digits_train_labels)
# 创建一个空列表来存储匹配补丁的坐标
positive_patches = []
# 定义移动步长
stride = 5
# 遍历测试图像
for i in range(0, test_img.shape[0] - 20 + stride, stride):
for j in range(0, test_img.shape[1] - 20 + stride, stride):
# 从测试图像中裁剪一个小块
patch = test_img[i:i + 20, j:j + 20].reshape(1, 400)
# 将图像块转换为HOG特征描述符
patch_hog = hog_descriptors(patch)
# 预测图像块的目标标签
_, patch_pred = svm_digits.predict(patch_hog.astype(float32))
# 如果找到匹配项,则存储其坐标值
if patch_pred == 1:
positive_patches.append((i, j))
# 将列表转换为数组
positive_patches = array(positive_patches)
# 遍历匹配坐标并绘制其边界框
for i in range(positive_patches.shape[0]):
rectangle(test_img, (positive_patches[i, 1], positive_patches[i, 0]),
(positive_patches[i, 1] + 20, positive_patches[i, 0] + 20), 255, 1)
# 显示测试图像
imshow(test_img, cmap='gray')
show()
最终生成的图像显示,我们已成功检测出测试图像中出现的两个数字 0。

我们考虑了一个简单的示例,但同样的思路可以很容易地应用于更具挑战性的现实问题。如果你计划将上述代码改编用于更复杂的任务,需要注意以下几点:
- 目标对象可能以不同的尺寸出现在图像中,因此你可能需要执行多尺度检测任务。
- 在为 SVM 生成正负样本时,不要陷入类别不平衡的问题。本教程中的示例图像变化较小(仅限于 10 个数字,没有尺度、光照、背景等变化),因此数据集不平衡对检测结果的影响很小。然而,在实际问题中,情况往往没有这么简单,类别分布不均可能导致性能下降。
总结
本文介绍了 OpenCV 中 SVM 的完整应用流程,包括:
1.使用 HOG 特征提取手写数字图像特征。
2.基于 OpenCV ml.SVM_create() 训练分类模型。
3.利用测试集评估模型精度。
4.构建随机拼接的测试图像,演示检测算法的实用性。
5.提出多尺度检测与类别平衡的重要性,为实际部署提供指导。
10万+

被折叠的 条评论
为什么被折叠?



