目录

神经网络模型概述

训练神经网络

为OpenCV转换模型

总结


已经开发了许多机器学习模型,每个模型都有优点和缺点。如果没有神经网络模型,此目录是不完整的。在OpenCV中,您可以使用其他框架开发的神经网络模型。在这篇文章中,您将了解在OpenCV中应用神经网络的工作流程。具体来说,您将学习:

  • OpenCV可以在其神经网络模型中使用什么
  • 如何为OpenCV准备神经网络模型

通过我的书《OpenCV中的机器学习》开始你的项目。它提供带有工作代码的自学教程


让我们开始吧。

OpenCV中运行神经网络模型
摄影:Nastya Dulhiier。保留部分权利。

神经网络模型概述

神经网络的另一个名称是多层感知器。它的灵感来自人脑的结构和功能。想象一个由互连节点组成的网络,每个节点都对通过它的数据执行简单的计算。这些节点或感知器相互通信,根据接收到的信息调整它们的连接。这些感知器被组织在有向图中,计算具有从输入到输出的确定顺序。它们的组织通常用顺序来描述。学习过程允许网络识别模式并做出预测,即使使用看不见的数据也是如此。

在计算机视觉中,神经网络处理图像识别、对象检测和图像分割等任务。通常,在模型中,执行三个高级操作:

  1. 特征提取:网络接收图像作为输入。然后,第一层分析像素,搜索边缘、曲线和纹理等基本特征。这些功能就像构建块一样,使网络对图像内容有一个基本的了解。
  2. 特征学习:更深层次建立在这些特征之上,组合和转换它们以发现更高层次、更复杂的模式。这可能涉及识别形状或物体。
  3. 输出生成:最后,网络的最后一层使用学习到的模式进行预测。根据任务,它可以对图像进行分类(例如,猫与狗)或识别它所包含的对象。

这些操作是学习的,而不是精心设计的。神经网络的力量在于它们的灵活性和适应性。通过微调神经元之间的连接并提供大量标记数据,我们可以训练它们以惊人的准确性解决复杂的视觉问题。但是,由于神经网络的灵活性和适应性,神经网络通常不是记忆和计算复杂性方面最有效的模型。

训练神经网络

由于模型的性质,训练通用神经网络并非易事。OpenCV中没有训练设施。因此,您必须使用另一个框架训练模型并将其加载到OpenCV中。在这种情况下,您希望使用OpenCV,因为您已经在使用OpenCV执行其他图像处理任务,并且不想在项目中引入另一个依赖项,或者因为OpenCV是一个更轻量级的库。

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

import matplotlib.pyplot as plt
import numpy as np
from tensorflow.keras.datasets import mnist

# Load MNIST data
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print(X_train.shape)
print(y_train.shape)

# Check visually
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显示一些随机样本,您应该会看到如下所示的图像:

此数据集的标签为09,表示图像上的数字。有许多模型可用于此分类问题。著名的LeNet5模型就是其中之一。让我们使用Keras语法创建一个:

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

# LeNet5 model
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的张量,以便卷积层期望额外的维度。然后,应将标签转换为单热向量以匹配网络输出的格式。

然后,您可以通过提供超参数来启动训练:损失函数应该是交叉熵,因为它是一个多类分类问题。Adam被用作优化器,因为它是通常的选择。在训练期间,您需要观察其预测准确性。训练应该很快。因此,让我们决定运行它100epoch,但如果您无法看到模型在连续四个epoch的验证集中的损失指标上有所改进,请提前停止。

代码如下:

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

# Reshape data to shape of (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)

# One-hot encode the output
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 3/100
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0513 - accuracy: 0.9838 - val_loss: 0.0446 - val_accuracy: 0.9865
Epoch 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纪元停止。

完成模型训练后,可以将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

# Load MNIST data
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print(X_train.shape)
print(y_train.shape)

# LeNet5 model
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")
])

# Reshape data to shape of (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')

# One-hot encode the output
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# Training
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。您可以使用以下命令在环境中安装它:

pip install tf2onnx

之后,您将获得来自模块的转换命令。例如,由于已将Keras模型保存为HDF5格式,因此可以使用以下命令将其转换为ONNX格式:

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

然后,创建一个文件。lenet5.onnx

 

要在OpenCV中使用它,您需要将模型作为网络对象加载到OpenCV中。如果它是TensorFlow协议缓冲区文件,则有一个函数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

# Load the frozen model in OpenCV
net = cv2.dnn.readNetFromONNX('lenet5.onnx')

# Prepare input image
(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))

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

print("count of test samples:", len(X_test))
print("accuracy:", (correct/(correct+wrong)))

OpenCV中运行神经网络模型与在TensorFlow中运行模型略有不同,您需要通过两个单独的步骤分配输入并获取输出。

在上面的代码中,将输出转换为“blob”,无需缩放和移位,因为这是模型的训练方式。您设置单个图像的输入,输出将是一个1×10数组。作为softmax输出,您可以使用该argmax函数获取模型的预测。随后对测试集的平均准确度的计算是微不足道的。上面的代码打印:

count of test samples: 10000
accuracy: 0.9889

总结

在这篇文章中,您通过其dnn模块学习了如何在OpenCV中使用神经网络。具体来说,你学到了

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

https://machinelearningmastery.com/running-a-neural-network-model-in-opencv/

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐