OpenCV 中基于 SVM 的图像分类与检测实战教程

部署运行你感兴趣的模型镜像

【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在这里插入图片描述

前言

支持向量机(Support Vector Machine, SVM)算法是 OpenCV 库中实现的最流行的监督式机器学习技术之一。

在本教程中,你将学习如何使用 OpenCV 的支持向量机算法来解决图像分类与检测问题。

完成本教程后,你将掌握:

  1. 支持向量机的若干重要特性。

  2. 如何将支持向量机应用于图像分类与检测问题。

本教程分为三部分:

  1. 回顾支持向量机的工作原理
  2. 将 SVM 算法应用于图像分类
  3. 使用 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)

我们将在前面的代码基础上增加最后一段代码,执行以下操作:

  1. 以预设步幅(stride)遍历测试图像。

  2. 在每次迭代中,从测试图像中裁剪出与数字子图像大小相同的图像块(20×20 像素)。

  3. 提取每个图像块的 HOG 描述符。

  4. 将这些 HOG 描述符输入训练好的 SVM,得到标签预测结果。

  5. 当检测到匹配时,保存对应的图像块坐标。

  6. 在原始测试图像上绘制检测结果的边界框。

# 创建一个空列表来存储匹配补丁的坐标
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.提出多尺度检测与类别平衡的重要性,为实际部署提供指导。

您可能感兴趣的与本文相关的镜像

Yolo-v5

Yolo-v5

Yolo

YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的Joseph Redmon 和Ali Farhadi 开发。 YOLO 于2015 年推出,因其高速和高精度而广受欢迎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值