NMS=None Maximum Suppress,非极大值抑制,简单来说就是目标检测结果里有个bbox置信度的score_threshold,还有多个bboxes重复IOU的iou_threshold。NMS和SoftNMS的区别在于:
import numpy as np
from typing import List
def softnms(bboxes:np.ndarray, scores:np.ndarray, iou_threshold=0.6, score_threshold=0.5, eps = 0.001, sigma2=1, method='hard'):
# bboxes是二维ndarray,dim=1对应的格式是x1,y1,x2,y2
# scores是一位ndarray
# 返回bboxes中没被抑制的bboxes的下标,也是个ndarray
sorted_indices = scores.argsort()[::-1]
sorted_bboxes, sorted_scores = bboxes[sorted_indices], scores[sorted_indices]
sorted_areas = np.clip(np.prod(sorted_bboxes[:,2:]-sorted_bboxes[:,:2], axis=1), a_min=eps, a_max=None)
bboxes_count = len(bboxes)
for i in range(bboxes_count-1):
min_max = np.minimum(sorted_bboxes[i,2:],sorted_bboxes[i+1:,2:])-np.maximum(sorted_bboxes[i,:2],sorted_bboxes[i+1:,:2])
min_max_clip = np.clip(min_max, a_min=eps, a_max=None) #把不相交的矩形框裁成0
inter = np.prod(min_max_clip, axis=1)
iou = inter / (sorted_areas[i]+sorted_areas[i+1:]-inter)
mask_indices = np.where(iou >= iou_threshold)[0] #表示要被抑制掉的下标,当然可能为空
# np.where返回的是一个tuple,tuple里第一个元素是下标的list
weights = np.ones_like(sorted_scores[i+1:])
# 这个weights是想要和sorted_scores基础上打折
# 注意这里写成np.ones_like(sorted_scores[i+1:])而不是np.ones_like(mask_indices),mask_indices可能为空,weights[mask_indices]没问题,但是sorted_scores[i+1:]*weights就会出问题
if method == 'linear':
weights[mask_indices] -= iou[mask_indices]
elif method == 'gaussian':
weights[mask_indices] *= np.exp(-iou[mask_indices]**2/sigma2)
else:
weights[mask_indices] = 0
sorted_scores[i+1:] = sorted_scores[i+1:]*weights
res = sorted_indices[sorted_scores > score_threshold]
return res
if __name__ == '__main__':
bboxes = np.array([
[0, 0, 1, 1],
[2, 2, 3, 3],
[0, 0, 2, 1],
[0, 0, 1, 2]
])
scores = np.array([0.7, 0.7, 0.5, 0.6])
res = softnms(bboxes, scores)
print(res)
再来个Pytorch版本:
import numpy as np
from typing import List
import torch
def softnms(bboxes:torch.Tensor, scores:torch.Tensor, iou_threshold=0.6, score_threshold=0.5, eps = 0.001, sigma2=1, method='hard'):
# bboxes是二维ndarray,dim=1对应的格式是x1,y1,x2,y2
# scores是一位ndarray
# 返回bboxes中没被抑制的bboxes的下标,也是个ndarray
sorted_indices = scores.argsort(descending=True)
sorted_bboxes, sorted_scores = bboxes[sorted_indices], scores[sorted_indices]
sorted_areas = torch.clip(torch.prod(sorted_bboxes[:,2:]-sorted_bboxes[:,:2], axis=1), min=eps, max=None)
bboxes_count = len(bboxes)
for i in range(bboxes_count-1):
inter_raw = torch.prod(torch.minimum(sorted_bboxes[i,2:],sorted_bboxes[i+1:,2:])-torch.maximum(sorted_bboxes[i,:2],sorted_bboxes[i+1:,:2]), axis=1)
inter = torch.clip(inter_raw, min=eps, max=None)
iou = inter / (sorted_areas[i]+sorted_areas[i+1:]-inter)
mask_indices = torch.where(iou >= iou_threshold)[0]
weights = torch.ones_like(sorted_scores[i+1:]) #注意这里写成np.ones_like(sorted_scores[i+1:])而不是np.ones_like(mask_indices),mask_indices可能为空,weights[mask_indices]没问题,但是sorted_scores[i+1:]*weights就会出问题
if method == 'linear':
weights[mask_indices] -= iou[mask_indices]
elif method == 'gaussian':
weights[mask_indices] *= np.exp(-iou[mask_indices]**2/sigma2)
else:
weights[mask_indices] = 0
sorted_scores[i+1:] = sorted_scores[i+1:]*weights
res = sorted_indices[sorted_scores > score_threshold]
return res
if __name__ == '__main__':
bboxes = torch.Tensor([
[0, 0, 1, 1],
[0, 0, 2, 1],
[0, 0, 1, 2]
])
scores = torch.Tensor([0.7, 0.5, 0.6])
res = softnms(bboxes, scores)
print(res)
YOLOv1的基本思想是把一副图片,首先reshape成448×448大小(由于网络中使用了全连接层,所以图片的尺寸需固定大小输入到CNN中),然后将划分成SxS个单元格(原文中S=7),以每个格子所在位置和对应内容为基础,来预测检测框和每个框的Confidence以及每个格子预测一共C个类别的概率分数。
SSD网络的特点是对不同尺度下的feature map中的每一个点都设置一些default box,这些default box有不同的大小和横纵比例,对这些default box进行分类和边框回归的操作。SSD的核心是对固定设置的default box(不同尺度feature map中每一个空间位置都设置一组default box,这里只考虑空间位置,不考虑feature的通道个数)计算属于各类物体的概率以及坐标调整的数值。这个计算方式是对每层的feature map做卷积操作,卷积核设定为3*3,卷积核的个数是与default box个数相关。
优点:SSD的优点是运行速度超过yolo,精度在一定条件下超过faster rcnn。缺点是需要人工设置先验框(prior box)和min_size,max_size和长宽比(aspect_ratio)值,网络中default_box的基础大小和形状不能直接通过学习获得,而是需要手工设置,虽然使用了图像金字塔的思路,但是对小目标的recall(召回率)依然一般简述SSD网络前向是如何计算的
1数据增强,获取训练样本,将训练数据统一resize到固定尺寸;
2.通过卷积网络获取feature map:①使用的卷积网络,前半部分使用基础分类网络获取各层的feature map,这部分称为base network。②下一步计算的输入,就是上述的不同尺寸的feature map;
3.通过卷积操作,从特征图中获取检测信息。①此处实现方式与yolo类似;②与Faster R-CNN类似,在特征图中每个点新建若干固定尺寸的anchor。检测信息包括每个anchor的信息。主要包括:confidence(代表这个anchor中是否存在物体)、分类信息以及bbox信息。
缺点:SSD对小目标的检测效果一般,作者认为小目标在高层没有足够的信息对小目标检测的改进可以从下面几个方面考虑:
其能检测超过9000种类别的物体
RetinaNet的作者对one-stage检测器准确率不高的问题原因进行探究,发现主要问题在于正负类别不均衡,提出Focal Loss来解决类别不平衡问题。目的是通过减少易分类样本的权重,从而使得模型在训练时更注重于难分类的样本。RetinaNet=ResNet+FPN+Two sub-network+Focal Loss; RetinaNet由backbone网络和两个子任务网络组成,backbone网络负责计算feature map,子任务网络一个负责目标分类,一个负责bbox回归,网络的loss使用Focal loss。
SSD的基础网络是VGG,且SSD在使用多层feature map时只是简单的在不同层的feature map上放default box,并没有真正将低维度特征和高维度特征进行融合。且SSD网络中使用的控制正负样本数量比的方法是难样本挖掘方法,loss是分类+回归的loss。而RetinaNet网络的基础网络是resnet+FPN,是真正将低维度的特征和高维度的特征进行了特征融合后再来做检测的。且控制正负样本的方法是使用Focal Loss。
Decoupled Head。检测框内的分类和检测框位置的回归要分开,因为分类和回归本质上是两个任务,在YoLo V3里这俩是concat起来的,loss实际上是有冲突的
Strong Data Augmentation。例如Mixup等技巧
Anchor Free的方式。YoloV3、V4、V5都是有anchor的,到YoloX正式把anchor去掉,简单来说就是把feature map里的anchor信息给存到了embedding里,细节可以看博客https://zhuanlan.zhihu.com/p/397993315,下图来自该博客:
Multi Positive。 一个正例会被分配到不同FPN层进行回归,这有点类似Focal loss的做法
SimOTA。运输优化算法,这里不得不提到OTA(Optimal Transport Assignment,Paper: https://arxiv.org/abs/2103.14259),这里简单解释一下,后续计划出个专栏来解释OTA。在目标检测中,有时候经常会出现一些模棱两可的anchor,如图3,即某一个anchor,按照正样本匹配规则,会匹配到两个gt,而retinanet这样基于IoU分配是会把anchor分配给IoU最大的gt,而OTA作者认为,将模糊的anchor分配给任何gt或背景都会对其他gt的梯度造成不利影响,因此,对模糊anchor样本的分配是特殊的,除了局部视图之外还需要其他信息。因此,更好的分配策略应该摆脱对每个gt对象进行最优分配的惯例,而转向全局最优的思想,换句话说,为图像中的所有gt对象找到全局的高置信度分配。(和DeTR中使用使用匈牙利算法一对一分配有点类似)
Two-Stage先对前景背景做了筛选,再进行回归,回归效果比较好,准度高但是相比较慢,One-Stage是直接对特征上的点进行直接回归,优点是速度快,因为用了多层特征图出框可能小目标效果比较好一点(个人看法),缺点是因为正负样本失衡导致效果较差,要结合难例挖掘。
one-stage算法对小目标检测效果较差,如果所有的anchor都没有覆盖到这个目标,那么这个目标就会漏检。
回答的是就是把RPN和二阶段拆开训,然后追问RPN在ENDTOEND中怎么回传,答TOTALLoss中有一阶段和二阶段的LOSS,只是回传影响的部分不一样。
RPN网络训练有两个Loss:
部分转载自https://blog.csdn.net/Bismarckczy/article/details/129717080,一般把anchor到gt之间如何匹配的方法称为label assignment,也就是给预设的anchor打上正负样本等标签,方便我们后续进一步回归。简单来说就是给gt分配超过IOU的anchor、给anchor分配超过IOU的gt、取不取最大IOU这样一系列策略
这个问题在YOLOv3: An Incremental Improvement中也有提到,个人理解是和label assignment机制有关系。YoloV3在下面解释的label assignment机制是「得到与gt最大iou的那个anchor作为正样本,小于定阈值的作为负样本,大于设定阈值但不是最大iou的为忽略样本。」像RetinaNet中就是「IoU低于0.4为negative,高于0.5为positive,其他为ignore」,yolov3的正负样本相对没那么悬殊,所以Focal Loss就没起到作用
条件2覆盖了1,条件3是对2的补充。另外faster rcnn里面应该GT可以有多个anchors,而一个anchor只对应一个GT。
IoU低于0.4为negative,高于0.5为positive,其他为ignore
限制正负样本比例为1:1,如果正样本不足,就用负样本补充,这种方法后面研究工作用的不多。通常针对类别不平衡问题可以从调整样本数或修改loss weight两方面去解决,常用的方法有OHEM、OHNM、class balanced loss和Focal loss。
Online Hard Example Mining, OHEM(2016)。将所有sample根据当前loss排序,选出loss最大的N个,其余的抛弃。这个方法就只处理了easy sample的问题
Online Hard Negative Mining, OHNM, SSD(2016)里使用的一个OHEM变种, 在Focal Loss里代号为OHEM 1:3。在计算loss时, 使用所有的positive anchor, 使用OHEM选择3倍于positive anchor的negative anchor。同时考虑了类间平衡与easy sample
Class Balanced Loss。计算loss时,正负样本上的loss分别计算, 然后通过权重来平衡两者。暂时没找到是在哪提出来的,反正就这么被用起来了。它只考虑了类间平衡
Focal Loss(2017), 最近提出来的。不会像OHEM那样抛弃一部分样本, 而是和Class Balance一样考虑了每个样本
部分转载自:https://zhuanlan.zhihu.com/p/475440014
其实就是多尺度的滑动窗口
两者都可以理解为整合特征图信息。concat是通道数的增加;add是特征图相加,通道数不变。 add是描述图像的特征下的信息量增多了,但是描述图像的维度本身并没有增加,只是每一维下的信息量在增加,这显然是对最终的图像的分类是有益的。而concatenate是通道数的合并,也就是说描述图像本身的特征数(通道数)增加了,而每一特征下的信息是没有增加。 concat每个通道对应着对应的卷积核。 而add形式则将对应的特征图相加,再进行下一步卷积操作,相当于加了一个先验:对应通道的特征图语义类似,从而对应的特征图共享一个卷积核(对于两路输入来说,如果是通道数相同且后面带卷积的话,add等价于concat之后对应通道共享同一个卷积核)。因此add可以认为是特殊的concat形式。但是add的计算量要比concat的计算量小得多。
多尺度训练可以分为两个方面:一个是图像金字塔,一个是特征金字塔
CenterNet有两篇paper,对于目标检测一般指的是《Objects as Points》,而不是《CenterNet:Keypoint Triplets for Object Detection》
CenterNet是属于anchor-free系列的目标检测算法的代表作之一,与它之前的目标算法相比,速度和精度都有不小的提高,尤其是和yolov3相比,在速度相同的情况下,CenterNet精度要比yolov3高好几个点。它的结构非常的简单,而且不需要太多了后处理,连NMS都省了(省的原因是we only have one positive “anchor” per object, and hence do not need NonMaximum Suppression (NMS)),直接检测目标的中心点和大小,实现了真正的anchor-free(A center point can be seen as a single shape-agnostic anchor,our CenterNet assigns the “anchor” based solely on location, not box overlap as Figure 3。其实只是把anchor换了,并不是彻底没了anchor)。
CenterNet论文中用到了三个主干网络:ResNet-18、DLA-34(Deep Layer Aggregation DLA is an image classification network with hierarchical skip connections)和Hourglass-104(Each hourglass module is a symmetric 5-layer down- and up-convolutional network with skip connections,可以看成是FPN的上下采样版本),实际应用中,也可以使用resnet-50等网络作为backbone;CenterNet的算法流程是:一张512x512(1x3x512x512)的图片输入到网络中,经过backbone特征提取后得到下采样32倍后的特征图(1x2048x16x16),然后再经过三层反卷积模块上采样到128x128的尺寸,最后分别送入三个head分支进行预测:分别预测物体的类别、长宽尺寸和中心点偏置。其中推理的核心是从headmap中提取需要的bounding box,通过使用3*3的最大池化,检查当前热点的值是否比周围的8个临近点值都大,每个类别取100个这样的点,经过box后处理后再进行阈值筛选,得到最终的预测框。
目前大多数深度学习算法模型要落地对算力要求还是比较高的,如果在服务器上,可以使用GPU进行加速,但是在边缘端或者算力匮乏的开发板子上,不得不对模型进一步的压缩或者改进,也可以针对特定的场景使用市面上现有的推理优化加速框架进行推理。目前来说比较常见的几种部署方案为:
在工作中,我通常会根据不同的任务选取不同的算法模型:
目标检测:yolov5、yolov3、CenterNet、SSD、Faster RCNN、EfficientDet;
图像分类:ResNet系列、ShuffleNetV2、EfficientNet;
实例分割:mask-rcnn、yolact、solo;
语义分割:deeplabv3、deeplabv3+、UNet;
文本检测:CTPN、PSENet、DBNet、YOLOV5;
文本识别:CRNN+CTC、CRNN+Attention;
通常,我比较喜欢性能好的模型,性能的指标由两部分,一个是精度,一个是速度。比如在目标检测中,用的比较多的是yolo系列,特别是v4、v5出来后。通常在图像分类的任务上,分类并不困难的情况下会选择一些轻量型的网络,能够一定程度上节省算力资源。其他领域的任务算法抉择也大同小异。
Mask rcnn网络是基于faster rcnn网络架构提出的新的目标检测网络。该网络可以在有效地完成目标检测的同时完成实例分割。Mask RCNN主要的贡献在于如下:
DETR的缺点就是慢,比Faster RCNN还慢!
把目标检测看成集合预测的问题(所以Hungarian loss就用上了,scipy里的linear sum assignment),扔掉了NMS。object_query作用类似于anchor和rpn网络里的proposals。系统架构如下图:
一些补充的细节:
来自DEFORMABLE DETR: DEFORMABLE TRANSFORMERS FOR END-TO-END OBJECT DETECTION,主要思路是把DETR做成多尺度,每个参考点仅关注邻域的一组采样点,这些采样点的位置并非固定,而是可学习的(和可变形卷积一样),从而实现了一种局部(local)&稀疏(sparse)的高效注意力机制。重点实现了以下两个策略:
这里面比较细了https://mp.weixin.qq.com/s/yNtKv6ae9JDAIdGD1rvqLA,直接说为啥比DETR快吧:
一般来说Dice Loss在分割任务中用的多些,Dice Loss实际就是优化F1 Score,分析参考https://blog.csdn.net/taoqick/article/details/124182037
IoU损失的最大问题是当两个物体没有互相覆盖时,损失值都会变成1,而不同的不覆盖情况明显也反应了检测框的优劣。可以看出当ground truth(黑色框)和预测框(绿色框)没有交集时,IOU的值都是0,而GIoU则拥有不同的值,而且和检测效果成正相关。
GLIP实际上是预训练的目标检测了,利用对比学习的思路,把文本和bbox对齐:
Kosmos1实际上是MetaLM(Language Models are General-Purpose Interfaces)这篇工作的延续,MetaLM的思路是
Semi-causal language modeling as a meta-pretraining task,把各种任务都融合到一个任务里来:
然而Kosmos1在VQA任务上效果并还没有特别好,紧接着Kosmos2就结合GLIP开始搞目标检测任务,Kosmos2(KOSMOS-2: Grounding Multimodal Large Language
Models to the World)基本等于Kosmos1+GLIP
这篇实际上就是图像分割领域里的预训练,数据工作做得非常扎实,几个关键的点:
参考: