语义分割的评价指标——MIoU

  • 前言
  • 代码


前言

MIoU(Mean Intersection over Union)是语义分割的一个评价指标,表示平均交并比,即数据集上每一个类别的IoU值的平均。

那么什么是IoU呢,以下面这张图直观地说一下:

语义分割Heatmap 语义分割miou一般多少_评价指标


图中,左边的圆表示某个类的真实标签,右边的圆表示其预测结果,

  • TP表示真实值为Positive,预测为Positive,称作是正确的Positive(True Positive=TP)
  • FN表示真实值为Positive,预测为Negative,称作是错误的Negative(False Negative=FN)
  • FP表示真实值为Negative,预测为Positive,称作是错误的Positive(False Positive=FP)
  • TN表示真实值为Negative,预测为Negative,称作是正确的Negative(True Negative=TN)

中间的TP部分即为真实值和预测值的交,FN+FP+TP部分即为真实值和预测值的并,IoU就是交与并的比值:语义分割Heatmap 语义分割miou一般多少_一维数组_02

MIoU的具体计算公式如下:
设i表示真实值,j表示预测值,语义分割Heatmap 语义分割miou一般多少_一维数组_03表示将i预测为j,
语义分割Heatmap 语义分割miou一般多少_评价指标_04这个公式和
语义分割Heatmap 语义分割miou一般多少_混淆矩阵_05是等价的,实际上,分母中语义分割Heatmap 语义分割miou一般多少_语义分割Heatmap_06表示i的所有预测结果,即FN+TP,语义分割Heatmap 语义分割miou一般多少_混淆矩阵_07表示所有预测为i的值,即FP+TP,最后减去一个重合的语义分割Heatmap 语义分割miou一般多少_语义分割Heatmap_08即TP。
那么TP、FN、FP怎么计算呢?这就涉及到混淆矩阵了。

假设有一张图片,其所有的像素分属于4个类别。在预测分割之后,我们可以根据真实值和预测值获得一个混淆矩阵,形如:

语义分割Heatmap 语义分割miou一般多少_语义分割Heatmap_09


每一行表示某一类真实值,每一列表示预测为某一类,语义分割Heatmap 语义分割miou一般多少_一维数组_03表示将i预测为j的像素数量。以类1为例,语义分割Heatmap 语义分割miou一般多少_一维数组_11表示实际为类1且预测为类1,即TP;语义分割Heatmap 语义分割miou一般多少_一维数组_12表示实际为类1却预测为其他类,即FN;语义分割Heatmap 语义分割miou一般多少_评价指标_13表示错误地预测为类1,即FP;其余为TN。其他的类别可以类比。

所以,IoU的交就是混淆矩阵对角线上的值,

IoU的并就是将混淆矩阵的每一行的和(语义分割Heatmap 语义分割miou一般多少_语义分割Heatmap_06)加上每一列的和(语义分割Heatmap 语义分割miou一般多少_混淆矩阵_07),再减去对角线上的值(语义分割Heatmap 语义分割miou一般多少_语义分割Heatmap_08)。

总结一下,计算MIoU的三个步骤:

  1. 计算混淆矩阵
  2. 计算每个类别的IoU
  3. 对每个类别的IoU取平均

代码

下面给出计算MIoU的代码:
首先是计算混淆矩阵:

def _fast_hist(label_true, label_pred, n_class):
    """
    label_true是转化为一维数组的真实标签,label_pred是转化为一维数组的预测结果,n_class是类别数
    hist是一个混淆矩阵
    hist是一个二维数组,可以写成hist[label_true][label_pred]的形式
    最后得到的这个数组的意义就是行下标表示的类别预测成列下标类别的数量
    """
    # mask在和label_true相对应的索引的位置上填入true或者false
    # label_true[mask]会把mask中索引为true的元素输出
    mask = (label_true >= 0) & (label_true < n_class)
    # n_class * label_true[mask].astype(int) + label_pred[mask]计算得到的是二维数组元素
    # 变成一位数组元素的时候的地址取值(每个元素大小为1),返回的是一个numpy的list
    # np.bincount()会给出索引对应的元素个数
    hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask],
                       minlength=n_class ** 2).reshape(n_class, n_class)
    return hist

接着是每个类别的IoU:

def per_class_iu(hist):
    # 矩阵的对角线上的值组成的一维数组/(矩阵的每行求和+每列求和-对角线上的值),返回值形状(n,)
    return np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist))

然后对每个类别的IoU取平均,得到mIoU:

np.mean(per_class_iu(hist))