前言
仍然是EAST算法的优化,因为出于安全考虑,将在毕业后公开,望大家谅解。
传统NMS
思路:首先,EAST算法使用的是Locality-Aware NMS,局部NMS优于传统NMS,我们原来的思路是将Locality-Aware NMS更换为Softer-NMS,让我们先分析一下传统NMS的缺点。先附一篇传统NMS的代码吧:
def nms(bounding_boxes, Nt):
if len(bounding_boxes) == 0:
return [], []
bboxes = np.array(bounding_boxes)
# 计算 n 个候选框的面积大小
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
scores = bboxes[:, 4]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
# 对置信度进行排序, 获取排序后的下标序号, argsort 默认从小到大排序
order = np.argsort(scores)
picked_boxes = [] # 返回值
while order.size > 0:
# 将当前置信度最大的框加入返回值列表中
index = order[-1]
picked_boxes.append(bounding_boxes[index])
# 获取当前置信度最大的候选框与其他任意候选框的相交面积
x11 = np.maximum(x1[index], x1[order[:-1]])
y11 = np.maximum(y1[index], y1[order[:-1]])
x22 = np.minimum(x2[index], x2[order[:-1]])
y22 = np.minimum(y2[index], y2[order[:-1]])
w = np.maximum(0.0, x22 - x11 + 1)
h = np.maximum(0.0, y22 - y11 + 1)
intersection = w * h
# 利用相交的面积和两个框自身的面积计算框的交并比, 将交并比大于阈值的框删除
ious = intersection / (areas[index] + areas[order[:-1]] - intersection)
left = np.where(ious < Nt)
order = order[left]
return picked_boxes
通过代码我们大致了解传统NMS的步骤:
1)根据置信度得分进行排序;
2)选择置信度最高的边界框添加到最终输出列表中,将其从原始边界框列表中删除;
3)计算所有边界框的面积;
4)计算置信度最高的边界框与其它候选框的IoU;
5)删除IoU大于阈值的边界框;(一般IOU取0.3~0.5)
6)重复上述过程,直至原始边界框列表为空。
传统NMS思路很简单,但是缺点也很明显,如果重复部分大于阈值直接删掉,这样如果有两个不同的文本框也重合了那么他也会被直接删掉,这就是我们所想优化的。
于是我们找到了另外一种优化方案:soft-NMS。
soft-NMS
先上代码:
def soft_nms(bboxes, Nt=0.3, sigma2=0.5, score_thresh=0.3, method=2):
# 在 bboxes 之后添加对于的下标[0, 1, 2...], 最终 bboxes 的 shape 为 [n, 5], 前四个为坐标, 后一个为下标
res_bboxes = deepcopy(bboxes)
N = bboxes.shape[0] # 总的 box 的数量
indexes = np.array([np.arange(N)]) # 下标: 0, 1, 2, ..., n-1
bboxes = np.concatenate((bboxes, indexes.T), axis=1) # concatenate 之后, bboxes 的操作不会对外部变量产生影响
# 计算每个 box 的面积
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
scores = bboxes[:, 4]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
for i in range(N):
# 找出 i 后面的最大 score 及其下标
pos = i + 1
if i != N - 1:
maxscore = np.max(scores[pos:], axis=0)
maxpos = np.argmax(scores[pos:], axis=0)
else:
maxscore = scores[-1]
maxpos = 0
# 如果当前 i 的得分小于后面的最大 score, 则与之交换, 确保 i 上的 score 最大
if scores[i] < maxscore:
bboxes[[i, maxpos + i + 1]] = bboxes[[maxpos + i + 1, i]]
scores[[i, maxpos + i + 1]] = scores[[maxpos + i + 1, i]]
areas[[i, maxpos + i + 1]] = areas[[maxpos + i + 1, i]]
# IoU calculate
xx1 = np.maximum(bboxes[i, 0], bboxes[pos:, 0])
yy1 = np.maximum(bboxes[i, 1], bboxes[pos:, 1])
xx2 = np.minimum(bboxes[i, 2], bboxes[pos:, 2])
yy2 = np.minimum(bboxes[i, 3], bboxes[pos:, 3])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
intersection = w * h
iou = intersection / (areas[i] + areas[pos:] - intersection)
# Three methods: 1.linear 2.gaussian 3.original NMS
if method == 1: # linear
weight = np.ones(iou.shape)
weight[iou > Nt] = weight[iou > Nt] - iou[iou > Nt]
elif method == 2: # gaussian
weight = np.exp(-(iou * iou) / sigma2)
else: # original NMS
weight = np.ones(iou.shape)
weight[iou > Nt] = 0
scores[pos:] = weight * scores[pos:]
# select the boxes and keep the corresponding indexes
inds = bboxes[:, 5][scores > score_thresh]
keep = inds.astype(int)
return res_bboxes[keep]
步骤和普通NMS类似,不同在于当IOU大于阈值,算法不再直接删除,而是降低分数且IOU越大就降低的越大。然后放回去继续重复运算。这样就避免了刚才说的情况。下面我们说一下Locality-Aware NMS,和为何我们的原本优化思路行不通。
Locality-Aware NMS
同样,我们先附代码:
import numpy as np
from shapely.geometry import Polygon
def intersection(g, p):
# 取g,p中的几何体信息组成多边形
g = Polygon(g[:8].reshape((4, 2)))
p = Polygon(p[:8].reshape((4, 2)))
# 判断g,p是否为有效的多边形几何体
if not g.is_valid or not p.is_valid:
return 0
# 取两个几何体的交集和并集
inter = Polygon(g).intersection(Polygon(p)).area
union = g.area + p.area - inter
if union == 0:
return 0
else:
return inter / union
def weighted_merge(g, p):
# 取g,p两个几何体的加权(权重根据对应的检测得分计算得到)
g[:8] = (g[8] * g[:8] + p[8] * p[:8]) / (g[8] + p[8])
# 合并后的几何体的得分为两个几何体得分的总和
g[8] = (g[8] + p[8])
return g
def standard_nms(S, thres):
# 标准NMS
order = np.argsort(S[:, 8])[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
ovr = np.array([intersection(S[i], S[t]) for t in order[1:]])
inds = np.where(ovr <= thres)[0]
order = order[inds + 1]
return S[keep]
def nms_locality(polys, thres=0.3):
'''
locality aware nms of EAST
:param polys: a N*9 numpy array. first 8 coordinates, then prob
:return: boxes after nms
'''
S = [] # 合并后的几何体集合
p = None # 合并后的几何体
for g in polys:
if p is not None and intersection(g, p) > thres: # 若两个几何体的相交面积大于指定的阈值,则进行合并
p = weighted_merge(g, p)
else: # 反之,则保留当前的几何体
if p is not None:
S.append(p)
p = g
if p is not None:
S.append(p)
if len(S) == 0:
return np.array([])
return standard_nms(np.array(S), thres)
Locality-Aware NMS:1.先对所有的output box集合结合相应的阈值(大于阈值则进行合并,小于阈值则不和并),依次遍历进行加权合并,得到合并后的bbox集合;2.对合并后的bbox集合进行标准的NMS操作。这里可以看到,首先,当IOU大于阈值,文本框会被直接合并,然后再去NMS,如果IOU小于阈值,就将文本框保留下来。
那么,为什么说利用soft-NMS优化行不通呢。这种合并方式是将面积扩大,而不是删除,很大程度上避免了误删的情况。虽然接下来的操作依然会删除一些文本框,但是相对之下这种方式删除的属于少数情况,而且相比于降低分数,合并无异于更合理。所以说再去用soft-NMS优化意义不大。
Locality-Aware NMS的优化思路
书接上段,在Locality-Aware NMS,大家可能想到了一个问题。为什么合并后还会有NMS操作。不应该都合并了吗。大家思考以下这种情况:假设原来有两个文本框并不重合,但是在合并以后出现了重合部分,这是合理的。也就是说相离很远的两个文本框,在逐渐合并中出现了重合部分。但是想一个问题,如果这两个仍然还都是文本框呢?这就涉及到我们的优化思路。一、我们可以再次进行合并,但是如果是这样,逻辑上这样的问题会一直存在,这样的合并是永无止境的(当然也不是,其实大约两三次就会合并完重复几次也是可以的),而且太大的文本框其实包含了一些没有文字的区域。当然这也是一种优化思路。二、我们可以调节阈值,我们可以看到编程中,合并的阈值和NMS的阈值是相同的,这有点赶尽杀绝的味道,但其实不是,这类似重复了两次同样的操作,只不过第一次是合并,第二次是过滤,相当于1.5次合并。其实他和思路一有异曲同工的作用。只不过是重复次数比较少。然后我们看这两个阈值,我们可以把合并的阈值调小一些,这一定意义上就相当于让更多的文本框合并。效果类似于多次合并,然后我们把NMS的阈值调大一点,也就是把门槛放宽一点,可以一定程度上防止误删。这就是我们的优化思路。
最后
这就是我的优化思路,可能不太成熟,望大家见谅。