语义分割是像素级别的分类,其常用评价指标:
像素准确率(Pixel Accuracy,PA)、
类别像素准确率(Class Pixel Accuray,CPA)、
类别平均像素准确率(Mean Pixel Accuracy,MPA)、
交并比(Intersection over Union,IoU)、
平均交并比(Mean Intersection over Union,MIoU),
其计算都是建立在混淆矩阵(Confusion Matrix)的基础上。因此,了解基本的混淆矩阵知识对理解上述5个常用评价指标是很有益处的!
Q: 什么是混淆矩阵?
A: 顾名思义,其就是一个矩阵,可理解为一张表格,但矩阵前加上“混淆”之后,很容易让人理解产生误差,即:“混淆矩阵真混淆(一脸懵逼)”。
下面是一句话解释混淆矩阵:
混淆矩阵就是统计分类模型的分类结果,即:统计归对类,归错类的样本的个数,然后把结果放在一个表里展示出来,这个表就是混淆矩阵。(参考链接)
初步理解混淆矩阵,当以二分类混淆矩阵作为入门,多分类混淆矩阵都是以二分类为基础作为延伸的!
Q: 什么是二分类?
A: 顾名思义,分类器(又叫:网络模型、学习器)对两个类别进行分类处理的问题,就叫二分类
对于二分类问题,将类别1称为正例(Positive),类别2称为反例(Negative),分类器预测正确记作真(True),预测错误记作(False),由这4个基本术语相互组合,构成混淆矩阵的4个基础元素,为:
看到此处,可能对混淆矩阵有了初步了解,但大脑里依旧是:“混淆矩阵很混淆”的状态…
且慢,下面举个例子:
假如:宠物店有10只动物,其中6只狗,4只猫(真实值),现有一个模型将这10只动物进行分类,分类结果为(预测结果为):5只狗,5只猫(预测值),对分类结果画出对应混淆矩阵进行分析(狗:正例,类别1,猫:反例,类别2):(参考链接)
等等…见此表是不是依旧懵逼?(那就对了…)
分析混淆矩阵的3个要点:(参考链接)
①矩阵对角线上的数字,为当前类别预测正确的类别数目;非对角线数字,预测都是错误的!
如:对角线数字5,含义为:预测值为狗,实际是狗的预测数目,即:预测正确(同理:数字4);非对角线数字1,含义为:预测值为猫,实际是狗的预测数目,即:预测错误。
②矩阵每一行数字求和的值,其含义:真实值中,真实情况下属于该行对应类别的数目!
如:第一行,5+1=6,表示真实情况狗有6只.
③矩阵每一列数字求和的值,其含义:预测值中,预测为该列对应类别的数目!
如:第一列,5+0=5,表示模型预测为狗的数目有5只;第二列,1+4=5,表示模型预测为猫的数目有5只(预测有对有错,对4只,错1只)
现小小总结一下这3个小点:
口诀:对角全为对,横看是真实,竖看是预测
解释:混淆矩阵对角元素全是预测正确的,数字的值表示各类别预测正确的数目;横(行)的数字求和,表示某类别真实值的个数,竖(列)的数字求和,表示模型预测为该类别的个数!
此外:对列求和的理解挺“别扭”的,分享一下我的理解技巧:看列时,首先想到是以模型预测为出发点(既然是预测,肯定有对有错),其次是模型对该列对应类别的预测总数是多少,最后才判断预测的对与错,即:“列是预测,先定总数不管对错,后判正误”。
比如:第2列,模型对猫(类别2)预测了1+4=5只(此时,不看预测对与错),再分析,第2列第1行非对角线元素,预测错误(预测值是猫,实际是狗),第2列第2行为对角元素,预测正确(预测值是猫,实际是猫),即:在猫这个类上,预测了5次,对了4次,错了1次!
上面分析了一大堆,最终还是不够精炼,于是大佬们定义了几个公式:
上述公式对应上述例子:
上面是由二分类引出的关于混淆矩阵及其相关公式的介绍,语义分割一般都是多分类的,但也有二分类,对于二分类的语义分割评价指标可参考上述介绍理解,对于多分类的语义分割评价指标,其是基于二分类的思想进行发展延展的,即:将混淆矩阵行、列扩宽(类别增多),进行计算。
二分类和多分类混淆矩阵相关公式的计算都遵循上述口诀:“对角都对,横看真实,竖看预测”,所以遵循此原则,就很容易理解二分类和多分类的语义分割指标。
列举一个多分类(三类别)泛化一下知识点,防止读者学习过拟合!
以类别1为例,计算公式为:
更多计算例子,见:
4.4.2分类模型评判指标(一) - 混淆矩阵(Confusion Matrix)
先回顾一下上述的例子:
宠物店有10只动物,其中6只狗,4只猫(真实值),现有一个模型将这10只动物进行分类,分类结果为(预测结果为):5只狗,5只猫(预测值)
此例子中:
进行分类的基础是:动物,然后将动物区分为狗、猫
语义分割中:
进行分类的基础是:图片中的像素点,然后将像素预测为是什么类别
进行上述区分,目的是让大家知道:不管进行分类的是动物,还是图片像素点,其混淆矩阵的获取、评价指标公式的计算都是一样的!
下面开始正题:
一般论文中,对语义分割模型的评估指标有:
那么问题来了,交集和并集是如何来的?
继续以二分类为例说明,求正例(类别1)的IoU:
如图所示,A代表真实值(ground truth),B代表预测样本(prediction),预测值和真实值的关系如下:
因此,可知正例(类别1)的IoU为:
IoU = TP / (TP + FN + FP)
此处有个比较疑惑的点是:TN
有什么用?
因为我们求的是正例(Positive)的IoU,即:只用与P有关的混淆矩阵相关元素:TP、FP、FN,TN是与P无关,所以对于求正例的IoU无用!
如何找与P有关的混淆矩阵元素?
“画线法”
求正例的IoU,在真实情况和预测结果正例中各画一条线,线所过之处的元素则与正例有关!
同理,若求反例的IoU,则:
与求反例的IoU有关的元素有:TN FN FP,公式为:
IoU = TN / (TN + FP + FN)
此时是不是有种豁然开朗的感觉?
同理,将画线法
延伸到多分类的情形,其余类别的计算公式也是如此得来!
对于IoU的理解,可延伸阅读一下:
语义分割的评价指标——IoU
语义分割代码阅读—评价指标mIoU的计算
上述对语义分割中常用5个评价指标进行了简介,下面列举一个例子,按照评价指标计算的三个步骤进行讲解:
当你跑完模型,得到网络预测结果图时,用标注图(ground truth)和预测图(pridiction)来得出网络的性能评估指标。语义分割评估指标代码大同小异,一般都是基于FCN源码的score.py中fast_hist()函数
的思想来进行计算的。
下面假定输入图片大小为3*3,图片中的像素点表示其分类(即:0代表类别1,1代表类别2,2代表类别3),如图:左图a为标注图,右图b为预测图(参考链接)
左、右图进行对比可知,b中预测值有两个像素点分类错误,即:
混淆矩阵由:fast_hist(a, b, n)
函数求出,此函数也是评估指标相关代码的关键!
'''
FCN中score.py原版代码
参考链接:https://blog.csdn.net/qq_21368481/article/details/80424754
功能:
产生n×n的混淆矩阵
参数a:标注图(转换为一行输入),即真实的标签
参数b:预测图(转换为一行输入),即预测的标签
参数n: 类别数
'''
def fast_hist(a, b, n):
#k为掩膜(去除了255这些点(即标签图中的白色的轮廓),其中的a>=0是为了防止bincount()函数出错)
k = (a >= 0) & (a < n)
#bincount()函数用于统计数组内每个非负整数的个数
#详见https://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html
return np.bincount(n * a[k].astype(int) + b[k], minlength=n**2).reshape(n, n) #核心代码
'''
fast_hist(a, b, n)函数的变形代码,都是为了求混淆矩阵
参考链接:https://blog.csdn.net/kkkkk0826/article/details/102962168
'''
def genConfusionMatrix(self, imgPredict, imgLabel):
# remove classes from unlabeled pixels in gt image and predict
mask = (imgLabel >= 0) & (imgLabel < self.numClass)
label = self.numClass * imgLabel[mask] + imgPredict[mask]
count = np.bincount(label, minlength=self.numClass**2) # 核心代码
confusionMatrix = count.reshape(self.numClass, self.numClass)
return confusionMatrix
上述函数的功能就是基于标注图和预测图求出混淆矩阵,进而求取相关评估指标的值!
下面对求混淆矩阵的核心代码,进行引用讲解(此大佬写得太好了,链接:FCN源码解读之score.py)
核心代码:np.bincount(n * a[k].astype(int) + b[k], minlength=n**2)
(np.bincount函数学习链接:numpy.bincount详解)
其作用是产生一行n * n个元素的向量,向量中的每个元素存储统计结果,假如该向量为d,则其中的d(i*n + j)表示预测结果为类别j,实际标签为类别i的所有像素点的数目。
将上述的a、b和n输入fast_hist(a, b, n),所产生的d为:d=(3,0,0,0,2,1,0,1,2),其中的d(1*3 + 1)=d(4)表示预测类别为1,实际标签也为1的所有像素点数目为2。
通过reshape(n, n)将向量d转换为3*3的矩阵,其结果如下表(该矩阵即为下表中的绿色部分):
其中绿色的3*3表格统计的含义,拿数字3所在的这一格为例,即预测标签中被预测为类别0的且其真实标签也为0的所有像素点数目之和。
下面继续引用大佬讲解,遵循:对角都对,横看真实,竖看预测 原则
表格分析注意小点:
①绿色表格中对角线元素上的数字即为该类别预测正确的像素点数目,非对角线元素都是预测错误的,拿最后一行的数字1为例,其含义即为有一个原本应属于类别2的像素点被错误地预测为类别1;
②绿色表格的每一行求和得到的数字的含义是真实标签中属于某一类别的所有像素点数目,拿第一行为例,3+0+0=3,即真实属于类别0的像素点一共3个;
③绿色表格的每一列求和得到的数字的含义是预测为某一类别的所有像素点数目,拿第二列为例,0+2+1=3,即预测为类别1的所有像素点共有3个。
根据混淆矩阵,则可进行评价指标计算了!
基于上述混淆矩阵,下面列举一下具体计算值!
PA = 对角线元素之和 / 矩阵所有元素之和 = (3 + 2 + 2) / (3 + 2 + 2 + 0 + 0 + 0 + 0 + 1 + 1)= 0.78
Pi = 对角线值 / 对应列的像素总数
P类别1 = 3 / ( 3 + 0 + 0) = 1
P类别2 = 2 / ( 0 + 2 + 1) = 0.67
P类别3 = 2 / ( 0 + 1 + 2) = 0.67
MPA = sum(Pi) / 类别数 = ( P类别1 + P类别2 + P类别3 ) / 3 = 0.78
IoUi = 对角线值 / 与该值有关元素的求和 [画线法]
第一种求法:(混淆矩阵,以对角线值画横、竖线,将相关元素求和)
IoU类别1 = 3 / (3 + 0 + 0 + 0 + 0) = 1
IoU类别2 = 2 / (0 + 2 + 1 + 0 + 1) = 0.5
IoU类别3 = 2 / (0 + 1 + 2 + 0 + 1) = 0.5
第二种求法:(代码中用的求法:SAUB = SA + SB - SA∩B)(参考链接)
IoU类别1 = 3 / [(3 + 0 + 0 ) + ( 3 + 0 + 0) - 3] = 1
IoU类别2 = 2 / [(0 + 2 + 1 ) + (0 + 2 + 1) - 2] = 0.5
IoU类别3 = 2 / [(0 + 1 + 2 ) + (0 + 1 + 2) - 2] = 0.5
MIoU = sum(IoUi) / 类别数 = (1 + 0.5 + 0.5) / 3 = 0.67
参考:
【语义分割】评价指标总结及代码实现
"""
refer to https://github.com/jfzhang95/pytorch-deeplab-xception/blob/master/utils/metrics.py
"""
import numpy as np
__all__ = ['SegmentationMetric']
"""
confusionMetric # 注意:此处横着代表预测值,竖着代表真实值,与之前介绍的相反
P\L P N
P TP FP
N FN TN
"""
class SegmentationMetric(object):
def __init__(self, numClass):
self.numClass = numClass
self.confusionMatrix = np.zeros((self.numClass,)*2)
def pixelAccuracy(self):
# return all class overall pixel accuracy
# PA = acc = (TP + TN) / (TP + TN + FP + TN)
acc = np.diag(self.confusionMatrix).sum() / self.confusionMatrix.sum()
return acc
def classPixelAccuracy(self):
# return each category pixel accuracy(A more accurate way to call it precision)
# acc = (TP) / TP + FP
classAcc = np.diag(self.confusionMatrix) / self.confusionMatrix.sum(axis=1)
return classAcc # 返回的是一个列表值,如:[0.90, 0.80, 0.96],表示类别1 2 3各类别的预测准确率
def meanPixelAccuracy(self):
classAcc = self.classPixelAccuracy()
meanAcc = np.nanmean(classAcc) # np.nanmean 求平均值,nan表示遇到Nan类型,其值取为0
return meanAcc # 返回单个值,如:np.nanmean([0.90, 0.80, 0.96, nan, nan]) = (0.90 + 0.80 + 0.96) / 3 = 0.89
def meanIntersectionOverUnion(self):
# Intersection = TP Union = TP + FP + FN
# IoU = TP / (TP + FP + FN)
intersection = np.diag(self.confusionMatrix) # 取对角元素的值,返回列表
union = np.sum(self.confusionMatrix, axis=1) + np.sum(self.confusionMatrix, axis=0) - np.diag(self.confusionMatrix) # axis = 1表示混淆矩阵行的值,返回列表; axis = 0表示取混淆矩阵列的值,返回列表
IoU = intersection / union # 返回列表,其值为各个类别的IoU
mIoU = np.nanmean(IoU) # 求各类别IoU的平均
return mIoU
def genConfusionMatrix(self, imgPredict, imgLabel): # 同FCN中score.py的fast_hist()函数
# remove classes from unlabeled pixels in gt image and predict
mask = (imgLabel >= 0) & (imgLabel < self.numClass)
label = self.numClass * imgLabel[mask] + imgPredict[mask]
count = np.bincount(label, minlength=self.numClass**2)
confusionMatrix = count.reshape(self.numClass, self.numClass)
return confusionMatrix
def Frequency_Weighted_Intersection_over_Union(self):
# FWIOU = [(TP+FN)/(TP+FP+TN+FN)] *[TP / (TP + FP + FN)]
freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix)
iu = np.diag(self.confusion_matrix) / (
np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -
np.diag(self.confusion_matrix))
FWIoU = (freq[freq > 0] * iu[freq > 0]).sum()
return FWIoU
def addBatch(self, imgPredict, imgLabel):
assert imgPredict.shape == imgLabel.shape
self.confusionMatrix += self.genConfusionMatrix(imgPredict, imgLabel)
def reset(self):
self.confusionMatrix = np.zeros((self.numClass, self.numClass))
if __name__ == '__main__':
imgPredict = np.array([0, 0, 1, 1, 2, 2]) # 可直接换成预测图片
imgLabel = np.array([0, 0, 1, 1, 2, 2]) # 可直接换成标注图片
metric = SegmentationMetric(3) # 3表示有3个分类,有几个分类就填几
metric.addBatch(imgPredict, imgLabel)
pa = metric.pixelAccuracy()
cpa = metric.classPixelAccuracy()
mpa = metric.meanPixelAccuracy()
mIoU = metric.meanIntersectionOverUnion()
print('pa is : %f' % pa)
print('cpa is :') # 列表
print(cpa)
print('mpa is : %f' % mpa)
print('mIoU is : %f' % mIoU)
相关函数详解,请参考链接:
【语义分割】评价指标代码函数:np.sum()、np.nansum()、np.nanmean()、np.diag()、np.bincount()
测评截图:
步骤一:输入预测和标记图片 (4个类别分类,图片大小:512*512)
imgPredict = pic_predict # 预测图片
imgLabel = pic_label # 标注图片
步骤二:获取混淆矩阵 (特别注意:此代码混淆矩阵,行为预测值,列为真实值,因此:CPA的计算需要翻转一下)
可计算上述值的和为:262144,即:图片512 * 512的值,表示模型共预测了512 * 512个像素点的类别
步骤三:评估指标计算
还可参考:
语义分割常用指标详解(附代码)
不知道我说清楚了吗?
欢迎点赞!
欢迎大家留言讨论!