# 语义分割的评价指标计算(含Numpy实现)

语义分割的评价指标计算(含Numpy实现)

语义分割的评价指标:

推荐B站up主“霹雳吧啦Wz” 的视频《语义分割前言》 https://www.bilibili.com/video/BV1ev411P7dR/

从视频第10分钟开始,有很形象很直观的演示。

代码实现(Numpy)

这里我没有使用混淆矩阵(因为没有必要),而是只用了3个1维的向量组,分别是:

  1. 对真实标签中每一个类别进行计数 label_count
  2. 对预测标签中每一个类别进行计数 pred_count
  3. 计算预测与真实标签匹配的每一个类别的像素个数 match_count


图片来源自上文提到的视频的截图

其实从上面这个图就能看出来:

label_count, pred_count,match_count 其实也就分别对应着:

  1. 对混淆矩阵的每一行求和
  2. 对混淆矩阵的每一列求和
  3. 混淆矩阵的对角线元素

用这三个一维的向量同样可以实现PA, mPA, IoU, mIoU的计算:

  1. PA :match_count求和 / 总像素个数 = sum(match_count) / sum(pred_count) = sum(match_count) / sum(label_count)
  2. mPA :match_count [i] / pred_count[i] ( i in range(类别数) )然后再求平均
  3. IoU: match_count [i] / (pred_count[i] + label_count[i] - match_count[i])
  4. mIoU: 对IoU数组求平均 = mean(IoU)

1.Numpy实现与使用测试代码

import time
import numpy as np

class ComputeScore:
    def __init__(self, num_classes):
        self.num_classes = num_classes

        self.label_count = np.zeros((num_classes,), dtype='int32')
        self.pred_count = np.zeros((num_classes,), dtype='int32')
        self.match_count = np.zeros((num_classes,), dtype='int32')

        self.PA = 0
        self.PA_By_Classes = np.zeros((num_classes,), dtype='float32')
        self.mPA = 0
        self.IoU = np.zeros((num_classes,), dtype='float32')
        self.mIoU = 0

        # 防止除数为0 出现nan
        self.eps = np.full((num_classes,), 1e-5, dtype='float32')

    def addBatch(self, label, pred):
        label = label.flatten()
        pred = pred.flatten()
        batch_label_count = np.bincount(label, minlength=self.num_classes)
        batch_pred_count = np.bincount(pred, minlength=self.num_classes)
        batch_match_count = np.bincount(label[label == pred], minlength=self.num_classes)

        self.label_count = self.label_count + batch_label_count
        self.pred_count = self.pred_count + batch_pred_count
        self.match_count = self.match_count + batch_match_count

    def __compute_PA(self):
        self.PA = (np.sum(self.match_count)) / (np.sum(self.label_count + self.eps))
        return self.PA

    def __compote_PA_By_Classes(self):
        self.PA_By_Classes = self.match_count / (self.pred_count + self.eps)
        return self.PA_By_Classes

    def __compute_mPA(self):
        self.mPA = np.mean(self.PA_By_Classes)
        return self.mPA

    def __compute_IoU(self):
        intersection = self.match_count
        union = self.pred_count + self.label_count - self.match_count  + self.eps
        self.IoU = intersection / union
        return self.IoU

    def __compute_mIoU(self):
        self.mIoU = np.mean(self.IoU)
        return self.mIoU

    def display(self):
        self.__update_score()
        print('self.label_count', self.label_count)
        print('self.pred_count', self.pred_count)
        print('self.match_count', self.match_count)
        print('self.PA', self.PA)
        print('self.PA_By_Classes', self.PA_By_Classes)
        print('self.mPA', self.mPA)
        print('self.IoU', self.IoU)
        print('self.mIoU', self.mIoU)
        print('点到为止')

    def __update_score(self):
        _ = self.__compute_PA()
        _ = self.__compote_PA_By_Classes()
        _ = self.__compute_mPA()
        _ = self.__compute_IoU()
        _ = self.__compute_mIoU()

    def get_all_score(self):
        self.__update_score()
        return self.PA, self.mPA, self.IoU, self.mIoU


# 使用方法
if __name__ == '__main__':
    # 创建一个计算指标的对象
    compute_score = ComputeScore(23)
    # 随机生成取值范围为[0,23)内整数,形状为(600,400)的真实标签和预测标签
    label = np.random.randint(0, 23, size=(600, 400))
    pred = np.random.randint(0, 23, size=(600, 400))

    st = time.time()
    for i in range(10000):
        # 将Ground Truth和预测标签通过addBatch函数,更新计算指标的对象
        compute_score.addBatch(label, pred)
    et0 = time.time()
    compute_score.display()
    # 返回语义分割的指标
    PA, mPA, IoU, mIoU = compute_score.get_all_score()
    print( '-'*10, '\n')
    print(PA)
    print(mPA)
    print(IoU)
    print(mIoU)
    et1 = time.time()
    print( f'总用时{et1 - st}秒, 计算分数用时{et1 - et0}秒' )

2.基于Tensor的实现(多核运行,很快,基于torch)

import time
import numpy as np
import torch

class ComputeScoreTensor:
    def __init__(self, num_classes):
        self.num_classes = num_classes

        self.label_count = torch.zeros((num_classes), dtype=torch.int32)
        self.pred_count = torch.zeros((num_classes), dtype=torch.int32)
        self.match_count = torch.zeros((num_classes), dtype=torch.int32)

        self.PA = torch.zeros(1, dtype=torch.float32)
        self.PA_By_Classes = torch.zeros((num_classes), dtype=torch.float32)
        self.mPA = torch.zeros(1, dtype=torch.float32)
        self.IoU = torch.zeros((num_classes), dtype=torch.float32)
        self.mIoU = torch.zeros(1, dtype=torch.float32)

        # 防止除数为0
        self.eps = torch.zeros((num_classes), dtype=torch.float32)
        # TypeError: new_full(): argument 'size' (position 1) must be tuple of SymInts, not int
        # self.eps = self.eps.new_full((num_classes), 1e-5)
        # (num_classes) 被认为成是一个int 而不是一个数字的元组 (num_classes,) 它就懂了
        self.eps = self.eps.new_full((num_classes,), 1e-5)

    def addBatch(self, label, pred):
        label = label.flatten()
        pred = pred.flatten()
        batch_label_count = torch.bincount(label, minlength=self.num_classes)
        batch_pred_count = torch.bincount(pred, minlength=self.num_classes)
        batch_match_count = torch.bincount(label[label == pred], minlength=self.num_classes)

        self.label_count = self.label_count + batch_label_count
        self.pred_count = self.pred_count + batch_pred_count
        self.match_count = self.match_count + batch_match_count

    def __compute_PA(self):
        self.PA = (torch.sum(self.match_count)) / (torch.sum(self.label_count + self.eps))
        return self.PA

    def __compote_PA_By_Classes(self):
        self.PA_By_Classes = self.match_count / (self.pred_count + self.eps)
        return self.PA_By_Classes

    def __compute_mPA(self):
        self.mPA = torch.mean(self.PA_By_Classes)
        return self.mPA

    def __compute_IoU(self):
        intersection = self.match_count
        union = self.pred_count + self.label_count - self.match_count + self.eps
        self.IoU = intersection / union
        return self.IoU

    def __compute_mIoU(self):
        self.mIoU = torch.mean(self.IoU)
        return self.mIoU

    def display(self):
        self.__update_score()
        print('self.label_count', self.label_count)
        print('self.pred_count', self.pred_count)
        print('self.match_count', self.match_count)
        print('self.PA', self.PA)
        print('self.PA_By_Classes', self.PA_By_Classes)
        print('self.mPA', self.mPA)
        print('self.IoU', self.IoU)
        print('self.mIoU', self.mIoU)
        print('点到为止')

    def __update_score(self):
        _ = self.__compute_PA()
        _ = self.__compote_PA_By_Classes()
        _ = self.__compute_mPA()
        _ = self.__compute_IoU()
        _ = self.__compute_mIoU()

    def get_all_score(self):
        self.__update_score()
        # return self.PA, self.mPA, self.IoU, self.mIoU

        # float()是把tensor的dtype进行转换 返回的仍然是tensor(0.0435)这样的
        # tolist()确实把tensor转成了python中的list
        # return self.PA.float(), self.mPA.float(), self.IoU.tolist(), self.mIoU.float()

        # item()可以满足要求
        # IoU是多个元素的tensor item()是会报错的
        # ValueError: only one element tensors can be converted to Python scalars
        return self.PA.item(), self.mPA.item(), self.IoU.tolist(), self.mIoU.item()

# 使用方法
if __name__ == '__main__':
    # 创建一个计算指标的对象
    compute_score = ComputeScoreTensor(23)

    # 随机生成取值范围为[0,23)内整数,形状为(600,400)的真实标签和预测标签
    label = np.random.randint(0, 23, size=(600, 400))
    pred = np.random.randint(0, 23, size=(600, 400))
    # print(label.dtype) # int32
    label = torch.from_numpy(label)
    pred = torch.from_numpy(pred)
    # print(label.dtype) # torch.int32
    st = time.time()
    for i in range(10000):
        # 将Ground Truth和预测标签通过addBatch函数,更新计算指标的对象
        compute_score.addBatch(label, pred)
    et0 = time.time()
    compute_score.display()
    # 返回语义分割的指标
    PA, mPA, IoU, mIoU = compute_score.get_all_score()
    print( '-'*10, '\n')
    print(PA)
    print(mPA)
    print(IoU)
    print(mIoU)
    et1 = time.time()
    print( f'总用时{et1 - st}秒, 计算分数用时{et1 - et0}秒' )

你可能感兴趣的:(numpy,python,深度学习,计算机视觉)