在 OpenCV 中使用神经网络:从 Keras 训练到 ONNX 推理
本文介绍了如何在 OpenCV 的 dnn 模块中使用神经网络。内容涵盖从使用 Keras 训练 LeNet5 模型、将模型保存为 HDF5,再转换为 ONNX 格式,最后在 OpenCV 中加载并进行推理的完整流程,并展示了在 MNIST 测试集上的准确率示例。
【精选优质专栏推荐】
- 《AI 技术前沿》 —— 紧跟 AI 最新趋势与应用
- 《网络安全新手快速入门(附漏洞挖掘案例)》 —— 零基础安全入门必看
- 《BurpSuite 入门教程(附实战图文)》 —— 渗透测试必备工具详解
- 《网安渗透工具使用教程(全)》 —— 一站式工具手册
- 《CTF 新手入门实战教程》 —— 从题目讲解到实战技巧
- 《前后端项目开发(新手必知必会)》 —— 实战驱动快速上手
每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。
文章目录

前言
人们已经开发了许多机器学习模型,每种都有其优缺点。如果没有神经网络模型,这个目录就不完整。在 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 中加载模型并运行推理
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)