论文地址:https://arxiv.org/pdf/1506.02640.pdf
Tensorflow版本yolo v1:https://github.com/gliese581gg/YOLO_tensorflow
参考博客
https://blog.csdn.net/m0_37192554/article/details/81092514
https://blog.csdn.net/qq_27825451/article/details/88941474
所要解决的问题:对象识别和定位
输入一张图片,要求输出其中所包含的对象的类别----(识别),以及每个对象的位置(就是包含对象的矩形框)--定位.
对象识别和定位,可以看成两个任务:
(1)找到图片中某个存在对象的区域,
(2)识别出该区域中具体是哪个对象.
如何做?
最简单的想法,
step 1:遍历图片中所有可能的位置(448x448=200704, 共20多万个位置)
step2: 在每个位置构造不同尺寸(就是宽高)的矩形,----这个不同尺寸大小就很难刻画了,无穷多的尺寸啊,比如现在常规的9种类型
step3: 判断下上面得到的矩形中有没有物体,若有,说下是啥物体,狗还是猫?
经过3个步骤,任务完成,但是,这个计算量200704*9=1806336(180万左右的矩形框啊),累不累?这效率太低了,蛮干啊!!!
RCNN 系如何做?
这个系列开创性的提出了候选区(region proposals)的方法,先从图片中搜索出一些可能存在对象的候选区(selective search),大概2000个左右,然后对这2000个候选区进行对象识别,大幅提升了对象识别和定位的效率.
YOLO如何做?
1. 把图片划分成SXS=7*7=49个网格,每个网格(grid)预测B=2个box矩形框和c个(具体多少个类别,根据实际需要决定,文中c=20)类别,这样对一张448x448图片总共预测49*2=98个bouding box,可以理解为98个候选区.
note:
1. 相比于原来需要判断180万左右的矩形框,这里我们只需要搞定98个框,这任务量可是断崖般下降啊.
2. 实际上,yolo并没有真正去掉候选区,而是采用了预定义的候选区,也就是98个框,它们很粗略的覆盖了图片的整个区域,
3. rcnn虽然会找到一些候选区,但毕竟是候选,等真正识别出其中的对象以后,还要对候选区进行微调,使之更接近真实的bounding box, 这个过程就是边框回归:将候选区bounding box调整到更接近真实的bounding box.既然反正最后都是要调整的,干嘛还要先费劲去找候选区呢?大致有个范围就行了,所以yolo就这么干了.------去掉候选区这个步骤.
yolo实现方案
将这个模型简单化为:
1.其实前面就是提取特征后,后面为7*7*30的输出,下面说一下模型输入输出,模型的输出为什么是7*7*30呢?
首先来看7×7表示的意思
其实对于每一个grid代表了(448/7)的区域表达,当然这里的7*7也是可以调节大小的,那30又是如何形成的通道大小呢?
A:2个bounding box的位置(8个通道)
每个bouding box需要4个数值来表示其位置,(center_x,center_y,width,height),即bounding box的中心点的(x,y)坐标,boudning box的宽度,高度),2个bouding box共需要8个数值来表示其位置。
B:2个bounding box的置信度(2个通道)
bouding box的置信度=该bounding box内存在对象的概率×该bounding box与该对象实际
c:20分类概率(20个通道)
下面我们来说一下剩下20维度的分类通道,每一个通道代表一个类别的分类概率,因为yolo支持识别20种不同的对象(人,鸟,猫等),所以这里有20个值表示该网格位置存在人和一种对象的概率。
因为只是一些常规的神经网络结构,所以,理解yolo的设计的时候,重要的是理解输入和输出的映射关系,
1.首先将图片resize到448x448,。主要是因为YOLO的网络中,卷积层最后接了两个全连接层,全连接层是要求固定大小的向量作为输入,所以倒推回去也就要求原始图像有固定的尺寸。那么YOLO设计的尺寸就是448*448。
2。将上述448x448的图片划分为SXS的网格(S=7),这里可以认为网络从一个448*448的原始图片提取出了一个7*7的特征图,如下图所示,从7*7特征图的角度来看原始的448x448图片,可以看成将448x448的图片分成了7*7个grid,每个小grid中有448/7=64,64x64个像素,物体的中心落在哪一个网格内,这个网格就负责预测该物体的置信度,类别以及位置。如下图
3.网络最后的输出是 S×S×(B*5+C) 的数据块 ,其中,对于一个box,其负责5个预测值(cent_x,cent_y,w,h, p),每个grid对应一个B*5+C维张量输出.
x,y:box的中心坐标,注意这里是相对于自身网格的中心坐标,范围0-1,相对于是什么意思呢?相对于的体现在:比如图片尺寸448×448,一个box在这张图片上的box中心点位置为(120,220),可以计算得到这个box位于第3行第2列的grid中,我们可以称为第 16个grid, 则相对于这个grid,中心点位置为(120,220)的box相对于这个grid做偏移,error=(box-grid)/448
w,h:box的宽,高, 这里是相对于整张图片的宽高,w,h,所以范围也是0-1,相对于的体现在:比如图片尺寸448×448,一个box在这张图片上的宽=100,高=200,则相对于之后,这个box的宽=100/448=0.223, 这个box的高=200/448=0.446
p:每一个格子包含物体的置信度
C:格子中是某一个物体的概率probability;
备注:训练开始阶段,网络预测的bounding box可能都是乱来的.
这个30维的向量包含了那些信息呢?
问题:
1.置信度是刻画什么呢?是说这个box中有狗的概率值?还是这个box的位置与groud_truth 之间的iou较大?
答:这个置信度confidence scores反映了模型对于这个box的预测,包含了两个方面
(1):该box里是否含有物体,注意这里是不管什么类别的,即不管是狗还是猫的,先看下有检测目标的概率.
(2):以及这个box的坐标预测的有多准,(多准是通过iou定义的,iou越大,说明越准,iou越小说明越不准).
置信度计算公式为:,这个公式是什么意思呢?
若这个box中不存在检测目标,即,既然都不存在检测目标,自然不存在什么iou了,则confidence score自然=0,如果存检测目标的话,则,此时confidence score则为预测box与groud truth box之间的iou.
更具体的来讲: 若box中存在检测目标,则期望其置信度confidence score=iou.
因此: 本质上来讲,置信度刻画的是预测box与groud truth box之间的iou,没有去管这个box中是否有狗.
① 20个对象分类的概率
因为YOLO支持识别20种不同的对象(人、鸟、猫、汽车、椅子等),所以这里有20个值表示该网格grid位置存在任一种对象的概率。
,之所以写成条件概率,意思是如果该grid网格存在一个对象Object,那么它是类别c_i的概率是.
② 2个bounding box的位置
每个bounding box需要4个数值来表示其位置,(Center_x,Center_y,width,height),即(bounding box的中心点的x坐标,y坐标,bounding box的宽度,高度),2个bounding box共需要8个数值来表示其位置。
③ 2个bounding box的置信度
bounding box的置信度 = 该bounding box内存在对象的概率 * 该bounding box与该对象实际bounding box的IOU
用公式来表示就是
是bounding box内存在对象的概率,区别于上面第①点的 。并不管是哪个对象,它体现的是 有或没有对象的概率。第①点中的意思是假设已经有检测目标在box中了,这个对象具体是哪一类的概率。是预测的 bounding box 与 对象真实bounding box 的IOU(Intersection over Union,交并比)。要注意的是,现在讨论的30维向量中的bounding box是YOLO网络的输出,也就是预测的bounding box。所以体现了预测的bounding box与真实bounding box的接近程度。
还要说明的是,虽然有时说"预测"的bounding box,但这个IOU是在训练阶段计算的。等到了测试阶段(Inference),这时并不知道真实对象在哪里,只能完全依赖于网络的输出,这时已经不需要(也无法)计算IOU了,也就是说网络会自己预测一个值,这个值是如何计算得到的,是网络根据自身的权重和一系列激活函数计算出来的,我们给它起名字叫做置信度,并且期望这个值=.
另外论文中经常提到responsible。比如:Our system divides the input image into an S*S grid. If the center of an object falls into a grid cell, that grid cell is responsible for detecting that object. 这个 responsible 有点让人疑惑,对预测"负责"是啥意思。其实没啥特别意思,就是一个Object只由一个grid来进行预测,不要多个grid都抢着预测同一个Object。更具体一点说,就是在设置训练样本的时候,样本中的每个Object归属到且仅归属到一个grid,即便有时Object跨越了几个grid,也仅指定其中一个。具体就是计算出该Object的bounding box的中心位置,这个中心位置落在哪个grid,该grid对应的输出向量中该对象的类别概率是1(该gird负责预测该对象),所有其它grid对该Object的预测概率设为0(不负责预测该对象)。
备注:这里为什么要对每一个grid预测B个box(x,y,w,h,confidence),由于每个单元格grid预测多个边界框box(即文中的B,B取值为2)。但是其对应类别只有一个。那么在训练时,如果该单元格内确实存在目标,那么只选择与ground truth的IOU最大的那个边界框box来负责预测该目标,而其它边界框认为不存在目标。这样设置的一个结果将会使一个单元格grid对应的边界框box更加专业化,其可以分别适用不同大小,不同高宽比的目标,从而提升模型性能.
3.2 什么是物体的概率probability?
每一个栅格grid还要预测C个 conditional class probability(条件类别概率),即Pr(Classi|Object)。即在一个grid栅格包含一个Object的前提下,它属于某个类的概率。 作者在VOC上实验,所以C取为20(这里为什么不是类似Fast RCNN的21类,因为是否为背景,作者放到了上面的置信度confidence score中)
3.1.3 每一个grid的输出数据维度的对应关系
通过上面的3.1和3.2的说明,我们对每一个grid的预测会产生下面一些数据
上图展示的是那个“红色的grid”预测的两个不同的“黄色边框”box,每一个边框box所携带的信息是5维,故而产生了10维向量。
再参考一个图片如下:
上图中前面的两个红色圈圈就不说了,表示的每一个grid预测的两个box的信息,共10维,后面的20维是每一个类别的概率,最后一共组成30维的向量。
讨论
① 一张图片最多可以检测出7x7=49个对象
每个30维向量中只有一组(20个)对象分类的概率,也就只能预测出一个对象。所以输出的 7*7=49个 30维向量,最多表示出49个对象。
② 总共有 49*2=98 个候选区(bounding box)
每个30维向量中有2组bounding box,所以总共是98个候选区。
4. yolo网络的设计架构
Yolo采用卷积网络来提取特征,然后使用全连接层来得到预测值。网络结构参考GoogLeNet分类网络结构模型(灵感来源于GoogLeNet,但是并没有采取其inception的结构),使用1x1卷积层(此处1x1卷积层的存在是为了跨通道信息整合)+3x3卷积层简单替代,包含24个卷积层和2个全连接层,完整的网络结构如图所示
YOLO的结构非常简单,就是单纯的卷积、池化最后加了两层全连接。单看网络结构的话,和普通的CNN对象分类网络几乎没有本质的区别,最大的差异是最后输出层用线性函数做激活函数,因为需要预测bounding box的位置(数值型),而不仅仅是对象的概率。所以粗略来说,YOLO的整个结构就是输入图片经过神经网络的变换得到一个输出的张量,如下图所示。
我们通过开头的tensorflow版本的实现来看下每层如何展现的
def build_networks(self):
if self.disp_console : print "Building YOLO_small graph..."
self.x = tf.placeholder('float32',[None,448,448,3])
self.conv_1 = self.conv_layer(1,self.x,64,7,2) #卷积层1,def conv_layer(self,idx,inputs,filters,size,stride),这里卷积核shape=[7,7,64],stride=2,same方式,卷积后的输出维度=[224,224,64]
self.pool_2 = self.pooling_layer(2,self.conv_1,2,2)#池化层1,本质同卷积层一样,因为步长=2,输出的维度降低一半=[112,112,2]
self.conv_3 = self.conv_layer(3,self.pool_2,192,3,1) #卷积层2,卷积核=[3,3,192]卷积后的输出维度=[112,112,192]
self.pool_4 = self.pooling_layer(4,self.conv_3,256,2,2)#池化层2,卷积核=[2,2,2]因为步长=2,输出的维度降低一半=[56,56,256]
self.conv_5 = self.conv_layer(5,self.pool_4,128,1,1) #卷积层3,卷积核=[1,1,128]步长=1,卷积后的输出维度=[56,56,256]
self.conv_6 = self.conv_layer(6,self.conv_5,256,3,1) #卷积层4,卷积核=[3,3,256],步长=1,卷积后的输出维度=[56,56,256]
self.conv_7 = self.conv_layer(7,self.conv_6,256,1,1) #卷积层5,卷积核=[1,1,256],步长=1,卷积后的输出维度=[56,56,256]
self.conv_8 = self.conv_layer(8,self.conv_7,512,3,1) #卷积层6,卷积核=[3,3,512],步长=1,卷积后的输出维度=[56,56,512]
self.pool_9 = self.pooling_layer(9,self.conv_8,2,2)#池化层3,卷积核=[2,2,2]因为步长=2,输出的维度降低一半=[28,28,512]
--------------------------第一块------------------------------------------------
self.conv_10 = self.conv_layer(10,self.pool_9,256,1,1) #卷积层7
self.conv_11 = self.conv_layer(11,self.conv_10,512,3,1) #卷积层8
-------------------------第二块--------------------------------------------------
self.conv_12 = self.conv_layer(12,self.conv_11,256,1,1) #卷积层9
self.conv_13 = self.conv_layer(13,self.conv_12,512,3,1) #卷积层10
------------------------第三块------------------------------------------------
self.conv_14 = self.conv_layer(14,self.conv_13,256,1,1) #卷积层11
self.conv_15 = self.conv_layer(15,self.conv_14,512,3,1) #卷积层12
-----------------------第四块-----------------------------------------------
self.conv_16 = self.conv_layer(16,self.conv_15,256,1,1) #卷积层13
self.conv_17 = self.conv_layer(17,self.conv_16,512,3,1) #卷积层14
----------------------------------------------------------------------------
self.conv_18 = self.conv_layer(18,self.conv_17,512,1,1) #卷积层15,1x1x512
self.conv_19 = self.conv_layer(19,self.conv_18,1024,3,1) #卷积层16,3x3x1024
self.pool_20 = self.pooling_layer(20,self.conv_19,2,2)#池化层,输出维度[14,14,1024]
````````````````````````````第一块``````````````````````````````````````````
self.conv_21 = self.conv_layer(21,self.pool_20,512,1,1) #卷积层17
self.conv_22 = self.conv_layer(22,self.conv_21,1024,3,1) #卷积层18
````````````````````````````第二块``````````````````````````````````````````````````
self.conv_23 = self.conv_layer(23,self.conv_22,512,1,1) #卷积层19
self.conv_24 = self.conv_layer(24,self.conv_23,1024,3,1) #卷积层20
```````````````````````````````````````````````````````````````````````````````````
self.conv_25 = self.conv_layer(25,self.conv_24,1024,3,1) #卷积层21,3x3x1024
self.conv_26 = self.conv_layer(26,self.conv_25,1024,3,2) #卷积层22, 3x3x1024,步长=2,维度降低一半,输出维度=[7,7,1024]
self.conv_27 = self.conv_layer(27,self.conv_26,1024,3,1) #卷积层23,3x3x1024
self.conv_28 = self.conv_layer(28,self.conv_27,1024,3,1) #卷积层24,3x3x1024
self.fc_29 = self.fc_layer(29,self.conv_28,512,flat=True,linear=False)#
self.fc_30 = self.fc_layer(30,self.fc_29,4096,flat=False,linear=False)
#skip dropout_31
self.fc_32 = self.fc_layer(32,self.fc_30,1470,flat=False,linear=True)
self.sess = tf.Session()
self.sess.run(tf.initialize_all_variables())
self.saver = tf.train.Saver()
self.saver.restore(self.sess,self.weights_file)
if self.disp_console : print "Loading complete!" + '\n'
5. 损失函数
yolov1的损失函数全是均方误差,需要理解的是其含义。
(1) 为了平衡坐标预测误差和分类误差,yolo对坐标误差乘上了一个系数,以增加坐标回归误差所占的比重;
(2)为了平衡有目标的box和没有目标的box(正负样本),对负样本box乘了系数,即相对含有目标的正样本box来讲,不含目标的负样box对损失函数的贡献较小,这是因为实际上来讲,98个box中,大部分的box都不含目标,即负样本的数量较多,应该抑制这些负样本的影响.
(3)为了在一定程度上区别对待大物体和小物体(相对于大物体,小物体宽高偏移一点会引起更大的iou误差),yolo用开根的方法缓解.
举例来说,大小为10和大小为100的目标,预测大小分别为20和110,损失一样(都是10),但是显然小目标检测的更差一些,开根后,
,相当于强化了小目标的wh的损失。
4)第3行是存在对象的bounding box的置信度误差。带有意味着只有"负责"(IOU比较大)预测的那个bounding box的置信度才会计入误差。就像前面所说的,分成grid cell包含与不包含object两种情况。这里注意下因为每个grid cell包含两个bounding box,所以只有当ground truth 和该网格中的某个bounding box的IOU值最大的时候,才计算这项。
5)第五行表示预测类别的误差,注意前面的系数只有在grid cell包含object的时候才为1。
所以具体实现的时候是什么样的过程呢?
训练的时候:输入N个图像,每个图像包含M个object,每个object包含4个坐标(x,y,w,h)和1个label。然后通过网络得到7*7*30大小的三维矩阵。每个1*30的向量前5个元素表示第一个bounding box的4个坐标和1个confidence,第6到10元素表示第二个bounding box的4个坐标和1个confidence。最后20个表示这个grid cell所属类别。注意这30个都是预测的结果。然后就可以计算损失函数的第一、二 、五行。至于第二三行,confidence可以根据ground truth和预测的bounding box计算出的IOU和是否有object的0,1值相乘得到。真实的confidence是0或1值,即有object则为1,没有object则为0。 这样就能计算出loss function的值了。
一个grid cell中是否有object怎么界定?
首先要明白grid cell的含义,以文中7*7为例,这个size其实就是对输入图像(假设是448*448)不断提取特征然后sample得到的(缩小了64倍),然后就是把输入图像划分成7*7个grid cell,这样输入图像中的64个像素点就对应一个grid cell。回归正题,那么我们有每个object的标注信息,也就是知道每个object的中心点坐标在输入图像的哪个位置,那么不就相当于知道了每个object的中心点坐标属于哪个grid cell了吗,而只要object的中心点坐标落在哪个grid cell中,这个object就由哪个grid cell负责预测,也就是该grid cell包含这个object。另外由于一个grid cell会预测两个bounding box,实际上只有一个bounding box是用来预测属于该grid cell的object的,因为这两个bounding box到底哪个来预测呢?答案是:和该object的ground truth的IOU值最大的bounding box。
6.训练
YOLO的最后一层采用线性激活函数,其它层都是Leaky ReLU。训练中采用了drop out和数据增强(data augmentation)来防止过拟合。
leaky_relu
1.训练数据的准备
训练数据是指送入训练的每一个batch,一般都是(imgs_tensor,target_tensor),img部分需要做一些数据增强,比如颜色抖动,随机裁剪,平移变换,水平反转,做完数据增强最好可视化一下输出结果,确保box_label经过变换后是正确的。数据增强完后,根据box_label进行编码(就是按照yolo的思想生成S×S×30S×S×30 的数据块作为target,含有目标的confidence先设置为1,用于区别不含目标的,IOU target在线计算)。
7.预测
(inference)
训练好的YOLO网络,输入一张图片,将输出一个 7*7*30 的张量(tensor)来表示图片中所有网格包含的对象(概率)以及该对象可能的2个位置(bounding box)和可信程度(置信度)。测试时候输出的scores等于
既可以反映分类的准确率,又可以反映定位的准确率.(这个可以从程序实现中看出来)
for i in range(2):#因为有2个box,这里对两个box进行遍历
for j in range(20):#一个grid有20个类别的得分,对每个类别得分进行遍历
probs[:, :, i, j] = np.multiply(class_probs[:, :, j], scales[:, :, i])#概率得分score=类别概率*box的置信度=Pr(class_i)*iou,此处一个grid对应了40*2=80个概率得分
#一共有7*7=49个grid,所以共有49*40=1960个得分
为了从中提取出最有可能的那些对象和位置,YOLO采用NMS(Non-maximal suppression,非极大值抑制)算法。
8)NMS(非极大值抑制)
NMS方法并不复杂,其核心思想是:选择得分最高的box作为输出,与该box的iou>阈值的其他box舍弃掉,不断重复这一过程直到所有box处理完。
YOLO的NMS计算方法如下。
网络输出的7*7*30的张量,在每一个网格grid中,对象位于第j个bounding box的得分:代表着某个对象存在于第j个bounding box的可能性。
每个网格grid有:20个对象的概率*2个bounding box的置信度,共40个得分。49个网格共49*40=1960个得分。Andrew Ng建议每种对象分别进行NMS,那么每种对象有 1960/20=98 个得分。
NMS步骤如下:
1)设置一个Score的阈值,低于该阈值的候选对象排除掉(将该Score设为0)
2)遍历每一个对象类别
2.1)遍历该对象的98个得分
2.1.1)找到Score最大的那个对象及其bounding box,添加到输出列表
2.1.2)对每个Score不为0的候选对象,计算其与上面2.1.1输出对象的bounding box的IOU
2.1.3)根据预先设置的IOU阈值,所有高于该阈值(重叠度较高)的候选对象排除掉(将Score设为0)
2.1.4)如果所有bounding box要么在输出列表中,要么Score=0,则该对象类别的NMS完成,返回步骤2处理下一种对象
3)输出列表即为预测的对象
代码实现:
import numpy as np
w_img=448
h_img=448
threshold = 0.2
def interpret_output(output):
probs = np.zeros((7, 7, 2, 20))
class_probs = np.reshape(output[0:980], (7, 7, 20))#网络输出是7*7*30, 这里取出了7*7*20,就是类别预测值Pr(class_i)
scales = np.reshape(output[980:1078], (7, 7, 2))#7*7*20--7*7*22=1078为两个box的置信度,本质上是iou,当然了,在预测阶段是没有iou的,但因为我们的回归
#目标是让这个值回归到iou,所以理想情况下,这个可以认为是iou
boxes = np.reshape(output[1078:], (7, 7, 2, 4))#后面的就是两个box的(x,y,w,h),
offset = np.transpose(np.reshape(np.array([np.arange(7)] * 14), (2, 7, 7)), (1, 2, 0))
boxes[:, :, :, 0] += offset
boxes[:, :, :, 1] += np.transpose(offset, (1, 0, 2))
boxes[:, :, :, 0:2] = boxes[:, :, :, 0:2] / 7.0#相当于把预测的box的中心均匀分布到原始图像上,并归一化为0-1之间的值
boxes[:, :, :, 2] = np.multiply(boxes[:, :, :, 2], boxes[:, :, :, 2])#因为预测的是sqrt(w),sqrt(h),这里再平方回去
boxes[:, :, :, 3] = np.multiply(boxes[:, :, :, 3], boxes[:, :, :, 3])
boxes[:, :, :, 0] *= w_img#还原到原始图片的尺寸*448,(当然原始图片若不是448,比如是720,这里是乘以原始图片的尺寸
boxes[:, :, :, 1] *= h_img
boxes[:, :, :, 2] *= w_img
boxes[:, :, :, 3] *= h_img
for i in range(2):#因为有2个box,这里对两个box进行遍历
for j in range(20):#一个grid有20个类别的得分,对每个类别得分进行遍历
probs[:, :, i, j] = np.multiply(class_probs[:, :, j], scales[:, :, i])#概率得分score=类别概率*box的置信度=Pr(class_i)*iou,此处一个grid对应了40*2=80个概率得分
#一共有7*7=49个grid,所以共有49*40=1960个得分
filter_mat_probs = np.array(probs >= threshold, dtype='bool')#找出那些概率得分>0.2的值
filter_mat_boxes = np.nonzero(filter_mat_probs)
boxes_filtered = boxes[filter_mat_boxes[0], filter_mat_boxes[1], filter_mat_boxes[2]]
probs_filtered = probs[filter_mat_probs]
classes_num_filtered = np.argmax(filter_mat_probs, axis=3)[
filter_mat_boxes[0], filter_mat_boxes[1], filter_mat_boxes[2]]
argsort = np.array(np.argsort(probs_filtered))[::-1]
boxes_filtered = boxes_filtered[argsort]
probs_filtered = probs_filtered[argsort]
classes_num_filtered = classes_num_filtered[argsort]
for i in range(len(boxes_filtered)):
if probs_filtered[i] == 0: continue
for j in range(i + 1, len(boxes_filtered)):
if iou(boxes_filtered[i], boxes_filtered[j]) >iou_threshold:#本质是执行nms
probs_filtered[j] = 0.0
filter_iou = np.array(probs_filtered > 0.0, dtype='bool')
boxes_filtered = boxes_filtered[filter_iou]
probs_filtered = probs_filtered[filter_iou]
classes_num_filtered = classes_num_filtered[filter_iou]
result = []
for i in range(len(boxes_filtered)):
result.append(
[self.classes[classes_num_filtered[i]], boxes_filtered[i][0], boxes_filtered[i][1], boxes_filtered[i][2],
boxes_filtered[i][3], probs_filtered[i]])
return result