日萌社
人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)
4.2. 辅助功能
学习目标
- 能够实现两个目标框的交并比
- 了解候选框在多目标跟踪中的表达方式及相应转换方法
IOU是交并比(Intersection-over-Union)是目标检测中使用的一个概念是产生的候选框(candidate bound)与原标记框(ground truth bound)的交叠率,即它们的交集与并集的比值。最理想情况是完全重叠,即比值为1。在多目标跟踪中,用来判别跟踪框和目标检测框之间的相似度。
1.计算交并比
IoU是两个区域的交除以两个区域的并得出的结果
def iou(bb_test, bb_gt):
"""
在两个box间计算IOU
:param bb_test: box1 = [x1y1x2y2]
:param bb_gt: box2 = [x1y1x2y2]
:return: 交并比IOU
"""
xx1 = np.maximum(bb_test[0], bb_gt[0])
yy1 = np.maximum(bb_test[1], bb_gt[1])
xx2 = np.minimum(bb_test[2], bb_gt[2])
yy2 = np.minimum(bb_test[3], bb_gt[3])
w = np.maximum(0., xx2 - xx1)
h = np.maximum(0., yy2 - yy1)
wh = w * h
o = wh / ((bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1]) + (bb_gt[2] - bb_gt[0]) * (
bb_gt[3] - bb_gt[1]) - wh)
return o
2.候选框的表示形式
在该项目中候选框有两种表示形式:
- [x1,y1,x2,y2] 表示左上角坐标和右下角坐标,目标检测的结果以该方式表示
- [x,y,s,r]表示中心点坐标,s 是面积尺度,r是纵横比,卡尔曼滤波器中进行运动估计是使用该方式。
这两种方式要进行相互的转换。
- 将候选框从坐标形式转换为中心点坐标和面积的形式
def convert_bbox_to_z(bbox):
"""
将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r]。其中x,y是框的中心坐标,s是面积,尺度,r是宽高比
:param bbox: [x1,y1,x2,y2] 分别是左上角坐标和右下角坐标
:return: [ x, y, s, r ] 4行1列,其中x,y是box中心位置的坐标,s是面积,r是纵横比w/h
"""
w = bbox[2] - bbox[0]
h = bbox[3] - bbox[1]
x = bbox[0] + w / 2.
y = bbox[1] + h / 2.
s = w * h
r = w / float(h)
return np.array([x, y, s, r]).reshape((4, 1))
- 将候选框从中心面积的形式转换为坐标的形式
def convert_x_to_bbox(x, score=None):
"""
将[cx,cy,s,r]的目标框表示转为[x_min,y_min,x_max,y_max]的形式
:param x:[ x, y, s, r ],其中x,y是box中心位置的坐标,s是面积,r
:param score: 置信度
:return:[x1,y1,x2,y2],左上角坐标和右下角坐标
"""
w = np.sqrt(x[2] * x[3])
h = x[2] / w
if score is None:
return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2.]).reshape((1, 4))
else:
return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2., score]).reshape((1, 5))
总结
-
IOU的计算方法:两个区域的交比上两个区域的并,即为IOU
-
候选框不同表示方式之间的转换:
-
[x1,y1,x2,y2] 表示左上角坐标和右下角坐标
-
[x,y,s,r]表示中心点坐标,s 是面积尺度,r是纵横比
-
计算交并比
import numpy as np
"""
IOU 交并比(Intersection-over-Union)
是目标检测中使用的一个概念,是产生的候选框(candidate bound)与原标记框(ground truth bound)的交叠率,
即它们的交集与并集的比值。最理想情况是完全重叠,即比值为1。在多目标跟踪中,用来判别跟踪框和目标检测框之间的相似度。
计算交并比
IoU是两个区域的交除以两个区域的并得出的结果
"""
def iou(bb_test, bb_gt):
"""
在两个box间计算IOU
:param bb_test: box1 = [x1y1x2y2] 即 [左上角的x坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标]
:param bb_gt: box2 = [x1y1x2y2]
:return: 交并比IOU
"""
xx1 = np.maximum(bb_test[0], bb_gt[0]) #获取交集面积四边形的 左上角的x坐标
yy1 = np.maximum(bb_test[1], bb_gt[1]) #获取交集面积四边形的 左上角的y坐标
xx2 = np.minimum(bb_test[2], bb_gt[2]) #获取交集面积四边形的 右下角的x坐标
yy2 = np.minimum(bb_test[3], bb_gt[3]) #获取交集面积四边形的 右下角的y坐标
w = np.maximum(0., xx2 - xx1) #交集面积四边形的 右下角的x坐标 - 左上角的x坐标 = 交集面积四边形的宽
h = np.maximum(0., yy2 - yy1) #交集面积四边形的 右下角的y坐标 - 左上角的y坐标 = 交集面积四边形的高
wh = w * h #交集面积四边形的宽 * 交集面积四边形的高 = 交集面积
"""
两者的交集面积,作为分子。
两者的并集面积作为分母。
一方box框的面积:(bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1])
另外一方box框的面积:(bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1])
"""
o = wh / ( (bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1])
+ (bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1])
- wh)
return o
"""
候选框的表示形式
在该项目中候选框有两种表示形式:
[x1,y1,x2,y2] 表示左上角坐标和右下角坐标,目标检测的结果以该方式表示。
[x,y,s,r]表示中心点坐标,s 是面积尺度,r 是纵横比,卡尔曼滤波器中进行运动估计是使用该方式。
这两种方式要进行相互的转换。
将候选框从坐标形式[x1,y1,x2,y2] 转换为 中心点坐标和面积的形式[x,y,s,r]
"""
def convert_bbox_to_z(bbox):
"""
将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r]。
其中x、y是框的中心坐标点,s 是面积尺度,r 是宽高比w/h
:param bbox: [x1,y1,x2,y2] 分别是左上角坐标和右下角坐标 即 [左上角的x坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标]
:return: [ x, y, s, r ] 4行1列,其中x、y是box中心位置的坐标,s是面积,r是纵横比w/h
"""
w = bbox[2] - bbox[0] #右下角的x坐标 - 左上角的x坐标 = 检测框的宽
h = bbox[3] - bbox[1] #右下角的y坐标 - 左上角的y坐标 = 检测框的高
x = bbox[0] + w / 2. #左上角的x坐标 + 宽/2 = 检测框中心位置的x坐标
y = bbox[1] + h / 2. #左上角的y坐标 + 高/2 = 检测框中心位置的y坐标
s = w * h #检测框的宽 * 高 = 检测框面积
r = w / float(h) #检测框的宽w / 高h = 宽高比
#因为卡尔曼滤波器的输入格式要求为4行1列,因此该[x, y, s, r]的形状要转换为4行1列再输入到卡尔曼滤波器
return np.array([x, y, s, r]).reshape((4, 1))
"""
将候选框从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2]
"""
def convert_x_to_bbox(x, score=None):
"""
将[cx,cy,s,r]的目标框表示转为[x_min,y_min,x_max,y_max]的形式
:param x:[ x, y, s, r ],其中x,y是box中心位置的坐标,s是面积,r是纵横比w/h
:param score: 置信度
:return:[x1,y1,x2,y2],左上角坐标和右下角坐标
"""
"""
x[2]:s是面积,原公式s的来源为s = w * h,即检测框的宽 * 高 = 检测框面积。
x[3]:r是纵横比w/h,原公式r的来源为r = w / float(h),即检测框的宽w / 高h = 宽高比。
x[2] * x[3]:s*r 即(w * h) * (w / float(h)) = w^2
sqrt(x[2] * x[3]):sqrt(w^2) = w
"""
w = np.sqrt(x[2] * x[3]) #sqrt(w^2) = w
h = x[2] / w #w * h / w = h
if score is None:
return np.array([x[0] - w / 2., #检测框中心位置的x坐标 - 宽 / 2 = 左上角的x坐标
x[1] - h / 2., #检测框中心位置的y坐标 - 高 / 2 = 左上角的y坐标
x[0] + w / 2., #检测框中心位置的x坐标 + 宽 / 2 = 右下角的x坐标
x[1] + h / 2.] #检测框中心位置的y坐标 + 高 / 2 = 右下角的y坐标
).reshape((1, 4))
else:
return np.array([x[0] - w / 2.,
x[1] - h / 2.,
x[0] + w / 2.,
x[1] + h / 2.,
score]).reshape((1, 5))
非极大值抑制(NMS)
"""
非极大值抑制(NMS)
1.输入数据:
通过SVM分类器对每个锚框分类好之后,每个锚框都带上了预测类别标签值和该预测类别的置信度score,最终每个锚框都放到对应的类别列表中。
2.迭代过程:
对每个分类列表中的锚框进行处理,比如对某个类别的列表中所有锚框根据其预测类别的置信度score按从大到小进行排序,
首先类别的列表中取出第一个score值最大的锚框放到输出列表中,然后类别的列表中剩余的所有锚框逐一和输出列表中第一个锚框进行计算IoU值(交并比),
把IoU值>0.5的锚框都丢弃掉,只留下IoU值<0.5的锚框继续进行下一轮比较。
下一轮比较中,仍然先把分类列表中剩余的(score值最大)第一个锚框放到输出列表中,
然后分类列表中剩余的所有锚框再和输出列表中最后添加进去的锚框进行计算IoU值(交并比),
同样的把IoU值>0.5的锚框都丢弃掉,只留下IoU值<0.5的锚框,以此类推继续进行下一轮比较。
"""
import numpy as np
def nms(bounding_boxes, confidence_score, threshold):
"""
:param bounding_boxes: 检测的 boxes 及对应的 scores
:param confidence_score: 置信度score
:param threshold: 设定的阈值
:return:
"""
# boxes 位置
x1 = bounding_boxes[:, 0] # 左上角的x坐标
y1 = bounding_boxes[:, 1] # 左上角的y坐标
x2 = bounding_boxes[:, 2] # 右下角的x坐标
y2 = bounding_boxes[:, 3] # 右下角的y坐标
#一般的都是左上角的x/y坐标小,右下角的x/y坐标大,因为x/y坐标轴的零点位于左上角处。
# 右下角的x坐标x2 - 左上角的x坐标x1 = 长
# 右下角的y坐标y2 - 左上角的y坐标y1 = 高
areas = (x2 - x1 + 1) * (y2 - y1 + 1) # 各 box 的面积 = 长*高
# argsort从小到大排序,返回的是元素的索引值,[::-1]表示倒排变成从大到小 排序,排序后返回的结果为元素索引值[0, 2, 1]
order = confidence_score.argsort()[::-1] # boxes 的按照 置信度score 从大到小 排序
keep_box = [] # 记录保留下的 boxes 作为输出列表
keep_confidence_score = [] # 记录保留下的 置信度score 作为输出列表
while order.size > 0:
i = order[0] # score 最大的 box 对应的 index
keep_box.append(i) # 将本轮 score 最大的 box 的 index 保留
keep_confidence_score.append(i) # 将本轮 score 最大的 box 的 置信度score
"""
1.候选框的坐标表示为(左上角的x坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标)。
2.一般的都是左上角的x/y坐标小,右下角的x/y坐标大,因为x/y坐标轴的零点位于左上角处。
3.分母:两者的并集面积;分子:两者的交集面积;求出的是剩余锚框的IoU值(交并比)。
4.获取交集面积四边形的左上角的x/y坐标和右下角的x/y坐标
xx1 = np.maximum(x1[i], x1[order[1:]]):获取交集面积四边形的左上角的x坐标
yy1 = np.maximum(y1[i], y1[order[1:]]):获取交集面积四边形的左上角的y坐标
xx2 = np.minimum(x2[i], x2[order[1:]]):获取交集面积四边形的右下角的x坐标
yy2 = np.minimum(y2[i], y2[order[1:]]):获取交集面积四边形的右下角的y坐标
"""
# 计算剩余 boxes 与当前 box 的重叠程度 IoU
""" 类别的列表中剩余的所有锚框逐一和输出列表中第一个锚框进行 计算交集,作为分子 """
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
""" 交集面积,作为分子 """
inter = w * h
"""
areas[i]:最后添加进输出列表的的锚框的面积
areas[order[1:]]:类别列表中的剩余锚框的面积
areas[i] + areas[order[1:]] - inter:两者相加再减去两者的交集求出两者的并集面积
两者的交集面积作为分子,两者的并集面积作为分母,求出的分别都是剩余锚框的 IoU值(交并比)
"""
ovr = inter / (areas[i] + areas[order[1:]] - inter)
"""
np.where()[0] 表示行的索引
np.where()[1] 表示列的索引
把剩余锚框的<=阈值的锚框都留下继续进行下一轮比较
此处的np.where()[0]取出的是类别列表中剩余锚框的索引值
"""
# 保留 IoU 小于设定阈值的 boxes
inds = np.where(ovr <= threshold)[0]
#只保留 <=阈值的锚框,根据所取出类别列表中剩余锚框的索引值取出该部分锚框进行下一轮比较
order = order[inds + 1]
return keep_box, keep_confidence_score
def union(au, bu, area_intersection):
"""
计算并集
:param au:
:param bu:
:param area_intersection:
:return:
"""
# 计算a的面积
area_a = (au[2] - au[0]) * (au[3] - au[1])
# 计算b的面积
area_b = (bu[2] - bu[0]) * (bu[3] - bu[1])
# a和b的面积-交集面积=总共面积
area_union = area_a + area_b - area_intersection
return area_union
def intersection(ai, bi):
"""
计算交集
:param ai:a框坐标
:param bi:b框坐标
:return:
"""
# 1、取出交集的左上角点
x = max(ai[0], bi[0])
y = max(ai[1], bi[1])
# 2、取出交集的右下角点,并减去左上角点值,计算出交集长宽
w = min(ai[2], bi[2]) - x
h = min(ai[3], bi[3]) - y
# 3、如果一个为0,返回交集面积为0
if w < 0 or h < 0:
return 0
return w*h
def iou(a, b):
"""
计算交并比
:param a: a框坐标
:param b: b框坐标
:return:
"""
# 1、如果a,b 传入有问题
if a[0] >= a[2] or a[1] >= a[3] or b[0] >= b[2] or b[1] >= b[3]:
return 0.0
# 2、计算IOU
# 交集区域
area_i = intersection(a, b)
# 并集区域
area_u = union(a, b, area_i)
return float(area_i) / float(area_u + 1e-6) # 防止分母为0,加一个稳定系数
if __name__ == '__main__':
# 1.候选框的坐标表示为(左上角的x坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标)
# 2.一般的都是左上角的x/y坐标小,右下角的x/y坐标大,因为x/y坐标轴的零点位于左上角处。
bounding_boxes = np.array([(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)])
confidence_score = np.array([0.9, 0.75, 0.8]) # 置信度score
threshold = 0.4 # 阈(yu)值
keep_box, keep_confidence_score = nms(bounding_boxes,confidence_score,threshold)
print(bounding_boxes[keep_box]) #[[187 82 337 317]]
print(confidence_score[keep_confidence_score]) # [0.9]