专注计算机视觉前沿资讯和技术干货
微信公众号:极市平台
官网:https://www.cvmart.net/


极市导读:本文来自6月份出版的新书《OpenCV深度学习应用与性能优化实践》,由Intel与阿里巴巴高级图形图像专家联合撰写,系统地介绍了OpenCV DNN 推理模块原理和实践。

深度学习理论的广泛研究促进了其在不同场景的应用。在计算机视觉领域,图像分类、目标检测、语义分割和视觉风格变换等基础任务的性能也因为采用了深度学习的方法而有了飞跃性的提升。本章将为读者梳理深度学习方法在这些基本应用场景的应用情况,并结合OpenCV深度学习模块的示例程序,从源代码和实际运行两个层面进行讲解。下文对书中图像分类部分内容进行摘录:

图像分类是计算机视觉领域的基础任务之一,在各种基于视觉的人工智能应用中,图像分类都扮演着重要的角色。例如,在智能机器人应用中,我们需要对所采集的视频中的每一帧进行主要物体的检测和分类,并以此作为进一步决策的基础。

近些年,图像分类与深度学习的飞速发展有着密不可分的关系。在2012年的ILSVRC (ImageNet Large Scale Visual Recognition Competition,ImageNet大规模视觉识别挑战赛)大赛上,AlexNet横空出世,以压倒性优势战胜了传统图像分类算法而夺得冠军,开启了计算机视觉领域的深度学习革命。2015年,ResNet首次在图像分类准确度上战胜人类。2017年,随着SENet的夺冠,最后一届ILSVRC大赛落下帷幕。下面为大家梳理一下历届ILSVRC大赛中出现的经典网络结构。

图像分类经典网络结构

自2012年ILSVRC大赛AlexNet夺冠以来,直至2017年最后一届SENet夺冠,所有冠军都被各种深度神经网络所摘得。历届ILSVRC大赛的经典网络结构及其特点如表9-1所示。

这些网络结构不仅可应用在图像分类中,而且可作为其他计算机视觉任务(如目标检测、语义分割和视觉风格变换)的骨干(backbone)网络,用来提取图像特征。因此,它们对整个计算机视觉技术的发展有着深远的影响。

下面我们摘录OpenCV官方Wiki上的DNN模块运行效率统计表,看一下AlexNet、GoogLeNet和ResNet-50在OpenCV DNN模块中的运行效率。

测试系统软硬件配置如下:

各软件组件的版本信息如表9-2所示。

CPU实现的运行时间如表9-3所示,该时间取的是50次运行的中位数时间,中位数时间可以排除多次推理运算中某些过于异常的值对平均值的干扰。另外,所有网络模型都采用32位浮点数据格式进行计算。在神经网络的推理计算中,可以采用量化方法把32位浮点精度的模型参数降低到16位浮点精度以节省数据读取带宽提高运算效率,但是并不是所有算法都支持针对16位浮点精度的实现,为了便于比较,测试都采用32位浮点精度。

GPU实现的运行时间如表9-4所示。

从上面的数据可以看到,DNN模块的OpenCL实现跟原生C++实现性能相近,而Intel-Caffe MKLDNN的加速性能最好,原因是多方面的。首先MKLDNN是针对Intel CPU进行高度优化的神经网络计算库,能够充分发挥Intel CPU的性能。其次,该测试使用的CPU硬件性能比较强劲(8核心,4.0GHz运行频率),而集成的GPU是中低配置。最后,测试的3种网络模型的运算量不算太大,未能充分发挥GPU的并发特性。

接下来,我们以GoogLeNet(Inception-v1)为例,详细讲解其网络结构和设计原理,然后结合OpenCV中的图像分类示例程序讲解GoogLeNet模型的实际使用。

GoogLeNet

GoogLeNet自2014年提出以来,总共演进了4个版本,由于第1版是后续几个版本的基础,本节主要介绍2014年的第1版,即GoogLeNet v1。

GoogLeNet v1是2014年ILSVRC大赛的冠军模型,它延续了自LeNet以来的典型卷积网络结构,即多个卷积层前后堆叠,然后通过全连接层输出最终的特征值。GoogLeNet的结构如图9-1所示。

下面对图9-1中各列进行解释。type列表示层或者模块的类型,其中inception代表一个Inception模块,GoogLeNet中总共堆叠了9个Inception模块,convolution表示卷积层,max pool表示最大池化层,avg pool表示平均池化层,dropout代表随机裁剪操作,linear是全连接层,softmax表示最后对输出特征值进行sotfmax操作。patch size列表示卷积核大小,stride表示卷积运算的步进值。output size列表示输出特征图的长、宽和通道数。Depth列表示该层或者模块重复连接的次数。#1×1,#3×3,#5×5列分别表示Inception模块中的1×1,3×3,5×5卷积核大小的卷积分支的输出通道数。pool proj列表示池化投影的输出通道数。#3×3 reduce和#5×5 reduce列表示Inception结构中3×3和5×5卷积核卷积之前的1×1卷积的输出通道数。params列表示参数数目。ops列表示运算量。Inception模块是GoogLeNet的最大创新点,它的初衷是增加卷积核尺寸种类的同时降低训练参数数量,下面对Inception v1模块进行讲解,它的结构如图9-1所示。

Inception模块使用1×1卷积对前层数据进行降维处理并分成多路,然后用3×3,5×5卷积对降维后的分支进行卷积运算,同时将各个卷积结果和3×3最大池化的结果按通道进行连接。这种创新的结构使得网络参数大大降低的同时保留了很好的特征表达能力,达到了深度和参数数量的双赢。

为什么使用多种尺寸的卷积核有助于提高特征表达能力呢?我们以图9-3为例,最左边的狗占据了图的大部分,中间的狗占了图的一部分,而最右边的狗占了图的很小一部分。采用多种尺寸的卷积核可以学习到不同尺度的特征,使网络具有更好的特征适应性。

接下来,我们结合DNN模块图像分类示例程序看一下图像分类应用的具体实现。

图像分类程序源码分析

我们借助OpenCV的示例程序来介绍图像分类应用的主要步骤。OpenCV DNN模块示例程序囊括了各种不同应用场景,它们有着相似的代码结构和流程,如图9-4所示。各种示例应用源代码的区别主要体现在最后一步:推理结果的解析和可视化。本节将详细讲解代码的每个步骤,之后各节的源码分析将重点聚焦于应用特定的参数及推理结果的解析和可视化。

下面分析图像分类示例程序源码。

首先引入必要的头文件,参见代码清单9-1。其中,fstream和sstream是C++标准库头文件,用于文件读取和文本处理。dnn.hpp、imgproc.hpp、highgui.hpp提供OpenCV API声明,common.hpp提供了一些DNN示例程序通用的函数,例如,查找输入文件位置,从模型配置文件中读取默认的运行时参数等。

9-1 引入必要的头文件

#include <fstream>
#include <sstream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include "common.hpp"

代码清单9-2定义了命令行参数,下面逐一讲解。

9-2 命令行参数定义

std::string keys="{ help  h | | Print help message. }""{ @alias  | | An alias name of model to extract preprocessing parameters from models.yml file. }""{ zoo  | models.yml | An optional path to file with preprocessing parameters }""{ input i  | | Path to input image or video file. Skip this argument to capture frames from a camera.}""{ framework f | | Optional name of an origin framework of the model. Detect it automatically if it does not set. }""{ classes  | Optional path to a text file with names of classes. }""{ backend  | 0 | Choose one of computation backends: ""0: automatically (by default), ""1: Halide language (http://halide-lang.org/), ""2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), ""3: OpenCV implementation }""{ target  | 0 | Choose one of target computation devices: ""0: CPU target (by default), ""1: OpenCL, ""2: OpenCL fp16 (half-float precision), ""3: VPU }";

接下来引用命名空间,参见代码清单9-3。我们的代码用到了cv和dnn命名空间中的API,通过显式声明命名空间,方便后续的API调用。

9-3 声明命名空间及定义全局变量

using namespace cv;
using namespace dnn;

接下来定义用于存放类别名称的变量classes:

std::vector<std::string> classes;

下面进入主函数。

首先,解析命令行参数,参见代码清单9-4。

9-4 主函数(解析命令行参数)

int main(int argc, char** argv)
{CommandLineParser parser(argc, argv, keys);const std::string modelName=parser.get<String>("@alias");const std::string zooFile=parser.get<String>("zoo");keys +=genPreprocArguments(modelName, zooFile);parser=CommandLineParser(argc, argv, keys);parser.about("Use this script to run classification deep learning networks using OpenCV.");if (argc==1 || parser.has("help")){parser.printMessage();return 0;}float scale=parser.get<float>("scale");Scalar mean=parser.get<Scalar>("mean");bool swapRB=parser.get<bool>("rgb");int inpWidth=parser.get<int>("width");int inpHeight=parser.get<int>("height");String model=findFile(parser.get<String>("model"));String config=findFile(parser.get<String>("config"));String framework=parser.get<String>("framework");int backendId=parser.get<int>("backend");int targetId=parser.get<int>("target");

如果命令行参数提供了类别文件路径,则解析类别文件并将类别名称存储到全局变量classes,参见代码清单9-5。

9-5 主函数(类别文件解析)

if (parser.has("classes"))
{std::string file=parser.get<String>("classes");std::ifstream ifs(file.c_str());if (!ifs.is_open())CV_Error(Error::StsError, "File " + file + " not found");std::string line;while (std::getline(ifs, line)){classes.push_back(line);}
}

接下来进行异常情况检查,包括命令行参数异常,以及缺失模型文件异常,参加代码清单9-6。

9-6 主函数(异常情况检查)

if (!parser.check()){parser.printErrors();return 1;}CV_Assert(!model.empty());

加载网络模型,创建DNN模块网络对象,并设置加速后端和目标运算设备,参加代码清单9-7。

9-7 主函数(初始化网络并创建显示窗口)

Net net=readNet(model, config, framework);
net.setPreferableBackend(backendId);
net.setPreferableTarget(targetId);

接下来,创建用于显示结果的窗口对象。代码如下:

static const std::string kWinName="Deep learning image classification in OpenCV";
namedWindow(kWinName, WINDOW_NORMAL);

然后,创建图像输入对象cap,用于读取指定的图片、视频文件,参见代码清单9-8。如果没有指定图片或视频文件,则从摄像头读取视频帧。

9-8 主函数(创建图像输入对象)

VideoCapture cap;
if (parser.has("input"))
cap.open(parser.get<String>("input"));
elsecap.open(0);

接下来进入图像处理循环,循环起始部分通过cap对象读取一帧图像,参见代码清单9-9。

9-9 图像处理循环(读取一帧图像)

Mat frame, blob;
while (waitKey(1) < 0)
{cap >> frame;if (frame.empty()){waitKey();break;}

然后调用blobFromImage()函数将读入的图像转换成网络模型的输入(blob),并设置网络对象,参见代码清单9-10。blobFromImage()函数会对图像进行一系列的预处理,包括调整大小、减均值、交换红蓝颜色通道等,最终返回一个一维数组(N、C、H、W)。其中,N代表批大小,实时应用中通常为1,即一次处理一帧图像数据;C代表图像通道数,一般为3,即R、G、B三种颜色;H、W分别代表图像的高度和宽度。

9-10 图像处理循环(设置网络输入)

blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight),
mean, swapRB, false);
net.setInput(blob);

接下来运行网络模型推理,代码如下:

Mat prob=net.forward();

网络推理的输出数据对象prob包含1000个概率值,分别对应1000个图像类别。

至此,网络推理运算部分结束,接下来进行推理结果的解析和可视化,参见代码清单9-11和代码清单9-12。

9-11 图像处理循环(解析网络推理输出)

Point classIdPoint;
double confidence;
// 找到概率值最大的类别id,该类别为图像所属分类
minMaxLoc(prob.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
int classId=classIdPoint.x;

9-12 图像处理循环(可视化推理结果)

// 获取网络推理运算耗时,并叠加到原始图像上
std::vector<double> layersTimes;
double freq=getTickFrequency() / 1000;
double t=net.getPerfProfile(layersTimes) / freq;
std::string label=format("Inference time: %.2f ms", t);
putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX,0.5, Scalar(0, 255, 0));
// 将图像类别标签和概率值叠加到原始图像上
label=format("%s: %.4f", (classes.empty() ?format("Class #%d", classId).c_str() :classes[classId].c_str()),confidence);
putText(frame, label, Point(0, 40), FONT_HERSHEY_SIMPLEX,0.5, Scalar(0, 255, 0));
// 显示图像
imshow(kWinName, frame);}// 循环结束,退出主函数return 0;
}

以上内容摘自**《OpenCV深度学习应用与性能优化实践》**一书,经出版方授权发布。


关注极市平台公众号(ID:extrememart),获取计算机视觉前沿资讯/技术干货/招聘面经等

超详细!使用OpenCV深度学习模块在图像分类下的应用实践相关推荐

  1. 超详细 WSL2安装+深度学习环境配置

    超详细 WSL2安装+深度学习环境配置 一点记录 ! 正式开始 ! 先决条件 WSL 启用 一.命令安装 二.手动安装(推荐方式) 三.设置 WSL 默认大版本 四.更新 WSL2 版本(可选) 五. ...

  2. FFmpeg深度学习模块的历史、现状和计划

    本文来自英特尔资深图形图像软件工程师 郭叶军在LiveVideoStack线上分享的内容,详细介绍了FFmpeg中深度学习模块的历史.现状及未来计划,并针对深度学习模块总体架构与代码实践做详细解析. ...

  3. 【LiveVideoStack线上分享】FFmpeg深度学习模块架构与代码实践

    FFmpeg可谓是音视频开发中的一把瑞士军刀,其中filter提供了很多音视频特效与图像处理的功能,除了传统的FFmpeg+OpenGL/OpenCV以外,深度学习模块提供了一种新的方式.本周四晚19 ...

  4. opencv opencl加速_回放 | OpenCV Webinar 3:OpenCV深度学习应用与原理分析

    OpenCV DNN模块提供了深度学习的推理,支持Caffe.Tensoflow.Torch.Darknet.ONNX等格式的模型,无需用户安装对应的深度学习框架,也无需进行模型格式转换,直接调用DN ...

  5. opencv 计数后不动了 训练模型时_用OpenCV,深度学习和Python进行年龄识别

    (给Python编程开发加星标,提升编程技能.) 在本教程中,您将学习如何使用OpenCV,深度学习和Python执行年龄的自动识别/预测. 学完本教程后,您将能够以相当高的精确度去自动预测静态图像文 ...

  6. 海思NNIE开发(一):海思Hi3559AV100/Hi3519AV100 NNIE深度学习模块开发与调试记录

    海思NNIE开发系列文章: 海思NNIE开发(一):海思Hi3559AV100/Hi3519AV100 NNIE深度学习模块开发与调试记录 海思NNIE开发(二):FasterRCNN在海思NNIE平 ...

  7. 图像超分中的深度学习网络

    图像超分中的深度学习网络 质量评估 操作通道 有监督算法 预上采样 后采样超分 逐步上采样 迭代上下采样 上采样的学习方式 残差块 递归学习 多路径学习 密集连接 通道注意力机制 其他卷积 像素递归网 ...

  8. 超详综述 | 基于深度学习的命名实体识别

    ©PaperWeekly 原创 · 作者|马敏博 单位|西南交通大学硕士生 研究方向|命名实体识别 论文名称:A Survey on Deep Learning for Named Entity Re ...

  9. 第 11 章 基于小波技术进行图像融合--MATLAB人工智能深度学习模块

    matlab实现基于小波技术进行图像融合–人工智能深度学习模块 该案例相对简单.实现程序 % MAINFORM MATLAB code for MainForm.fig % MAINFORM, by ...

最新文章

  1. AsyncHttpServer 异步回调,并发
  2. PrimeTime指南——合理设置约束
  3. 智能家居物联网化将成为AWE大会最大看点
  4. html怎么加断点快捷键,HTML添加断点 - osc_vyztkm1b的个人空间 - OSCHINA - 中文开源技术交流社区...
  5. linux创建django项目,Ubuntu 16.04下配置Django项目
  6. 矩池云上如何安装nvcc
  7. hdu 1203 I NEED A OFFER!
  8. python跳一跳编程构造_python实现微信跳一跳辅助工具步骤详解
  9. 菜鸟学Linux 第055篇笔记 php基础
  10. 自考 02333 软件工程 思维导图 结构化方法
  11. 常见比特和比特率单位换算
  12. 【调剂】长江大学张菲菲教授招收硕士生
  13. Android Home键拦截
  14. cogs 1752 [BOI2007]摩基亚Mokia(cdq分治+树状数组)
  15. spinner requestlayout() improperly called by during layout running second layout pass
  16. 基于扰动观测器的直流电机调速系统,(售出不退慎拍!) 有计算公式,仿真模型
  17. Microsoft edge视频(广告)加速播放
  18. 第八章 SQL修改数据库
  19. scp 命令简明介绍
  20. 22万抢注“活动”双拼域名,95后创业者却因困意失之交臂

热门文章

  1. [NOIP 2010普及组 No.3] 导弹拦截
  2. 关于flex布局,我大多数常用的几个点
  3. 软件测试 homework2
  4. Android StageFrightMediaScanner源码解析
  5. 速读训练软件_记忆力训练:如何提高注意力呢?
  6. 【论文阅读整理】TagFi: Locating Ultra-Low Power WiFi Tags Using Unmodified WiFi Infrastructure
  7. vsprintf用法解析
  8. colab出现input output error问题
  9. 北斗导航 | 复杂环境下卫星导航算法(理论)
  10. 毕业论文 | 基于脉冲耦合神经网络(PCNN)的图像特征提取:论文及源代码及参考文献