mmdetection の Mask Rcnn は、ターゲット検出とセマンティック セグメンテーションの両方を実現できる非常に優れた検出ネットワークです。公式にも非常に詳細なドキュメントガイダンスがありますが、初心者には親切ではなく、たまたま著者が以前書いた mmlab シリーズには可視化に関する詳細なドキュメントがなかったので、ここで紹介します。
独自のデータ セットを作成し、独自のモデルをトレーニングする方法に関する具体的なチュートリアルは次のとおりです。
mmdetect2d で独自のデータ セットをトレーニング (1) - ラベル付けデータ処理
mmdetect2d で独自のデータ セットをトレーニング (2) - モデルのトレーニング
上記の 2 つのチュートリアルを通じて、独自の構成ファイル、チェックポイント ファイル、トレーニング ログ xxx.log.json ファイルを取得するようにトレーニングできます。次の視覚化では、これら 3 つを使用します。
注: mmdetection を実行するとき、pycharm を使用します。ここで奇妙なバグがあります。つまり、プログラムをターミナルで直接実行すると、mmcv のバージョンが等しくないなどのバグが発生しますが、実行できます。走ってください(しないでください。どうやって知っているかを尋ねたら、1週間立ち往生することになります...)。したがって、バグがあるときに急いで自分を否定しないで、もっと調査してください。
モデル検出導入(検出結果の可視化)
モデルの検出結果を可視化するにはさまざまな方法がありますが、ここでは 1. API を呼び出して直接検出する方法、2. test.py を使用して検出する方法の 2 つを説明します。
1.test.pyを使用する
test.py を実行するには、構成ファイルとチェックポイント ファイルが必要です。その他は次のようにオプションのパラメーターです。
python tools/test.py configs/myconfig.py checkpoints/last.pth \
--show --out result_file.pkl --show-dir result/test_result --eval segm
# --show 决定是否现实图片
# --out 将结果输出为pkl格式的文件
# --show-dir 将测试得到的文件存到目标文件夹下
# --eval 选择需要评估的指标,比如segm是分割的情况,这是mask rcnn网络会有这个结果,还有bbox等
2. APIを呼び出す
mmdet には多くの既製の API が統合されており、検出結果を直接表示するために使用することもできます。
from mmdet.apis import init_detector, inference_detector, show_result_pyplot
import cv2
import numpy as np
# config文件地址
config_file = 'others_ct/ct_head_mask_rcnn_r50_rpn_100_coco/mask_rcnn_r50_fpn_1x_coco.py'
# checkpoint文件地址
checkpoint_file = 'others_ct/ct_head_mask_rcnn_r50_rpn_100_coco/latest.pth'
# 选择使用的显卡
device = 'cuda:0'
# 模型载入
model = init_detector(config_file, checkpoint_file, device=device)
# 待检测的图片地址
img = 'data/coco_ct/val2017/Snipaste_2022-07-28_15-48-49.jpg'
# 检测结果输出
result = inference_detector(model, img)
#bbox_result, mask_result = result
# 现实检测结果
show_result_pyplot(model, img, result, score_thr=0.3)
3. 独自の検証セットのアノテーションを比較する
先ほどはlabelmeを使って直接読みました。もちろんb形式だけでは足りないようです。mmdetectionで直接読み込むこともできます。私はbrowse_dataset.pyを使います。トレーニングセットを表示するために使用できます。もちろん、テスト セットを表示したい場合は、ファイル内の data dict 内の train dict の ann_file パスと img_profix パスを val dict のものと同じになるように変更する構成を使用できます
。この実用的な方法の利点の--show # 如果你要现实每一张的话
1 つは、--output-dir ../mydir # 浏览的图片可以保存到该地址下
test.py によって予測された画像である比較的きれいな比較画像を取得できることです。この画像にはマークが付けられます。ただし、結果を同じ画像に表示し、ラベルと予測を同じ画像に配置する場合は、、mmdetection は対応するコードも提供します。tools/analyze_results.py を使用するだけです。使用方法は次のとおりです。
python tools/analysis_tools/analyze_results.py \
${
CONFIG} \
${
PREDICTION_PATH} \
${
SHOW_DIR} \
[--show] \
[--wait-time ${
WAIT_TIME}] \
[--topk ${
TOPK}] \
[--show-score-thr ${
SHOW_SCORE_THR}] \
[--cfg-options ${
CFG_OPTIONS}]
{
CONFIG}: 是config文件路径
{
PREDICTION_PATH}: 是test.py得到的pkl文件路径
{
SHOW_DIR}: 是绘制的到的图片存放的目录
--show: 决定是否显示,不指定的话,默认为不显示
--wait-time时间的间隔,若为 0 表示持续显示
--topk: 根据最高或最低 topk 概率排序保存的图片数量,若不指定,默认设置为 20
--show-score-thr: 能够展示的概率阈值,默认为 0
--cfg-options: 如果指定,可根据指定键值对覆盖更新配置文件的对应选项
DICE(DSC)計算
Mask-rcnn ネットワークを使用しています。セマンティック セグメンテーションの結果を計算したいです。理論的には、mmdetection のサイコロの損失を直接使用して計算できますが、数日間探しても見つかりませんでした。主な理由は、出力方法が分からないので手動で書きました。主な原理は、マークされたマスクの面積、予測されたマスクの面積、および 2 つの面積を 1 つずつ計算することです。
セマンティック セグメンテーションでは、Dice = 2 * (A∩B) /(|A| + |B|)
ただし、ここで注意すべき点が 1 つあります。単一カテゴリの検出を行っているため、検出結果は最大 1 つになります。複数のカテゴリがある場合は、コードの変更が必要になる可能性があり、後で使用するときにコードが更新されます。
そこでマスク部分のnumpy配列出力をmmdetectionに保存しました。具体的には、image.py で def imshow_det_bboxes 関数を見つけて、segms を決定する部分に出力命令を追加します。
if segms is not None:
##############在原代码中的if语句里的开头部分添加该部分代码#################
# 获取输出的的图片名称,这里一定要在test.py 和 browse_dataset.py的时候,选择保存文件,即必须有--show-dir和路径
file_tmp = str(out_file)
# 设置npy文件的名称
file_name = file_tmp[:-3] + 'npy'
# 将segms的numpy数组保存为npy文件
np.save(file_name, segms)
####################################################################
次に、上記の test.py ファイルとbrowse_dataset.pyを実行すると、画像と同じ名前の npy ファイルが --show-dir の下に生成されます。サイコロの計算には、ファイルのこの部分を使用します。
したがって、具体的なコードは次のとおりです。
import numpy as np
import os
# 存放预测结果的路径(前面test.py结果的--show-dir)
pred_root_path = '/home/kevin/mmlab/mmdetection/tools/result004'
# 存放标签的路径(前面dataset.py结果的--show-dir)
label_root_path = '/home/kevin/mmlab/mmdetection/tools/misc/ct_results'
# 读取预测结果路径下的文件
file_list_tmp = os.listdir(pred_root_path)
file_list = []
dice_list = []
for i in file_list_tmp:
# 判定是否为npy文件
if i[-3:] == 'npy':
# 获取预测的npy文件路径
pred_path = pred_root_path + '/' + i
# 获取同名的标签文件路径
label_path = label_root_path + '/' + i
print(pred_path)
print(label_path)
# 导入ndarray文件
pred = np.load(pred_path)
label = np.load(label_path)
# 标签文件中的数组是Ture和False组成,需要变成1、0
pred_1 = pred + 0
# 因为browse得到的标签文件会有0.5的比例Flip来达到数据增强的目的,即图像会水平翻转
# 所以产生的mask文件也有一半的概率是水平翻转过的,需要将其翻转回来
pred_2 = pred_1[:,:,::-1]
# 有时候检测为空但还是会有pred的npy文件,原因我也不清楚,所以这里是加了个判定,如果输出为空则直接跳过。
# 预测的mask文件形状都是(n, 512, 512)形状的,n取决于预测的个数,我是单目标,所以最多只有一个。
if pred_1.shape == (0, 512, 512):
continue
# 同上
label = label + 0
# 将 (1, 512, 512) 展平为 (262144,),目的是方便计算
b1_1 = pred_1.flatten()
b1_2 = pred_2.flatten()
b2 = label.flatten()
# 分别计算预测的和标签的两个mask部分的面积,因为翻转对面积不影响,这里只计算一次
pred_area = np.sum(b1_1)
label_area = np.sum(b2)
# 计算IoU
iou_pred_label_1 = b1_1 * b2
iou_pred_label_2 = b1_2 * b2
iou_area_1 = np.sum(iou_pred_label_1)
iou_area_2 = np.sum(iou_pred_label_2)
# print(iou_area_1, iou_area_2)
# IoU小说明该图被翻转过了,因此选大的那个
iou_area = max(iou_area_1, iou_area_2)
print(iou_area)
# 防止为空文件
if (pred_area + label_area) > 0:
dice = (2 * iou_area) / (pred_area + label_area)
else:
dice = 0
# 将得到的dice加入list
dice_list.append(dice)
# print("dice_list is:", dice_list)
# 求和前将list转为numpy数组
dice_np = np.array(dice_list)
dice_sum = np.sum(dice_np)
# 计算总共有多少张图片,因为一些检测为空,所以用的是browse的结果,由于结果包含图片和npy,所以除以2
pic_num = len(os.listdir(label_root_path)) / 2
print('num is:', pic_num)
# 计算平均Dice
Dice = dice_sum / pic_num
print('Dice is:', Dice)
トレーニングログの可視化
この部分は主に mmdetection のトレーニングで得られた json ファイルを可視化するためのものです コードは主に github からのものです どれだったか忘れました (readme に元のアドレスがありません...) mmdetection の結果を可視化するために特別に作成されましたとても強いです!!。使用中にキーエラーが発生した場合は、json ファイルの最初の行にある env_info を削除してください。
import json
import matplotlib.pyplot as plt
import sys
import os
from collections import OrderedDict
class visualize_mmdetection():
def __init__(self, path):
self.log = open(path)
self.dict_list = list()
self.loss_rpn_bbox = list()
self.loss_rpn_cls = list()
self.loss_bbox = list()
self.loss_cls = list()
self.loss = list()
self.acc = list()
def load_data(self):
for line in self.log:
info = json.loads(line)
# print('info:', info)
if info['mode'] == 'train':
self.dict_list.append(info)
for i in range(1, len(self.dict_list)):
for value, key in dict(self.dict_list[i]).items():
# 读取每一行的信息
loss_rpn_cls_value = dict(self.dict_list[i])['loss_rpn_cls']
loss_rpn_bbox_value = dict(self.dict_list[i])['loss_rpn_bbox']
loss_bbox_value = dict(self.dict_list[i])['loss_bbox']
loss_cls_value = dict(self.dict_list[i])['loss_cls']
loss_value = dict(self.dict_list[i])['loss']
acc_value = dict(self.dict_list[i])['acc']
# 将其保存至对应列表中
self.loss_rpn_cls.append(loss_rpn_cls_value)
self.loss_rpn_bbox.append(loss_rpn_bbox_value)
self.loss_bbox.append(loss_bbox_value)
self.loss_cls.append(loss_cls_value)
self.loss.append(loss_value)
self.acc.append(acc_value)
# 清除list中的重复项
self.loss_rpn_cls = list(OrderedDict.fromkeys(self.loss_rpn_cls))
self.loss_rpn_bbox = list(OrderedDict.fromkeys(self.loss_rpn_bbox))
self.loss_bbox = list(OrderedDict.fromkeys(self.loss_bbox))
self.loss_cls = list(OrderedDict.fromkeys(self.loss_cls))
self.loss = list(OrderedDict.fromkeys(self.loss))
self.acc = list(OrderedDict.fromkeys(self.acc))
def show_chart(self):
plt.rcParams.update({
'font.size': 15})
plt.figure(figsize=(20, 20))
plt.subplot(321, title='loss_rpn_cls', ylabel='loss')
plt.plot(self.loss_rpn_cls)
plt.subplot(322, title='loss_rpn_bbox', ylabel='loss')
plt.plot(self.loss_rpn_bbox)
plt.subplot(323, title='loss_cls', ylabel='loss')
plt.plot(self.loss_cls)
plt.subplot(324, title='loss_bbox', ylabel='loss')
plt.plot(self.loss_bbox)
plt.subplot(325, title='total loss', ylabel='loss')
plt.plot(self.loss)
plt.subplot(326, title='accuracy', ylabel='accuracy')
plt.plot(self.acc)
plt.suptitle((sys.argv[1][5:] + "\n training result"), fontsize=30)
plt.savefig(('output/' + sys.argv[1][5:] + '_result.png'))
if __name__ == '__main__':
x = visualize_mmdetection(sys.argv[1])
x.load_data()
x.show_chart()
直接使用する場合:
python visualize.py xxxx.json
xxxx.json は生成された json ファイルで、結果は次のようになります。
PR曲線の描画
mmdetection で PR 曲線を描画するための組み込みコードがあるかどうかはわかりません。これは他のブロガーによって書かれたコードへの参照です。
import os
import mmcv
import numpy as np
import matplotlib.pyplot as plt
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
from mmcv import Config
from mmdet.datasets import build_dataset
# config文件路径
CONFIG_FILE = '/home/kevin/mmlab/mmdetection/tools/others_ct/r18/mask_rcnn_r18_fpn_1x_coco.py'
# test.py得到的pkl文件路径
RESULT_FILE = '/home/kevin/mmlab/mmdetection/tools/r18_result.pkl'
## 对比不同网络之间的结果
#CONFIG_FILE_01 = '/home/kevin/mmlab/mmdetection/tools/others_ct/r18/mask_rcnn_r18_fpn_1x_coco.py'
#RESULT_FILE_01 = '/home/kevin/mmlab/mmdetection/tools/r18_result.pkl'
#CONFIG_FILE_02 = '/home/kevin/mmlab/mmdetection/others_ct/ct_head_mask_rcnn_r50_rpn_100_coco/mask_rcnn_r50_fpn_1x_coco.py'
#RESULT_FILE_02 = '/home/kevin/mmlab/mmdetection/tools/r50_result.pkl'
#CONFIG_FILE_03 = '/home/kevin/mmlab/mmdetection/tools/others_ct/ct_head_mask_rcnn_r101_rpn_100_coco/mask_rcnn_r101_fpn_1x_coco.py'
#RESULT_FILE_03 = '/home/kevin/mmlab/mmdetection/tools/r101_result.pkl'
# 绘制曲线
def plot_pr_curve(config_file, result_file, metric="bbox"):
"""plot precison-recall curve based on testing results of pkl file.
Args:
config_file (list[list | tuple]): config file path.
result_file (str): pkl file of testing results path.
metric (str): Metrics to be evaluated. Options are
'bbox', 'segm'.
"""
cfg = Config.fromfile(config_file)
# turn on test mode of dataset
if isinstance(cfg.data.test, dict):
cfg.data.test.test_mode = True
elif isinstance(cfg.data.test, list):
for ds_cfg in cfg.data.test:
ds_cfg.test_mode = True
# build dataset
dataset = build_dataset(cfg.data.test)
# load result file in pkl format
pkl_results = mmcv.load(result_file)
# convert pkl file (list[list | tuple | ndarray]) to json
json_results, _ = dataset.format_results(pkl_results)
# initialize COCO instance
coco = COCO(annotation_file=cfg.data.test.ann_file)
coco_gt = coco
coco_dt = coco_gt.loadRes(json_results[metric])
# initialize COCOeval instance
coco_eval = COCOeval(coco_gt, coco_dt, metric)
coco_eval.evaluate()
coco_eval.accumulate()
coco_eval.summarize()
# extract eval data
precisions = coco_eval.eval["precision"]
'''
precisions[T, R, K, A, M]
T: 是IOU的阈值,值为0-9,依次对应,[0.5 : 0.05 : 0.95],
R: 召回率的阈值,[0 : 0.01 : 1], idx from 0 to 100
K: 检测类别的索引,我只有1类,所以为0
A: 检测的大小,对应 (all, small, medium, large), 索引值0-3
M: 最大检测数量, 对应(1, 10, 100), 索引值0-2
'''
pr_array1 = precisions[0, :, 0, 0, 2]
pr_array2 = precisions[1, :, 0, 0, 2]
pr_array3 = precisions[2, :, 0, 0, 2]
pr_array4 = precisions[3, :, 0, 0, 2]
pr_array5 = precisions[4, :, 0, 0, 2]
pr_array6 = precisions[5, :, 0, 0, 2]
pr_array7 = precisions[6, :, 0, 0, 2]
pr_array8 = precisions[7, :, 0, 0, 2]
pr_array9 = precisions[8, :, 0, 0, 2]
pr_array10 = precisions[9, :, 0, 0, 2]
# 计算平均值 [email protected]:0.95
pr_array = pr_array1 + pr_array2 +pr_array3 +pr_array4 + pr_array5 + \
pr_array6 + pr_array7 + pr_array8 + pr_array9 +pr_array10
print(pr_array/10)
x = np.arange(0.0, 1.01, 0.01)
# 绘制PR曲线
plt.plot(x, pr_array1, label="iou=0.5")
plt.plot(x, pr_array2, label="iou=0.55")
plt.plot(x, pr_array3, label="iou=0.6")
plt.plot(x, pr_array4, label="iou=0.65")
plt.plot(x, pr_array5, label="iou=0.7")
plt.plot(x, pr_array6, label="iou=0.75")
plt.plot(x, pr_array7, label="iou=0.8")
plt.plot(x, pr_array8, label="iou=0.85")
plt.plot(x, pr_array9, label="iou=0.9")
plt.plot(x, pr_array10, label="iou=0.95")
plt.xlabel("recall")
plt.ylabel("precison")
plt.xlim(0, 1.0)
plt.ylim(0, 1.01)
plt.grid(True)
plt.legend(loc="lower left")
# 保存图像
plt.savefig('PR_r18.png')
plt.show()
# 只绘制ap @ 0.5:0.95
# return pr_array/10
if __name__ == "__main__":
plot_pr_curve(config_file=CONFIG_FILE, result_file=RESULT_FILE, metric="bbox")
# pr_array_1 = plot_pr_curve(config_file=CONFIG_FILE_01, result_file=RESULT_FILE_01, metric="bbox")
# pr_array_2 = plot_pr_curve(config_file=CONFIG_FILE_02, result_file=RESULT_FILE_02, metric="bbox")
# pr_array_3 = plot_pr_curve(config_file=CONFIG_FILE_03, result_file=RESULT_FILE_03, metric="bbox")
# x = np.arange(0.0, 1.01, 0.01)
# plot PR curve
# plt.plot(x, pr_array_1, label="r18")
# plt.plot(x, pr_array_2, label="r50")
# plt.plot(x, pr_array_3, label="r100")
# plt.xlabel("recall")
# plt.ylabel("precison")
# plt.xlim(0, 1.0)
# plt.ylim(0, 1.01)
# plt.grid(True)
# plt.legend(loc="lower left")
# plt.savefig('PR_r18_r50_r101.png')
# plt.show()
ここで問題があります。つまり、plt を使用して画像を保存する場合は、ply.show の前に行う必要があります。そうでない場合は、デフォルトで表示後に画像を出力するため、空の画像として保存されます。保存する画像がありません。
結果のグラフは次のとおりです。
モデルの複雑さの計算
この公式文書にはpython tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
次のような結果があります。
==============================
Input shape: (3, 1280, 800)
Flops: 239.32 GFLOPs
Params: 37.74 M
==============================
出力画像の加工
mmdetection によって検出またはプレビューされた画像はすべて image.py に実装されています。たとえば、セマンティック セグメンテーションの効果を実現するためにマスク rcnn のみを使用する場合、bbox と label の出力は実際には少し冗長になります。また、def imshow_det_bboxes では、draw_bboxes とdraw_labels をコメントアウトするだけです。