MIoU
全稱為Mean Intersection over Union
,平均交并比??勺鳛檎Z義分割系統(tǒng)性能的評價指標(biāo)。
- P:Prediction預(yù)測值
- G:Ground Truth真實值
其中IOU
: 交并比就是該類的真實標(biāo)簽和預(yù)測值的交和并的比值
單類的交并比可以理解為下圖:
1. 語義分割的評價指標(biāo)
True Positive (
TP
): 把正樣本成功預(yù)測為正。
True Negative (TN
):把負(fù)樣本成功預(yù)測為負(fù)。
False Positive (FP
):把負(fù)樣本錯誤地預(yù)測為正。
False Negative (FN
):把正樣本錯誤的預(yù)測為負(fù)。
-
(1) Accuracy
準(zhǔn)確率,指的是“預(yù)測正確的樣本數(shù)÷樣本數(shù)總數(shù)
”。計算公式為:
A c c u r a c y = T P + T N T P + T N + F P + F N Accuracy = \frac{TP+TN}{TP+TN+FP+FN} Accuracy=TP+TN+FP+FNTP+TN? -
(2) Precision
精確率或者精度,指的是預(yù)測為Positive的樣本,占所有預(yù)測樣本的比率
P r e c i s i o n = T P T P + F P Precision= \frac{TP}{TP+FP} Precision=TP+FPTP? -
(3)Recall
召回率,指的是預(yù)測為Positive的樣本,占所有Positive樣本的比率
P r e c i s i o n = T P P Precision= \frac{TP}{P} Precision=PTP? -
(4) F1 score
: 綜合考慮了precision
和recall
兩方面的因素,做到了對于兩者的調(diào)和,即:既要“求精”也要“求全”,做到不偏科。
F 1 s c o r e = 2 ? p r e c i s i o n ? r e c a l l p r e c i s i o n + r e c a l l F1 score= \frac{2*precision*recall}{precision+recall} F1score=precision+recall2?precision?recall?
- (5)
MIOU
作為為語義分割最重要標(biāo)準(zhǔn)度量。其計算兩個集合的交集和并集之比,在語義分割的問題中,這兩個集合為真實值和預(yù)測值。在每個類上計算IoU,之后平均。計算公式如下
M I O U = 1 k + 1 ∑ i = 0 k T P F N + F P + T P MIOU =\frac{1}{k+1}\sum_{i=0}^{k}\frac{TP}{FN+FP+TP} MIOU=k+11?i=0∑k?FN+FP+TPTP?
等價于:
M I O U = 1 k + 1 ∑ i = 0 k p i i ∑ j = 0 k p i j + ∑ j = 0 k p j i ? p i i MIOU=\frac{1}{k+1}\sum_{i=0}^{k}\frac{p_{ii}}{\sum_{j=0}^k p_{ij} + \sum_{j=0}^k p_{ji} -p_{ii}} MIOU=k+11?i=0∑k?∑j=0k?pij?+∑j=0k?pji??pii?pii??
其中:
p
i
i
p_{ii}
pii? 真實為類別i,預(yù)測也為i的像素個數(shù)
,也就是正確預(yù)測的像素個數(shù)TP
;
p
i
j
p_{ij}
pij?表示真實為類別i,但預(yù)測為類別j的像素個數(shù)
,也就是FN
;
p
j
i
p_{ji}
pji?表示真實為類別j,但預(yù)測為類別i的像素個數(shù)
, 也就是FP
注意
: 對于多分類
,TN
為0 ,即沒有所謂的負(fù)樣本
2. 混淆矩陣計算
- 計算
MIoU
,我們需要借助混淆矩陣
來進行計算。 -
混淆矩陣就是統(tǒng)計分類模型的分類結(jié)果
,即:統(tǒng)計歸對類,歸錯類的樣本的個數(shù),然后把結(jié)果放在一個表里展示出來,這個表就是混淆矩陣 - 其
每一列代表預(yù)測值(pred)
,每一行代表的是實際的類別(gt)
-
對角都對TP,橫看真實,豎看預(yù)測
: 每一行之和,為該行對應(yīng)類(如Cat)的總數(shù);每一列之和為該列對應(yīng)類別的預(yù)測的總數(shù)。
2.1 np.bincount的使用
在計算混淆矩陣
時,可以利用np.bincount
函數(shù)方便我們計算。
numpy.bincount(x, weights=None, minlength=None)
- 該方法返回每個索引值在x中出現(xiàn)的次數(shù)
- 給一個向量
x
,x中最大的元素記為j
,返回一個向量1行j+1
列的向量y,y[i
]代表i在x中出現(xiàn)的次數(shù)
#x中最大的數(shù)為7,那么它的索引值為0->7
x = np.array([0, 1, 1, 3, 2, 1, 7])
#索引0出現(xiàn)了1次,索引1出現(xiàn)了3次......索引5出現(xiàn)了0次......
np.bincount(x)
#因此,輸出結(jié)果為:array([1, 3, 1, 1, 0, 0, 0, 1])
-
minlength
也是一個常用的參數(shù),表示輸出的數(shù)組長度至少為minlength
,如果x中最大的元素加1大于數(shù)組長度,那么數(shù)組的長度以x中最大元素加1為準(zhǔn)(例如,如果數(shù)組中最大元素為3,minlength=5,那么數(shù)組的長度為5;如果數(shù)組中最大元素為7,minlength=5,那么數(shù)組的最大長度為7+1=8,這里之所以加1是因為元素0也占了一個索引)。舉個例子說明:
# a中最大的數(shù)為3,因此數(shù)組長度為4,那么它的索引值為0->3
a = np.array([2, 2, 1, 3 ])
# 本來數(shù)組的長度為4,但指定了minlength為7,因此現(xiàn)在數(shù)組長度為7(多的補0),所以現(xiàn)在它的索引值為0->6
np.bincount(x, minlength=7)
# 輸出結(jié)果為:array([0, 1, 2, 1, 0, 0, 0])
# a中最大的數(shù)為4,因此bin的數(shù)量為5,那么它的索引值為0->4
x = np.array([4, 2, 3, 1, 2])
# 數(shù)組的長度原本為5,但指定了minlength為1,因為5 > 1,所以這個參數(shù)不起作用,索引值還是0->4
np.bincount(x, minlength=1)
# 輸出結(jié)果為:array([0, 1, 2, 1,1])
2.2 混淆矩陣計算
# 設(shè)標(biāo)簽寬W,長H
def fast_hist(a, b, n):
#--------------------------------------------------------------------------------#
# a是轉(zhuǎn)化成一維數(shù)組的標(biāo)簽,形狀(H×W,);b是轉(zhuǎn)化成一維數(shù)組的預(yù)測結(jié)果,形狀(H×W,)
#--------------------------------------------------------------------------------#
k = (a >= 0) & (a < n)
#--------------------------------------------------------------------------------#
# np.bincount計算了從0到n**2-1這n**2個數(shù)中每個數(shù)出現(xiàn)的次數(shù),返回值形狀(n, n)
# 返回中,寫對角線上的為分類正確的像素點
#--------------------------------------------------------------------------------#
return np.bincount(n * a[k].astype(int) + b[k], minlength=n ** 2).reshape(n, n)
- 產(chǎn)生
n×n
的混淆矩陣統(tǒng)計表-
參數(shù)a
:即:真實的標(biāo)簽gt
, 需要reshape
為一行輸入 -
參數(shù)b
:即預(yù)測的標(biāo)簽pred
,它是經(jīng)過argmax
輸出的預(yù)測8位標(biāo)簽圖,每個像素表示為類別索引
(reshape為一行輸入), -
參數(shù)n
:類別數(shù)cls_num
-
- 首先過濾
gt
中,類別超過n
的索引,確保gt
的分類都包含在n個類別中
k = (a >= 0) & (a < n)
- 如果要去掉背景,不將背景計算在混淆矩陣,則可以寫為:
k = (a > 0) & (a < n) #去掉了背景,假設(shè)0是背景
- 然后利用
np.bincount
生成元素個數(shù)為n*n
的數(shù)組,并且reshape
為 n × n n \times n n×n的混淆矩陣,這樣確?;煜仃?code>行和列都為類別class
的個數(shù)n
-
n*n
數(shù)組中,每個元素的值,表示為0~n*n
的索引值在x
中出現(xiàn)的次數(shù),這樣就獲得了最終混淆矩陣。這里的x表示為n * a[k] + b[k]
,為啥這么定義呢?
,
舉例如下
:將圖片的gt
標(biāo)簽a和pred
輸出圖片b
,都轉(zhuǎn)換為一行; a和b中每個元素代表類別索引
- 前面
8, 9, 4, 7, 6
都預(yù)測正確, 對于預(yù)測正確的像素來說,n * a + b
就是對角線
的值; 假設(shè)n=10,有10類。n * a + b
就是88, 99, 44, 77, 66
- 緊接著6預(yù)測成了5, 因此n * a + b就是65
- 88, 99, 44, 77, 66就是對角線上的值(
如下圖紅框
,65就是預(yù)測錯誤,并且能真實反映把6預(yù)測成了5(如下圖藍(lán)框
)
3. 語義分割指標(biāo)計算
3.1 IOU計算
方式1(推薦)
計算每個類別的IOU計算:
I
O
U
=
T
P
F
N
+
F
P
+
T
P
IOU =\frac{TP}{FN+FP+TP}
IOU=FN+FP+TPTP?
def per_class_iu(hist):
return np.diag(hist) / np.maximum((hist.sum(1) + hist.sum(0) - np.diag(hist)), 1)
-
輸入hist
表示 2維的混淆矩陣
,大小為n*n
(n為類別數(shù)) - 混淆矩陣
對角線
元素值,表示每個類別預(yù)測正確的數(shù)TP
:
np.diag(hist)
- 其中:混淆矩陣所對應(yīng)
行
中,每一行為對應(yīng)類別(如類1
)的統(tǒng)計值中,對角線位置為正常預(yù)測為該類別的統(tǒng)計值(TP
),其他位置則是錯誤的將該類別預(yù)測為其他的類別FN
: 因此每個類別的FP統(tǒng)計值為:
FN =hist.sum(1) -TP = hist.sum(1) - np.diag(hist)
- 同理,預(yù)測為該類別所對應(yīng)的
列中
,對角線為正確預(yù)測,其他位置則是將其他類別錯誤的預(yù)測為該列所對應(yīng)的類別
,也就是FP
FP =hist.sum(0) -TP = hist.sum(0) - np.diag(hist)
因此分母FN_FP+TP=np.maximum(hist.sum(1) + hist.sum(0) - np.diag(hist),1)
, 這里加上np.maximum
確保了分母不為0
方式2
def IOU(pred,target,n_classes = args.num_class ):
ious = []
# ignore IOU for background class
for cls in range(1,n_classes):
pred_inds = pred == cls
target_inds = target == cls
# target_sum = target_inds.sum()
intersection = (pred_inds[target_inds]).sum()
union = pred_inds.sum() + target_inds.sum() - intersection
if union == 0:
ious.append(float('nan')) # If there is no ground truth,do not include in evaluation
else:
ious.append(float(intersection)/float(max(union,1)))
return ious
參考:https://github.com/dilligencer-zrj/code_zoo/blob/master/compute_mIOU
3.2 Precision 計算
每個類別的Precision
計算如下:
P r e c i s i o n = T P T P + F P Precision= \frac{TP}{TP+FP} Precision=TP+FPTP?
def per_class_Precision(hist):
return np.diag(hist) / np.maximum(hist.sum(0), 1)
- 其中
np.diag(hist)
為TP值,hist.sum(0)
表示為TP+FP
,np.maximum
確保確保分母不為0
3.3 總體的Accuracy計算
總體的Accuracy
計算如下:
A
c
c
u
r
a
c
y
=
T
P
+
T
N
T
P
+
T
N
+
F
P
+
F
N
Accuracy = \frac{TP+TN}{TP+TN+FP+FN}
Accuracy=TP+TN+FP+FNTP+TN?
由于是多類別,沒有負(fù)樣本,因此TN
為0。文章來源:http://www.zghlxwxcb.cn/news/detail-786651.html
def per_Accuracy(hist):
return np.sum(np.diag(hist)) / np.maximum(np.sum(hist), 1)
3.4 Recall 計算
recall指的是預(yù)測為Positive的樣本,占所有Positive樣本的比率
P
r
e
c
i
s
i
o
n
=
T
P
P
Precision= \frac{TP}{P}
Precision=PTP?文章來源地址http://www.zghlxwxcb.cn/news/detail-786651.html
def per_class_PA_Recall(hist):
return np.diag(hist) / np.maximum(hist.sum(1), 1)
- 每一行統(tǒng)計值為該類別樣本的真實數(shù)量P, 因此
P = hist.sum(1)
3.5 MIOU計算
def compute_mIoU(gt_dir, pred_dir, png_name_list, num_classes, name_classes=None):
print('Num classes', num_classes)
#-----------------------------------------#
# 創(chuàng)建一個全是0的矩陣,是一個混淆矩陣
#-----------------------------------------#
hist = np.zeros((num_classes, num_classes))
#------------------------------------------------#
# 獲得驗證集標(biāo)簽路徑列表,方便直接讀取
# 獲得驗證集圖像分割結(jié)果路徑列表,方便直接讀取
#------------------------------------------------#
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]
#------------------------------------------------#
# 讀取每一個(圖片-標(biāo)簽)對
#------------------------------------------------#
for ind in range(len(gt_imgs)):
#------------------------------------------------#
# 讀取一張圖像分割結(jié)果,轉(zhuǎn)化成numpy數(shù)組
#------------------------------------------------#
pred = np.array(Image.open(pred_imgs[ind]))
#------------------------------------------------#
# 讀取一張對應(yīng)的標(biāo)簽,轉(zhuǎn)化成numpy數(shù)組
#------------------------------------------------#
label = np.array(Image.open(gt_imgs[ind]))
# 如果圖像分割結(jié)果與標(biāo)簽的大小不一樣,這張圖片就不計算
if len(label.flatten()) != len(pred.flatten()):
print(
'Skipping: len(gt) = {:d}, len(pred) = {:d}, {:s}, {:s}'.format(
len(label.flatten()), len(pred.flatten()), gt_imgs[ind],
pred_imgs[ind]))
continue
#------------------------------------------------#
# 對一張圖片計算21×21的hist矩陣,并累加
#------------------------------------------------#
hist += fast_hist(label.flatten(), pred.flatten(), num_classes)
# 每計算10張就輸出一下目前已計算的圖片中所有類別平均的mIoU值
if name_classes is not None and ind > 0 and ind % 10 == 0:
print('{:d} / {:d}: mIou-{:0.2f}%; mPA-{:0.2f}%; Accuracy-{:0.2f}%'.format(
ind,
len(gt_imgs),
100 * np.nanmean(per_class_iu(hist)),
100 * np.nanmean(per_class_PA_Recall(hist)),
100 * per_Accuracy(hist)
)
)
#------------------------------------------------#
# 計算所有驗證集圖片的逐類別mIoU值
#------------------------------------------------#
IoUs = per_class_iu(hist)
PA_Recall = per_class_PA_Recall(hist)
Precision = per_class_Precision(hist)
#------------------------------------------------#
# 逐類別輸出一下mIoU值
#------------------------------------------------#
if name_classes is not None:
for ind_class in range(num_classes):
print('===>' + name_classes[ind_class] + ':\tIou-' + str(round(IoUs[ind_class] * 100, 2)) \
+ '; Recall (equal to the PA)-' + str(round(PA_Recall[ind_class] * 100, 2))+ '; Precision-' + str(round(Precision[ind_class] * 100, 2)))
#-----------------------------------------------------------------#
# 在所有驗證集圖像上求所有類別平均的mIoU值,計算時忽略NaN值
#-----------------------------------------------------------------#
print('===> mIoU: ' + str(round(np.nanmean(IoUs) * 100, 2)) + '; mPA: ' + str(round(np.nanmean(PA_Recall) * 100, 2)) + '; Accuracy: ' + str(round(per_Accuracy(hist) * 100, 2)))
return np.array(hist, np.int), IoUs, PA_Recall, Precision
- 首先創(chuàng)建一個維度為
(num_classes, num_classes)
的空混淆矩陣hist
- 遍歷
pred_imgs
和gt_imgs
, 將遍歷得到的每一張pred
和label
展平(flatten
)到一維,輸入到fast_hist
計算單張圖片預(yù)測的混淆矩陣
,將每次的計算結(jié)果加到總的混淆矩陣hist
中
for ind in range(len(gt_imgs)):
#------------------------------------------------#
# 讀取一張圖像分割結(jié)果,轉(zhuǎn)化成numpy數(shù)組
#------------------------------------------------#
pred = np.array(Image.open(pred_imgs[ind]))
#------------------------------------------------#
# 讀取一張對應(yīng)的標(biāo)簽,轉(zhuǎn)化成numpy數(shù)組
#------------------------------------------------#
label = np.array(Image.open(gt_imgs[ind]))
# 如果圖像分割結(jié)果與標(biāo)簽的大小不一樣,這張圖片就不計算
if len(label.flatten()) != len(pred.flatten()):
print(
'Skipping: len(gt) = {:d}, len(pred) = {:d}, {:s}, {:s}'.format(
len(label.flatten()), len(pred.flatten()), gt_imgs[ind],
pred_imgs[ind]))
continue
#------------------------------------------------#
# 對一張圖片計算21×21的hist矩陣,并累加
#------------------------------------------------#
hist += fast_hist(label.flatten(), pred.flatten(), num_classes)
- 每
計算10
張就輸出一下目前已計算的圖片中所有類別平均的mIoU
值
# 每計算10張就輸出一下目前已計算的圖片中所有類別平均的mIoU值
if name_classes is not None and ind > 0 and ind % 10 == 0:
print('{:d} / {:d}: mIou-{:0.2f}%; mPA-{:0.2f}%; Accuracy-{:0.2f}%'.format(
ind,
len(gt_imgs),
100 * np.nanmean(per_class_iu(hist)),
100 * np.nanmean(per_class_PA_Recall(hist)),
100 * per_Accuracy(hist)
)
)
- 遍歷完成后,得到所有類別的Iou值
IoUs
以及PA_Recall
和Precision
,并逐類別輸出一下mIoU值
if name_classes is not None:
for ind_class in range(num_classes):
print('===>' + name_classes[ind_class] + ':\tIou-' + str(round(IoUs[ind_class] * 100, 2)) \
+ '; Recall (equal to the PA)-' + str(round(PA_Recall[ind_class] * 100, 2))+ '; Precision-' + str(round(Precision[ind_class] * 100, 2)))
- 最后在所有驗證集圖像上求所有類別平均的mIoU值
print('===> mIoU: ' + str(round(np.nanmean(IoUs) * 100, 2)) + '; mPA: ' + str(round(np.nanmean(PA_Recall) * 100, 2)) + '; Accuracy: ' + str(round(per_Accuracy(hist) * 100, 2)))
參考
- https://github.com/bubbliiiing/deeplabv3-plus-pytorch/blob/main/utils/utils_metrics.py
- https://github.com/dilligencer-zrj/code_zoo/blob/master/compute_mIOU
- https://www.jianshu.com/p/42939bf83b8a
到了這里,關(guān)于語義分割miou指標(biāo)計算詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!