공부/AIFFEL

Going Deeper(CV)_DJ 6 : GO/STOP! - Object Detection 시스템 만들기

dong_dong_2 2021. 4. 13. 20:44

1. 들어가며
    - 이번에는 object detection 모델을 통해 주변에 다른 차나 사람이 가까이 있는지 확인한 후 멈출 수 있는 자율주행 시스템을 만들어 보겠다.
    - 하지만 자율주행 시스템은 아직 완전하지 않기 때문에, 위험한 상황에서는 운전자가 직접 운전할 수 있도록 하거나 판단이 어려운 상황에서는 멈추도록 설계해야 한다.
    - 먼저 전체적인 시스템을 구성하기 위해서 보조장치의 역할과 이를 학습하기 위한 데이터셋 전처리를 수행하겠다.
    - Detection 모델을 학습시키기 위한 전체 파이프라인을 직접 제작하기에는 많은 시간이 들기 때문에 RetinaNet이라는 1-stage detector를 미리 학습시킨 라이브러리를 활용하겠다.
2. KITTI 데이터셋
    - 이번에 만들어 볼 자율주행 보조장치는 카메라에 사람이 가까워졌을 때, 그리고 차가 가까워져서 탐지된 크기가 일정 이상일 때 멈춰야 한다.


    - 이번에는 tensorflow_datasets에서 제공하는 KITTI 데이터셋을 사용한다.
    - KITTI 데이터셋은 자율주행을 위한 데이터셋으로 2D object detection 뿐만 아니라 깊이까지 포함한 3D object detection 라벨 등을 제공하고 있다.
    - 코드를 통해 라이브러리를 로딩하고, 데이터를 불러왔다. 주의할 점은 용량이 크므로(GB 단위) 다운로드 시 시간이 오래 걸린다.
    - 코드 실습으로 데이터를 보고, 정보를 확인했다.
    - 데이터셋은 7,481개의 학습 데이터(training data), 711개의 평가용 데이터(test data), 423개의 검증용 데이터(validation data)로 구성되어 있다.
    - 라벨에는 alpha, bbox, dimensions, location, occlided, rotation_y, truncated 등의 정보가 담겨있다.
3. 데이터 직접 확인하기
    - 여기서는 데이터셋을 직접 확인했다.
    - take를 통해서 데이터셋을 하나씩 뽑아볼 수 있는 TakeDataset을 얻었다. 이렇게 뽑은 데이터에는 image 등의 정보가 포함되어 있다.
    - 코드 실습을 통해 진행되었다.
4. RetinaNet
    - RetinaNet은 Focal Loss for Dense Object Detection 논문을 통해 공개된 detection 모델이다.
    - Detection 모델을 직접 만들기에는 많은 시간이 소요되기 때문에, 미리 모델을 구현한 라이브러리를 가져와 커스텀 데이터셋에 학습시키고 빠르게 사용해 봤다.
    - 1-stage detector 모델인 YOLO와 SSD는 2-stage detector인 Faster-RCNN 등보다 속도는 빠르지만 성능이 낮은 문제를 가지고 있다.
    - 이를 해결하기 위해서 RetinaNet에서는 focal loss와 FPN(Feature Pyramid Network)를 적용한 네트워크를 사용한다.


    - Focal Loss
       1) Focal loss는 기존의 1-stage detection 모델들(YOLO, SSD)이 물체 전경과 배경을 담고 있는 모든 그리드(grid)에 대해 한 번에 학습됨으로 인해서 생기는 클래스 간의 불균형을 해결하고자 도입되었다.
       2) 여기서 그리드(grid)와 픽셀(pixel)이 혼란스러울 수 있으나, 위 그림 왼쪽 7x7 feature level에서는 한 픽셀이고, 오른쪽의 image level(자동차 사진)에서 보이는 그리드는 각 픽셀의 receptive field이다.
       3) 그림에서 보이는 것처럼 우리가 사용하는 이미지는 물체보다는 많은 배경을 학습하게 된다. 논문에서는 이를 해결하기 위해서 Loss를 개선하여 정확도를 높였다.
       4) Focal loss는 우리가 많이 사용해왔던 교차 엔트로피를 기반으로 만들어졌다. 


       5) 위 그림을 보면 Focal loss는 그저 교차 엔트로피 CE(Pt)의 앞단에 간단히 (1 - Pt)^γ 라는 modulating factor를 붙여줬다.
       6) 교차 엔트로피의 개형을 보면 ground truth class에 대한 확률이 높으면 잘 분류된 것으로 판단되므로 손실이 줄어드는 것을 볼 수 있다.
       7) 하지만 확률이 1에 매우 가깝지 않은 이상 상당히 큰 손실로 이어진다.
       8) 이 상황은 물체 검출 모델을 학습시키는 과정에서 문제가 될 수 있다.
       9) 대부분의 이미지에서는 물체보다 배경이 많다.
      10) 따라서 이미지는 극단적으로 배경의 class가 많은 class imbalanced data라고 할 수 있다.
      11) 이렇게 너무 많은 배경 class에 압도되지 않도록 modulating factor로 손실을 조절해준다.
      12) 람다를 0으로 설정하면 modulating factor가 0이 되어 일반적인 교차 엔트로피가 되고, 람다가 커질수록 modulating이 강하게 적용되는 것을 확인할 수 있다.


    - FPN(Feature Pyramid Network)
       1) FPN은 특성을 피라미드처럼 쌓아서 사용하는 방식이다.
       2) CNN 백본 네트워크에서는 다양한 레이어의 결과값을 특성 맵(feature map)으로 사용할 수 있다.
       3) 이 때 컨볼루션 연산은 커널을 통해 일정한 영역을 보고 몇 개의 숫자로 요약해 내기 때문에, 
       4) 입력 이미지를 기준으로 생각하면 입력 이미지와 먼 모델의 뒷쪽의 특성 맵일수록 하나의 셀(cell)이 넓은 이미지 영역의 정보를 담고 있고,
       5) 입력 이미지와 가까운 앞쪽 레이어의 특성 맵일수록 좁은 범위의 정보를 담고 있다.
       6) 이를 receptive field라고 한다.
       7) 레이어가 깊어질수록 pooling을 거쳐 넓은 범위의 정보(receptive field)를 갖게 되는 것이다.
       8) FPN은 백본의 여러 레이어를 한꺼번에 쓰겠다라는데에 의의가 있다.
       9) SSD가 각 레이어의 특성 맵에서 다양한 크기에 대한 결과를 얻는 방식을 취했다면 RetinaNet에서는 receptive field가 넓은 뒷쪽의 특성 맵을 upsampling(확대)하여 앞단의 특성 맵과 더해서 사용했다.
      10) 레이어가 깊어질수록 feature map의 w, h방향의 receptive field가 넓어지는 것인데, 넓게 보는 것과 좁게 보는 것을 같이 쓰겠다는 목적이다.


      11) 위 그림은 RetinaNet 논문에서 FPN 구조가 어떻게 적용되었는지를 설명하는 그림이다.
      12) FPN은 각 level이 256채널로 이루어지는데, RetinaNet에서는 FPN의 P3부터 P7까지의 Pyramid level을 사용한다.
      13) 이를 통해 Classification Subnet과 Box Regression Subnet 2개의 Subnet을 구성하게 되는데, Anchor 갯수를 A라고 하면 최종적으로 Classification Subnet은 K개 class에 대해 KA개 채널을, Box Regression Subnet은 4A개 채널을 사용하게 된다.
5. keras-retinanet 실습 (1) 데이터 포맷 변경
    - Keras RetinaNet은 케라스(Keras) 라이브러리로 구현된 RetunaNet이다.
    - 현재는 텐서플로우 2 버전을 지원하는 리포지토리(repository)도 만들어졌으나 아직 커스템 데이터셋을 학습하는 방법을 공식 문서로 제시하지 않고 있다.
    - 지금은 우선 Keras RetinaNet 리포지토리를 이용해보도록 하자.
    - 우리가 가진 tensorflow_datasets의 KITTI 데이터셋을 그대로 사용해서 Keras RetinaNet을 학습시키기 위해서는 라이브러리를 수정해야 한다.
    - 하지만 이보다 더 쉬운 방법은 해당 모델을 훈련할 수 있는 공통된 데이터셋 포맷인 CSV 형태로 모델을 변경해주는 방법이다.
    - 클래스 및 바운딩 박스 정보 추출
       1) 우리는 tensorflow_dataset의 API를 사용해 이미지와 각 이미지에 해당하는 바운딩 박스 라벨의 정보를 얻을 수 있다.
       2) 그렇다면 API를 활용하여 데이터를 추출, 이를 포맷팅 하여 CSV 형태로 한 줄씩 저장해 보자.
       3) 한 라인에 이미지 파일의 위치, 바운딩 박스 위치, 그리고 클래스 정보를 가지는 CSV 파일을 작성하도록 코드를 작성하고, 이를 사용해 CSV 파일을 생성했다.
       4) 우리가 생각하는 브레이크 시스템은 차와 사람을 구분해야 하는 점을 유의해야 한다.
       5) 코드 실습을 통해 구현했다.
    - 클래스 맵핑
       1) 데이터셋에서 클래스는 문자열(string)으로 표시되지만, 모델에게 데이터를 알려줄 때에는 숫자를 사용해 클래스를 표시해야 한다.
       2) 이 때 모두 어떤 클래스가 있고, 각 클래스가 어떤 인덱스(index)에 맵핑(mapping)될지 미리 정하고 저장해 두어야 학습을 한 후 추론(inference)을 할 때에도 숫자 인덱스로 나온 정보를 클래스 이름으로 바꿔 해석할 수 있다.
       3) 코드 실습을 통해 구현했다.
6. keras-retinanet (2) 셋팅
    - 코드 실습을 통해 RetinaNet 셋팅을 했다.
7. keras-retinanet (3) 시각화
    - 이제 위에서 변환한 모델을 load하고 추론 및 시각화를 해보자.
    - load된 모델을 통해 추론을 하고 시각화를 하는 함수를 작성했다. 일정 점수 이하는 경우를 제거해야 함을 유의해야 한다.
    - 코드 실습을 통해 구현했다.
8. 프로젝트 : 자율주행 보조 시스템 만들기