# 원본 kitti Dataset는 10개의 Class로 되어 있음. 'Car Van Truck Pedestrian Person_sitting Cyclist Tram Misc DontCare'
CLASSES = ('Car', 'Truck', 'Pedestrian', 'Cyclist')
cat2label = {k:i for i, k in enumerate(CLASSES)}
print(cat2label)
cat2label['Car']
# {'Car': 0, 'Truck': 1, 'Pedestrian': 2, 'Cyclist': 3}
# 0
# data_anno {label}에 넣기 위한것
image_list = mmcv.list_from_file('/content/kitti_tiny/train.txt')
lines = mmcv.list_from_file('/content/kitti_tiny/training/label_2/000064.txt')
#print(lines)
content = [line.strip().split(' ') for line in lines]
bbox_names = [x[0] for x in content]
#print(bbox_names)
# bounding box 읽기
bboxes = [ [float(info) for info in x[4:8]] for x in content]
print(bboxes)
# [[657.65, 179.93, 709.86, 219.92], [731.51, 180.39, 882.28, 275.8], [715.18, 175.63, 762.77, 203.9], [816.58, 59.74, 1112.51, 266.07], [626.78, 174.27, 647.77, 192.18], [546.19, 168.97, 554.01, 177.09]]
import copy
import os.path as osp
import cv2
import mmcv
import numpy as np
from mmdet.datasets.builder import DATASETS
from mmdet.datasets.custom import CustomDataset
# 반드시 아래 Decorator 설정 할것.@DATASETS.register_module() 설정 시 force=True를 입력하지 않으면 Dataset 재등록 불가.
@DATASETS.register_module(force=True)
class KittyTinyDataset(CustomDataset):
CLASSES = ('Car', 'Truck', 'Pedestrian', 'Cyclist')
# __init__ 가 없는것은 customdataset것을 이용
##### self.data_root: /content/kitti_tiny/ self.ann_file: /content/kitti_tiny/train.txt self.img_prefix: /content/kitti_tiny/training/image_2
#### ann_file: /content/kitti_tiny/train.txt
# annotation에 대한 모든 파일명을 가지고 있는 텍스트 파일을 __init__(self, ann_file)로 입력 받고, 이 self.ann_file이 load_annotations()의 인자로 입력
def load_annotations(self, ann_file):
print('##### self.data_root:', self.data_root, 'self.ann_file:', self.ann_file, 'self.img_prefix:', self.img_prefix)
print('#### ann_file:', ann_file)
cat2label = {k:i for i, k in enumerate(self.CLASSES)}
image_list = mmcv.list_from_file(self.ann_file)
# 포맷 중립 데이터를 담을 list 객체
data_infos = []
for image_id in image_list: # 000000
filename = '{0:}/{1:}.jpeg'.format(self.img_prefix, image_id)
# 원본 이미지의 너비, 높이를 image를 직접 로드하여 구함.
image = cv2.imread(filename)
height, width = image.shape[:2]
# 개별 image의 annotation 정보 저장용 Dict 생성. key값 filename 에는 image의 파일명만 들어감(디렉토리는 제외)
data_info = {'filename': str(image_id) + '.jpeg',
'width': width, 'height': height}
# 개별 annotation이 있는 서브 디렉토리의 prefix 변환.
label_prefix = self.img_prefix.replace('image_2', 'label_2')
# 개별 annotation 파일을 1개 line 씩 읽어서 list 로드
lines = mmcv.list_from_file(osp.join(label_prefix, str(image_id)+'.txt'))
# 전체 lines를 개별 line별 공백 레벨로 parsing 하여 다시 list로 저장. content는 list의 list형태임.
# ann 정보는 numpy array로 저장되나 텍스트 처리나 데이터 가공이 list 가 편하므로 일차적으로 list로 변환 수행.
content = [line.strip().split(' ') for line in lines]
# 오브젝트의 클래스명은 bbox_names로 저장.
bbox_names = [x[0] for x in content]
# bbox 좌표를 저장
bboxes = [ [float(info) for info in x[4:8]] for x in content]
# 클래스명이 해당 사항이 없는 대상 Filtering out, 'DontCare'sms ignore로 별도 저장.
gt_bboxes = []
gt_labels = []
gt_bboxes_ignore = []
gt_labels_ignore = []
# 파일 내용을 읽는 loop
for bbox_name, bbox in zip(bbox_names, bboxes):
# 만약 bbox_name이 클래스명에 해당 되면, gt_bboxes와 gt_labels에 추가, 그렇지 않으면 gt_bboxes_ignore, gt_labels_ignore에 추가
if bbox_name in cat2label:
gt_bboxes.append(bbox)
# gt_labels에는 class id를 입력
gt_labels.append(cat2label[bbox_name])
else:
gt_bboxes_ignore.append(bbox)
gt_labels_ignore.append(-1)
# 개별 image별 annotation 정보를 가지는 Dict 생성. 해당 Dict의 value값은 모두 np.array임.
data_anno = {
'bboxes': np.array(gt_bboxes, dtype=np.float32).reshape(-1, 4),
'labels': np.array(gt_labels, dtype=np.long),
'bboxes_ignore': np.array(gt_bboxes_ignore, dtype=np.float32).reshape(-1, 4),
'labels_ignore': np.array(gt_labels_ignore, dtype=np.long)
}
# image에 대한 메타 정보를 가지는 data_info Dict에 'ann' key값으로 data_anno를 value로 저장.
data_info.update(ann=data_anno)
# 전체 annotation 파일들에 대한 정보를 가지는 data_infos에 data_info Dict를 추가
data_infos.append(data_info)
return data_infos
### Config 설정하고 Pretrained 모델 다운로드
config_file = '/content/mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'
checkpoint_file = '/content/mmdetection/checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth'
model = build_detector(cfg.model, train_cfg=cfg.get('train_cfg'), test_cfg=cfg.get('test_cfg'))
model.CLASSES = datasets[0].CLASSES
# /usr/local/lib/python3.7/dist-packages/mmdet-2.12.0-py3.7.egg/mmdet/models/backbones/resnet.py:400: UserWarning: DeprecationWarning: pretrained is a deprecated, please use "init_cfg" instead
# warnings.warn('DeprecationWarning: pretrained is a deprecated, '
mmdetection의 중립 annotation 포맷 변환. 해당 포맷은 텍스트로 변환하지 않음. 바로 메모리 상의 list로 생성됨.
filename, width, height, ann을 Key로 가지는 Dictionary를 이미지 개수대로 가지는 list 생성.
filename: 이미지 파일명(디렉토리는 포함하지 않음)
width: 이미지 너비
height: 이미지 높이
ann: bbounding box와 label에 대한 정보를 가지는 Dictionary
bboxes: 하나의 이미지에 있는 여러 Object 들의 numpy array. 4개의 좌표값(좌상단, 우하단)을 가지고, 해당 이미지에 n개의 Object들이 있을 경우 array의 shape는 (n, 4)
labels: 하나의 이미지에 있는 여러 Object들의 numpy array. shape는 (n, )
bboxes_ignore: 학습에 사용되지 않고 무시하는 bboxes. 무시하는 bboxes의 개수가 k개이면 shape는 (k, 4)
labels_ignore: 학습에 사용되지 않고 무시하는 labels. 무시하는 bboxes의 개수가 k개이면 shape는 (k,)
kitti Dataset을 중립 데이터형태로 변환하여 메모리 로드
# 원본 kitti Dataset는 10개의 Class로 되어 있음. 'Car Van Truck Pedestrian Person_sitting Cyclist Tram Misc DontCare'
CLASSES = ('Car', 'Truck', 'Pedestrian', 'Cyclist')
cat2label = {k:i for i, k in enumerate(CLASSES)}
print(cat2label)
cat2label['Car']
# {'Car': 0, 'Truck': 1, 'Pedestrian': 2, 'Cyclist': 3}
# 0
image_list = mmcv.list_from_file('/content/kitti_tiny/train.txt')
lines = mmcv.list_from_file('/content/kitti_tiny/training/label_2/000064.txt')
#print(lines)
content = [line.strip().split(' ') for line in lines]
bbox_names = [x[0] for x in content]
#print(bbox_names)
bboxes = [ [float(info) for info in x[4:8]] for x in content]
print(bboxes)
# [[657.65, 179.93, 709.86, 219.92], [731.51, 180.39, 882.28, 275.8], [715.18, 175.63, 762.77, 203.9], [816.58, 59.74, 1112.51, 266.07], [626.78, 174.27, 647.77, 192.18], [546.19, 168.97, 554.01, 177.09]]
dataset, config의 관계를 이해하는 것이 가장 중요하다.
custom dataset 생성 주요 로직
0. dataset을 위한 config 설정(data_root, ann_file, img_prefix)
- build_dataset()으로 아래 작업해줌
1. customdataset객체를 mmdetection framework에 등록
- @DATASETS.register_module(force=True) customdataset을 상속한 클래스 생성
- load_annotations()을 재정의 하여 middle format으로 원본 소스 변환
2. config에 설정된 주요값으로 customdataset 객체 생성
import copy
import os.path as osp
import cv2
import mmcv
import numpy as np
from mmdet.datasets.builder import DATASETS
from mmdet.datasets.custom import CustomDataset
# 반드시 아래 Decorator 설정 할것.@DATASETS.register_module() 설정 시 force=True를 입력하지 않으면 Dataset 재등록 불가.
@DATASETS.register_module(force=True)
class KittyTinyDataset(CustomDataset):
CLASSES = ('Car', 'Truck', 'Pedestrian', 'Cyclist')
##### self.data_root: /content/kitti_tiny/ self.ann_file: /content/kitti_tiny/train.txt self.img_prefix: /content/kitti_tiny/training/image_2
#### ann_file: /content/kitti_tiny/train.txt
# annotation에 대한 모든 파일명을 가지고 있는 텍스트 파일을 __init__(self, ann_file)로 입력 받고, 이 self.ann_file이 load_annotations()의 인자로 입력
def load_annotations(self, ann_file):
print('##### self.data_root:', self.data_root, 'self.ann_file:', self.ann_file, 'self.img_prefix:', self.img_prefix)
print('#### ann_file:', ann_file)
cat2label = {k:i for i, k in enumerate(self.CLASSES)}
image_list = mmcv.list_from_file(self.ann_file)
# 포맷 중립 데이터를 담을 list 객체
data_infos = []
for image_id in image_list:
filename = '{0:}/{1:}.jpeg'.format(self.img_prefix, image_id)
# 원본 이미지의 너비, 높이를 image를 직접 로드하여 구함.
image = cv2.imread(filename)
height, width = image.shape[:2]
# 개별 image의 annotation 정보 저장용 Dict 생성. key값 filename 에는 image의 파일명만 들어감(디렉토리는 제외)
data_info = {'filename': str(image_id) + '.jpeg',
'width': width, 'height': height}
# 개별 annotation이 있는 서브 디렉토리의 prefix 변환.
label_prefix = self.img_prefix.replace('image_2', 'label_2')
# 개별 annotation 파일을 1개 line 씩 읽어서 list 로드
lines = mmcv.list_from_file(osp.join(label_prefix, str(image_id)+'.txt'))
# 전체 lines를 개별 line별 공백 레벨로 parsing 하여 다시 list로 저장. content는 list의 list형태임.
# ann 정보는 numpy array로 저장되나 텍스트 처리나 데이터 가공이 list 가 편하므로 일차적으로 list로 변환 수행.
content = [line.strip().split(' ') for line in lines]
# 오브젝트의 클래스명은 bbox_names로 저장.
bbox_names = [x[0] for x in content]
# bbox 좌표를 저장
bboxes = [ [float(info) for info in x[4:8]] for x in content]
# 클래스명이 해당 사항이 없는 대상 Filtering out, 'DontCare'sms ignore로 별도 저장.
gt_bboxes = []
gt_labels = []
gt_bboxes_ignore = []
gt_labels_ignore = []
for bbox_name, bbox in zip(bbox_names, bboxes):
# 만약 bbox_name이 클래스명에 해당 되면, gt_bboxes와 gt_labels에 추가, 그렇지 않으면 gt_bboxes_ignore, gt_labels_ignore에 추가
if bbox_name in cat2label:
gt_bboxes.append(bbox)
# gt_labels에는 class id를 입력
gt_labels.append(cat2label[bbox_name])
else:
gt_bboxes_ignore.append(bbox)
gt_labels_ignore.append(-1)
# 개별 image별 annotation 정보를 가지는 Dict 생성. 해당 Dict의 value값은 모두 np.array임.
data_anno = {
'bboxes': np.array(gt_bboxes, dtype=np.float32).reshape(-1, 4),
'labels': np.array(gt_labels, dtype=np.long),
'bboxes_ignore': np.array(gt_bboxes_ignore, dtype=np.float32).reshape(-1, 4),
'labels_ignore': np.array(gt_labels_ignore, dtype=np.long)
}
# image에 대한 메타 정보를 가지는 data_info Dict에 'ann' key값으로 data_anno를 value로 저장.
data_info.update(ann=data_anno)
# 전체 annotation 파일들에 대한 정보를 가지는 data_infos에 data_info Dict를 추가
data_infos.append(data_info)
return data_infos
data_root, ann_file, img_prefix의 활용
- 학습용, 검증용, 테스트용으로 dataset이 만들어져야한다.
소스 데이터들의 학습용, 검증용, 테스트용 분리유형
- image 들과 annotation파일이 학습용, 검증용, 테스트용 디렉토리에 별도로 분리
- 별도의 메타 파일에서 각 image, annotation 지정
- image들 각 용도에 맞게 디렉토리별 분리, annotation은 각 용도별 하나만 가짐(3개 json)
* img_prefix는 여러개의 image들을 포함할 수 있는 디렉토리 형태로 지정되지만, ann_file은 단하나만 지정할 수 있음
# 학습 data config 셋팅
train.data_root=/content/kitti_tiny
train.ann_file='train.txt'
train.img_prefix='training/image_2'
# 학습 kitti data config 셋팅
train_dataset = kittiYinydataset(ann_file = train.data_root,
data_root = train.ann_file,
img_prefix = train.img_prefix )
from mmdet.apis import init_detector, inference_detector
import mmcv
config_file = '/content/mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'
checkpoint_file = '/content/mmdetection/checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth'
model = init_detector(config_file, checkpoint_file, device='cuda:0')
# https://github.com/open-mmlab/mmdetection/blob/master/demo/video_demo.py 대로 video detection 수행.
import cv2
video_reader = mmcv.VideoReader('/content/data/John_Wick_small.mp4')
video_writer = None
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
video_writer = cv2.VideoWriter('/content/data/John_Wick_small_out1.mp4', fourcc, video_reader.fps,(video_reader.width, video_reader.height))
for frame in mmcv.track_iter_progress(video_reader):
result = inference_detector(model, frame)
frame = model.show_result(frame, result, score_thr=0.4)
video_writer.write(frame)
if video_writer:
video_writer.release()
Custom된 frame처리 로직을 적용하여 Video Inference 수행.
기존에 사용한 get_detected_img()를 그대로 사용함.
# model과 원본 이미지 array, filtering할 기준 class confidence score를 인자로 가지는 inference 시각화용 함수 생성.
import numpy as np
# 0부터 순차적으로 클래스 매핑된 label 적용.
labels_to_names_seq = {0:'person',1:'bicycle',2:'car',3:'motorbike',4:'aeroplane',5:'bus',6:'train',7:'truck',8:'boat',9:'traffic light',10:'fire hydrant',
11:'stop sign',12:'parking meter',13:'bench',14:'bird',15:'cat',16:'dog',17:'horse',18:'sheep',19:'cow',20:'elephant',
21:'bear',22:'zebra',23:'giraffe',24:'backpack',25:'umbrella',26:'handbag',27:'tie',28:'suitcase',29:'frisbee',30:'skis',
31:'snowboard',32:'sports ball',33:'kite',34:'baseball bat',35:'baseball glove',36:'skateboard',37:'surfboard',38:'tennis racket',39:'bottle',40:'wine glass',
41:'cup',42:'fork',43:'knife',44:'spoon',45:'bowl',46:'banana',47:'apple',48:'sandwich',49:'orange',50:'broccoli',
51:'carrot',52:'hot dog',53:'pizza',54:'donut',55:'cake',56:'chair',57:'sofa',58:'pottedplant',59:'bed',60:'diningtable',
61:'toilet',62:'tvmonitor',63:'laptop',64:'mouse',65:'remote',66:'keyboard',67:'cell phone',68:'microwave',69:'oven',70:'toaster',
71:'sink',72:'refrigerator',73:'book',74:'clock',75:'vase',76:'scissors',77:'teddy bear',78:'hair drier',79:'toothbrush' }
def get_detected_img(model, img_array, score_threshold=0.3, is_print=True):
# 인자로 들어온 image_array를 복사.
draw_img = img_array.copy()
bbox_color=(0, 255, 0)
text_color=(0, 0, 255)
# model과 image array를 입력 인자로 inference detection 수행하고 결과를 results로 받음.
# results는 80개의 2차원 array(shape=(오브젝트갯수, 5))를 가지는 list.
results = inference_detector(model, img_array)
# 80개의 array원소를 가지는 results 리스트를 loop를 돌면서 개별 2차원 array들을 추출하고 이를 기반으로 이미지 시각화
# results 리스트의 위치 index가 바로 COCO 매핑된 Class id. 여기서는 result_ind가 class id
# 개별 2차원 array에 오브젝트별 좌표와 class confidence score 값을 가짐.
for result_ind, result in enumerate(results):
# 개별 2차원 array의 row size가 0 이면 해당 Class id로 값이 없으므로 다음 loop로 진행.
if len(result) == 0:
continue
# 2차원 array에서 5번째 컬럼에 해당하는 값이 score threshold이며 이 값이 함수 인자로 들어온 score_threshold 보다 낮은 경우는 제외.
result_filtered = result[np.where(result[:, 4] > score_threshold)]
# 해당 클래스 별로 Detect된 여러개의 오브젝트 정보가 2차원 array에 담겨 있으며, 이 2차원 array를 row수만큼 iteration해서 개별 오브젝트의 좌표값 추출.
for i in range(len(result_filtered)):
# 좌상단, 우하단 좌표 추출.
left = int(result_filtered[i, 0])
top = int(result_filtered[i, 1])
right = int(result_filtered[i, 2])
bottom = int(result_filtered[i, 3])
caption = "{}: {:.4f}".format(labels_to_names_seq[result_ind], result_filtered[i, 4])
cv2.rectangle(draw_img, (left, top), (right, bottom), color=bbox_color, thickness=2)
cv2.putText(draw_img, caption, (int(left), int(top - 7)), cv2.FONT_HERSHEY_SIMPLEX, 0.37, text_color, 1)
if is_print:
print(caption)
return draw_img
import time
def do_detected_video(model, input_path, output_path, score_threshold, do_print=True):
cap = cv2.VideoCapture(input_path)
codec = cv2.VideoWriter_fourcc(*'XVID')
vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
vid_fps = cap.get(cv2.CAP_PROP_FPS)
vid_writer = cv2.VideoWriter(output_path, codec, vid_fps, vid_size)
frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print('총 Frame 갯수:', frame_cnt)
btime = time.time()
while True:
hasFrame, img_frame = cap.read()
if not hasFrame:
print('더 이상 처리할 frame이 없습니다.')
break
stime = time.time()
img_frame = get_detected_img(model, img_frame, score_threshold=score_threshold, is_print=False)
if do_print:
print('frame별 detection 수행 시간:', round(time.time() - stime, 4))
vid_writer.write(img_frame)
# end of while loop
vid_writer.release()
cap.release()
print('최종 detection 완료 수행 시간:', round(time.time() - btime, 4))
do_detected_video(model, '/content/data/John_Wick_small.mp4', '/content/data/John_Wick_small_out2.mp4', score_threshold=0.4, do_print=True)
frame별 detection 수행 시간: 0.304
frame별 detection 수행 시간: 0.3139
frame별 detection 수행 시간: 0.2853
frame별 detection 수행 시간: 0.2918
frame별 detection 수행 시간: 0.2905
frame별 detection 수행 시간: 0.299
frame별 detection 수행 시간: 0.3006
frame별 detection 수행 시간: 0.2887
frame별 detection 수행 시간: 0.295
frame별 detection 수행 시간: 0.2932
frame별 detection 수행 시간: 0.2991
frame별 detection 수행 시간: 0.303
frame별 detection 수행 시간: 0.2913
frame별 detection 수행 시간: 0.2953
frame별 detection 수행 시간: 0.3003
frame별 detection 수행 시간: 0.3022
frame별 detection 수행 시간: 0.3018
frame별 detection 수행 시간: 0.2954
frame별 detection 수행 시간: 0.2975
frame별 detection 수행 시간: 0.3034
frame별 detection 수행 시간: 0.292
frame별 detection 수행 시간: 0.288
frame별 detection 수행 시간: 0.2939
frame별 detection 수행 시간: 0.2911
frame별 detection 수행 시간: 0.2969
frame별 detection 수행 시간: 0.2903
frame별 detection 수행 시간: 0.2912
frame별 detection 수행 시간: 0.2906
frame별 detection 수행 시간: 0.2947
frame별 detection 수행 시간: 0.2956
frame별 detection 수행 시간: 0.2936
frame별 detection 수행 시간: 0.2939
frame별 detection 수행 시간: 0.2925
frame별 detection 수행 시간: 0.2939
frame별 detection 수행 시간: 0.3
frame별 detection 수행 시간: 0.2862
frame별 detection 수행 시간: 0.2961
frame별 detection 수행 시간: 0.2958
frame별 detection 수행 시간: 0.2943
frame별 detection 수행 시간: 0.2931
frame별 detection 수행 시간: 0.2856
frame별 detection 수행 시간: 0.2911
frame별 detection 수행 시간: 0.2967
frame별 detection 수행 시간: 0.2944
frame별 detection 수행 시간: 0.2933
frame별 detection 수행 시간: 0.2886
frame별 detection 수행 시간: 0.2926
frame별 detection 수행 시간: 0.2991
frame별 detection 수행 시간: 0.2963
frame별 detection 수행 시간: 0.2885
frame별 detection 수행 시간: 0.2917
frame별 detection 수행 시간: 0.292
frame별 detection 수행 시간: 0.3013
frame별 detection 수행 시간: 0.2963
frame별 detection 수행 시간: 0.2903
더 이상 처리할 frame이 없습니다.
최종 detection 완료 수행 시간: 17.738
# 아래를 수행하기 전에 kernel을 restart 해야 함.
# 런타임 초기화하면 설치한 파일 날라감
from mmdet.apis import init_detector, inference_detector
import mmcv
MS-COCO 데이터 기반으로 Faster RCNN Pretrained 모델을 활용하여 Inference 수행
Faster RCNN Pretrained 모델 다운로드
Faster RCNN용 Config 파일 설정.
Inference 용 모델을 생성하고, Inference 적용
# pretrained weight 모델을 다운로드 받기 위해서 mmdetection/checkpoints 디렉토리를 만듬.
!cd mmdetection; mkdir checkpoints
# pretrained model faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth 설치
!wget -O /content/mmdetection/checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth http://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
# pretrained model 경로 확인
!ls -lia /content/mmdetection/checkpoints
# total 163376
# 3951436 drwxr-xr-x 2 root root 4096 Oct 10 13:13 .
# 3932841 drwxr-xr-x 19 root root 4096 Oct 10 13:13 ..
# 3951438 -rw-r--r-- 1 root root 167287506 Aug 28 2020 faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
# config 파일을 설정하고, 다운로드 받은 pretrained 모델을 checkpoint로 설정.
config_file = '/content/mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'
checkpoint_file = '/content/mmdetection/checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth'
# config 파일과 pretrained 모델을 기반으로 Detector 모델을 생성.
from mmdet.apis import init_detector, inference_detector
model = init_detector(config_file, checkpoint_file, device='cuda:0') # gpu 지정
# /usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/core/anchor/builder.py:17: UserWarning: ``build_anchor_generator`` would be deprecated soon, please use ``build_prior_generator``
# '``build_anchor_generator`` would be deprecated soon, please use '
# Use load_from_local loader
# mmdetection은 상대 경로를 인자로 주면 무조건 mmdetection 디렉토리를 기준으로 함.
%cd mmdetection
from mmdet.apis import init_detector, inference_detector
# init_detector() 인자로 config와 checkpoint를 입력함.
model = init_detector(config='configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py', checkpoint='checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth')
# /content/mmdetection
# /usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/core/anchor/builder.py:17: UserWarning: ``build_anchor_generator`` would be deprecated soon, please use ``build_prior_generator``
# '``build_anchor_generator`` would be deprecated soon, please use '
# Use load_from_local loader
img = '/content/mmdetection/demo/demo.jpg'
# inference_detector의 인자로 string(file경로), ndarray가 단일 또는 list형태로 입력 될 수 있음.
results = inference_detector(model, img)
# /usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/datasets/utils.py:69: UserWarning: "ImageToTensor" pipeline is replaced by "DefaultFormatBundle" for batch inference. It is recommended to manually replace it in the test data pipeline in your config file.
# 'data pipeline in your config file.', UserWarning)
# /usr/local/lib/python3.7/dist-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at /pytorch/c10/core/TensorImpl.h:1156.)
# return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
# /usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/core/anchor/anchor_generator.py:324: UserWarning: ``grid_anchors`` would be deprecated soon. Please use ``grid_priors``
# warnings.warn('``grid_anchors`` would be deprecated soon. '
# /usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/core/anchor/anchor_generator.py:361: UserWarning: ``single_level_grid_anchors`` would be deprecated soon. Please use ``single_level_grid_priors``
# '``single_level_grid_anchors`` would be deprecated soon. '
type(results), len(results)
# (list, 80)
# len이 80개인데, detected 가 80개가 아니라 cocodataset이 80개임
np.where(arr1[:, 4] > 0.1) # row는 전부, 4:class confidence 가 0.1보다 큰 경우
# (array([0, 1]),)
# row 기준으로
# model과 원본 이미지 array, filtering할 기준 class confidence score를 인자로 가지는 inference 시각화용 함수 생성.
def get_detected_img(model, img_array, score_threshold=0.3, is_print=True):
# 인자로 들어온 image_array를 복사.
draw_img = img_array.copy()
bbox_color=(0, 255, 0)
text_color=(0, 0, 255)
# model과 image array를 입력 인자로 inference detection 수행하고 결과를 results로 받음.
# results는 80개의 2차원 array(shape=(오브젝트갯수, 5))를 가지는 list.
results = inference_detector(model, img_array)
# 80개의 array원소를 가지는 results 리스트를 loop를 돌면서 개별 2차원 array들을 추출하고 이를 기반으로 이미지 시각화
# results 리스트의 위치 index가 바로 COCO 매핑된 Class id. 여기서는 result_ind가 class id
# 개별 2차원 array에 오브젝트별 좌표와 class confidence score 값을 가짐.
for result_ind, result in enumerate(results):
# 개별 2차원 array의 row size가 0 이면 해당 Class id로 값이 없으므로 다음 loop로 진행.
if len(result) == 0:
continue
# 2차원 array에서 5번째 컬럼에 해당하는 값이 score threshold이며 이 값이 함수 인자로 들어온 score_threshold 보다 낮은 경우는 제외.
result_filtered = result[np.where(result[:, 4] > score_threshold)]
# 해당 클래스 별로 Detect된 여러개의 오브젝트 정보가 2차원 array에 담겨 있으며, 이 2차원 array를 row수만큼 iteration해서 개별 오브젝트의 좌표값 추출.
for i in range(len(result_filtered)):
# 좌상단, 우하단 좌표 추출.
left = int(result_filtered[i, 0])
top = int(result_filtered[i, 1])
right = int(result_filtered[i, 2])
bottom = int(result_filtered[i, 3])
caption = "{}: {:.4f}".format(labels_to_names_seq[result_ind], result_filtered[i, 4])
cv2.rectangle(draw_img, (left, top), (right, bottom), color=bbox_color, thickness=2)
cv2.putText(draw_img, caption, (int(left), int(top - 7)), cv2.FONT_HERSHEY_SIMPLEX, 0.37, text_color, 1)
if is_print:
print(caption)
return draw_img
import matplotlib.pyplot as plt
img_arr = cv2.imread('/content/mmdetection/demo/demo.jpg')
detected_img = get_detected_img(model, img_arr, score_threshold=0.3, is_print=True)
# detect 입력된 이미지는 bgr임. 이를 최종 출력시 rgb로 변환
detected_img = cv2.cvtColor(detected_img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(12, 12))
plt.imshow(detected_img)
/usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/core/anchor/anchor_generator.py:324: UserWarning: ``grid_anchors`` would be deprecated soon. Please use ``grid_priors``
warnings.warn('``grid_anchors`` would be deprecated soon. '
car: 0.9888
car: 0.9872
car: 0.9832
car: 0.9713
car: 0.9678
car: 0.9594
car: 0.9593
car: 0.9568
car: 0.9510
car: 0.9458
car: 0.9440
car: 0.9331
car: 0.8663
car: 0.8268
car: 0.7535
car: 0.7166
car: 0.6008
car: 0.5920
car: 0.5540
car: 0.5435
car: 0.4768
car: 0.4612
car: 0.4010
car: 0.3439
bench: 0.9778
bench: 0.4170
chair: 0.7779
/usr/local/lib/python3.7/dist-packages/mmdet-2.17.0-py3.7.egg/mmdet/core/anchor/anchor_generator.py:361: UserWarning: ``single_level_grid_anchors`` would be deprecated soon. Please use ``single_level_grid_priors``
'``single_level_grid_anchors`` would be deprecated soon. '
<matplotlib.image.AxesImage at 0x7f7f78bc4a50>
tf.keras.utils.plot_model(model, show_shapes=True, rankdir="BT", show_dtype=True) # wide하게 분포되어 있기 때문에 parameter operator가 엄청 많다 / 이중에서 영향력 있는 것만 강한 연결성을 띄게 된다
Visualizing and Understanding Convolutional Networks
CNN을 제대로 이해하기 위한 논문 CNN을 시각화 하여 왜 성능이 향상되었는지를 탐구하는 논문 과거의 관점으로 AlexNet에 대한 '통찰'과 '진단'을 제공한다
Convolutional layer를 통과하고나서 Max pooling을 거칠때 가장 영향력 있는 값들로 축약되기 때문에 큰 특징만 남게 된다
따라서 반대로 pooling을 거치기 전으로 복원시킨다면 큰 특징만 남아 있기 때문에 Detail한 특징들은 사라지게 될 것이다
AlexNet의네트워크구조대로통과시킨데이터들을각레이어별로복원시키게되면
가장중요한특징들만남도록복원된다는것을시각적으로확인할수있다
시각화된결과를보면AlexNet의구조는처음레이어에서간단한특징들을뽑아내다가
레이어가깊어지면깊어질수록'복잡한 특징'을구분할수있도록학습이되는것을알수있다
기존 AlexNet의 첫번째 Layer에서 filter 11x11, stride 4를 filter 7x7, stride 2로 바꾸었다
두번째 layer에서는 stride 1, zero-padding 2에서 stride 2로 바꾸었다
=> 기존에는 filter가 컸기 때문에 작은 것들의 특징을 잘 못잡는다는 문제가 있었다
결과 => 파라미터가 줄어들었고, 좀 더 디테일한 특징을 알아낼 수 있도록 학습하게되었다
receptive field를 대신할 수 있다 @@@ 의미 확인
일부분을 가림으로써 어떤 부분이 가장 영향력이 있는지를 확률적으로 계산하였다 (abliation study)
특정 부분을 가렸을 때 가장 성능이 안좋다면 그 부분은 가장 영향력이 있는 부분인 것이다 (파란색 부분이 가장 영향력 있는 부분)
a와 c는 AlexNet b와 d는 ZFNet
Layer 1
a 그림에서는 dead feature가 꽤 있고 좀 더 흐릿하게 구분된 반면
c 그림에서는 dead feature가 굉장히 많이 줄고 특징이 좀 더 선명하게 구분되었다
filter사이즈와 stride를 줄임으로써 좀 더 특징을 잘 구분할 수 있게 학습이되었다고 할 수 있다
Layer 2
c 그림에서 보면 빈 공간이 많고 aliasing(계단현상)이 발견된 반면
d 그림에서는 빈 공간이 적고 특징이 더 선명하게 잘 구분되도록 학습이 되었다
Network In Network
네트워크 안에 네트워크를 넣는 방식 convolution layer사이에 mlp가 있다 (사실상 1x1 convolution을 사용한 것)
Flatten의 문제
import tensorflow as tf
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
X_train = X_train.reshape(-1,28,28,1)/255
# AlexNet
input_ = tf.keras.Input((224,224,3))
x = tf.keras.layers.Conv2D(96,11,4)(input_)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(256, 5, padding='same')(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Conv2D(384,3)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Conv2D(384,3)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(256, 3)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Flatten()(x) # 일렬로 만들어 지기 때문에 위치 정보를 잃고 학습할 weight가 많이 생긴다 (FC만 썼을때 위치정보를 잃는다)
x = tf.keras.layers.Dense(4096)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Dropout(0.5)(x)
x = tf.keras.layers.Dense(4096, kernel_initializer=tf.keras.initializers.Constant(1))(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Dropout(0.5)(x)
x = tf.keras.layers.Dense(1000, activation='softmax')(x)
model = tf.keras.models.Model(input_,x)
# AlexNet Flatten x / Global average pooling
input_ = tf.keras.Input((224,224,3))
x = tf.keras.layers.Conv2D(96,11,4)(input_)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(256, 5, padding='same')(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Conv2D(384,3)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Conv2D(384,3)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(256, 3)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Conv2D(1000, 3)(x) # 1x1 convolution
x = tf.keras.layers.GlobalAveragePooling2D()(x) # 1차원으로 바뀌었기 때문에 Flatten과 똑같다. 하지만 Flatten보다 Parameter를 훨씬 줄일 수 있다
x = tf.keras.layers.Dense(1000)(x)
model = tf.keras.models.Model(input_,x)
import tensorflow as tf
input_ = tf.keras.Input(shape=(32,32,1))
x = tf.keras.layers.Conv2D(6, 5, activation='tanh')(input_) # filter 개수, filter 크기 / stride는 생략되었기 때문에 1이라 가정한다 / padding: valid
x = tf.keras.layers.AvgPool2D(2)(x) # 기본적으로 non-overlapping 방식이다
x = tf.keras.layers.Conv2D(16, 5, activation='tanh')(x)
x = tf.keras.layers.MaxPool2D(6, 5)(x)
x = tf.keras.layers.Flatten()(x)
x = tf.keras.layers.Dense(120, activation='tanh')(x)
x = tf.keras.layers.Dense(84, activation='tanh')(x)
x = tf.keras.layers.Dense(10, activation='softmax')(x)
model = tf.keras.models.Model(input_, x)
Overfitting을 줄이기 위한 방법으로써 이미지 데이터를 변형해서 학습 데이터의 수를 늘리는 방법이다
이미지 데이터를 좌우반전, 상하반전, 회전, 자르기 등을 하여 학습하지 않은 데이터를 대처할 수 있는 일반적인 모델을 만들어주는 데에 효과적인 방법이다
예시
Dropout
Dropout은 특정 확률로 hidden layer의 node 출력값을 0으로 설정하는 방법이다 이렇게 dropout된 node들은 foward, backward propagation에 관여를 하지 않는다 dropout이 사용된 NN는 입력이 주어질 때마다 달라진 신경망 구조를 거치게 되고 이는 노드간의 의존성을 약화시켜 가장 영향력 있는 노드의 연결성이 강화되는 방식으로 학습을 하게된다 따라서 성능은 비슷하지만 연산 복잡도를 줄이면서 overfitting 문제를 해결하는 방법이 될 수 있다 ※ Fully connected layer 다음에 Dropuout을 사용한다
Result
Ensemble기법을 사용하고 pre-trained 모델인 경우에 에러율이 더 낮았다
※ CNNs는 Boosting기법(Ensemble)을 사용한 방법
AlexNet
논문 구현
하드웨어 메모리가 부족하여 GPU 병렬 연산을 위해 CNN 구조를 절반으로 나누어 설계되었다
# 평균은 0, 표준분포는 0.01인 정규분포 값으로 가중치를 초기화 한다
bias = tf.keras.initializers.Constant(1) # relu 때문에 초기화 값을 1로 지정한다 (음수가 들어가면 dying relu현상이 발생하기 때문)
input_ = tf.keras.Input((224,224,3))
x = tf.keras.layers.Conv2D(96,5,4)(input_)
x = tf.keras.layers.MaxPool2D(3,2)(x) # overlapping pooling
x = tf.keras.layers.Conv2D(256,3, padding='same', bias_initializer=bias)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.MaxPool2D(3,2)(x)
x = tf.keras.layers.Conv2D(384,3, padding='same', bias_initializer='ones')(x) # ones 단축키는 버전에따라 지원하지 않을 수 있다 -
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(384,3, padding='same', bias_initializer=bias)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(256,3, padding='same', bias_initializer=bias)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Flatten()(x)
x = tf.keras.layers.Dense(4096, bias_initializer=bias)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Dropout(0.5)(x)
x = tf.keras.layers.Dense(4096, bias_initializer=bias)(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Dropout(0.5)(x)
x = tf.keras.layers.Dense(1000, bias_initializer=bias, activation='softmax')(x)
model = tf.keras.models.Model(input_,x) # tensorflow from_logit
shared-weight를 사용하기 때문에 같은 값이면 같은 특징이다라는 점을 활용할 수 있고,
locally connected하기 때문에 부분적인 특징을 보고 특징이 있는지 없는지 여부를 판단할 수 있다
(Locality를 잃지 않는다)
CNN의 가정
1. Stationarity of statistics
- 정상성
- 이미지에서의 정상성이란 이미지의 한 부분에 대한 통계가 다른 부분들과 동일하다는 가정을 한다
- 이미지에서 한 특징이 위치에 상관없이 여러 군데 존재할 수 있고 특정 부분에서 학습된 특징 파라미터를 이용해
다른 위치에서도 동일한 특징을 추출할 수 있다는 의미이다
2. Locality of pixel dependencies
- 이미지는 작은 특징들로 구성되어 있기 때문에 각 픽셀들의 종속성은 특징이 있는 작은 지역으로 한정된다.
- 이미지를 구성하는 특징들은 이미지 전체가 아닌 일부 지역에 근접한 픽셀들로만 구성되고
근접한 픽셀들끼리만 종속성을 가진다
위 그림에서 왼쪽의 경우와 오른쪽의 경우는 같은 것이라 판단할 수 있지만
가운데의 것도 같은 것이라고 판단할 수 있을까?
convolutional layer를 한 번만 통과했다면 다른 것이라 판단할 수 있지만
layer를 여러번 통과한다면 세 가지 경우 모두 같은 특성이라고 볼수 있게 된다
=> layer가 많으면 많을 수록 좋은 점
TypeMarkdownand LaTeX:𝛼^2
import tensorflow as tf
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
tf.keras.layers.Dense
tf.keras.layers.LocallyConnected2D # weight를 공유하지 않는다 / 애매하기 때문에 이것도 저것도 아닌 상황에서 성능이 좋을 수 있다
tf.keras.layers.Conv2D
tf.keras.layers.MaxPool2D (tf.keras.layers.MaxPooling2D)
tf.keras.layers.AvgPool2D (tf.keras.layers.AveragePooling2D)
tf.keras.layers.Conv2D is tf.keras.layers.Convolution2D # 단축 표현
# True
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
data = load_digits()
padding
import numpy as np
import scipy
from scipy.ndimage import convolve
from scipy.signal import convolve, convolve2d
a = np.array([-1,0,1])
b = np.arange(5)
convolve(a, b, 'valid')
# array([-2, -2, -2])
a = np.array([[-1,0],[1,0]])
b = np.arange(9).reshape(3,3)
convolve2d(a,b,'full') # zero padding을 함으로써 값이 공평한 횟수로 연산된다 즉, 공평하게 특성을 검출할 수 있다
# array([[ 0, -1, -2, 0],
# [-3, -3, -3, 0],
# [-3, -3, -3, 0],
# [ 6, 7, 8, 0]])
import mglearn
# dtype이 uint8일 때 최대값은 255, 최소값은 0
# MinMaxScaler로 정규화 할 경우 0과 1사이로 값이 바뀌기 때문에 정사각형 형태로 데이터가 분포한다
# 따라서 방향에 대한 크기변화가 없기 때문에 빠른 학습속도와 정확한 학습결과를 기대할 수 있다
mglearn.plot_scaling.plot_scaling()
왜 convolution 연산에 대해서 합을 할까?
convolution 연산을 할때 element wise 연산을 하게되면(분리된 채널에서 합쳐질 때)특성을 알 수 없을 수도 있다
물론 depth wise convolution은 각각의 특성만 독립적으로 연산하는 경우도 있다
그러나 더하는 것이 성능이 더 좋고, convolution 연산 결과가 원본 이미지의 의미가 변하는 경우는 거의 나오지 않는다
왜 그런 경우가 나오지 않을까?
weight와 bias는 학습을 통해서 찾기 때문에 채널별로 서로 다른 결과가 나올수 있도록 학습이 되기 때문이다
(kernel은 특성을 잘 파악하도록 학습이 된다)
합하는 것이 왜 좋을까?
전통적인 NN에 영향을 받아서 hierarchy한 것을 고려했기 때문에 hierarchy 특징을 학습할 수 있다
image = data.images.reshape(1797,8,8,1) # CNN에서 사용하는 연산하기 위해서 데이터 하나가 3차원이 되도록 데이터를 변화 시켰다
layer1 = tf.keras.layers.Conv2D(2, (3,3)) # filter 개수, filter 모양(단축 표현 가능 (3,3)=>3)
layer1.built # 일시키기 전까지 초기화가 안된다 => lazy Evaluation / 내부적으로 im2col
# False
layer1(image[0]) # 동시에 여러개 연산하기 때문에 안된다 / 데이터를 하나 연산하더라도 4차원 형태로 만들어 줘야 한다
# ValueError: Input 0 of layer conv2d_1 is incompatible with the layer: : expected min_ndim=4, found ndim=3. Full shape received: (8, 8, 1)