MMDetection實戰:MMDetection訓練與測試
持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第19天,點選檢視活動詳情 @[toc]
摘要
MMDetection是商湯和港中文大學針對目標檢測任務推出的一個開源專案,它基於Pytorch實現了大量的目標檢測演算法,把資料集構建、模型搭建、訓練策略等過程都封裝成了一個個模組,通過模組呼叫的方式,我們能夠以很少的程式碼量實現一個新演算法,大大提高了程式碼複用率。
GitHub連結:https://github.com/open-mmlab/mmdetection。
Gitee連結:https://gitee.com/open-mmlab/mmdetection。
主分支程式碼目前支援 PyTorch 1.5 以上的版本。主要特性:
- 模組化設計
MMDetection 將檢測框架解耦成不同的模組元件,通過組合不同的模組元件,使用者可以便捷地構建自定義的檢測模型
- 豐富的即插即用的演算法和模型
MMDetection 支援了眾多主流的和最新的檢測演算法,例如 Faster R-CNN,Mask R-CNN,RetinaNet 等。
- 速度快
基本的框和 mask 操作都實現了 GPU 版本,訓練速度比其他程式碼庫更快或者相當,包括 Detectron2, maskrcnn-benchmark 和 SimpleDet。
- 效能高
MMDetection 這個演算法庫源自於 COCO 2018 目標檢測競賽的冠軍團隊 MMDet 團隊開發的程式碼,之後持續進行了改進和提升。
配置檔案引數詳解
faster_rcnn_r50_fpn_1x_coco.py檔案為例,這個檔案包含四個檔案,分別是:faster_rcnn_r50_fpn.py、coco_detection.py、schedule_1x.py、default_runtime.py。
configs/_base_/schedules/schedule_1x.py
```python optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)# 設定優化器型別 optimizer_config = dict(grad_clip=None) # 梯度裁剪配置
optimizer_config = dict(
delete=True, grad_clip=dict(max_norm=35, norm_type=2))
lr 引數
lr_config = dict( policy='step', # lr decay的方式,其餘的還有consine cyclic warmup='linear', # 初始的學習率增加的策略為線性增加 warmup_iters=500, # warmup迭代500次 warmup_ratio=0.001, # warmup的初始學習比率。 step=[8, 11]) # 在8-11個epoch後開始進行lr decay runner = dict(type='EpochBasedRunner', max_epochs=12) # runner配置,預設epoch為12 ```
faster_rcnn_r50_fpn.py
```python
model settings
model = dict( type='FasterRCNN',#model型別 backbone=dict( type='ResNet',#backone型別 depth=50,#網路層數 num_stages=4,# resnet的stage數量 out_indices=(0, 1, 2, 3), # 輸出的stage的序號 frozen_stages=1,# 凍結的stage數量,即該stage不更新引數,-1表示所有的stage都更新引數 norm_cfg=dict(type='BN', requires_grad=True),#表示所採用的歸一化運算元,一般是 BN 或者 GN。requires_grad 表示該運算元是否需要梯度,也就是是否進行引數更新 norm_eval=True,#控制整個骨架網路的歸一化運算元是否需要變成 eval 模式 style='pytorch',# 網路風格:如果設定pytorch,則stride為2的層是conv3x3的卷積層;如果設定caffe,則stride為2的層是第一個conv1x1的卷積層 init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')),# 表明backbone使用預訓練引數,標註其位置 neck=dict( type='FPN',# FPN特徵融合neck in_channels=[256, 512, 1024, 2048],# FPN接受的channels,和backnone resnet的stage2-5的輸出channels對應 out_channels=256,# feature pyramid每一層的輸出channel數 num_outs=5),# 輸出的feature pyramid特徵層數 rpn_head=dict( type='RPNHead',# RPN網路型別 in_channels=256,# RPN網路的輸入通道數 feat_channels=256,# 特徵層的通道數 anchor_generator=dict( type='AnchorGenerator', scales=[8],# 生成的anchor的baselen,baselen = sqrt(w*h),w和h為anchor的寬和高 ratios=[0.5, 1.0, 2.0],# anchor的寬高比 strides=[4, 8, 16, 32, 64]),# 在每個特徵層上的anchor的步長(對應於原圖) bbox_coder=dict( type='DeltaXYWHBBoxCoder', target_means=[.0, .0, .0, .0],# 均值 target_stds=[1.0, 1.0, 1.0, 1.0]),# 均值 loss_cls=dict( type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), loss_bbox=dict(type='L1Loss', loss_weight=1.0)), roi_head=dict( type='StandardRoIHead', bbox_roi_extractor=dict( type='SingleRoIExtractor', roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), out_channels=256, featmap_strides=[4, 8, 16, 32]), bbox_head=dict( type='Shared2FCBBoxHead',# 對應head類 in_channels=256,# head接受的是feature pyramid的輸出,in_channels表示進入head時的通道數是256 fc_out_channels=1024, roi_feat_size=7, num_classes=80,# 使用coco資料集,所以是80類 bbox_coder=dict( type='DeltaXYWHBBoxCoder', target_means=[0., 0., 0., 0.], target_stds=[0.1, 0.1, 0.2, 0.2]), reg_class_agnostic=False, loss_cls=dict( type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), loss_bbox=dict(type='L1Loss', loss_weight=1.0))), # model training and testing settings train_cfg=dict( rpn=dict( assigner=dict( type='MaxIoUAssigner',# RPN網路的正負樣本劃分 pos_iou_thr=0.7,# RPN網路的正負樣本劃分 neg_iou_thr=0.3,# 負樣本的iou閾值 min_pos_iou=0.3,# 正樣本的iou最小值。如果assign給ground truth的anchors中最大的IOU低於0.3,則忽略所有的anchors,否則保留最大IOU的anchor match_low_quality=True, ignore_iof_thr=-1),# 忽略bbox的閾值,當ground truth中包含需要忽略的bbox時使用,-1表示不忽略 sampler=dict( type='RandomSampler',# 正負樣本提取器型別 num=256,# 需提取的正負樣本數量 pos_fraction=0.5,# 正樣本比例 neg_pos_ub=-1,# 最大負樣本比例,大於該比例的負樣本忽略,-1表示不忽略 add_gt_as_proposals=False),# 把ground truth加入proposal作為正樣本 allowed_border=-1,# 允許在bbox周圍外擴一定的畫素 pos_weight=-1,# 正樣本權重,-1表示不改變原始的權重 debug=False),# debug模式 rpn_proposal=dict( nms_pre=2000, max_per_img=1000, nms=dict(type='nms', iou_threshold=0.7),# nms閾值 min_bbox_size=0), rcnn=dict( assigner=dict( type='MaxIoUAssigner',# RCNN網路正負樣本劃分 pos_iou_thr=0.5,# 正樣本的iou閾值 neg_iou_thr=0.5,# 負樣本的iou閾值 min_pos_iou=0.5,# 正樣本的iou最小值。如果assign給ground truth的anchors中最大的IOU低於0.3,則忽略所有的anchors,否則保留最大IOU的anchor match_low_quality=False, ignore_iof_thr=-1),# 忽略bbox的閾值,當ground truth中包含需要忽略的bbox時使用,-1表示不忽略 sampler=dict( type='RandomSampler',# 正負樣本提取器型別 num=512,# 需提取的正負樣本數量 pos_fraction=0.25,# 正樣本比例 neg_pos_ub=-1,# 最大負樣本比例,大於該比例的負樣本忽略,-1表示不忽略 add_gt_as_proposals=True),# 把ground truth加入proposal作為正樣本 pos_weight=-1,# 正樣本權重,-1表示不改變原始的權重 debug=False)), test_cfg=dict( rpn=dict( nms_pre=1000,# 在nms之前保留的的得分最高的proposal數量 max_per_img=1000, nms=dict(type='nms', iou_threshold=0.7), min_bbox_size=0), # 最小bbox尺寸 rcnn=dict( score_thr=0.05, nms=dict(type='nms', iou_threshold=0.5),# nms閾值 max_per_img=100) # soft-nms is also supported for rcnn testing # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05) )) ```
環境準備
CUDA:11.3
新建虛擬環境openmm
python
conda create --name openmm python=3.7
然後,啟用環境。
Win10執行命令:
python
activate openmm
UBuntu執行命令:
python
source activate openmm
進入虛擬環境後,安裝pytorch,輸入命令:
python
conda install pytorch torchvision torchaudio cudatoolkit=11.3
安裝mmcv,執行命令:
python
pip install mmcv-full
安裝mmcv-full,等待的時間較長。如果不報錯誤,耐心等待即可。
安裝完成後,下載mmdetection, 地址連結:https://gitee.com/open-mmlab/mmdetection。
下載完成後,解壓,然後pycharm開啟。
新增剛才新建的虛擬環境。
在Terminal中啟用openmm虛擬環境,防止虛擬環境沒有切換過來。
然後,安裝mmdet,在Terminal中執行命令:
python
python setup.py install
在安裝mmdet的過程中,會自動下載所需要的安裝包。如果存在不能下載的情況,需要單獨安裝。直到出現下圖即可。
驗證環境
在工程的根目錄新建checkpoints資料夾,下載預訓練權重檔案,連結:
http://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
下載完成後,將其放入到checkpoints資料夾
新建demo.py檔案,插入程式碼:
```python from mmdet.apis import init_detector, inference_detector
config_file = 'configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'
從 model zoo 下載 checkpoint 並放在 checkpoints/
檔案下
網址為: http://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
checkpoint_file = 'checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth' device = 'cuda:0' img='demo/demo.jpg'
初始化檢測器
model = init_detector(config_file, checkpoint_file, device=device)
推理演示影象
result=inference_detector(model, img) model.show_result(img, result, out_file='result.jpg') ```
執行程式碼:
看到這張圖說明環境沒有問題。
接下來,使用這個環境訓練自定義資料集。
訓練
製作資料集
Labelme標註的資料集地址連結:
https://download.csdn.net/download/hhhhhhhhhhwwwwwwwwww/63242994?spm=1001.2014.3001.5503
有32個類別,分別是:‘c17’, ‘c5’, ‘helicopter’, ‘c130’, ‘f16’, ‘b2’, ‘other’, ‘b52’, ‘kc10’, ‘command’, ‘f15’, ‘kc135’, ‘a10’, ‘b1’, ‘aew’, ‘f22’, ‘p3’, ‘p8’, ‘f35’, ‘f18’, ‘v22’, ‘f4’, ‘globalhawk’, ‘u2’, ‘su-27’, ‘il-38’, ‘tu-134’, ‘su-33’, ‘an-70’, ‘su-24’, ‘tu-22’, ‘il-76’。
先將其轉為COCO資料集,轉換程式碼如下:
```python
-- coding:utf-8 --
!/usr/bin/env python
import json import os import shutil
from labelme import utils import numpy as np import glob import PIL.Image labels={'c17':0,'c5':1,'helicopter':2,'c130':3,'f16':4, 'b2':5,'other':6,'b52':7,'kc10':8,'command':9,'f15':10, 'kc135':11,'a10':12,'b1':13,'aew':14,'f22':15,'p3':16,'p8':17, 'f35':18,'f18':19,'v22':20,'f4':21,'globalhawk':22,'u2':23,'su-27':24, 'il-38':25,'tu-134':26,'su-33':27,'an-70':28,'su-24':29,'tu-22':30,'il-76':31} class MyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, np.integer): return int(obj) elif isinstance(obj, np.floating): return float(obj) elif isinstance(obj, np.ndarray): return obj.tolist() else: return super(MyEncoder, self).default(obj)
class labelme2coco(object): def init(self, labelme_json=[], save_json_path='./tran.json'): ''' :param labelme_json: 所有labelme的json檔案路徑組成的列表 :param save_json_path: json儲存位置 ''' self.labelme_json = labelme_json self.save_json_path = save_json_path self.images = [] self.categories = [] self.annotations = [] # self.data_coco = {} self.label = [] self.annID = 1 self.height = 0 self.width = 0
self.save_json()
def data_transfer(self):
for num, json_file in enumerate(self.labelme_json):
imagePath=json_file.split('.')[0]+'.jpg'
imageName=imagePath.split('\\')[-1]
# print(imageName)
with open(json_file, 'r') as fp:
data = json.load(fp) # 載入json檔案
self.images.append(self.image(data, num,imageName))
for shapes in data['shapes']:
label = shapes['label'].lower()
if label not in self.label:
self.categories.append(self.categorie(label))
self.label.append(label)
points = shapes['points'] # 這裡的point是用rectangle標註得到的,只有兩個點,需要轉成四個點
# points.append([points[0][0],points[1][1]])
# points.append([points[1][0],points[0][1]])
self.annotations.append(self.annotation(points, label, num))
self.annID += 1
def image(self, data, num,imagePath):
image = {}
img = utils.img_b64_to_arr(data['imageData']) # 解析原圖片資料
# img=io.imread(data['imagePath']) # 通過圖片路徑開啟圖片
# img = cv2.imread(data['imagePath'], 0)
height, width = img.shape[:2]
img = None
image['height'] = height
image['width'] = width
image['id'] = num + 1
# image['file_name'] = data['imagePath'].split('/')[-1]
image['file_name'] = imagePath
self.height = height
self.width = width
return image
def categorie(self, label):
categorie = {}
categorie['supercategory'] = 'Cancer'
categorie['id'] = labels[label] # 0 預設為背景
categorie['name'] = label
return categorie
def annotation(self, points, label, num):
annotation = {}
annotation['segmentation'] = [list(np.asarray(points).flatten())]
annotation['iscrowd'] = 0
annotation['image_id'] = num + 1
# annotation['bbox'] = str(self.getbbox(points)) # 使用list儲存json檔案時報錯(不知道為什麼)
# list(map(int,a[1:-1].split(','))) a=annotation['bbox'] 使用該方式轉成list
annotation['bbox'] = list(map(float, self.getbbox(points)))
annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]
# annotation['category_id'] = self.getcatid(label)
annotation['category_id'] = self.getcatid(label) # 注意,原始碼預設為1
# print(label,annotation['category_id'])
annotation['id'] = self.annID
return annotation
def getcatid(self, label):
for categorie in self.categories:
if label == categorie['name']:
return categorie['id']
return 1
def getbbox(self, points):
# img = np.zeros([self.height,self.width],np.uint8)
# cv2.polylines(img, [np.asarray(points)], True, 1, lineType=cv2.LINE_AA) # 畫邊界線
# cv2.fillPoly(img, [np.asarray(points)], 1) # 畫多邊形 內部畫素值為1
polygons = points
mask = self.polygons_to_mask([self.height, self.width], polygons)
return self.mask2box(mask)
def mask2box(self, mask):
'''從mask反算出其邊框
mask:[h,w] 0、1組成的圖片
1對應物件,只需計算1對應的行列號(左上角行列號,右下角行列號,就可以算出其邊框)
'''
# np.where(mask==1)
index = np.argwhere(mask == 1)
rows = index[:, 0]
clos = index[:, 1]
# 解析左上角行列號
left_top_r = np.min(rows)+1 # y
left_top_c = np.min(clos)+1 # x
# 解析右下角行列號
right_bottom_r = np.max(rows)
right_bottom_c = np.max(clos)
# return [(left_top_r,left_top_c),(right_bottom_r,right_bottom_c)]
# return [(left_top_c, left_top_r), (right_bottom_c, right_bottom_r)]
# return [left_top_c, left_top_r, right_bottom_c, right_bottom_r] # [x1,y1,x2,y2]
return [left_top_c, left_top_r, right_bottom_c - left_top_c,
right_bottom_r - left_top_r] # [x1,y1,w,h] 對應COCO的bbox格式
def polygons_to_mask(self, img_shape, polygons):
mask = np.zeros(img_shape, dtype=np.uint8)
mask = PIL.Image.fromarray(mask)
xy = list(map(tuple, polygons))
PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)
mask = np.array(mask, dtype=bool)
return mask
def data2coco(self):
data_coco = {}
data_coco['images'] = self.images
data_coco['categories'] = self.categories
data_coco['annotations'] = self.annotations
return data_coco
def save_json(self):
self.data_transfer()
self.data_coco = self.data2coco()
# 儲存json檔案
json.dump(self.data_coco, open(self.save_json_path, 'w'), indent=4, cls=MyEncoder) # indent=4 更加美觀顯示
def copy_image(dirs,files,image_type): for txt in files: image_path=txt.split('.')[0]+"."+image_type image_name=image_path.replace('\','/').split('/')[-1] new_path=os.path.join(dirs,image_name) shutil.copyfile(image_path, new_path)
labelme_json = glob.glob('USA-Labelme/*.json') from sklearn.model_selection import train_test_split trainval_files, test_files = train_test_split(labelme_json, test_size=0.2, random_state=55) print(trainval_files) os.makedirs('train2017',exist_ok=True) os.makedirs('val2017',exist_ok=True) copy_image('train2017',trainval_files,'jpg') copy_image('val2017',test_files,'jpg') labelme2coco(trainval_files, 'instances_train2017.json') labelme2coco(test_files, 'instances_val2017.json') ```
在mmdetection-master的根目錄下面,新建data資料夾,然後再data資料夾下面新建coco資料夾,在coco資料夾下面新建annotations資料夾,將訓練集和驗證集的json放進去。將train2017資料夾和val2017資料夾放到coco資料夾下面,目錄如下:
python
mmdetection
├── data
│ ├── coco
│ │ ├── annotations
│ │ ├── train2017
│ │ └── val2017
如下圖:
到這裡資料集製作完成了。
修改配置檔案
configs/演算法/配置檔案。開啟配置檔案修改num_classes的個數,COCO預設是80,我們按照實際的類別修改即可。
例:configs/yolo/yolov3_d53_mstrain-608_273e_coco.py
但是,有的模型在的類別找不到,那麼我們如何找到呢?比如
configs/ssd/ssd300_coco.py,這個配置檔案裡面就沒有num_classes這個欄位,我們尋找最上面的_base_欄位。
也有ssd300.py這個檔案,將其開啟。
找到了num_classes這個欄位,將其修改為資料集的類別個數。我們本次使用的資料集的類別是32,所以將其修改為32。
修改該學習率,路徑“configs/ssd/ssd300_coco.py”。如下圖:
將2e-3改為2e-4,否則會出現loss為NAN的問題。
修改該BatchSize,路徑“configs/ssd/ssd300_coco.py”。如下圖:
這是針對每張顯示卡設定Batchsize。調整到合適的大小就可以訓練了。
修改epoch,在configs/ssd/ssd300_coco.py中新增
python
runner = dict(type='EpochBasedRunner', max_epochs=500)
修改資料集的類別
mmdet/core/evaluation/classnames.py找到def coco_classes():
將COCO類別替換為自己資料的類別。本例是:
python
def coco_classes():
return [
'c17', 'c5', 'helicopter', 'c130', 'f16', 'b2',
'other', 'b52', 'kc10', 'command', 'f15',
'kc135', 'a10', 'b1', 'aew', 'f22', 'p3', 'p8',
'f35', 'f18', 'v22', 'f4', 'globalhawk', 'u2', 'su-27',
'il-38', 'tu-134', 'su-33', 'an-70', 'su-24', 'tu-22',
'il-76']
mmdet/datasets/coco.py找到class CoCoDataset(CustomDataset):
將COCO的類別替換為自己資料集的類別。本例如下:
```python class CocoDataset(CustomDataset): CLASSES = ('c17', 'c5', 'helicopter', 'c130', 'f16', 'b2', 'other', 'b52', 'kc10', 'command', 'f15', 'kc135', 'a10', 'b1', 'aew', 'f22', 'p3', 'p8', 'f35', 'f18', 'v22', 'f4', 'globalhawk', 'u2', 'su-27', 'il-38', 'tu-134', 'su-33', 'an-70', 'su-24', 'tu-22', 'il-76')
PALETTE = [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230),
(106, 0, 228), (0, 60, 100), (0, 80, 100), (0, 0, 70),
(0, 0, 192), (250, 170, 30), (100, 170, 30), (220, 220, 0),
(175, 116, 175), (250, 0, 30), (165, 42, 42), (255, 77, 255),
(0, 226, 252), (182, 182, 255), (0, 82, 0), (120, 166, 157),
(110, 76, 0), (174, 57, 255), (199, 100, 0), (72, 0, 118),
(255, 179, 240), (0, 125, 92), (209, 0, 151), (188, 208, 182),
(0, 220, 176), (255, 99, 164), (92, 0, 73), (133, 129, 255)]
```
開始訓練
由於修改了引數,在訓練之前還要重新編譯一次。否則之前修改的引數不會生效。再次執行命令:
python
python setup.py install
然後就可以開始訓練了。執行命令:
python
python tools/train.py configs/ssd/ssd300_coco.py
對train.py重要引數的解析:
--work-dir
:指定訓練儲存模型和日誌的路徑--resume-from
:從預訓練模型chenkpoint
中恢復訓練--no-validate
:訓練期間不評估checkpoint--gpus
:指定訓練使用GPU的數量(僅適用非分散式訓練)--gpu-ids
: 指定使用哪一塊GPU(僅適用非分散式訓練)--seed
:隨機種子--deterministic
:是否為CUDNN後端設定確定性選項--options
: arguments in dict--launcher
: {none,pytorch,slurm,mpi} job launcher--local_rank
: LOCAL_RANK--autoscale-lr
: automatically scale lr with the number of
測試
測試執行程式碼:
``` python tools/test.py configs/ssd/ssd300_coco.py work_dirs/ssd300_coco/epoch_348.pth --out ./test_result/mask_rcnn_r50_fpn_1x/latest.pkl --eval bbox segm --s how
```
然後我們就可以看到測試結果:
完整程式碼和資料集:
https://download.csdn.net/download/hhhhhhhhhhwwwwwwwwww/85331635
- YoloV5實戰:手把手教物體檢測——YoloV5
- 基於阿里Semantatic Human Matting演算法,實現精細化人物摳圖
- PPv3-OCR自定義資料從訓練到部署
- 如何下載pytorch的歷史版本?
- WinForm——Button總結
- WinForm——MDI窗體
- 升級 pip
- 將8位的tif圖片改為png圖片
- RepLKNet實戰:使用RepLKNet實現對植物幼苗的分類(非官方)(二)
- 關於OpenCV imread和imdecode讀取圖片是BGR的證明
- opencv讀取圖片通道以及顯示
- 萬字整理聯邦學習系統架構設計參考
- 編譯器堆空間不足
- 【影象分類】實戰——使用EfficientNetV2實現影象分類(Pytorch)
- MMDetection實戰:MMDetection訓練與測試
- UNet語義分割實戰:使用UNet實現對人物的摳圖
- MobileVIT實戰:使用MobileVIT實現影象分類
- SwinIR實戰:如何使用SwinIR和預訓練模型實現圖片的超分
- 【影象分類】手撕ResNet——復現ResNet(Pytorch)
- Deeplab實戰:使用deeplabv3實現對人物的摳圖