机器学习模型层出不穷,各有优劣。没有神经网络模型,这个目录就不完整了。在OpenCV中,你可以使用其他框架开发的神经网络模型。在这篇文章中,您将了解在OpenCV中应用神经网络的工作流程。具体来说,您将学习
- OpenCV在其神经网络模型中可以使用什么
- 如何为OpenCV准备神经网络模型
通过我的书《OpenCV 机器学习》启动您的项目。它提供了带有可用代码的自学教程。
让我们开始吧。

在 OpenCV 中运行神经网络模型
照片由 Nastya Dulhiier 拍摄。部分权利保留。
神经网络模型概述
神经网络的另一个名称是多层感知器。它受到人脑结构和功能的启发。想象一个由相互连接的节点组成的网络,每个节点都对通过它的数据执行简单的计算。这些节点或“感知器”相互通信,并根据接收到的信息调整连接。这些感知器组织成一个有向图,计算从输入到输出具有确定性顺序。它们的组织通常用顺序的层来描述。学习过程使网络即使在处理未见过的数据时也能识别模式并做出预测。
在计算机视觉领域,神经网络可以处理图像识别、对象检测和图像分割等任务。通常,在模型内部会执行三个高级操作:
- 特征提取:网络接收图像作为输入。第一层分析像素,寻找诸如边缘、曲线和纹理等基本特征。这些特征就像构建块,让网络对图像内容有一个初步的了解。
- 特征学习:更深的层在此基础上进行组合和转换,以发现更高级、更复杂的模式。这可能涉及识别形状或对象。
- 输出生成:最后,网络的最后一层利用学习到的模式进行预测。根据任务的不同,它可以对图像进行分类(例如,猫与狗)或识别其中包含的对象。
这些操作是学习而来的,而非手工设计的。神经网络的强大之处在于其灵活性和适应性。通过微调神经元之间的连接并提供大量的标记数据,我们可以训练它们以惊人的准确性解决复杂的视觉问题。但由于其灵活性和适应性,神经网络在内存和计算复杂度方面通常不是最高效的模型。
训练神经网络
由于模型本身的性质,训练一个通用的神经网络并非易事。OpenCV中没有训练设施。因此,您必须使用其他框架训练模型,然后在OpenCV中加载它。您希望使用OpenCV是因为您已经在项目中使用了OpenCV进行其他图像处理任务,并且不想为项目引入其他依赖项,或者因为OpenCV是一个更轻量级的库。
例如,考虑经典的MNIST手写数字识别问题。为了简单起见,我们将使用Keras和TensorFlow来构建和训练模型。该数据集可以从TensorFlow获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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() |
两个print语句的输出如下
1 2 |
(60000, 28, 28) (60000,) |
可以看到,数据集提供的是28×28的灰度数字图像。训练集有60,000个样本。您可以使用matplotlib显示一些随机样本,您应该会看到类似下面的图像
该数据集的标签从0到9,表示图像上的数字。对于这个分类问题,有很多模型可供选择。著名的LeNet5模型就是其中之一。让我们用Keras语法创建一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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() |
最后一行为您展示了如下的神经网络架构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
模型: "sequential" ________________________________________________________________________________ 层 (类型) 输出形状 参数 # ================================================================================ 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 ================================================================================ 总参数:61706 (241.04 KB) 可训练参数:61706 (241.04 KB) 不可训练参数:0 (0.00 Byte) ________________________________________________________________________________ |
该网络包含三个卷积层和两个全连接层。最后一个全连接层的输出是一个10个元素的向量,表示输入图像对应于10个数字之一的概率。
在Keras中训练这样的网络并不困难。
首先,需要将输入从28×28的图像像素重新格式化为28×28×1的张量,以提供卷积层所需的额外维度。然后,应该将标签转换为独热编码向量,以匹配网络输出的格式。
然后,您可以通过提供超参数来启动训练:由于这是多类分类问题,损失函数应为交叉熵。Adam用作优化器,因为它是通常的选择。在训练期间,您希望观察其预测准确率。训练应该很快。因此,我们决定将其运行100个epoch,但如果验证集上的损失指标连续四个epoch没有改进,则让它提前停止。
想开始学习 OpenCV 机器学习吗?
立即参加我的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import numpy as np from tensorflow.keras.utils import to_categorical from tensorflow.keras.callbacks import EarlyStopping # 将数据重塑为 (n_sample, height, width, n_channel) 的形状 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) # 对输出进行独热编码 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]) |
运行此模型将打印如下的进度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
时期 1/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.1567 - accuracy: 0.9528 - val_loss: 0.0795 - val_accuracy: 0.9739 时期 2/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0683 - accuracy: 0.9794 - val_loss: 0.0677 - val_accuracy: 0.9791 时期 3/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0513 - accuracy: 0.9838 - val_loss: 0.0446 - val_accuracy: 0.9865 时期 4/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0416 - accuracy: 0.9869 - val_loss: 0.0438 - val_accuracy: 0.9863 Epoch 5/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0349 - accuracy: 0.9891 - val_loss: 0.0389 - val_accuracy: 0.9869 Epoch 6/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0300 - accuracy: 0.9903 - val_loss: 0.0435 - val_accuracy: 0.9864 Epoch 7/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0259 - accuracy: 0.9914 - val_loss: 0.0469 - val_accuracy: 0.9864 Epoch 8/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0254 - accuracy: 0.9918 - val_loss: 0.0375 - val_accuracy: 0.9891 Epoch 9/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0209 - accuracy: 0.9929 - val_loss: 0.0479 - val_accuracy: 0.9853 Epoch 10/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0178 - accuracy: 0.9942 - val_loss: 0.0396 - val_accuracy: 0.9882 Epoch 11/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0182 - accuracy: 0.9938 - val_loss: 0.0359 - val_accuracy: 0.9891 Epoch 12/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0150 - accuracy: 0.9952 - val_loss: 0.0445 - val_accuracy: 0.9876 Epoch 13/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0146 - accuracy: 0.9950 - val_loss: 0.0427 - val_accuracy: 0.9876 Epoch 14/100 1875/1875 [==============================] - 7s 4ms/step - loss: 0.0141 - accuracy: 0.9954 - val_loss: 0.0453 - val_accuracy: 0.9871 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格式,其中包含模型架构和层权重。
1 |
model.save("lenet5.h5") |
构建模型的完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#!/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") ]) # 将数据重塑为 (n_sample, height, width, n_channel) 的形状 X_train = np.expand_dims(X_train, axis=3).astype('float32') X_test = np.expand_dims(X_test, axis=3).astype('float32') # 对输出进行独热编码 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]) model.save("lenet5.h5") |
将模型转换为OpenCV格式
OpenCV在其dnn
模块中支持神经网络。它可以解析由多种框架保存的模型,包括TensorFlow 1.x。但对于上面保存的Keras模型,最好先转换为ONNX格式。
将Keras模型(HDF5格式)或通用TensorFlow模型(Protocol Buffer格式)转换的工具是Python模块tf2onnx
。您可以在您的环境中通过以下命令安装它:
1 |
pip install tf2onnx |
之后,您可以使用该模块的转换命令。例如,由于您已将Keras模型保存为HDF5格式,您可以使用以下命令将其转换为ONNX格式:
1 |
python -m tf2onnx.convert --keras lenet5.h5 --output lenet5.onnx |
然后,会创建一个名为lenet5.onnx
的文件。
要在OpenCV中使用它,您需要将模型加载到OpenCV中作为一个网络对象。如果是TensorFlow Protocol Buffer文件,有函数cv2.dnn.readNetFromTensorflow('frozen_graph.pb')
可用。在这篇文章中,您使用的是ONNX文件。因此,应该是cv2.dnn.readNetFromONNX('model.onnx')
该模型假设输入为“blob”,您应该使用以下方式调用模型:
1 2 3 4 |
net = cv2.dnn.readNetFromONNX('model.onnx') blob = cv2.dnn.blobFromImage(numpyarray, scale, size, mean) net.setInput(blob) output = net.forward() |
blob也是一个numpy数组,但重新格式化以添加批量维度。
在OpenCV中使用该模型只需要几行代码。例如,我们再次从TensorFlow数据集中获取图像,并检查所有测试集样本以计算模型准确率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import numpy as np import cv2 from tensorflow.keras.datasets import mnist # 加载OpenCV中的冻结模型 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
函数获得模型的预测。后续计算测试集的平均准确率很简单。上面的代码打印
1 2 |
测试样本数量:10000 准确率:0.9889 |
总结
在这篇文章中,您学习了如何通过 OpenCV 的 dnn
模块使用神经网络。具体来说,您学习了
- 如何训练神经网络模型并将其转换为 ONNX 格式供 OpenCV 使用
- 如何在 OpenCV 中加载模型并运行模型
谢谢。非常有帮助!
Dawit,非常感谢!如果您有任何问题,请随时告知我们。
很棒的内容!
Chikopa,感谢您的支持!
非常有帮助!谢谢!
vish_1499,非常感谢!我们感谢您的支持!
聘请我,先生,给我一个机会在贵公司工作,我将全力以赴为贵公司贡献力量
Soniya,您好……抱歉,我无法直接帮助您找到工作。
但我有一些建议。
我建议你将学习重点放在如何端到端地解决预测建模问题上,从问题定义到做出预测。我在这里描述了这个过程
应用机器学习流程
我建议您使用您选择的工具/库来练习这个过程,并开发一个已完成的机器学习项目作品集。此作品集可用于展示您不断增长的技能,并提供一个代码库,供您在更大、更复杂的项目中使用。
你可以在这里了解更多关于开发机器学习作品集的信息
建立机器学习作品集
我建议你在较小的公司和初创企业寻找工作,这些公司更看重你交付成果的能力(通过你的作品集证明),而不是老式的招聘方式(例如,拥有相关专业的学位)。