语义分割指标---MIoU详细介绍(原理及代码)⼀.IOU理解
在语义分割的问题中,交并⽐就是该类的真实标签和预测值的交和并的⽐值
单类的交并⽐可以理解为下图:
TP: 预测正确,真正例,模型预测为正例,实际是正例
微信红包扫雷技巧
FP: 预测错误,假正例,模型预测为正例,实际是反例
FN: 预测错误,假反例,模型预测为反例,实际是正例
TN: 预测正确,真反例,模型预测为反例,实际是反例
水的笔画
IoU = TP / (TP + FN + FP)
⼆.MIoU
MIOU就是该数据集中的每⼀个类的交并⽐的平均,计算公式如下:
Pij表⽰将i类别预测为j类别。
三.混淆矩阵
1.原理
以西⽠书上的图为例:
举⼀个简单的例⼦,假设有⼀个四种类别的分割实例,其混淆矩阵如下:
每⼀⾏之和是该类的真实样本数量,每⼀列之和是预测为该类的样本数量。第⼀⾏代表有20个实际为类1的样本被分为类1,有2个实际为类1的样本被分为类2,有1个实际为类1的样本被分为类3,有1个实际为类1的样本被分为类4。
2.代码
先介绍⼀个函数np.bincount。
其⼤致的意思是,给⼀个向量x,x中最⼤的元素记为j,返回⼀个向量1⾏j+1列的向量y,y[i]代表i在x中出现的次数。
x中最⼤的数为7,那么它的索引值为0->7
x = np.array([0,1,1,3,2,1,7])
索引0出现了1次,索引1出现了3次......索引5出现了0次......
np.bincount(x)
因此,输出结果为:array([1,3,1,1,0,0,0,1])
如果minlength被指定,那么输出数组中的索引⾄少为它指定的数。即下标为0->minlength-1
我们可以看到x中最⼤的数为3,那么它的索引值为0->3
微信怎么看黑名单x = np.array([3,2,1,3,1])
本来bin的数量为4,现在我们指定了参数为7,因此现在bin的数量为7,所以现在它的索引值为0->6
np.bincount(x, minlength=7)
因此,输出结果为:array([0,2,1,2,0,0,0])
再来看混淆矩阵计算的代码
'''
产⽣n×n的分类统计表
参数a:标签图(转换为⼀⾏输⼊),即真实的标签
参数b:score层输出的预测图(转换为⼀⾏输⼊),即预测的标签
参数n:类别数
'''
def fast_hist(a, b, n):
#k为掩膜(去除了255这些点(即标签图中的⽩⾊的轮廓),其中的a>=0是为了防⽌bincount()函数出错)
k =(a >=0)&(a < n)
return np.bincount(n * a[k].astype(int)+ b[k], minlength=n**2).reshape(n, n)
输⼊a为标签图的向量,b为预测出的向量。n是类别个数。以如下这个例⼦举列,左边为真实的标签图,右边为预测的结果标签。
a=np.array([0,1,0,2,1,0,2,2,1])
b=np.array([0,2,0,2,1,0,1,2,1])
k =(a >=0)&(a <3)
社会主义和资本主义的本质区别k:
array([ True, True, True, True, True, True, True, True, True]) k的作⽤是防⽌出错,确保标签的分类都包含在n的类别中
n=3
n * a[k].astype(int)
b[k]
y=n * a[k].astype(int)+ b[k]
array([0, 3, 0, 6, 3, 0, 6, 6, 3])
array([0, 2, 0, 2, 1, 0, 1, 2, 1])
y:
array([0, 5, 0, 8, 4, 0, 7, 8, 4])
x=np.bincount(n * a[k].astype(int)+ b[k], minlength=n**2).reshape(n, n)
x:
array([[3, 0, 0],
[0, 2, 1],
[0, 1, 2]], dtype=int64)
混淆矩阵如下图:
这⾥为什么要先对真实标签*n然后加上预测标签,再进⾏bincount。个⼈理解:
对真实标签*3可以将最后的结果每三个看成⼀组,从前到后分别代表该类真实为i类别,预测为0,1,2类别的个数,⽅便最后resize 成3*3。
以真实标签中的0类别为例,真实标签中的0x3后还是0,与预测标签对位相加后,如果预测的也是0,那结果也是0,通过
bincount,得到的最终向量第⼀个数是相加向量中0的个数,也就是代表预测为0真实也为0的个数。如果预测的是1,0x3+1=1,通过bincount会统计所有为1的个数放在结果的第⼆位,代表真实为0预测为1的个数。
其他分类同理。
四.利⽤混淆矩阵求iou和miou
计算每⼀个分类的iou代码,参数hist是混淆矩阵
def per_class_iu(hist):
# 矩阵的对⾓线上的值组成的⼀维数组/矩阵的所有元素之和,返回值形状(n,)
return np.diag(hist)/(hist.sum(1)+ hist.sum(0)- np.diag(hist))
mIOU就是每⼀个类的IOU的平均,所以只需要对每⼀个类都按照上⾯计算IOU,再求平均获得mIOU就⾏了
古拉格大酒店
计算miou代码
通过该矩阵可以计算
IoU类别0 = 3 / (3 + 0 + 0 + 0 + 0) = 1
IoU类别1 = 2 / (0 + 2 + 1 + 0 + 1) = 0.5
IoU类别2 = 2 / (0 + 1 + 2 + 0 + 1) = 0.5
MIoU = sum(IoUi) / 类别数 = (1 + 0.5 + 0.5) / 3 = 0.67
实际项⽬中的代码
# 设标签宽W,长H
def fast_hist(a, b, n):
#--------------------------------------------------------------------------------#
# a是转化成⼀维数组的标签,形状(H×W,);b是转化成⼀维数组的预测结果,形状(H×W,)
#--------------------------------------------------------------------------------#
k =(a >=0)&(a < n)顺治
#--------------------------------------------------------------------------------#
# np.bincount计算了从0到n**2-1这n**2个数中每个数出现的次数,返回值形状(n, n)
李鸿章传# 返回中,写对⾓线上的为分类正确的像素点
#--------------------------------------------------------------------------------#
return np.bincount(n * a[k].astype(int)+ b[k], minlength=n **2).reshape(n, n)
def per_class_iu(hist):
return np.diag(hist)/ np.maximum((hist.sum(1)+ hist.sum(0)- np.diag(hist)),1)
def per_class_PA(hist):
return np.diag(hist)/ np.maximum(hist.sum(1),1)
def compute_mIoU(gt_dir, pred_dir, png_name_list, num_class, name_class):
print('Num class', num_class)
#-----------------------------------------#
# 创建⼀个全是0的矩阵,是⼀个混淆矩阵
#-----------------------------------------#
hist = np.zeros((num_class, num_class))
#------------------------------------------------#
喜用神金# 获得验证集标签路径列表,⽅便直接读取
# 获得验证集图像分割结果路径列表,⽅便直接读取
#------------------------------------------------#
gt_imgs =[join(gt_dir, x +".png")for x in png_name_list]
pred_imgs =[join(pred_dir, x +".png")for x in png_name_list]
#------------------------------------------------#
# 读取每⼀个(图⽚-标签)对