深度学习与OpenCV DNN模块:权威指南

深度学习与OpenCV DNN模块:权威指南_第1张图片
计算机视觉领域自20世纪60年代末就已经存在。图像分类和目标检测是计算机视觉领域的一些最古老的问题,研究人员已经努力解决了几十年。使用神经网络和深度学习,我们已经达到了一个阶段,计算机可以开始真正地理解和识别一个物体,并具有很高的准确性,甚至在许多情况下超过了人类。要学习神经网络和计算机视觉的深度学习,OpenCV的DNN模块是一个很好的起点。由于其高度优化的CPU性能,初学者也可以很容易地开始,即使他们没有一个非常强大的GPU启用系统.

希望这篇博文能成为最好的起点之一
深度学习与OpenCV DNN模块:权威指南_第2张图片
使用深度学习和OpenCV的DNN模块的图像分类(左)和目标检测(右)示例。

不仅是理论部分,我们还介绍了OpenCV DNN的实践经验。我们将详细讨论图像和实时视频中的分类和目标检测。

什么是OpenCV DNN模块?

我们都知道OpenCV是最好的计算机视觉库之一。此外,它还具有运行深度学习推理的功能。最好的部分是支持从不同的框架加载不同的模型,使用它我们可以进行一些深度学习功能。从3.3版开始,支持来自不同框架的模型的特性就成为了OpenCV的一部分。尽管如此,许多新进入该领域的人并不知道OpenCV的这个伟大特性。因此,他们往往会错过许多有趣和好的学习机会。

为什么选择OpenCV DNN模块?

OpenCV DNN模块只支持图像和视频的深度学习推理。它不支持微调和训练。不过,OpenCV DNN模块可以作为任何初学者进入基于深度学习的计算机视觉领域的完美起点。

OpenCV DNN模块最好的地方之一是它对英特尔处理器进行了高度优化。通过对实时视频进行推理,可以获得较好的帧率,用于目标检测和图像分割等应用。当我们使用一个预先训练过的模型,使用DNN模块时,我们通常会得到更高的FPS
深度学习与OpenCV DNN模块:权威指南_第3张图片
不同框架下CPU上的图像分类推理速度比较。

以上结果是DenseNet121模型的推理时机。令人惊讶的是,OpenCV比TensorFlow的原始实现快得多,但却落后于PyTorch。事实上,TensorFlow的推断时间接近1秒,而OpenCV的推断时间不到200毫秒。

上述基准测试是在撰写本文时使用最新版本完成的。它们是PyTorch 1.8.0, OpenCV 4.5.1和TensorFlow 2.4。所有测试都是在谷歌Colab上完成的,它具有Intel Xeon处理器2.3Ghz处理器。

即使在物体检测的情况下也是如此。

深度学习与OpenCV DNN模块:权威指南_第4张图片
上图显示了在最初的Darknet框架和OpenCV上使用Tiny YOLOv4进行视频FPS的结果。基准测试是在2.6Ghz的英特尔i7第八代笔记本电脑CPU上完成的。我们可以在同一个视频中看到,OpenCV的DNN模块以35 FPS运行,而OpenMP编译的Darknet和AVX以15 FPS运行。而Darknet(不含OpenMP或AVX)的《Tiny YOLOv4》则是最慢的,仅以3 FPS的速度运行。考虑到我们在两种情况下都使用原始的Darknet Tiny YOLOv4模型,这是一个巨大的差异。

上面的图表显示了OpenCV DNN模块在与cpu一起工作时的实际用途和功能。由于它的快速推理时间(即使在cpu上),它可以作为计算能力有限的边缘设备上的出色部署工具。基于ARM处理器的边缘设备就是最好的例子。下图很好地证明了这一点.
深度学习与OpenCV DNN模块:权威指南_第5张图片
柱状图比较不同框架和不同神经网络模型在树莓派3B上运行时的FPS(帧每秒)

上面的图显示了在树莓派3B上运行的不同框架和模型的FPS。结果令人印象深刻。对于SqueezeNet和MobileNet模型,OpenCV在FPS方面超过了所有其他框架。对GoogLeNet来说,OpenCV排在第二位,TensorFlow是最快的。对于 Network in Network,OpenCV树莓FPS是最慢的。

上面的几个图展示了优化后的OpenCV,以及它用于神经网络推理的速度。这些数据是我们选择详细了解OpenCV DNN模块的一个很好的理由。

OpenCV DNN模块支持的不同深度学习功能

我们已经建立了利用OpenCV DNN模块,可以对图像和视频进行基于深度学习的计算机视觉推理。让我们看看它支持的所有功能。有趣的是,我们能想到的大多数深度学习和计算机视觉任务都得到了支持。下面的列表将让我们对这些特性有一个很好的了解。

  • Image classification.
  • Object Detection.
  • Image segmentation.
  • Text detection and recognition.
  • Pose estimation.
  • Depth estimation.
  • Person and face verification and detection.
  • Person Reid.

这个列表很广泛,提供了很多实际的深度学习用例。通过访问OpenCV存储库的Wiki页面了解更多细节。

令人印象深刻的事实是,根据系统硬件、计算能力(我们稍后将看到它们),有许多模型可供选择。从真正的计算密集型模型到能够在低功耗边缘设备上运行的模型,我们可以为每个用例找到一个模型。

注意,不可能在一篇博客文章中遍历上述所有用例。因此,我们将详细讨论目标检测和人体姿态估计.

OpenCV DNN模块支持的不同模型

为了支持上面讨论的所有应用程序,我们需要大量预训练模型。此外,还有许多state-of-the-art模型可供选择。下表列出了根据不同深度学习应用的一些模型。
深度学习与OpenCV DNN模块:权威指南_第6张图片
上述模型并不详尽。还有更多的模型。如前所述,在单个博客中完整列出或详细讨论每一项几乎是不可能的。上面的列表让我们很好地了解了DNN模块在探索计算机视觉中的深度学习方面的实用性。

OpenCV DNN模块支持的不同框架

看看上面所有的模型,我想到的一个问题是,“所有这些模型都是由单个框架支持的吗?”事实上,不是。

OpenCV DNN模块支持许多流行的深度学习框架。下面是OpenCV DNN模块支持的深度学习框架。

Caffe
要在OpenCV DNN中使用预训练的Caffe模型,我们需要两件事。一个是模型。包含预训练权重的Caffemodel文件。另一个是扩展名为.prototxt的模型架构文件。它就像一个具有JSON结构的纯文本文件,包含所有神经网络层的定义。要清楚了解这个文件,请访问这个链接。

TensorFlow

为了加载预训练的TensorFlow模型,我们还需要两个文件。模型权重文件和protobuf文本文件包含模型配置。权重文件有一个.pb扩展名,它是一个protobuf文件,包含所有预先训练的权重。如果你以前使用过TensorFlow,你会知道.pb文件是我们在保存模型和冻结权重之后得到的模型检查点。模型配置保存在protobuf文本文件中,该文件扩展名为.pbtxt。

注意:在更新版本的TensorFlow中,模型权重文件可能不是.pb格式。如果你试图使用你自己保存的模型之一(可能是.ckpt或.h5格式),这也是正确的。在这种情况下,在模型与OpenCV DNN模块一起使用之前,需要执行一些中间步骤。在这种情况下,将模型转换为ONNX格式,然后再转换为.pb格式是确保一切按预期工作的最好方法。

Torch和PyTorch

为了加载Torch模型文件,我们需要包含预训练权重的文件。通常,该文件的扩展名为。t7或.net。但由于最新的PyTorch模型具有.pth扩展名,首先转换为ONNX是最好的方法。转换到ONNX后,可以直接加载,因为OpenCV DNN支持ONNX型号。

Darknet
OpenCV DNN模块也支持著名的Darknet框架。如果他们在Darknet框架中使用了官方的YOLO模型,你就会意识到这一点。
通常,为了加载Darknet模型,我们需要一个扩展名为.weights的模型权重文件。对于Darknet模型,网络配置文件总是一个.cfg文件。

使用从Keras和PyTorch等不同框架转换为ONNX格式的模型
通常,在PyTorch或TensorFlow等框架中训练过的模型可能无法直接与OpenCV DNN模块一起使用。在这些情况下,我们通常将模型转换为ONNX格式(Open Neural Network Exchange),然后可以按原样使用,甚至转换为TensorFlow或PyTorch等其他框架支持的格式。

要加载ONNX模型,我们只需要支持OpenCV DNN模块的. ONNX权重文件。
请访问官方OpenCV文档,了解不同框架、它们的权重文件和配置文件。

很可能,上面的列表涵盖了所有著名的深度学习框架。要获得OpenCV DNN模块支持的所有框架和模型,请访问官方Wiki页面。

我们在这里的所有模型都经过测试,可以完美地与OpenCV DNN模块一起工作。理论上,上述框架中的任何一个模型都可以与DNN模块适配。我们只需要找到正确的权重文件和相应的神经网络架构文件。当我们开始本教程的编码部分时,事情会变得更清楚。

使用OpenCV DNN模块进行图像分类的完整指南

在本节中,我们将使用OpenCV DNN模块对图像进行分类。我们将详细介绍每个步骤,以便在本节结束时清楚地了解所有内容。

我们将使用使用Caffe框架在非常著名的ImageNet数据集上训练的神经网络模型。具体来说,我们将使用DensNet121深度神经网络模型进行分类任务。它的优点是它对来自ImageNet数据集的1000个类进行了预训练。我们可以猜测,我们想要分类的图像将已经被模型训练过。这使得我们可以从广泛的图像中进行选择。

我们将使用下面的老虎图像进行图像分类任务。
深度学习与OpenCV DNN模块:权威指南_第7张图片
简单地说,下面是我们在对图像进行分类时要遵循的步骤。

  • 加载类名文件并提取所需的标签。
  • 加载预训练的神经网络模型。
  • 加载图像,并转换为适合深度学习模型输入的格式。
  • 将输入图像通过模型向前传播,得到输出

现在让我们详细查看每个步骤和代码。

导入模块和加载类文件
我们需要为Python代码导入OpenCV和Numpy模块。对于c++,我们需要包含OpenCV和OpenCV的DNN库。

源码地址:https://github.com/yuanxinshui/DeepLearnCV/tree/main/Deep-Learning-with-OpenCV-DNN-Module

Python:

import cv2
import numpy as np

C++

#include 
#include 
#include 
#include 
#include 
 
using namespace std;
using namespace cv;
using namespace dnn;

记得我们讨论过,我们将使用的DenseNet121模型在ImageNet数据集中国训练过1000个类。我们需要一些方法来将这1000个类加载到内存中并方便地访问它们。这些类通常在文本文件中可用。其中一个文件叫做classification_classes_ILSVRC2012.txt文件,它包含以下格式的所有类名。

tench, Tinca tinca
goldfish, Carassius auratus
great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias
tiger shark, Galeocerdo cuvieri
hammerhead, hammerhead shark

每一行包含单个图像的所有标签或类名。例如,第一行包含tench, Tinca Tinca。这两个名字属于同一种鱼。同样,第二行有两个金鱼的名字。一般来说,第一个名字是几乎所有人都认识的最常见的名字。

让我们看看如何加载这样一个文本文件,并从每行中提取第一个名字,以便在对图像进行分类时将它们用作标签。

Python:

# read the ImageNet class names
with open('../../input/classification_classes_ILSVRC2012.txt', 'r') as f:
   image_net_names = f.read().split('\n')
# final class names (just the first word of the many ImageNet names for one image)
class_names = [name.split(',')[0] for name in image_net_names]

C++

std::vector class_names;
   ifstream ifs(string("../../input/classification_classes_ILSVRC2012.txt").c_str());
   string line;
   while (getline(ifs, line))
   {
       class_names.push_back(line);
   } 

首先,打开并读取类名文件,并将所有类以以下格式存储在image_net_names列表中。

[‘tench, Tinca tinca’, ‘goldfish, Carassius auratus’, ‘great white shark, white shark, man-eater, man-eating shark’, ...]

但是,我们只需要每一行的第一个名字。这就是第二行代码所做的。对于image_net_names列表中的每个元素,我们使用逗号(,)作为分隔符分隔元素,并且只保留第一个元素。这些名称保存在class_names列表中。现在,列表看起来如下所示。

['tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead',]

加载预训练的DenseNet121模型
如前所述,我们将使用经过Caffe深度学习框架训练的DenseNet121模型。

我们需要模型权重文件(.caffemodel)和模型配置文件(.prototxt)。
让我们看看代码,然后进入模型加载的解释部分。

Python:

# load the neural network model
model = cv2.dnn.readNet(model='../../input/DenseNet_121.caffemodel', config='../../input/DenseNet_121.prototxt', framework='Caffe')

C++

// load the neural network model
   auto model = readNet("../../input/DenseNet_121.prototxt",
                       "../../input/DenseNet_121.caffemodel",
                       "Caffe");

您可以看到,我们正在使用OpenCV DNN模块的一个名为readNet()的函数,该函数接受三个输入参数。

  • model:这是预训练权重文件的路径。在我们的例子中,它是预先训练的Caffe模型。
  • config:这是模型配置文件的路径,在本例中是Caffe模型的.prototxt文件。
  • framework:最后,我们需要提供要从其中加载模型的框架名称。对我们来说,它是Caffe框架。

除了readNet()函数之外,DNN模块还提供了从特定框架加载模型的函数,在这些框架中我们不需要提供框架参数。下面是这些函数。

  • readNetFromCaffe():用于加载预训练的Caffe模型并接受两个参数。它们是prototxt文件的路径和Caffe模型文件的路径。
  • readNetFromTensorflow():我们可以使用这个函数直接加载预训练的TensorFlow模型。它还接受两个参数。一个是到冻结模型图的路径,另一个是到模型体系结构protobuf文本文件的路径。
  • readNetFromTorch():我们可以使用它来加载使用Torch .save()函数保存的Torch和PyTorch模型。我们需要提供模型路径作为参数。
  • readNetFromDarknet():这用于加载使用DarkNet框架训练的模型。我们还需要提供两个参数。一个是模型权重的路径,另一个是模型配置文件的路径。
  • readNetFromONNX():我们可以使用它来加载ONNX模型,我们只需要提供ONNX模型文件的路径。

读取图像并为模型输入做好准备
我们将像往常一样,使用OpenCV的imread()函数从磁盘中读取图像。注意,我们还需要处理一些其他细节。我们使用DNN模块加载的预训练模型并不直接将读取的图像作为输入。在那之前我们需要做一些预处理。

Python:

# load the image from disk
image = cv2.imread('../../input/image_1.jpg')
# create blob from image
blob = cv2.dnn.blobFromImage(image=image, scalefactor=0.01, size=(224, 224), mean=(104, 117, 123))

C++

// load the image from disk
Mat image = imread("../../input/image_1.jpg");
// create blob from image
Mat blob = blobFromImage(image, 0.01, Size(224, 224), Scalar(104, 117, 123));

在读取图片时,我们假设它位于当前目录之前的两个目录中,并且位于输入文件夹中。接下来的几个步骤至关重要。我们有一个blobFromImage()函数,用来将图像转换为合适的格式正输入模型。让我们看一下所有的参数,并详细学习一下。

  • image:这是我们刚才用imread()函数读取的输入图像。
  • scalefactor:根据这个值缩放图像。它的默认值为1,这意味着不执行缩放。
  • size:这是图像将被调整到的大小。我们提供了224×224的大小,因为大多数在ImageNet数据集上训练的分类模型都时这个大小。
  • mean: mean这个变量非常重要。这些实际上是从图像的RGB颜色通道中减去的平均值,归一化输入。

这里还有一件事需要注意。所有的深度学习模型都需要批量输入。然而,我们这里只有一张图像。然而,我们在这里得到的blob输出实际上是[1,3,224,224]的形状。注意,blobFromImage()函数已经添加了一个额外的批处理维度。这将是神经网络模型的最终和正确的输入格式。

模型前向传播
Python

# set the input blob for the neural network
model.setInput(blob)
# forward pass image blog through the model
outputs = model.forward()

输出是一个数组,包含所有的预测。但是在我们能够正确地看到输出和类标签之前,我们需要完成几个预处理步骤

目前,输出的形状为(1,1000,1,1),很难提取类标签。因此,下面的代码块重塑了输出,在此之后,我们可以轻松地获得正确的类标签并将标签ID映射到类名。

Python:

final_outputs = outputs[0]
# make all the outputs 1D
final_outputs = final_outputs.reshape(1000, 1)
# get the class label
label_id = np.argmax(final_outputs)
# convert the output scores to softmax probabilities
probs = np.exp(final_outputs) / np.sum(np.exp(final_outputs))
# get the final highest probability
final_prob = np.max(probs) * 100.
# map the max confidence to the class label names
out_name = class_names[label_id]
out_text = f"{out_name}, {final_prob:.3f}"
# put the class name text on top of the image
cv2.putText(image, out_text, (25, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.imwrite('result_image.jpg', image)

C++

// set the input blob for the neural network
model.setInput(blob);
// forward pass the image blob through the model
Mat outputs = model.forward(); 
Point classIdPoint;
double final_prob;
minMaxLoc(outputs.reshape(1, 1), 0, &final_prob, 0, &classIdPoint);
int label_id = classIdPoint.x; 
// Print predicted class.
string out_text = format("%s, %.3f", (class_names[label_id].c_str()), final_prob);
// put the class name text on top of the image
putText(image, out_text, Point(25, 50), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 255, 0), 2);
imshow("Image", image);
imwrite("result_image.jpg", image);

CMakelists

以下两个CMakeLists.txt都可以用来编译

cmake_minimum_required(VERSION 3.0.0)    #设置cmake最小版本,可以在终端通过cmake --version查看
project(classify VERSION 0.1.0)         # 项目名  版本号

include(CTest)
enable_testing()

set(OpenCV_DIR /home/SMCV/einrj/imgproc/opencv-4.4.0/build) # 设置Opencv目录                           
find_package(OpenCV REQUIRED)
find_package(Threads REQUIRED)

add_executable(classify detect_img.cpp)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX__STANDARD_REQUIRED ON)

include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(classify ${OpenCV_LIBS} ${CMAKE_THREAD_LIBS_INIT})

或者

c# cmake needs this line
cmake_minimum_required(VERSION 3.1)

# Define project name
project(opencv_detect_project)
set(OpenCV_DIR /home/SMCV/einrj/imgproc/opencv-4.4.0/build)
# Find OpenCV, you may need to set OpenCV_DIR variable
# to the absolute path to the directory containing OpenCVConfig.cmake file
# via the command line or GUI
find_package(OpenCV REQUIRED)

# If the package has been found, several variables will
# be set, you can find the full list with descriptions
# in the OpenCVConfig.cmake file.
# Print some message showing some of them
message(STATUS "OpenCV library status:")
message(STATUS "    config: ${OpenCV_DIR}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

# Declare the executable target built from your sources
add_executable(opencv_detect_vid detect_img.cpp)

# Link your application with OpenCV libraries
target_link_libraries(opencv_detect_vid PRIVATE ${OpenCV_LIBS})

执行编译

cmake .
make

在我们对输出进行reshape之后,它的形状为(1000,1),这表明它有1000行对应于1000个标签。每一行保存与类标签对应的分数,如下所示。

[[-1.44623446e+00]
[-6.37421310e-01]
 [-1.04836571e+00]
 [-8.40160131e-01]
…
]

在上面的Python代码中,我们使用 n p . e x p ( f i n a l o u t p u t s ) / n p . s u m ( n p . e x p ( f i n a l o u t p u t s ) ) np.exp(final_{outputs}) / np.sum(np.exp(final_{outputs})) np.exp(finaloutputs)/np.sum(np.exp(finaloutputs))将分数转换为softmax概率。然后我们将概率最高的分数与100相乘,得到预测的分数百分比。

在执行代码之后,我们将得到以下输出:
深度学习与OpenCV DNN模块:权威指南_第8张图片
DenseNet121模型准确地预测出了老虎的图像,准确率也高达91%。

在上面的章节中,我们看到了如何使用OpenCV DNN模块使用DenseNet121神经网络模型进行图像分类。为了更好地理解DNN模块的工作原理,我们也详细地介绍了每一个步骤。

接下来我们将使用OpenCV DNN在图像和视频中进行目标检测。

基于OpenCV DNN的目标检测

使用OpenCV DNN模块,我们可以很容易上手目标检测任务。像分类一样,我们将加载图像、选择适当的模型,并根据输入进行前向推理。在目标检测中预处理步骤将会有一点不同。我们将在接下来的博文中讨论所有这些问题。

基于OpenCV DNN的图像目标检测

就像分类一样,这里我们也会利用预先训练的模型。这些模型在MS COCO数据集上进行了训练,MS COCO数据集是目前基于深度学习的目标检测模型的benhmarkc数据集。

MS COCO几乎有80类物品,从人到汽车,再到牙刷。该数据集包含80类日常对象。我们还将使用一个文本文件来加载MS COCO数据集中的所有标签,以进行对象检测

对于目标检测,我们将使用以下图像
深度学习与OpenCV DNN模块:权威指南_第9张图片
我们将使用MobileNet SSD (Single Shot Detector),它已经在MS COCO数据集上使用TensorFlow深度学习框架进行了训练。SSD模型通常比其他对象检测模型更快。此外,MobileNet骨干网也更加轻量化。因此MobileNet SSD是一个好的模型去学习目标检测。

让我们从编码部分开始。

Python

import cv2
import numpy as np

C++

#include 
#include 
#include 
#include 
#include 
 
using namespace std;
using namespace cv;
using namespace dnn;

Python

# load the COCO class names
with open('object_detection_classes_coco.txt', 'r') as f:
   class_names = f.read().split('\n')
 
# get a different color array for each of the classes
COLORS = np.random.uniform(0, 255, size=(len(class_names), 3))

C++

std::vector class_names;
   ifstream ifs(string("../../../input/object_detection_classes_coco.txt").c_str());
string line;
while (getline(ifs, line))
{
    class_names.push_back(line);
}

接下来我们读取object_detection_classes_coco.txt文件,我们将每个类名存储在class_names列表中。

class_names列表类似如下所示

['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', … 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush', '']

加载MobileNet SSD模型并输入

我们将使用readNet()函数加载MobileNet SSD模型,我们在前面也使用过该函数。
Python

# load the DNN model
model = cv2.dnn.readNet(model='frozen_inference_graph.pb',                config='ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt',framework='TensorFlow')

C++

// load the neural network model
auto model = readNet("../../../input/frozen_inference_graph.pb",
"../../../input/ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt", "TensorFlow");

接下来,我们将从磁盘读取图像并准备输入blob文件。
Python

# read the image from disk
image = cv2.imread('../../input/image_2.jpg')
image_height, image_width, _ = image.shape
# create blob from image
blob = cv2.dnn.blobFromImage(image=image, size=(300, 300), mean=(104, 117, 123), swapRB=True)
# set the blob to the model
model.setInput(blob)
# forward pass through the model to carry out the detection
output = model.forward()

C++

// read the image from disk
Mat image = imread("../../../input/image_2.jpg");
int image_height = image.cols;
int image_width = image.rows;
//create blob from image
Mat blob = blobFromImage(image, 1.0, Size(300, 300), Scalar(127.5, 127.5, 127.5),true, false);
//create blob from image
model.setInput(blob);
//forward pass through the model to carry out the detection
Mat output = model.forward();
Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr());

对于目标检测,我们在blobFromImage()函数中使用了一个稍微不同的参数值。

  • 我们将大小指定为300×300,因为这是SSD模型在几乎所有框架中通常期望的输入大小。对于TensorFlow也是一样的。
  • 这次我们还使用了swapRB参数。通常,OpenCV以BGR格式读取图像,而对于目标检测,模型通常期望输入为RGB格式。因此,swapRB参数将交换图像的R和B通道,使其成为RGB格式。

我们的输出结构如下

[[[[0.00000000e+00 1.00000000e+00 9.72869813e-01 2.06566155e-02 1.11088693e-01 2.40461200e-01 7.53399074e-01]]]]
  • 在这里,索引位置1包含了从1到80的类标签。
  • 索引位置2为confidence得分。这不是一个概率分数,而是模型对属于它检测到的类的对象的信心。
  • 在最后四个值中,前两个是x, y边界框坐标,最后两个分别是边界框的宽度和高度。

循环检测并绘制边界框

Python

# loop over each of the detection
for detection in output[0, 0, :, :]:
   # extract the confidence of the detection
   confidence = detection[2]
   # draw bounding boxes only if the detection confidence is above...
   # ... a certain threshold, else skip
   if confidence > .4:
       # get the class id
       class_id = detection[1]
       # map the class id to the class
       class_name = class_names[int(class_id)-1]
       color = COLORS[int(class_id)]
       # get the bounding box coordinates
       box_x = detection[3] * image_width
       box_y = detection[4] * image_height
       # get the bounding box width and height
       box_width = detection[5] * image_width
       box_height = detection[6] * image_height
       # draw a rectangle around each detected object
       cv2.rectangle(image, (int(box_x), int(box_y)), (int(box_width), int(box_height)), color, thickness=2)
       # put the FPS text on top of the frame
       cv2.putText(image, class_name, (int(box_x), int(box_y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
 
cv2.imshow('image', image)
cv2.imwrite('image_result.jpg', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

C++

for (int i = 0; i < detectionMat.rows; i++){
       int class_id = detectionMat.at(i, 1);
       float confidence = detectionMat.at(i, 2);
      
       // Check if the detection is of good quality
       if (confidence > 0.4){
           int box_x = static_cast(detectionMat.at(i, 3) * image.cols);
           int box_y = static_cast(detectionMat.at(i, 4) * image.rows);
           int box_width = static_cast(detectionMat.at(i, 5) * image.cols - box_x);
           int box_height = static_cast(detectionMat.at(i, 6) * image.rows - box_y);
           rectangle(image, Point(box_x, box_y), Point(box_x+box_width, box_y+box_height), Scalar(255,255,255), 2);
           putText(image, class_names[class_id-1].c_str(), Point(box_x, box_y-5), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,255,255), 1);
       }
   }   
 
   imshow("image", image);
   imwrite("image_result.jpg", image);
   waitKey(0);
   destroyAllWindows();
  • 在for循环中,首先,我们提取当前检测到的对象的可信度得分。如前所述,我们可以从索引位置2得到它。
  • 然后我们使用if块来检查被检测对象的置信度是否高于某个阈值。我们只是在绘制信心值高于0.4的边界框。
  • 我们获得类ID并将其映射到MS COCO类名。然后我们为当前类获得一个单一的颜色来绘制边界框,并将类标签文本放在边界框的顶部。
  • 然后我们提取边界框的x和y坐标

这是我们使用OpenCV DNN在图像中进行目标检测所需的所有代码。执行该代码将得到以下结果。
深度学习与OpenCV DNN模块:权威指南_第10张图片
在上图中,我们可以看到效果似乎不错。该模型正在检测几乎所有可见的物体。然而,也有一些不正确的预测。例如,MobileNet SSD模型将右侧自行车检测为的摩托车。MobileNet ssd往往会犯这样的错误,因为它们在实时应用程序中会犯这样的错误,以准确性换取速度。

基于OpenCV DNN的视频目标检测

视频中对象检测的代码将非常类似于图像。会有一些变化,因为我们将在视频帧而不是图像上进行预测。

这几行代码完全类似于图像中的目标检测。让我们先完成这一部分。

Python

import cv2
import time
import numpy as np
 
# load the COCO class names
with open('object_detection_classes_coco.txt', 'r') as f:
   class_names = f.read().split('\n')
 
# get a different color array for each of the classes
COLORS = np.random.uniform(0, 255, size=(len(class_names), 3))
 
# load the DNN model
model = cv2.dnn.readNet(model='frozen_inference_graph.pb',                       config='ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt',framework='TensorFlow')
 
# capture the video
cap = cv2.VideoCapture('../../input/video_1.mp4')
# get the video frames' width and height for proper saving of videos
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
# create the `VideoWriter()` object
out = cv2.VideoWriter('video_result.mp4', cv2.VideoWriter_fourcc(*'mp4v'), 30, (frame_width, frame_height))

C++

#include 
#include 
#include 
#include 
#include 
 
using namespace std;
using namespace cv;
using namespace dnn;
 
 
int main(int, char**) {
   std::vector<std::string> class_names;
   ifstream ifs(string("../../../input/object_detection_classes_coco.txt").c_str());
   string line;
   while (getline(ifs, line))
   {
       class_names.push_back(line);
   } 
  
   // load the neural network model
   auto model = readNet("../../../input/frozen_inference_graph.pb",
"../../../input/ssd_mobilenet_v2_coco_2018_03_29.pbtxt.txt","TensorFlow");
 
   // capture the video
   VideoCapture cap("../../../input/video_1.mp4");
   // get the video frames' width and height for proper saving of videos
   int frame_width = static_cast<int>(cap.get(3));
   int frame_height = static_cast<int>(cap.get(4));
   // create the `VideoWriter()` object
   VideoWriter out("video_result.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(frame_width, frame_height));

我们可以看到大部分代码是相同的。我们正在加载相同的MS COCO类文件和相同的MobileNet SSD模型。

这里,我们使用videcapture()对象捕捉视频,而不是图像。我们还创建了一个VideoWriter()对象,用于正确保存生成的视频帧。

在视频中循环检测每帧对象

到目前为止,我们已经准备好了视频和MobileNet SSD模型。下一步是循环播放每一帧视频,并在每一帧中进行目标检测。这样,我们将把每一帧都看作是一幅图像。

Python

# detect objects in each frame of the video
while cap.isOpened():
   ret, frame = cap.read()
   if ret:
       image = frame
       image_height, image_width, _ = image.shape
       # create blob from image
       blob = cv2.dnn.blobFromImage(image=image, size=(300, 300), mean=(104, 117, 123), swapRB=True)
       # start time to calculate FPS
       start = time.time()
       model.setInput(blob)
       output = model.forward()       
       # end time after detection
       end = time.time()
       # calculate the FPS for current frame detection
       fps = 1 / (end-start)
       # loop over each of the detections
       for detection in output[0, 0, :, :]:
           # extract the confidence of the detection
           confidence = detection[2]
           # draw bounding boxes only if the detection confidence is above...
           # ... a certain threshold, else skip
           if confidence > .4:
               # get the class id
               class_id = detection[1]
               # map the class id to the class
               class_name = class_names[int(class_id)-1]
               color = COLORS[int(class_id)]
               # get the bounding box coordinates
               box_x = detection[3] * image_width
               box_y = detection[4] * image_height
               # get the bounding box width and height
               box_width = detection[5] * image_width
               box_height = detection[6] * image_height
               # draw a rectangle around each detected object
               cv2.rectangle(image, (int(box_x), int(box_y)), (int(box_width), int(box_height)), color, thickness=2)
               # put the class name text on the detected object
               cv2.putText(image, class_name, (int(box_x), int(box_y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
               # put the FPS text on top of the frame
               cv2.putText(image, f"{fps:.2f} FPS", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
      
       cv2.imshow('image', image)
       out.write(image)
       if cv2.waitKey(10) & 0xFF == ord('q'):
           break
   else:
       break
 
cap.release()
cv2.destroyAllWindows()

C++

while (cap.isOpened()) {
       Mat image;
       bool isSuccess = cap.read(image);
       if (! isSucess) break;
      
       int image_height = image.cols;
       int image_width = image.rows;
       //create blob from image
       Mat blob = blobFromImage(image, 1.0, Size(300, 300), Scalar(127.5, 127.5, 127.5),
                               true, false);
       //create blob from image
       model.setInput(blob);
       //forward pass through the model to carry out the detection
       Mat output = model.forward();
      
       Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr<float>());
      
       for (int i = 0; i < detectionMat.rows; i++){
           int class_id = detectionMat.at<float>(i, 1);
           float confidence = detectionMat.at<float>(i, 2);
 
           // Check if the detection is of good quality
           if (confidence > 0.4){
               int box_x = static_cast<int>(detectionMat.at<float>(i, 3) * image.cols);
               int box_y = static_cast<int>(detectionMat.at<float>(i, 4) * image.rows);
               int box_width = static_cast<int>(detectionMat.at<float>(i, 5) * image.cols - box_x);
               int box_height = static_cast<int>(detectionMat.at<float>(i, 6) * image.rows - box_y);
               rectangle(image, Point(box_x, box_y), Point(box_x+box_width, box_y+box_height), Scalar(255,255,255), 2);
               putText(image, class_names[class_id-1].c_str(), Point(box_x, box_y-5), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,255,255), 1);
           }
       }
      
       imshow("image", image);
       out.write(image);
       int k = waitKey(10);
       if (k == 113){
           break;
       }
   }
 
cap.release();
destroyAllWindows();
}

在上面的代码块中,模型检测每一帧中的对象,直到视频中没有需要循环的帧。需要注意的一些重要事项:

  • 我们将检测前的开始时间存储在start变量中,检测结束后的结束时间存储在end变量中。
  • 以上的时间变量帮助我们计算FPS(每秒帧数)。我们计算FPS并将其存储在FPS中。
  • 在代码的最后一部分,我们还将在当前帧的基础上编写计算出的FPS,以了解在运行MobileNet SSD模型时我们可以期望的速度
  • 最后,我们将每一帧在屏幕上进行可视化并将它们保存到磁盘。

执行上述代码将得到以下输出。

在GPU上推理

我们也可以在GPU上运行所有的分类和检测推理。为此,我们需要用从源代码编译OpenCV DNN模块。

  • 如果你在Ubuntu上,请访问LearnOpenCV.com的这篇文章,来编译带GPU 的OpenCV。
  • 如果在windows上,访问此链接来编译带GPU 的OpenCV。

为了在GPU上运行推断,我们需要对c++和Python代码做一个简单的更改。

Python:

net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)

C++:

net.setPreferableBackend(DNN_BACKEND_CUDA);
net.setPreferableTarget(DNN_TARGET_CUDA);

加载神经网络模型后添加上述两行代码。第一行代码确保如果DNN模块支持CUDA GPU模型来进行神经网络将的反向传播。

第二行代码告诉我们所有的神经网络计算都将发生在GPU上,而不是CPU上。使用CUDA支持的GPU,我们在目标检测视频推断方面应该比CPU获得更高的FPS。即使是图像,推理时间也应该比CPU低很多。

总结

神经网络和深度学习已经达到了计算机能够理解和识别高精度物体的阶段。有时,它们甚至在某些用例中超过人类。
OpenCV款模块:

  • 是模型推理的首选,尤其是在Intel cpu上。
  • 安装方便。
  • 提供现成的,随时可以使用适合大多数用例的模型和算法。
  • 虽然DNN模块不具备训练功能,但仍然对边缘设备具有强大的部署支持。

参考

https://learnopencv.com/deep-learning-with-opencvs-dnn-module-a-definitive-guide/

你可能感兴趣的:(opencv,深度学习,深度学习,opencv,计算机视觉)