博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java自动驾驶:汽车检测
阅读量:2117 次
发布时间:2019-04-29

本文共 10854 字,大约阅读时间需要 36 分钟。

导言

在这篇文章中,我们将用Java构建一个实时视频对象检测应用程序,用于对汽车进行检测,这是自动驾驶系统的关键组成部分。在之前的文章中,我们构建了一个图像分类器,本文我们要检测物体(如汽车、行人),并用矩形标记它们。

640?wx_fmt=jpeg

对象分类

首先,我们存在对象分类的问题,假如有一个图像,我们想知道这个图像是否包含任何特定的类别,即包含汽车或者不包含。

640?wx_fmt=png

我们在之前的文章中看到如何使用VGG-16等现有架构和转移学习构建图像图像分类器。

对象本地化

现在我们能够很自信的说出图像是否具有特定对象,通常使用矩形对对象进行标记。

640?wx_fmt=png

除了图像分类,还需要指定图像中对象的位置,这是通过定义边界框完成的。边界框通常由中心(b x,b y),矩形高度(b h和矩形宽度(b w表示。

640?wx_fmt=jpeg

现在,我们需要在训练数据中为图像中的每个对象定义这四个变量。此外,网络不仅会输出图像类别编号的概率(即20%cat [1],70%dog [2],10%tiger [3]),还会输出上面定义边界框的四个变量。

只需要提供边界框(中心、高度、宽度),我们的模型通过提供更多详细的图像内容视图来输出/预测更多信息。

640?wx_fmt=png

不难想象在图像训练数据中加入更多的点可以让我们更深入的了解图像。例如,将点放在人脸上(如眼睛、嘴巴)可以告诉我们这个人正在微笑、哭泣等。

物体检测

我们甚至可以更进一步,不仅定位一个对象,还可以定位图像中的多个对象。这就需要我们进行物体检测。

640?wx_fmt=jpeg

虽然结构没有很大的改变,但是这个问题有点难度,因为需要准备大量的数据。原则上,我们只是将图像划分为较小的矩形,对于每个矩形,有相同的另外五个变量:c,(b x,b y),b h,b w 和正常预测概率(20%的猫[1],70%的狗[2])。在下文中我们将讲解更多的细节。

滑动窗口解决方案

这是一个非常直观的解决方案,你可以自己动手。我们不使用普通的汽车图像,而是尽可能的裁剪,因此图像只包含汽车。

使用裁剪后的图像训练类似于VGG-16或任何其他深度卷积神经网络。

640?wx_fmt=png

这样做很有效,但该模型经过训练只可以检测有汽车的图像,因此它对真实图像造成麻烦,因为真实的图像中不仅有汽车,还有树木、人物和标志。此外,真实图像会更大一些。

640?wx_fmt=jpeg

为了克服这些问题,我们只分析图像的一小部分,并试图弄明白该部分是否有车。更确切的说,我们可以使用滑动矩形窗口扫描图像,每次都让我们的模型预测其中是否有车。我们看这样的例子:

640?wx_fmt=gif

总而言之,我们使用正常的卷积神经网络(VGG-16)来训练具有不同大小的裁剪图像的模型,然后使用矩形来扫描图像以寻找对象(汽车)。我们可以看到,通过使用不同尺寸的矩形,找出不同形状和位置的汽车。

这个算法并不复杂,并且非常有效。但是它有两个缺点。

1、性能。我们需要多次向模型询问预测结果。每次矩形移动时,都需要执行模型以获得预测。还要针对不同的矩形尺寸再次执行此操作。解决性能的一种方法是增加矩形的步幅,但是可能导致无法检测到一些对象。在过去,模型大多是线性的,并且具有手工设计的特征,因此预测并不昂贵。因此,这个算法非常好。如今,网络规模(VGG-16的参数为1.38亿个),这种算法速度很慢,对自动驾驶等实时视频对象检测几乎没有用。

640?wx_fmt=jpeg

当移动矩形(向右和向下)时,许多共享像素不会被重复使用,而是重新计算。在接下来的部分,我们将看到一种通过使用卷积来克服此问题的最先进技术。

640?wx_fmt=jpeg

2、即使使用不同大小的边界框,也可能无法使用边界框精确的标记对象。该模型可能无法输出非常准确的边界框。例如,框可以仅包括对象的一部分。接下来的部分将探讨YOLO算法,它为我们解决了这个问题。

卷积滑动窗口解决方案

在之前的文章中看到,图像分类体系结构,无论其大小和配置如何,为不同数量的层提供完全连接的神经网络,并根据类输出多个预测。

为简单起见,我们将以较小的网络模型为例,但相同的逻辑对任何卷积网络(VGG-16、AlexNet)都有效。

640?wx_fmt=jpeg

这个简单的网络将大小为32*32*3的彩色图像作为输入。它使用相同的卷积3*3*64以获得32的输出。它使用最大池化层来减少宽度和高度,并保持第三维不变(16*16*64)。之后,我们为一个完全连接的神经网络提供两个隐藏层,每个隐藏层有256个和128个神经元。最后,输出五个类的概率(通常使用soft-max)。

让我们看看我们如何使用卷积层替换全连接层,同时保持相同效果。

640?wx_fmt=jpeg

刚用卷积滤波器替换了全连接层。实际上,16*16*256卷积滤波器是16*16*64*256的矩阵(多个滤波器),因为第三维始终与输入第三维相同。为简单起见,我们将其称为16*16*256.这实际上意味着相当于一个全连接层,因为输出1*1*256的每个元素都是输入16*16*64的线性函数。

卷积滑动窗口

为了看到用卷积滤波器替换全连接背后的想法,我们输入比32*32*3的原始输入图像大的输入图像。让我们拍摄36*36*3的输入图像:

640?wx_fmt=jpeg

此图像有四行四列(绿色36*36),比原始图像(蓝色32*32)多。如果我们使用stride = 2和全连接的滑动窗口,那么需要将原始图像大小移动九次以覆盖所有内容(三个移动显示为黑色矩形)。因此,也要执行九次模型。

让我们尝试将这个新的更大的矩阵作为输入应用于新的模型,仅使用卷积层。

640?wx_fmt=jpeg

正如我们所看到的,输出从1*1*5变为3*3*5,与全连接相比,输出将始终为1*1*5.回想一下,我们必须将滑动窗口移动九次以覆盖所有图像,是不是等于3*3?实际上,那些3*3各自代表滑动窗口的1*1*5级概率预测结果。

这是一种非常先进的技术,因为我们能够一次获得所有的九个结果,而无需多次执行具有数百万个参数的模型。

YOLO

640?wx_fmt=jpeg

虽然我们已经通过引入卷积滑动窗口解决了性能问题,但是模型仍然可能无法输出非常精确的边界框。让我们看看YOLO如何解决这个问题。

首先,我们通常会对每个图像进行标记并标记要检测的对象。每个对象都由带有四个变量的边界框标记,对象的中心(b x,b y,矩形高度(b h,矩形宽度(b w。之后,每个图像被分为较小数量的矩形,通常是19*19个矩形,但是为了简单,我们使用8*9.

640?wx_fmt=jpeg

边界框(红色)和对象可以是几个框(蓝色)的一部分,因此我们仅将对象和边界框分配给拥有对象中心的框(黄色)。我们用四个附件变量训练模型(除了告诉对象是汽车x,b y,b h和b w),并将它们分配给拥有中心的框(x,b y)。在使用此标记数据训练神经网络之后,让模型预测四个变量、值或边界框。

我们让模型学习如何使用边界框标记对象,而不是使用预定义的边界框大小进行扫描并尝试拟合对象。因此,边界框是灵活的,而且准确度更高。

让我们看看如何能够代表输出,因为除了1-car,2-pedestrian等类之外,我们还有其他四个变量(b x,b y,b h,b w。实际上,还添加了另一个变量c,它简单的告诉图像是否具有我们想要检测的任何对象。

640?wx_fmt=jpeg

  • c = 1(红色)表示至少有一个对象,因此值得查看概率和边界框。

  • c = 0(红色)表示图像没有我们想要的对象,因此我们不关心概率或边界框。

边界框规范

我们要以特定的方式标记训练数据,以使YOLO算法正常工作。YOLO V2格式要求边界框尺寸x,b y和b h,b w相对于原始图像宽度和高度。假设有一个图像300*400,边界框尺寸是Bwidth =30, Bheight=15, Bx=150, By=80,必须转换为Bw=30/300, Bh=15/400, Bx=150/300, By=80/400

本文展示了如何使用BBox标签工具标记数据。除了YOLO如何在内部标记训练数据之外,预测的执行方式也略有不同。

640?wx_fmt=jpeg

YOLO预测的边界框是相对于拥有对象中的框(黄色)定义的。框的左上角从(0,0)开始,右下角是(1,1)。中心(x,b y)肯定在范围0-1(Sigmoid函数确定),因为该点在框内。h,b w 与w和h 的值(黄色)成比例的计算,因此值可以大于1.在图中,我们看到边界框的宽度几乎是框宽的1.8倍。h约为框高h的1.6倍。

在预测之后,我们看到预测框与开头标记真实边界框相交多少。基本上,我们尝试最大化它们之间的交集,因此,在理想情况下,预测的边界框与标记的边界框完全相交。

原则是这样的:使用边界框()提供更专业的标记数据,拆分图像,并分配到包含中心的框(负责检测对象),使用卷积滑动窗口网络进行训练,并预测对象及其位置。

存在的两个问题

首先,即使在训练时间内,对象被分配到一个框(包含对象中心的框),在测试是(预测时)几个框(黄色)可能认为他们具有对象的中心(红色),因此为同一个对象定义自己的边界框。目前,Deeplearning 4j没有提供实现,所以在GitHub上找了一个简单的实现,它的作用是首先选择具有最大的框作为预测概率(它不仅有1或0值,而是可以在0-1范围内)。然后,删除与该框相交超过某个阈值的每个框。它再次启动相同的逻辑,直到没有剩余的边界框。

640?wx_fmt=jpeg

其次,由于我们预测多个物体(汽车、行人、信号灯等),可能会发生两个或多个物体的中心是相同的。使用anchor boxes,我们选择几种形状的边界框,我们发现更多用于我们想要检测的对象。YOLO V2论文使用k-means算法完成此操作,也可以手动完成。之后,我们修改输出以包含之前看到的相同结构(c,b x,b y,b h,b w,C1,C2 ...),但是对于每一个选定的anchor boxes形状,所以,现在可能有这样的事情:

640?wx_fmt=jpeg

应用

训练深度网络需要付出很大的努力,并且需要有意义的数据和处理能力。我们将使用Tiny YOLO,它基于Darknet参考网络,比普通的YOLO模型快得多,但是不太精确。要使用受过VOC训练的版本:

 
1wget https://pjreddie.com/media/files/tiny-yolo-voc.weights2./darknet detector test cfg/voc.data cfg/tiny-yolo-voc.cfg tiny-yolo-voc.weights data/dog.jpg//pjreddie.com/media/files/tiny-yolo-voc.weights 2./darknet detector test cfg/voc.data cfg/tiny-yolo-voc.cfg tiny-yolo-voc.weights data/dog.jpg

在GPU上,它以大于200FPS运行。

目前发布的版本Deeplearning 4j 0.9.1不停工TinyYOLO,但是0.9.2-SNAPSHOT确实如此。首先我们需要在Maven加载SNAPSHOT:

 
1
2    
3        
a
4        
http://repo1.maven.org/maven2/
5    
6    
7        
snapshots-repo
8        
https://oss.sonatype.org/content/repositories/snapshots
9        
10            
false
11        
12        
13            
true
14            
daily
15        
16    
17
18
19    
20        
org.deeplearning4j
21        
deeplearning4j-core
22        
${deeplearning4j}
23    
24    
25        
org.nd4j
26        
nd4j-native-platform
27        
${deeplearning4j}
28    
2    
3        
a
4        
http://repo1.maven.org/maven2/
5    
6    
7        
snapshots-repo
8        
https://oss.sonatype.org/content/repositories/snapshots
9        
10            
false
11        
12        
13            
true
14            
daily
15        
16    
17 18
19    
20        
org.deeplearning4j
21        
deeplearning4j-core
22        
${deeplearning4j}
23    
24    
25        
org.nd4j
26        
nd4j-native-platform
27        
${deeplearning4j}
28    

然后,准备较短的代码加载模型:

 
1private TinyYoloPrediction() {2    try {3        preTrained = (ComputationGraph) new TinyYOLO().initPretrained();4        prepareLabels();5    } catch (IOException e) {6        throw new RuntimeException(e);7    }8}private TinyYoloPrediction() {
2    try {
3        preTrained = (ComputationGraph) new TinyYOLO().initPretrained(); 4        prepareLabels(); 5    } catch (IOException e) {
6        throw new RuntimeException(e); 7    } 8}

preareLabels只是使用来自用于训练模型的数据集PASCAL VOC的标签。

视频帧捕获使用JavaCV与CarVideoDetection:

 
1FFmpegFrameGrabber grabber; 2grabber = new FFmpegFrameGrabber(f); 3grabber.start(); 4while (!stop) { 5    videoFrame[0] = grabber.grab(); 6    if (videoFrame[0] == null) { 7        stop(); 8        break; 9    }10    v[0] = new OpenCVFrameConverter.ToMat().convert(videoFrame[0]);11    if (v[0] == null) {12        continue;13    }14    if (winname == null) {15        winname = AUTONOMOUS_DRIVING_RAMOK_TECH + ThreadLocalRandom.current().nextInt();16    }17    if (thread == null) {18        thread = new Thread(() -> {19            while (videoFrame[0] != null && !stop) {20                try {21                    TinyYoloPrediction.getINSTANCE().markWithBoundingBox(v[0], videoFrame[0].imageWidth, videoFrame[0].imageHeight, true, winname);22                } catch (java.lang.Exception e) {23                    throw new RuntimeException(e);24                }25            }26        });27        thread.start();28    }29    TinyYoloPrediction.getINSTANCE().markWithBoundingBox(v[0], videoFrame[0].imageWidth, videoFrame[0].imageHeight, false, winname);30    imshow(winname, v[0]);  2grabber = new FFmpegFrameGrabber(f);  3grabber.start();  4while (!stop) {
5    videoFrame[0] = grabber.grab(); 6    if (videoFrame[0] == null) {
7        stop(); 8        break; 9    } 10    v[0] = new OpenCVFrameConverter.ToMat().convert(videoFrame[0]); 11    if (v[0] == null) {
12        continue; 13    } 14    if (winname == null) {
15        winname = AUTONOMOUS_DRIVING_RAMOK_TECH + ThreadLocalRandom.current().nextInt(); 16    } 17    if (thread == null) {
18        thread = new Thread(() -> {
19            while (videoFrame[0] != null && !stop) {
20                try {
21                    TinyYoloPrediction.getINSTANCE().markWithBoundingBox(v[0], videoFrame[0].imageWidth, videoFrame[0].imageHeight, true, winname); 22                } catch (java.lang.Exception e) {
23                    throw new RuntimeException(e); 24                } 25            } 26        }); 27        thread.start(); 28    } 29    TinyYoloPrediction.getINSTANCE().markWithBoundingBox(v[0], videoFrame[0].imageWidth, videoFrame[0].imageHeight, false, winname); 30    imshow(winname, v[0]);

所以代码正在做的是从视频中获取帧并传递给TinyYOLO预训练模型。从那里,首先将图像帧缩放到416*416*3,然后将其提供给TinyYOLO以预测和标记边界框。

 
1public void markWithBoundingBox(Mat file, int imageWidth, int imageHeight, boolean newBoundingBOx,String winName) throws Exception { 2    int width = 416; 3    int height = 416; 4    int gridWidth = 13; 5    int gridHeight = 13; 6    double detectionThreshold = 0.5; 7    Yolo2OutputLayer outputLayer = (Yolo2OutputLayer) preTrained.getOutputLayer(0); 8 9        INDArray indArray = prepareImage(file, width, height);10        INDArray results = preTrained.outputSingle(indArray);11        predictedObjects = outputLayer.getPredictedObjects(results, detectionThreshold);12        System.out.println("results = " + predictedObjects);13        markWithBoundingBox(file, gridWidth, gridHeight, imageWidth, imageHeight);14    imshow(winName, file);15}public void markWithBoundingBox(Mat file, int imageWidth, int imageHeight, boolean newBoundingBOx,String winName) throws Exception {
2    int width = 416; 3    int height = 416; 4    int gridWidth = 13; 5    int gridHeight = 13; 6    double detectionThreshold = 0.5; 7    Yolo2OutputLayer outputLayer = (Yolo2OutputLayer) preTrained.getOutputLayer(0); 8 9        INDArray indArray = prepareImage(file, width, height); 10        INDArray results = preTrained.outputSingle(indArray); 11        predictedObjects = outputLayer.getPredictedObjects(results, detectionThreshold); 12        System.out.println("results = " + predictedObjects); 13        markWithBoundingBox(file, gridWidth, gridHeight, imageWidth, imageHeight); 14    imshow(winName, file); 15}

在预测后,我们应该准备好边界框尺寸的预测值,我们已经实现了菲最大抑制,因为正如我们所提到的,YOLO在测试时预测每个对象有多个边界框。

之后,使用另一个线程(在播放视频的那个线程旁边),我们更新视频以获得矩形好喝检测到的对象的标签。

考虑到在CPU上运行,预测非常快(实时);在GPU上,将有更好的实时检测效果。

结果展示

只需要从源代码运行Run,或者如果你不想使用IDE打开它,只需运行mvn clean install exec:java即可。

运行后,可以看到以下视图:

640?wx_fmt=jpeg

640?wx_fmt=jpeg

长按二维码 ▲

订阅「架构师小秘圈」公众号

如有启发,帮我点个在看,谢谢↓

转载地址:http://ofmef.baihongyu.com/

你可能感兴趣的文章
Fiddler 抓包工具总结
查看>>
【雅思】雅思需要购买和准备的学习资料
查看>>
【雅思】雅思写作作业(1)
查看>>
【雅思】【大作文】【审题作业】关于同不同意的审题作业(重点)
查看>>
【Loadrunner】通过loadrunner录制时候有事件但是白页无法出来登录页怎么办?
查看>>
【English】【托业】【四六级】写译高频词汇
查看>>
【托业】【新东方全真模拟】01~02-----P5~6
查看>>
【托业】【新东方全真模拟】03~04-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST05~06-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST09~10-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST07~08-----P5~6
查看>>
solver及其配置
查看>>
JAVA多线程之volatile 与 synchronized 的比较
查看>>
Java集合框架知识梳理
查看>>
笔试题(一)—— java基础
查看>>
Redis学习笔记(二)— 在linux下搭建redis服务器
查看>>
Redis学习笔记(三)—— 使用redis客户端连接windows和linux下的redis并解决无法连接redis的问题
查看>>
Intellij IDEA使用(一)—— 安装Intellij IDEA(ideaIU-2017.2.3)并完成Intellij IDEA的简单配置
查看>>
Intellij IDEA使用(二)—— 在Intellij IDEA中配置JDK(SDK)
查看>>
Intellij IDEA使用(三)——在Intellij IDEA中配置Tomcat服务器
查看>>