0412.md 35.3 KB

The modern history of object recognition infographic

reference: The Modern History of Object Recognition — Infographic

Kinds of Object Recognition

  • Image Classfication : 내부 주요 Object를 기준으로 이미지를 분류하는 것.

  • Object Localization : 주요 Object를 포함한 이미지 영역을 예측하는 것. Image classification은 영역 내의 Object를 인지하는 데에 사용된다.

  • Object Recognition : 이미지에서 나타나는 모든 objects를 분류하고 지역화한다. 이 작업은 이미지 영역을 예측하고 그 안의 Object가 무엇인지 분류한다.

  • Semantic Segmentation : 이미지가 속한 객체 클래스별로 이미지의 각 픽셀에 레이블을 지정한다.

  • Instance Segmentation : 클래스에 더불어 instance에도 레이블을 지정한다.

Important CNN Concepts

  • Feature : 특정한 패턴(특징)이 input region에 나타날 때 활성화되는 숨겨진 뉴런. 이 뉴런이 감지하는 특징은 다음과 같다. (1) 뉴런의 활성화를 최대화하기위해 input region을 최적화 (2) 입력 픽셀에서 뉴런 활성화의 기울기를 시각화 (back propagation .. ) (3) 뉴런을 가장 많이 활성화하는 훈련 데이터셋의 이미지 영역 셋 시각화 즉, 주어진 input region의 특징을 뽑아낸 것이라고 볼 수 있다.

  • Receptive Field : feature의 활성화에 영향을 미치는 input image의 영역. 즉, 출력 레이어의 뉴런 하나에 영향을 미치는 입력 뉴런들의 공간 크기이다. (외부 자극이 전체에 영향을 끼치는 것이 아니라 특정 영역에만 영향을 준다는 의미를 갖는다.)

  • Feature Map : sliding window 방식으로 입력 맵의 서로 다른 위치에 동일한 feature detector를 적용하여 생성한 feature들의 집합이다. (convolution). 즉, 필터를 입력받은 데이터에 sliding window 방식으로 적용한 뒤, 얻어낸 결과를 feature map이라고 한다.

  • Fully connected layer as Feature Volume : Fully connected layers는 분류 작업 수행을 위해 ConvNet의 마지막에 붙는 레이어로서 k개의 hidden nodes를 갖는다. (1*1*k feature volume을 갖는다.) 이 feature volume은 각 피쳐 맵 당 하나의 피쳐를 가지고, 해당 receptive field는 전체 이미지를 포함한다. fc 계층의 가중치 행렬인 W는 CNN 커널로 변환할 수 있다. w*h*k 커널과 w*h*d의 피쳐 볼륨을 갖는 CNN은 1*1*k 피쳐 볼륨을 생성해낸다. 1*1*k 필터 커널을 1*1*d feature volume에 통합하면 1*1*k feature volumn이 생성된다. fc 레이어를 컨볼루션 레이어로 바꾸면 임이의 크기의 이미지에 ConvNet을 적용할 수 있다.

  • Deconvolution : convolution의 반대 과정이다. 쉽게 말하자면 f*g = h (f: filter, g: feature map(input of layer), h: output)일 때, f,h로부터 g를 구하는 과정을 deconvolution이라고 한다. 예를 들어 특정 레이어에서 어떤 값을 역추적했더니 "눈"이 나왔다는 것은 바로 사람의 얼굴에서 눈이 이 필터를 잘 활성화시킨다는 것이고, 이 필터는 눈의 특징을 잡아내는 역할을 한다는 것을 의미한다. 우리가 deconv를 통해 피쳐맵을 역추적하면 특정 필터가 처음의 input image에서부터 담당하는 부분을 시각적으로 알 수 있다.

  • End to End Learning : 입력에서 출력까지 전체 네트워크를 이루는 부분적인 네트워크 없이 한 번에 처리한다는 것을 의미한다.

(memo --> CNN 시각화를 통해 각 피쳐맵의 중간층을 활성화해 눈으로 확인할 수 있다. https://dambaekday.tistory.com/3)

Important Object Recognition Concepts

  • Bounding box proposal (Region of interest) : 말 그래도 이미지 내에서 관심있는 영역을 뜻한다. 입력 이미지에서 내부에 우리가 찾으려고 하는 개체가 포함될 가능성이 있는 직사각형 영역이다. bounding box는 두 개의 코너 좌표 (x0, y0, x1, y1)를 저장하거나 또는 중심 위치와 너비 및 높이(x, y, w, h)를 저장하는 4-요소 벡터로 표현될 수 있다. bounding box에는 일반적으로 내부에 개체가 포함되어있을 가능성이 어느 정도인지 나타내는 신뢰 점수가 함께 표시된다.

  • Intersection over Union : 두 박스의 교집합을 합집합으로 나눠준 값. 두 박스가 일치할 수록 1에 가까운 값이 나오게 된다. 이 수치는 서로 다른 두 박스가 동일한 물체에 쳐져 있다고 판별할 수 있도록 한다. R-CNN 논문에서는 IoU > 0.5 이면 동일한 물체를 대상으로 한 박스로 판단한다.

    iou
  • Non Maxium Suppression (NMS) : 동일한 물체에 여러개의 박스가 쳐져 있을 때, 가장 스코어가 높은 박스만 남기고 나머지를 제거하는 과정을 말한다. 이 때, 스코어는 IoU를 통해 판별한다.

    NMS
  • Selective Search : 물체가 포함되어있을 법한 박스를 찾아내는 방법.

    selective_search
  • Bounding box regression : selective search를 통해 찾은 박스 위치는 상당히 부정확하다. 따라서 성능을 끌어올리기 위해 박스 위치를 교정해주는 작업을 bounding box regression이라 말한다. 즉, CNN을 통과하여 추출된 벡터와 x, y, w, h를 조정하는 함수의 가중치를 곱해서 바운딩 박스를 조정해주는 선형 회귀를 학습시키는 것을 의미한다. 자세한 내용은 여기에서 Bounding Box Regression을 참조하면 수식과 함께 확인할 수 있다.

Metrics

Precision & Recall

  • Precision은 관련 Object만을 식별하는 능력을 나타낸다. Precision = TP / (TP + FP) 이며, 모든 식별 결과 중 옳게 검출한 비율을 의미한다.
  • Recall은 모든 관련 케이스(모든 ground truth bounding boxes)를 찾는 능력을 나타낸다. Recall = TP / (TP + FN) 이며, 마땅히 검출해내야하는 물체들 중에서 제대로 검출된 것의 비율을 의미한다.

PR Curve

PR Curve는 confidence 레벨에 대한 threshold 값의 변화에 의한 object detector의 성능을 평가하는 방법이다. 이는 x축을 Recall, y축을 Precision으로 하여 나타낸 그래프로 이 그래프를 통해 object detector의 퍼포먼스를 측정할 수 있다.

예를 들어, 15개의 얼굴이 존재하는 어떤 데이터넷에서 한 얼굴을 검출하는 알고리즘에 의해 총 10개의 얼굴이 검출되었다고 가정해보자. 여기서 confidence는 0% ~ 100%까지 모두 고려하였다.

|Detections|Confidences|TP or FP| |--|--|--| |A|57%|TP| |B|78%|TP| |C|43%|FP| |D|85%|TP| |E|91%|TP| |F|13%|FP| |G|45%|TP| |H|68%|FP| |I|95%|TP| |J|81%|TP|

10개 중 7개는 제대로 검출 (TP) 되었고, 3개는 잘못 검출되었다. (FP) 이때 Precision = 7 / 10 = 0.7이고, Recall은 7 / 15(실제 얼굴 개수) = 0.47이다.

위 결과를 confidence 레벨이 높은 순으로 재정렬하고, threshold를 95%로 정한다면 Precision = 1/1, Recall = 1/15 = 0.067이 된다. threshold를 91%로 낮추면 두 개가 검출된 것으로 판단할 것이고, Precision = 1, Recall = 2/15가 된다. 이렇게 threshold값을 검출들의 confidence 레벨에 맞게 낮춰가면 Precision과 Recall 값이 변화한다. 이 변화하는 Precision과 Recall 값들을 x축을 Recall, Y축을 Precision으로 그래프를 나타내면 그것이 바로 PR Curve이다. 즉, PR 곡선에서는 recall 값의 변화에 따른 precision 값을 확인할 수 있다.

Average Precision(AP)

PR Curve는 성능을 평가하기에 아주 좋은 방법이지만, 단 하나의 숫자로 성능을 평가할 수 있도록 하기 위해 AP가 도입되었다. AP는 PR Curve에서 선 아래쪽의 면적으로 계산된다. 주어진 PR Curve를 단조적으로 감소하는 그래프가 되게 하기 위해 살짝 손봐준뒤, 아래 면적을 계산함으로서 AP를 구한다.

mAP

물체 클래스가 여러개인 경우 각 클래스 당 AP를 구한 다음 그것을 모두 합한 값에 클래스의 갯수로 나눠줌으로서 mAP를 구할 수 있다. (https://towardsdatascience.com/map-mean-average-precision-might-confuse-you-5956f1bfa9e2) 다시 확인하고 글 남기기.

MOTA (Multiple Object Tracking Accuracy)

R-CNN

rcnn

R-CNN이 Object Detection을 수행하는 알고리즘 순서는 다음과 같다.

  1. 입력 이미지에 Selective Search 알고리즘을 적용하여 물체가 있을 법한 박스 2000개를 추출한다.
  2. 모든 박스를 227 x 227 크기로 리사이징한다. 이 때 박스의 비율은 고려하지 않는다.
  3. 미리 ImageNet 데이터를 통해 학습시켜놓은 CNN을 통과시켜 4096차원의 특징 벡터를 추출한다.
  4. 이 추출된 벡터를 가지고 각각의 클래스마다 학습시켜놓은 SVM Classifier를 통과시킨다.
  5. Bounding Box Regression을 적용하여 박스의 위치를 튜닝한다.

Fast R-CNN

CNN 특징 추출부터 classfication, bounding box regression까지 모두 하나의 모델에서 학습시키자!

fast r-cnn
  1. 전체 이미지를 미리 학습된 CNN을 통과시켜 피쳐맵을 추출한다.
  2. Selective Search를 통해서 찾은 각각의 ROI에 대해 ROI Pooling을 수행한다. 그 결과로 고정된 크기의 feature vector를 얻는다.
  3. feature vector는 fc layer들을 통과한 뒤, 두 개의 브랜치로 나뉘게 된다.
  4. 하나의 브랜치는 softmax를 통과하여 해당 ROI가 어떤 물체인지 분류한다.
  5. 또 하나의 브랜치는 bounding box regression을 통해서 selective search로 찾은 박스의 위치를 조정한다.
  • ROI Pooling Fast R-CNN에서는 먼저 입력 이미지를 CNN에 통과시켜 피쳐맵을 추출한다. 추출된 피쳐맵을 미리 정해놓은 H*W 크기에 맞게끔 그리드를 설정한다. 그리고 각각의 칸 별로 가장 큰 값을 추출하는 max pooling을 실시하면 결과값은 항상 H*W 크기의 피쳐맵이 되고, 이를 쭉 펼쳐 feature vector를 추출한다.

Faster R-CNN

Faster R-CNN의 핵심 아이디어는 Region Proposal Network이다. 해당 네트워크는 다음과 같은 구조를 갖는다.

faster r-cnn
먼저 feature map을 추출한 뒤, 이를 RPN에 전달하여 ROI를 계산한다. 여기서 얻은 ROI로 ROI Pooling을 진행한 다음 분류를 진행하여 Object Detection을 수행한다.

Region Proposal Network

rpn
RPN이 동작하는 알고리즘은 다음과 같다.
  1. CNN을 통해 뽑아낸 피쳐맵을 입력으로 받는다. 이 때, 피쳐맵의 크기를 HxWxC로 잡는다. (가로, 세로, 채널 수)

  2. 피쳐맵에 3x3 컨볼루션을 256 혹은 512 채널만큼 수행한다. 위 그림에서 intermediate layer에 해당한다. 이 때, padding을 1로 설정해주오 HxW가 보존될 수 있도록 한다. intermediate layer 결과 HxWx256 or HxWx512 크기의 두번째 피쳐맵을 얻는다.

  3. 두번째 피쳐맵을 입력받아 classfication과 bounding box regression 예측 값을 계산해주어야한다. 이때 fully convolution network의 특징을 갖는다.

  4. 먼저 분류를 수행하기 위해 1x1 컨볼루션을 18(object인지 나타내는 지표 수 2 * 앵커 개수 9) 채널 수 만큼 수행하며, 그 결과 HxWx18 크기의 피쳐맵을 얻는다. HxW 상의 하나의 인덱스는 피쳐맵 상의 좌표를 의미한다. 그 아래 18개의 채널은 각 해당 좌표를 앵커로 삼아 k개의 앵커 박스들이 개체인지 아닌지에 대한 예측값을 담고 있다. 즉, 한번의 1x1 컨볼루션으로 HxW 개의 앵커 좌표들에 대한 예측을 모두 수행한다. 이제 이 값들을 적절히 reshape 한 뒤, softmax를 이용하여 해당 앵커가 오브젝트일 확률 값을 얻는다.

  5. 다음으로 Bounding Box Regression 예측값을 얻기 위한 1x1 컨볼루션을 36채널 수 만큼 수행한다.

  6. 1~5를 통해 얻은 값들로 ROI를 계산한다. 먼저 Classification을 통해서 얻은 물체일 확률 값들을 정렬한 다음, 높은 순으로 K개의 앵커만 추려낸다. 그 다음 K개의 앵커들에 각각 Bounding box regression을 적용한다. 그 다음 Non-Maximum-Suppression을 적용하여 RoI을 구한다.

  7. 이렇게 찾은 ROI를 다시 첫 번째 피쳐맵에 project한 다음, ROI Pooling을 적용하고 다시 분류에 적용하여 물체의 종류를 알아낸다.

Feature Pyramid Network

feature pyramid

Object Detection 분야에서 풀리지 않은 문제는 바로 작은 물체를 탐지해내기 어렵다는 것이다. 이를 위해 이미지나 피쳐맵의 크기를 다양한 형태로 rescale하는 접근 방법이 있었다. 위 이미지와 관련된 설명은 다음과 같다.

(a)는 입력 이미지 자체를 여러 크기로 리사이징한 뒤, 각각의 이미지에서 물체를 탐지하는 방법이다.

(b)는 CNN 신경망을 통과하여 얻은 최종 단계의 피처맵으로 object detection을 수행하는 기법이다.

(c)는 CNN 신경망을 통과하는 중간 과정에 생성되는 피쳐맵들 각각에 Object Detection을 수행하는 기법이다.

(d)는 FPN에서 제안하는 방법으로서 먼저 신경망을 통과하며 단계별로 피쳐맵을 생성한다. 그리고 가장 상위 레이어에서부터 거꾸로 내려오면서 피쳐를 합쳐준 뒤, Object Detection을 수행한다. 이러한 방식을 통해 상위 레이어의 추상화된 정보와 하위 레이어의 작은 물체들에 대한 정보를 동시에 살리면서 Object Detection을 수행할 수 있게 된다.

Feature Fusion

feature fusion

FPN이 상위 피쳐맵과 하위 피쳐맵을 어떻게 합쳐주는지 알아보자. 기본적으로 FPN은 피쳐맵이 레이어를 통과하면서 해상도가 2배씩 작아진다고 가정한다. 때문에 상위 피쳐맵과 하위 피쳐맵을 합쳐주기 위해서는 해상도를 맞춰주어야 한다. nearest neighbor upsampling이라는 기법을 사용해 상위 피쳐맵의 해상도를 2배 키워주었다.

nearest

위 과정을 거쳐 해상도를 2배 키워준 뒤 하위 피쳐맵에서는 1x1 convolution을 수행하여 상위 피쳐맵과 동일한 채널 수를 갖도록 한다. 그 다음 해상도와 채널 수를 모두 맞춰준 두 피쳐맵을 element-wise 덧셈을 수행하여 합쳐주며 그 결과로 나온 피쳐맵에 object detection을 수행한다.

Mask R-CNN

미팅 당일 교수님 설명 듣고 추가할 예정.

About Detectron2

우선 Detectron2란 FAIR에서 파이토치를 베이스로 개발한 Object Detection & Segmentation Library이다. 고수준의 API가 아주 이용하기 쉽게 작성되어있고, 친절한 document를 제공하기 때문에 누구나 쉽게 이용할 수 있다. 하지만 좋은 API 내부에는 복잡한 구조가 있기 마련, 캡슐화가 잘 되어있기 때문에 내부를 뜯어보려면 매우 어렵다고 한다.

Detectron2는 최신의 Object Detection 알고리즘이 구현을 포함하고 있다. Fast R-CNN, Mask R-CNN을 비롯하여 여러 알고리즘들이 포함되어있다. 더불어 Modular design이기 때문에, Object Detection System의 어디든 내가 커스텀하여 갖다 붙일 수 있다. 그리고 매우 빠른 트레이닝 속도를 자랑한다.

Detectron2는 다음과 같은 모델 구조를 갖는다.

d2
  • Backbone Network

Input: Images

Output: Multi-Scaled Feature Maps

Backbone 네트워크에는 이미지들이 인풋으로 들어간다. 그리고 결과물로서는 서로 다른 스케일을 갖는 이미지로부터 피라미드 형태의 피쳐맵을 얻을 수 있다. Base-RCNN-FPN의 결과 피쳐들은 각각 P2(1/4 크기), P3(1/8), P4(1/16), P5(1/32), P6(1/64)라 불린다. 백본 네트워크의 결과물로 얻어진 피쳐맵은 다음 단계인 Region Proposal Network와, ROI Heads 둘 모두의 Input으로 이용된다.

Q. Note that non-FPN (‘C4’) architecture’s output feature is only from the 1/16 scale. 는 어떤 의미인지 이해가 잘 되지 않는다.

  • Region Proposal Network

Input: Mutli-Scaled Feature Maps

Output: Object Regions(Region Proposals)

Region Proposal Network에서는 Multi-Scaled Feature Maps를 바탕으로 Object Region을 얻는 과정을 거친다. 1000개의 box proposals를 confidence score와 함께 얻는다. 이 단계에서 얻은 결과는 ROI Heads의 Input으로도 이용된다.

  • ROI Heads

Input: Multi-Scaled Feature Maps, Region Proposals

Output: Box

RPN과 매우 유사하지만 더 fine-tuned 된 박스를 얻어내는 과정이다.

d2_detail

Structure of the detectron2 repository

Detectron2의 레포지토리 구조는 다음과 같이 이루어져있다.

// copy from Digging into Detectron2
detectron2
├─checkpoint  <- checkpointer and model catalog handlers
├─config      <- default configs and handlers
├─data        <- dataset handlers and data loaders
├─engine      <- predictor and trainer engines
├─evaluation  <- evaluator for each dataset
├─export      <- converter of detectron2 models to caffe2 (ONNX)
├─layers      <- custom layers e.g. deformable conv.
├─model_zoo   <- pre-trained model links and handler
├─modeling   
│  ├─meta_arch <- meta architecture e.g. R-CNN, RetinaNet
│  ├─backbone  <- backbone network e.g. ResNet, FPN
│  ├─proposal_generator <- region proposal network
│  └─roi_heads <- head networks for pooled ROIs e.g. box, mask heads
├─solver       <- optimizer and scheduler builders
├─structures   <- structure classes e.g. Boxes, Instances, etc
└─utils        <- utility modules e.g. visualizer, logger, etc
  1. Backbone Network: FPN (backbone/fpn.py) └ ResNet (backbone/resnet.py)
  2. Region Proposal Network: RPN(proposal_generator/rpn.py) ├ StandardRPNHead (proposal_generator/rpn.py) └ RPNOutput (proposal_generator/rpn_outputs.py)
  3. ROI Heads (Box Head): StandardROIHeads (roi_heads/roi_heads.py) ├ ROIPooler (poolers.py) ├ FastRCNNConvFCHead (roi_heads/box_heads.py) ├ FastRCNNOutputLayers (roi_heads/fast_rcnn.py) └ FastRCNNOutputs (roi_heads/fast_rcnn.py)

Deeper into the Backbone Network

Backbone 네트워크의 역할은 input image로부터 feature를 추출하는 것이다.

backbone

Backbone 네트워크의 input은 (B, 3, H, W) image이다. B, H, W는 batch 크기, 이미지의 높이 및 너비를 각각 나타낸다. 주의해야할 것은 input color channel의 순서가 RGB가 아닌 BGR이라는 점이다. RGB이미지를 넣었을 때, BGR에 비해 성능이 더 좋지 않은 결과를 얻을 것이다.

output은 (B,C,H/S, W/S) feature maps이다. C와 S는 각각 채널의 크기와 stride를 의미한다.

예를 들어, 높이 800, 너비 1280의 input image를 backbone 네트워크에 넣었을때, 결과물은 다음과 같이 나타난다.

# By default, C = 256
output["p2"].shape -> torch.Size([1, 256, 200, 320]) # stride = 4 
output["p3"].shape -> torch.Size([1, 256, 100, 160]) # stride = 8
output["p4"].shape -> torch.Size([1, 256, 50, 80])   # stride = 16
output["p5"].shape -> torch.Size([1, 256, 25, 40])   # stride = 32
output["p6"].shape -> torch.Size([1, 256, 13, 20])   # stride = 64

FPN을 통해 얻어진 feature를 시각화한 결과는 다음과 같다. P6는 P2에 비해 더 큰 receptive field를 가진다는 것을 결과를 통해 확인할 수 있다. 즉, FPN은 multi-scale의 feature maps를 뽑아낼 수 있다.

output of fpn

Backbone 네트워크를 구성하고 있던 ResNet과, ResNet을 통한 FPN의 자세한 구조는 우선은 다음으로 미루겠다. (너무 어렵다 😥)

How to load ground truth from a dataset and how the loaded data are processed before being fed to the network

Base-RCNN-FPN에서 ground truth data는 RPN과 Box Head에 사용된다. Annotation된 데이터는 Box label(물체의 위치와 사이즈를 나타낸다.)과 Category label(object class id)를 포함한다. 여기서 Category label은 ROI Heads를 위해 사용된다. 그 이유는 RPN은 object의 카테고리 분류를 학습하지 않기 때문이다.

Loading annotation data

Mapping data

Deeper into the Region Proposal Network

Deeper into the ROI(Box) Head

Tutorial of Detectron2

하나 하나 설명을 적지 않으면 나중에 다시 까먹곤 해서, 우선 Detectron2에서 공식적으로 제공하는 튜토리얼을 코드 블럭마다 쪼개어 설명을 달아보려고 한다.

우선, dependency를 설치해준다. detectron2는 pytorch base이므로, 필요한 패키지들을 import 한다.

# install dependencies: 
!pip install pyyaml==5.1
import torch, torchvision
print(torch.__version__, torch.cuda.is_available())
!gcc --version
# opencv is pre-installed on colab

이제, detectron2를 설치해주고, 필요한 라이브러리들을 추가해주자.

# install detectron2: (Colab has CUDA 10.1 + torch 1.8)
# See https://detectron2.readthedocs.io/tutorials/install.html for instructions
import torch
assert torch.__version__.startswith("1.8")   # need to manually install torch 1.8 if Colab changes its default version
!pip install detectron2 -f https://dl.fbaipublicfiles.com/detectron2/wheels/cu101/torch1.8/index.html
# exit(0)  # After installation, you need to "restart runtime" in Colab. This line can also restart runtime

# Some basic setup:
# Setup detectron2 logger
import detectron2
from detectron2.utils.logger import setup_logger
setup_logger()

# import some common libraries
import numpy as np
import os, json, cv2, random
from google.colab.patches import cv2_imshow

# import some common detectron2 utilities
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
from detectron2.utils.visualizer import Visualizer
from detectron2.data import MetadataCatalog, DatasetCatalog

먼저, pre-trained된 detectron2 모델을 돌려보자. 먼저 필요한 이미지는 COCO 데이터셋을 이용할 것이다. COCO 데이터셋에서 불러온 이미지는 다음과 같다.

!wget http://images.cocodataset.org/val2017/000000439715.jpg -q -O input.jpg
im = cv2.imread("./input.jpg")
cv2_imshow(im)

coco

그 다음, 우리는 detectron2 configuration과 detectron2 DefaultPredictor를 생성하여 위 이미지에 대해 inference를 수행할 것이다.

cfg = get_cfg() # detectron2의 default config를 불러온다.
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")) # value들을 file로부터 불러온다.
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5  # ROI_Heads의 threshold를 0.5로 지정한다.
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml") # model의 가중치를 지정한다.
predictor = DefaultPredictor(cfg) #  단일의 input 이미지에 대해 단일의 device에서 작동하는 주어진 config에 대해 간단한 end-to-end predictor를 생성한다. 
outputs = predictor(im) # 해당 이미지를 모델에 넣어 얻은 결과물을 반환한다.

Model의 Output Format에 대해 공식 문서로부터 알아보자. inference 모드에 있을 때, builtin 모델은 각 이미지에 대해 하나의 dict인 list[dict]를 출력한다. 모델이 수행하는 태스크에 따라 각 dict는 다음과 같은 필드를 포함할 수 있다.

  • pred_boxes : 탐지된 인스턴스 당 하나씩 N개의 boxes를 저장하는 Boxes
  • scores : Tensor이며, N개의 confidence scores 벡터를 나타낸다.
  • pred_classes : Tensor이며, [0,num_categories) 범위 내에 속하는 N개의 레이블 벡터
  • pred_masks : (N, H, W)의 shape를 갖는 Tensor로서 탐지된 각 인스턴스를 나타낸다.

.. 나머지는 여기에서 자세히 확인할 수 있다.

print(outputs["instances"].pred_classes)
print(outputs["instances"].pred_boxes)

# 결과
tensor([17,  0,  0,  0,  0,  0,  0,  0, 25,  0, 25, 25,  0,  0, 24],
       device='cuda:0')
Boxes(tensor([[126.6035, 244.8977, 459.8291, 480.0000],
        [251.1083, 157.8127, 338.9731, 413.6379],
        [114.8496, 268.6864, 148.2352, 398.8111],
        [  0.8217, 281.0327,  78.6072, 478.4210],
        [ 49.3954, 274.1229,  80.1545, 342.9808],
        [561.2248, 271.5816, 596.2755, 385.2552],
        [385.9072, 270.3125, 413.7130, 304.0397],
        [515.9295, 278.3744, 562.2792, 389.3802],
        [335.2409, 251.9167, 414.7491, 275.9375],
        [350.9300, 269.2060, 386.0984, 297.9081],
        [331.6292, 230.9996, 393.2759, 257.2009],
        [510.7349, 263.2656, 570.9865, 295.9194],
        [409.0841, 271.8646, 460.5582, 356.8722],
        [506.8767, 283.3257, 529.9403, 324.0392],
        [594.5663, 283.4820, 609.0577, 311.4124]], device='cuda:0'))

Visualizer를 이용하여 예측된 결과를 이미지 위에 그릴 수 있다.

v = Visualizer(im[:, :, ::-1], MetadataCatalog.get(cfg.DATASETS.TRAIN[0]), scale=1.2)
out = v.draw_instance_predictions(outputs["instances"].to("cpu"))
cv2_imshow(out.get_image()[:, :, ::-1])

coco_output

이제, COCO 데이터셋의 카테고리에는 존재하지 않는 ballon 데이터를 이용하여 detectron2 모델을 트레이닝 해 볼 것이다. balloon segementation dataset을 COCO 데이터셋을 이용해 pre-trained된 모델에 train시키고, 이 모델이 새로운 클래스인 balloon을 인지할 수 있도록 해 보자. 먼저, 필요한 데이터를 다운로드 받는다.

# download, decompress the data
!wget https://github.com/matterport/Mask_RCNN/releases/download/v2.1/balloon_dataset.zip
!unzip balloon_dataset.zip > /dev/null

다운받은 데이터셋을 detectron2의 트레이닝 시 요구되는 format에 맞게 변환하는 과정을 거친다.

# if your dataset is in COCO format, this cell can be replaced by the following three lines:
# from detectron2.data.datasets import register_coco_instances
# register_coco_instances("my_dataset_train", {}, "json_annotation_train.json", "path/to/image/dir")
# register_coco_instances("my_dataset_val", {}, "json_annotation_val.json", "path/to/image/dir")

from detectron2.structures import BoxMode

def get_balloon_dicts(img_dir):
    json_file = os.path.join(img_dir, "via_region_data.json")
    with open(json_file) as f:
        imgs_anns = json.load(f)

    dataset_dicts = []
    for idx, v in enumerate(imgs_anns.values()):
        record = {}

        filename = os.path.join(img_dir, v["filename"])
        height, width = cv2.imread(filename).shape[:2]

        record["file_name"] = filename
        record["image_id"] = idx
        record["height"] = height
        record["width"] = width

        annos = v["regions"]
        objs = []
        for _, anno in annos.items():
            assert not anno["region_attributes"]
            anno = anno["shape_attributes"]
            px = anno["all_points_x"]
            py = anno["all_points_y"]
            poly = [(x + 0.5, y + 0.5) for x, y in zip(px, py)]
            poly = [p for x in poly for p in x]

            obj = {
                "bbox": [np.min(px), np.min(py), np.max(px), np.max(py)],
                "bbox_mode": BoxMode.XYXY_ABS,
                "segmentation": [poly],
                "category_id": 0,
            }
            objs.append(obj)
        record["annotations"] = objs
        dataset_dicts.append(record)
    return dataset_dicts

for d in ["train", "val"]:
    DatasetCatalog.register("balloon_" + d, lambda d=d: get_balloon_dicts("balloon/" + d))
    MetadataCatalog.get("balloon_" + d).set(thing_classes=["balloon"])
balloon_metadata = MetadataCatalog.get("balloon_train")

데이터가 올바른지 확인하기 위해 training set에서 임의로 선택한 샘플을 시각화해보자.

dataset_dicts = get_balloon_dicts("balloon/train")
for d in random.sample(dataset_dicts, 3):
    img = cv2.imread(d["file_name"])
    visualizer = Visualizer(img[:, :, ::-1], metadata=balloon_metadata, scale=0.5)
    out = visualizer.draw_dataset_dict(d)
    cv2_imshow(out.get_image()[:, :, ::-1])

ballon_sample

이제, 풍선 데이터셋을 COCO 데이터셋으로 pre-trained된 R50-FPN Mask R-CNN 모델에 fine-tune 되도록 트레이닝해보자. 트레이닝하는 코드는 다음과 같다. 위에서 DefaultPredictor를 사용할 때 이용했던 get_cfg()를 또 활용하여 트레이닝한다. 여기서 DefaultTrainer는 default training logic을 이용하여 train하도록 한다. 그 과정은 다음과 같다.

  1. 주어진 config에 의해 정의된 모델, optimizer, dataloader을 이용하여 SimpleTrainer를 생성한다. 또한 LR schedular를 생성한다.
  2. 이전에 트레이닝 된 적이 있는지 확인하고 있다면 불러온다.
  3. config에 의해 정의된 common hooks를 등록한다.
from detectron2.engine import DefaultTrainer

cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
cfg.DATASETS.TRAIN = ("balloon_train",)
cfg.DATASETS.TEST = () 
cfg.DATALOADER.NUM_WORKERS = 2
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")  # Let training initialize from model zoo
cfg.SOLVER.IMS_PER_BATCH = 2
cfg.SOLVER.BASE_LR = 0.00025  # pick a good LR
cfg.SOLVER.MAX_ITER = 300    # 300 iterations seems good enough for this toy dataset; you will need to train longer for a practical dataset
cfg.SOLVER.STEPS = []        # do not decay learning rate
cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128   # faster, and good enough for this toy dataset (default: 512)
cfg.MODEL.ROI_HEADS.NUM_CLASSES = 1  # only has one class (ballon). (see https://detectron2.readthedocs.io/tutorials/datasets.html#update-the-config-for-new-datasets)
# NOTE: this config means the number of classes, but a few popular unofficial tutorials incorrect uses num_classes+1 here.

os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)
trainer = DefaultTrainer(cfg) 
trainer.resume_or_load(resume=False)
trainer.train()

트레이닝 한 결과 이미지를 확인하는 과정은 tutorial 코드에서 바로 확인할 수 있기 때문에 생략하고, AP를 통해 결과값을 평가하는 부분을 추가해보고자 한다. AP metric을 이용하여 퍼포먼스를 측정할 수 있다. AP는 보통 70% 이상이면 좋게 본다고 하는데, 이 부분은 정확하지 않다.

from detectron2.evaluation import COCOEvaluator, inference_on_dataset
from detectron2.data import build_detection_test_loader
evaluator = COCOEvaluator("balloon_val", ("bbox", "segm"), False, output_dir="./output/")
val_loader = build_detection_test_loader(cfg, "balloon_val")
print(inference_on_dataset(trainer.model, val_loader, evaluator))

# 결과
Running per image evaluation...
Evaluate annotation type *bbox*
COCOeval_opt.evaluate() finished in 0.01 seconds.
Accumulating evaluation results...
COCOeval_opt.accumulate() finished in 0.00 seconds.
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.668
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.847
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.797
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.239
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.549
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.795
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.222
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.704
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.766
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.567
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.659
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.847
[07/08 22:50:53 d2.evaluation.coco_evaluation]: Evaluation results for bbox: 
|   AP   |  AP50  |  AP75  |  APs   |  APm   |  APl   |
|:------:|:------:|:------:|:------:|:------:|:------:|
| 66.758 | 84.719 | 79.685 | 23.917 | 54.933 | 79.514 |

Running per image evaluation...
Evaluate annotation type *segm*
COCOeval_opt.evaluate() finished in 0.01 seconds.
Accumulating evaluation results...
COCOeval_opt.accumulate() finished in 0.00 seconds.
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.768
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.842
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.840
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.058
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.565
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.936
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.248
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.782
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.842
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.600
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.688
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.953
[07/08 22:50:53 d2.evaluation.coco_evaluation]: Evaluation results for segm: 
|   AP   |  AP50  |  AP75  |  APs  |  APm   |  APl   |
|:------:|:------:|:------:|:-----:|:------:|:------:|
| 76.799 | 84.203 | 83.958 | 5.838 | 56.506 | 93.572 |
OrderedDict([('bbox',
              {'AP': 66.7575984802854,
               'AP50': 84.71906024215401,
               'AP75': 79.6850976022887,
               'APl': 79.51426848548515,
               'APm': 54.933394319629045,
               'APs': 23.917443214909724}),
             ('segm',
              {'AP': 76.79883944079043,
               'AP50': 84.20295316611471,
               'AP75': 83.95779282808186,
               'APl': 93.57150630750836,
               'APm': 56.50588544163433,
               'APs': 5.8381956414264895})])

Reference