语义分割之MIoU原理与实现 您所在的位置:网站首页 python计算并集 语义分割之MIoU原理与实现

语义分割之MIoU原理与实现

2023-02-27 10:59| 来源: 网络整理| 查看: 265

一、MIoU简介

MIoU全称为Mean Intersection over Union,平均交并比。可作为语义分割系统性能的评价指标。

P:Prediction预测值 G:Ground Truth真实值

MIoU

假设有3个类别,各类别iou值如下:

iou_1, iou_2, iou_3 = 0.6, 0.7, 0.75 num_class = 3 miou = sum([iou_1, iou_2, iou_3]) / num_class 二、IoU简介

交并比是预测值和真实值的交集和并集之比。

图取自网络

要预测的图像中有人、树、草等... 我们针对“人”这个类别来直观看下,什么是交并比。 左上图和右上图,分别是标注好的真实值,和模型输出的预测值。

交集:真实值和预测值的交集就是下面这张图: 蓝线圈的是真实值,红线圈的是预测值,黄色区域就是交集。

并集:真实值和预测值的并集就是下面这张图: 蓝线圈的是真实值,红线圈的是预测值,黄色区域就是并集。

混淆矩阵(Confusion Matrix)

混淆矩阵可以用于直观展示每个类别的预测情况。并能从中计算精确值(Accuracy)、精确率(Precision)、召回率(Recall)、交并比(IoU)。

混淆矩阵(图取自网络)

混淆矩阵是n*n的矩阵(n是类别),对角线上的是正确预测的数量。

假设求dog的IoU 请对照着附图和代码看

dog_iou true_dog = (7+2+28+111+18+801+13+17+0+3) # 上图绿框 predict_dog = (1+0+8+48+13+801+4+17+1+0) # 上图黄框 # 因为分母的801加了两次,因此要减一次 iou_dog = 801 / true_dog + predict_dog - 801

按照dog求IoU的方法,对每个类别进行求值,再求平均,就是语义分割模型的MIoU值。 理论上说,MIoU值越大(越接近1),模型效果越好。

P:Prediction预测值 G:Ground Truth真实值

MIoU 代码实现

因为numpy能基于数组计算,因此MIoU的求解非常简洁。

生成混淆矩阵 import numpy as np def fast_hist(a, b, n): """ 生成混淆矩阵 a 是形状为(HxW,)的预测值 b 是形状为(HxW,)的真实值 n 是类别数 """ # 确保a和b在0~n-1的范围内,k是(HxW,)的True和False数列 k = (a >= 0) & (a < n) # 这句会返回混淆矩阵,具体在下面解释 return np.bincount(n * a[k].astype(int) + b[k], minlength=n ** 2).reshape(n, n)

np.bincount(array)会返回array当中每个元素的个数

例如下面例子的[1, 3, 5]当中,0个0,2个1,0个2,1个3...以此类推

np.bincount([1, 3, 5, 1]) # 返回array([0, 2, 0, 1, 0, 1], dtype=int64)

从上例可以看出,返回值的长度是输入array中的最大值加1 最大值是5,返回的列表长度为6(5+1)

np.bincount(array, minlength)中,minlength就是限制返回列表的最小长度

np.bincount([1, 3, 5, 1], minlength=10) # 返回array([0, 2, 0, 1, 0, 1, 0, 0, 0, 0], dtype=int64)

返回长度为10,末尾填充了0,0,0...

看回源代码,如果没有越界情况,a[k]可以看成a,b[k]可以看成b

return np.bincount(n * a[k].astype(int) + b[k], minlength=n ** 2).reshape(n, n) 举个例子:

前面8, 9, 4, 7, 6都预测正确 对于预测正确的像素来说,n * a + b就是对角线的值 假设n=10,有10类 n * a + b就是88, 99, 44, 77, 66

紧接着6预测成了5 因此n * a + b就是65

minlength=n**2是为了确保混淆矩阵一定是n*n的大小 因为最后reshape(n, n) 88, 99, 44, 77, 66就是对角线上的值(如下图红框),65就是预测错误,并且能真实反映把6预测成了5(如下图蓝框)

计算IoU def per_class_iou(hist): """ hist传入混淆矩阵(n, n) """ # 因为下面有除法,防止分母为0的情况报错 np.seterr(divide="ignore", invalid="ignore") # 交集:np.diag取hist的对角线元素 # 并集:hist.sum(1)和hist.sum(0)分别按两个维度相加,而对角线元素加了两次,因此减一次 iou = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist)) # 把报错设回来 np.seterr(divide="warn", invalid="warn") # 如果分母为0,结果是nan,会影响后续处理,因此把nan都置为0 iou[np.isnan(res)] = 0. return iou

per_class_iou返回每个类别的iou,再求平均就能得到MIoU了。 下面是我进行封装后的代码块,欢迎讨论优化:

def fast_hist(a, b, n): """ Return a histogram that's the confusion matrix of a and b :param a: np.ndarray with shape (HxW,) :param b: np.ndarray with shape (HxW,) :param n: num of classes :return: np.ndarray with shape (n, n) """ k = (a >= 0) & (a < n) return np.bincount(n * a[k].astype(int) + b[k], minlength=n ** 2).reshape(n, n) def per_class_iu(hist): """ Calculate the IoU(Intersection over Union) for each class :param hist: np.ndarray with shape (n, n) :return: np.ndarray with shape (n,) """ np.seterr(divide="ignore", invalid="ignore") res = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist)) np.seterr(divide="warn", invalid="warn") res[np.isnan(res)] = 0. return res class ComputeIoU(object): """ IoU: Intersection over Union """ def __init__(self): self.cfsmatrix = np.zeros((CONFIG.NUM_CLASSES, CONFIG.NUM_CLASSES), dtype="uint64") # confusion matrix self.ious = dict() def get_cfsmatrix(self): return self.cfsmatrix def get_ious(self): self.ious = dict(zip(range(CONFIG.NUM_CLASSES), per_class_iu(self.cfsmatrix))) # {0: iou, 1: iou, ...} return self.ious def get_miou(self, ignore=None): self.get_ious() total_iou = 0 count = 0 for key, value in self.ious.items(): if isinstance(ignore, list) and key in ignore or \ isinstance(ignore, int) and key == ignore: continue total_iou += value count += 1 return total_iou / count def __call__(self, pred, label): """ :param pred: [N, H, W] :param label: [N, H, W} Channel == 1 """ pred = pred.cpu().numpy() label = label.cpu().numpy() assert pred.shape == label.shape self.cfsmatrix += fast_hist(pred.reshape(-1), label.reshape(-1), CONFIG.NUM_CLASSES).astype("uint64")

调用方式:

from utils.metrics import ComputeIoU for epoch in epoches: training code: pass valid code: for batch in val_loader: compute_iou = ComputeIoU() pred = model(image) pred = torch.argmax(F.softmax(pred, dim=1), dim=1) compute_iou(pred, label) # 这时会不断累加混淆矩阵的值 ious = compute_iou.get_ious() miou = compute_iou.get_miou(ignore=0) cfsmatrix = compute_iou.get_cfsmatrix()


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有