【精选优质专栏推荐】


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

在这里插入图片描述

前言

人们已经开发了许多机器学习模型,每种都有其优缺点。如果没有神经网络模型,这个目录就不完整。在 OpenCV 中,你可以使用其他框架开发的神经网络模型。

在本文中,你将学习如何在 OpenCV 中使用神经网络。具体来说,你将学习:

  • OpenCV 可以使用哪些神经网络模型

  • 如何为 OpenCV 准备神经网络模型

神经网络模型概述

神经网络的另一名称是多层感知器(multilayer perceptrons),其灵感来源于人类大脑的结构和功能。可以想象成一个由互相连接的节点组成的网络,每个节点对流经的数据执行简单计算。这些节点,也称为“感知器”,通过彼此之间的通信,根据接收到的信息调整连接。感知器按有向图组织,计算从输入到输出有确定的顺序。它们的组织通常用顺序层(sequential layers)来描述。学习过程使网络能够识别模式,即使是未见过的数据,也能做出预测。

在计算机视觉中,神经网络处理的任务包括图像识别、目标检测和图像分割。通常,在模型内部会执行三类高层操作:

特征提取
网络接收图像作为输入。前几层分析像素,寻找基本特征,如边缘、曲线和纹理。这些特征就像积木,为网络提供对图像内容的初步理解。

特征学习
更深层的网络在这些特征基础上进行组合和转换,发现更高级、更复杂的模式。这可能包括识别形状或对象。

输出生成
最后几层利用学到的模式进行预测。根据任务不同,可能是对图像分类(例如猫与狗)或识别其中的对象。

这些操作是通过学习获得的,而非人工设计。神经网络的强大在于其灵活性和适应性。通过微调神经元之间的连接并提供大量标注数据,我们可以训练网络以极高精度解决复杂的视觉问题。但也正因为这种灵活性和适应性,神经网络通常在内存和计算复杂度方面不是最高效的模型。

训练神经网络

由于模型的特性,训练通用神经网络并非易事。OpenCV 本身不提供训练功能。因此,你必须使用其他框架训练模型,然后在 OpenCV 中加载它。在这种情况下使用 OpenCV 的原因可能是你已经在用 OpenCV 处理其他图像任务,不想为项目引入额外依赖,或者因为 OpenCV 是一个更轻量的库。

例如,考虑经典的 MNIST 手写数字识别问题。为了简单起见,我们使用 Keras 和 TensorFlow 来构建和训练模型。数据集可以从 TensorFlow 获取。

import matplotlib.pyplot as plt
import numpy as np
from tensorflow.keras.datasets import mnist
 
# 加载 MNIST 数据
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print(X_train.shape)
print(y_train.shape)
 
# 可视化检查
fig, ax = plt.subplots(4, 5, sharex=True, sharey=True)
idx = np.random.randint(len(X_train), size=4*5).reshape(4,5)
for i in range(4):
    for j in range(5):
        ax[i][j].imshow(X_train[idx[i][j]], cmap="gray")
plt.show()

两个打印语句输出:

(60000, 28, 28)
(60000,)

可以看到,数据集提供的数字为 28×28 灰度图像格式。训练集有 60,000 个样本。你可以使用 matplotlib 显示一些随机样本,看到类似如下的图像:

在这里插入图片描述

这个数据集的标签为 0 到 9,表示图像上的数字。针对这个分类问题,有许多模型可以使用,其中著名的 LeNet5 就是一个例子。我们用 Keras 语法创建一个模型:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Flatten

# LeNet5 模型
model = Sequential([
    Conv2D(6, (5,5), input_shape=(28,28,1), padding="same", activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(16, (5,5), activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(120, (5,5), activation="tanh"),
    Flatten(),
    Dense(84, activation="tanh"),
    Dense(10, activation="softmax")
])
model.summary()

最后一行会显示神经网络的结构如下:

Model: "sequential"
________________________________________________________________________________
 Layer (type)                            Output Shape           Param
================================================================================
 conv2d (Conv2D)                         (None, 28, 28, 6)      156
 average_pooling2d (AveragePooling2D)    (None, 14, 14, 6)      0
 conv2d_1 (Conv2D)                       (None, 10, 10, 16)     2416
 average_pooling2d_1 (AveragePooling2D)  (None, 5, 5, 16)       0
 conv2d_2 (Conv2D)                       (None, 1, 1, 120)      48120
 flatten (Flatten)                       (None, 120)            0
 dense (Dense)                           (None, 84)             10164
 dense_1 (Dense)                         (None, 10)             850
================================================================================
Total params: 61706 (241.04 KB)
Trainable params: 61706 (241.04 KB)
Non-trainable params: 0 (0.00 Byte)
________________________________________________________________________________

这个网络包含三层卷积层,后面跟着两层全连接层。最终的全连接层输出一个 10 维向量,表示输入图像属于 10 个数字中某一类的概率。

在 Keras 中训练这样的网络并不困难。

首先,需要将输入从 28×28 的图像像素重新格式化为 28×28×1 的张量,以满足卷积层对额外维度的要求。同时,标签应转换为 one-hot 向量,以匹配网络输出格式。

然后,你可以开始训练,设置超参数:由于这是多类分类问题,损失函数使用交叉熵(cross entropy);优化器选择 Adam,这是常用的选择。在训练过程中,观察预测准确率即可。训练速度应较快。这里设定训练 100 个 epoch,但如果在验证集上连续四个 epoch 损失没有改善,则提前停止训练。

代码如下:

import numpy as np
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.callbacks import EarlyStopping

# 将数据重塑为 (样本数, 高度, 宽度, 通道数) 的形状
X_train = np.expand_dims(X_train, axis=3).astype('float32')
X_test = np.expand_dims(X_test, axis=3).astype('float32')
print(X_train.shape)

# 对输出进行 one-hot 编码
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# 编译模型
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

# 设置早停回调
earlystopping = EarlyStopping(monitor="val_loss", patience=4, restore_best_weights=True)

# 开始训练
model.fit(
    X_train, y_train,
    validation_data=(X_test, y_test),
    epochs=100,
    batch_size=32,
    callbacks=[earlystopping]
)

运行该模型会输出训练进度,例如:

Epoch 1/100
1875/1875 [==============================] - 7s 4ms/step - loss: 0.1567 - accuracy: 0.9528 - val_loss: 0.0795 - val_accuracy: 0.9739
Epoch 2/100
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0683 - accuracy: 0.9794 - val_loss: 0.0677 - val_accuracy: 0.9791
...
Epoch 15/100
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0147 - accuracy: 0.9951 - val_loss: 0.0404 - val_accuracy: 0.9890

由于早停规则,训练在第 15 个 epoch 停止。

完成训练后,你可以将 Keras 模型保存为 HDF5 格式,这会包含模型结构和各层权重:

model.save("lenet5.h5")

构建整个模型的完整代码如下:

#!/usr/bin/env python

import numpy as np
from tensorflow.keras.datasets import mnist
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Flatten
from tensorflow.keras.models import Sequential
from tensorflow.keras.utils import to_categorical

# 加载 MNIST 数据
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print(X_train.shape)
print(y_train.shape)

# 构建 LeNet5 模型
model = Sequential([
    Conv2D(6, (5,5), input_shape=(28,28,1), padding="same", activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(16, (5,5), activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(120, (5,5), activation="tanh"),
    Flatten(),
    Dense(84, activation="tanh"),
    Dense(10, activation="softmax")
])

# 将数据重塑为 (样本数, 高度, 宽度, 通道数)
X_train = np.expand_dims(X_train, axis=3).astype('float32')
X_test = np.expand_dims(X_test, axis=3).astype('float32')

# 对标签进行 one-hot 编码
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# 编译并训练模型
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
earlystopping = EarlyStopping(monitor="val_loss", patience=4, restore_best_weights=True)
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=32, callbacks=[earlystopping])

# 保存模型为 HDF5 格式
model.save("lenet5.h5")

将模型转换为 OpenCV 使用

OpenCV 的 dnn 模块支持神经网络,可以加载多种框架保存的模型,包括 TensorFlow 1.x 的模型。但对于上面保存的 Keras 模型,最好先将其转换为 ONNX 格式。

用于转换 Keras 模型(HDF5 格式)或通用 TensorFlow 模型(Protocol Buffer 格式)的工具是 Python 模块 tf2onnx。

你可以在环境中安装它:

pip install tf2onnx

安装后,可以使用该模块进行转换。例如,将保存的 Keras 模型 HDF5 文件转换为 ONNX 格式:

python -m tf2onnx.convert --keras lenet5.h5 --output lenet5.onnx

这会生成 lenet5.onnx 文件。

在 OpenCV 中使用时,需要将模型加载为网络对象。如果是 TensorFlow 的 Protocol Buffer 文件,可以使用:

cv2.dnn.readNetFromTensorflow('frozen_graph.pb')

而这里我们使用的是 ONNX 文件,因此应使用:

cv2.dnn.readNetFromONNX('model.onnx')

该模型假设输入为 “blob”,调用方法如下:

net = cv2.dnn.readNetFromONNX('model.onnx')
blob = cv2.dnn.blobFromImage(numpyarray, scale, size, mean)
net.setInput(blob)
output = net.forward()

这里的 blob 也是一个 numpy 数组,但重新格式化以增加批次维度。

在 OpenCV 中使用模型只需要几行代码。例如,我们再次从 TensorFlow 数据集中获取图像,并检查测试集所有样本以计算模型准确率:

import numpy as np
import cv2
from tensorflow.keras.datasets import mnist

# 加载 ONNX 模型
net = cv2.dnn.readNetFromONNX('lenet5.onnx')

# 准备输入图像
(X_train, y_train), (X_test, y_test) = mnist.load_data()
correct = 0
wrong = 0
for i in range(len(X_test)):
    img = X_test[i]
    label = y_test[i]

    blob = cv2.dnn.blobFromImage(img, 1.0, (28, 28))

    # 推理
    net.setInput(blob)
    output = net.forward()
    prediction = np.argmax(output)
    if prediction == label:
        correct += 1
    else:
        wrong += 1

print("测试样本总数:", len(X_test))
print("准确率:", (correct/(correct+wrong)))

在 OpenCV 中运行神经网络与在 TensorFlow 中略有不同,需要分两步设置输入和获取输出。

在上面的代码中,你将输出转换为 “blob”,没有进行缩放或偏移,因为模型就是这样训练的。你输入单张图像,输出是一个 1×10 的数组。作为 softmax 输出,可以使用 argmax 函数获得模型的预测结果。随后对测试集计算平均准确率非常简单。

运行结果如下:

测试样本总数: 10000
准确率: 0.9889

总结

在本文中,你学习了如何通过 OpenCV 的 dnn 模块使用神经网络。具体包括:

  • 如何训练神经网络模型并将其转换为 ONNX 格式,以便 OpenCV 使用
  • 如何在 OpenCV 中加载模型并运行推理
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐