CenterNet (Objects as Points) を無料GPU（Colab）で動かしてみる

# はじめに
今更ながら[CenterNet (Objects as Points)](https://arxiv.org/abs/1904.07850)を使ってみました。
検出モデルではYOLOを使うことが多いですが、YOLOv3より圧倒的に強いようです。
![スクリーンショット 2019-05-28 20.16.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370065/32bc25d2-0478-830c-bd1f-0fe30845ac6b.png)

速くて精度が高いということで、リアルタイム検知に使えそうですね！

# CenterNetとは
物体検出で領域(矩形)を予測するのではなく、人物姿勢推定で主流になっているヒートマップによるキーポイント検出を、物体検出に適用し、中心点のみ予測を行い領域の大きさや角度などはその属性として推定しようという研究です。
具体的には、入力画像をストライド幅でダウンサンプルし、ガウシアンカーネルを用いてヒートマップを作理、それを推定するという形をとっています。ガウス分布のσの決め方は難しそうです。。
<img width="1236" alt="fig2 (1).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370065/933a51f3-627a-a858-547a-c79248b5c190.png">

# Google Colab上でCenterNetを使う
CenterNetの公式pytorch実装はdeformable convolutionを使っているため、CPUでは動かせません。[Keras実装](https://github.com/see--/keras-centernet)ではCPUで動きますが、手元のmacでは4秒くらいかかり使い物にはなりませんでした。。
そこで、無料で簡単にGPUが使える[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb)で動かします。
ちなみに、AWSt2.xlargeあたりを使えば、githubのインストールに従ってそのまま簡単にインストールできました。

## インストール
インストールは基本的には[CenterNetのgithub](https://github.com/xingyizhou/CenterNet/blob/master/readme/INSTALL.md)に従います。
ここでは、google driveのルート直下にColab Notebooksというフォルダを作り、その中にlib_centerというフォルダを作り作業します。

### マウント
```python
from google.colab import drive
drive.mount('/content/drive')
```

### クローン
CenterNetレポジトリをクローンし移動します

```python
from google.colab import drive
drive.mount('/content/drive')
```

### cudaのバージョン変更
```
!wget https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda-repo-ubuntu1604-9-0-local_9.0.176-1_amd64-deb
!dpkg --install cuda-repo-ubuntu1604-9-0-local_9.0.176-1_amd64-deb
!sudo apt-key add /var/cuda-repo-9-0-local/7fa2af80.pub
!sudo apt-get update
!sudo apt-get install cuda-9-0
!nvcc --version
```
```
%cd '/content/drive/My Drive/Colab Notebooks/lib_center/cudnn'
!sudo dpkg -i libcudnn7_7.1.4.18-1+cuda9.0_amd64.deb
!sudo dpkg -i libcudnn7-dev_7.1.4.18-1+cuda9.0_amd64.deb
!sudo dpkg -i libcudnn7-doc_7.1.4.18-1+cuda9.0_amd64.deb
```

### Pytorchのバージョン変更
```python
!pip install -U torch==0.4.1 torchvision==0.2.2
```

### requirementsのインストール
```
%cd '/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet'
!pip install -r requirements.txt
```

### CenterNetのmake
```python
%cd '/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet/src/lib/models/networks/DCNv2/src/cuda'
!sudo apt-get install gcc-6 g++-6
%cd /usr/bin
!sudo rm gcc g++
!sudo ln -s gcc-6 gcc
!sudo ln -s g++-6 g++
# compile dcn
!nvcc -c -o dcn_v2_im2col_cuda.cu.o dcn_v2_im2col_cuda.cu -x cu -Xcompiler -fPIC
!nvcc -c -o dcn_v2_im2col_cuda_double.cu.o dcn_v2_im2col_cuda_double.cu -x cu -Xcompiler -fPIC
# compile dcn-roi-pooling
!nvcc -c -o dcn_v2_psroi_pooling_cuda.cu.o dcn_v2_psroi_pooling_cuda.cu -x cu -Xcompiler -fPIC
!nvcc -c -o dcn_v2_psroi_pooling_cuda_double.cu.o dcn_v2_psroi_pooling_cuda_double.cu -x cu -Xcompiler -fPIC
%cd '/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet/src/lib/models/networks/DCNv2'
!python build.py
!python build_double.py
%cd '/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet/src/lib/external'
!make
```

## 実際に動かす！
### モデルのダウンロード
[github上のリンク](https://github.com/xingyizhou/CenterNet/blob/master/readme/MODEL_ZOO.md)からctdet_coco_dla_2x.pthをダウンロードしCenterNet/models以下に配置。

### import

```python
%cd "/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet/src"
import sys
import cv2
import matplotlib.pyplot as plt
from detectors.detector_factory import detector_factory
from opts import opts
%matplotlib inline
CENTERNET_PATH = "/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet/src/lib"
sys.path.insert(0, CENTERNET_PATH)

MODEL_PATH = "../models/ctdet_coco_dla_2x.pth"
TASK = 'ctdet' # or 'multi_pose' for human pose estimation
opt = opts().init('{} --load_model {}'.format(TASK, MODEL_PATH).split(' '))
detector = detector_factory[opt.task](opt)

coco_names = ['person', 'bicycle', 'car', 'motorbike', 'aeroplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'sofa', 'pottedplant', 'bed', 'diningtable', 'toilet', 'tvmonitor', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush',] 

```

### 物体検知
```python
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import colorsys
from pylab import rcParams

rcParams['figure.figsize'] = 10,10

# Generate colors for drawing bounding boxes.
hsv_tuples = [(x / len(coco_names), 1., 1.)
              for x in range(len(coco_names))]
colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
colors = list(
    map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
        colors))
np.random.seed(10101)  # Fixed seed for consistent colors across runs.
np.random.shuffle(colors) 

def write_rect(image, box, cl, score):
    font = ImageFont.truetype(font='/content/drive/My Drive/Colab Notebooks/lib_center/FiraMono-Bold.otf', size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
    thickness = (image.size[0] + image.size[1]) // 300
    
    
    label = '{} {:.2f}'.format(coco_names[cl], score)
    draw = ImageDraw.Draw(image)
    label_size = draw.textsize(label, font)

    left, top, right, bottom = box
    top = max(0, np.floor(top + 0.5).astype('int32'))
    left = max(0, np.floor(left + 0.5).astype('int32'))
    bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
    right = min(image.size[0], np.floor(right + 0.5).astype('int32'))

    if top - label_size[1] >= 0:
        text_origin = np.array([left, top - label_size[1]])
    else:
        text_origin = np.array([left, top + 1])

    # My kingdom for a good redistributable image drawing library.
    for i in range(thickness):
        draw.rectangle(
            [left + i, top + i, right - i, bottom - i],
            outline=colors[cl])
    draw.rectangle(
        [tuple(text_origin), tuple(text_origin + label_size)],
        fill=colors[cl])
    draw.text(text_origin, label, fill=(0, 0, 0), font=font)
```

### 実行
```python
img = "/content/drive/My Drive/Colab Notebooks/lib_center/CenterNet/images/17790319373_bd19b24cfc_k.jpg"
rets = detector.run(img)['results']

img = Image.open(img)

for i in range(len(rets)):
    ret = rets.get(i+1)
    if ret.shape[0]==0:
        continue
    # score>0.3以上だけ表示
    ret = ret[ret[:,4]>0.3]
    for box in ret:
        write_rect(img, box[:4], i, box[4])

plt.imshow(img)
```
以下のような画像が得られれば成功です。
![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370065/a898fa1b-2b07-7446-ca9d-9617b5a436ce.png)

# おわり
実際にCenterNetを動かすことができました。
CPUだと遅いので使えないですが、GPUが使える環境では強力な武器になりそうですね。
次は、自前データでの学習をさせてみます。