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

加入极市专业CV交流群,与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度 等名校名企视觉开发者互动交流!

同时提供每月大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流。关注 极市平台 公众号 ,回复 加群,立刻申请入群~

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

深度学习理论的广泛研究促进了其在不同场景的应用。在计算机视觉领域,图像分类、目标检测、语义分割和视觉风格变换等基础任务的性能也因为采用了深度学习的方法而有了飞跃性的提升。本章将为读者梳理深度学习方法在这些基本应用场景的应用情况,并结合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"));else cap.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深度学习应用与性能优化实践》
以上内容摘自《OpenCV深度学习应用与性能优化实践》一书,经出版方授权发布。
(0)

相关推荐

  • 几种经典的图像分类模型

    计算机视觉中经典的卷积神经网络结构 声明:本文图片和文字来自百度AI Studio 网站,仅仅是做一个搬运处理,以便于后期需要时查询. LeNet:Yan LeCun等人于1998年第一次将卷积神经网 ...

  • OpenCV4.0-alpha发布!新增多个深度学习特性

    OpenCV4.0-alpha发布!新增多个深度学习特性 (欢迎关注"我爱计算机视觉"公众号,一个有价值有深度的公众号~) 本定于2018年7月发布的OpenCV4.0再次跳票,昨 ...

  • 【从零学习OpenCV 4】深度神经网络应用实例

    重磅干货,第一时间送达 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍<OpenCV 4开发详解>.为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通 ...

  • 1*1卷积核的作用

    作用:1. 实现跨通道的交互和信息整合:2. 进行卷积核通道数的降维和升维详细介绍:(作用1) image.png图5 多通道+多卷积核做卷积示示意图.缩进如图5,输入图像layer m-1有4个通道 ...

  • 【opencv】调用darknet模型实现实时目标检测

    继[opencv]调用caffe.tensorflow.darknet模型 之后,我们接下来运行一个具体的实例. 1.下载模型 Darknet是一个用C语言编写的小众的神经网络框架,即和tensorf ...

  • (8条消息) opencv 使用DNN模块调用Tensorflow的Mask

    文章目录 环境准备 利用opencv自带的工具生成对应的模型对应的描述文件 DNN模块使用 demo 效果 环境准备 Python3 OpenCV 4.5.0 (4版本以上都可以) Mask-RCNN ...

  • opencv调用darknet

    本文主要介绍如何通过opencv调用已经训练好的darknet模型进行目标检测 1.模型及配置文件下载 需要下载以下文件 已经训练好的模型权重文件 **.weights 模型配置文件 yolov3.c ...

  • 超详细 | 脑梗死高阶学习笔记

    [临床表现]好发于中老年人,男女发病比例相似.患者通常有某些未加注意的前驱症状(如头昏.头痛等),部分患者有短暂性脑缺血发作病史或高血压动脉硬化病史.患者多在休息或睡眠中发病,常表现为不能说话,一侧肢 ...

  • 论文速递 | 一份超全易懂的深度学习在图像去噪的综述

    加入极市专业CV交流群,与6000+来自腾讯,华为,百度,北大,清华,中科院等名企名校视觉开发者互动交流!更有机会与李开复老师等大牛群内互动! 同时提供每月大咖直播分享.真实项目需求对接.干货资讯汇总 ...

  • 超详细!均胜电子深度研究报告!

    嗨(#^.^#)大家好!我叫诗诗,90后,广东财经大学金融硕士,目前在广州某证券公司做证券研究员,每晚回家都会复盘,还会不定时分享最新的行业研报以及策略研报给大家. 由于证券从业人员不能炒股,就偷偷用 ...

  • 超详细解读Java接口:模块通信协议以及默认方法和静态方法

    有不少学习Java的同学一直有个疑问,不仅在初学者中很普遍,连许多经验丰富的老手也很难表述清楚,那就是:Java接口到底是什么? 来看看孙鑫老师的讲解,本文干货含量拉满,这可能是距离你深入理解Java ...

  • OpenCV深度学习人脸识别示例——看大佬如何秀恩爱

    PyImageSearch博主Adrian Rosebrock昨日发表博文,展示如何使用OpenCV的深度学习工具在小库上进行人脸识别.昨天是我们中国人的传统佳节--中秋节, Adrian推送博文时也 ...

  • OpenCV深度学习文本检测示例程序(EAST text detector)

    OpenCV 3.4.2和即将发布的OpenCV 4加入了一个强大的文本检测算法--EAST text detector,该算法来自于旷视科技发表于CVPR2017的论文<EAST: An Ef ...

  • 经验分享:超详细的C研发学习路线(含面试指南)

    学习建议 https://m.toutiao.com/is/JbbJWSG/ 对于技术岗,软件基础知识可以说是个人的硬实力,是你能通过面试的一个大前提.而说到软件基础的学习,对于很多还没有基础的同学可 ...

  • 史上超全素描五官解析,超详细,建议收藏学习

    眼睛[素描头像教学--眼睛塑造]....................................... 头像里,眼睛的重要性是不言而喻的.作画时,应把握好它的角度,厚度,深度以及细节的刻画.. ...

  • 彩铅动物-斗牛犬的超详细的画法步骤及注意事项(下)

    首先看下面的视频 下面是步骤图解 接着上篇:通过以彩铅动物-斗牛犬为例我们进行了非常详细的解说,希望可以帮助到你们,如果有不明白的留言区告诉我... 本文接着上篇继续分享下面的5 .6.7三点.注意: ...