ChainerCVの結果をOpenCVで描画したい

# はじめに

[ChainerCV](https://github.com/chainer/chainercv)は、物体認識や物体検出、セマンティックセグメンテーションなどの
画像処理に関するDeep Learningのプログラムをまとめた大変使いやすく、とても便利なライブラリです。
この記事を書いている間にYOLOv3まで実装されていました（すごい）。

ただ、Chainerの画像フォーマットがOpenCVと違っていたり、描画関数がmatplotlibであったりだと、
少し困ることがあるので、OpenCV版の描画関数を作成してみました。

# 1 画像の変換

ChainerフォーマットからOpenCVフォーマットへの変換と、逆バージョンの関数を作成しました。
ChainerとOpenCVのフォーマットの違いは,以下の２点

|         | 配列の並び           | 画素の並び | 型 |
|:--------|--------------------:|:---------:|:--:|
| Chainer | [channel, row, col] | [R, G, B] | np.float32 |
| OpenCV  | [row, col, channel] | [B, G, R] | np.uint8 |

なので、tracposeで配列の並び替え、cv2.cvtColor()で色の変換をすると、OpenCVフォーマットになります
（Chainerフォーマットは逆変換）。

```python
import numpy as np
import cv2

# Chainer -> OpenCV
def trans_img_cv2(img):
    buf = np.asanyarray(img, dtype=np.uint8).transpose(1, 2, 0)
    dst = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    return dst

# OpenCV -> Chainer
def trans_img_chainer(img):
    buf = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    dst = np.asanyarray(buf, dtype=np.float32).transpose(2, 0, 1)
    return dst
```

# 2 物体検出の描画

物体検出のバウンディングボックスとラベル名、スコア値を描画します。
ChainerCVの出力結果は`[y1, x1, y2, x2]`という座標で保存されていますが、
cv2.rectangleの座標の入力は'tuple(x, y)'と,xの座標が先になるので、注意が必要です。

```python
import numpy as np
import cv2

# 物体検出のバウンディングボックスとラベル名、スコア値を描画
def vis_bbox_cv2(img, bbox, label, score, label_names=voc_bbox_label_names):
    dst = trans_img_cv2(img)

    if label.max() >= len(label_names):
        raise ValueError('The values of label exceed the number of classes')

    for c,box in enumerate(bbox):
        cv2.rectangle(dst,(box[1],box[0]),(box[3],box[2]),(0,0,255),2,1)
        cv2.rectangle(dst,(int(box[1]),int(box[0]-25)),(box[3],box[0]),(255,255,255),-1)
        p_str = '{}:{:0.3f}'.format(label_names[label[c]],score[c])
        cv2.putText(dst,p_str, (box[1],int(box[0]-5)),cv2.FONT_HERSHEY_COMPLEX,0.7,(0,0,0),1)
    return dst

if __name__ == '__main__':

    ### モデル読み込みなどのプログラム割愛 ###

    img = utils.read_image(args.image, color=True)
    bboxes, labels, scores = model.predict([img])
    bbox, label, score = bboxes[0], labels[0], scores[0]

    dst = vis_bbox_cv2(img, bbox, label, score, label_names=voc_bbox_label_names)
    cv2.imshow('View',dst)
    cv2.waitKey()
```

## 結果

バウンディングボックスは、赤枠にしました。
上の部分に白枠を設定し、そこにラベルとスコアを描画しています。
文字の自動調整ができなかったので、小さい枠だとラベル値とスコアがはみ出します。
※ モデルは、ChainerCVのexampleにあるSSD300を使用しています。
※ 画像は、Pascal VOCの画像を使用しています。

### ●入力画像
![2008_007171.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/7978d140-c014-287e-0423-b67127f2e8a4.jpeg)

### ●出力結果
![output.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/bdf1835a-bcfa-002c-1637-c6f441d9d07f.jpeg)

# 3 セマンティックセグメンテーションの描画

ラベル画像をOpenCVフォーマットで出力します。


```python
# セマンティックセグメンテーションの描画関数
def vis_semantic_segmentation_cv2(img, label, label_colors, alpha=1.0):
    img_list = []
    for rows in label:
        buf = [label_colors[col] for col in rows]
        img_list.append(buf)

    dst = np.array(img_list, dtype=np.uint8)

    if img is not None and alpha < 1.0:
        buf_img = trans_img_cv2(img)
        beta = 1.0 - alpha
        dst = cv2.addWeighted(dst,alpha,buf_img,beta,1.0)

    return dst

if __name__ == '__main__':

    ### モデル読み込みなどのプログラム割愛 ###

    img = utils.read_image(args.image, color=True)
    labels = model.predict([img])
    label = labels[0]

    origin = trans_img_cv2(img)
    dst = vis_semantic_segmentation_cv2(img, label, camvid_label_colors)
    cv2.imshow('origin',origin)
    cv2.imshow('View',dst)
    cv2.waitKey()
```

## 結果

各ラベルごとに色分けされた画像を出力します。
入力画像とalpha値を入れると、入力画像にラベル画像を重ねて描画できます。
※ モデルは、ChainerCVのexampleにあるSegNetを使用しています。
※ 画像は、CamVidの画像を使用しています。

### ●入力画像

![Seq05VD_f01140.png](https://qiita-image-store.s3.amazonaws.com/0/104675/0d20f4a0-ba5e-7385-0320-ac4b4306c010.png)

### ●出力画像

![output.png](https://qiita-image-store.s3.amazonaws.com/0/104675/10c5018e-a066-9919-f445-f374bca6080b.png)

### ●入力画像とalpha値を設定した出力結果

以下のように設定すると、入力画像を重ね合わせた結果を出力します。

```python
dst = vis_semantic_segmentation_cv2(img, label, camvid_label_colors, 0.5)
```

![output.png](https://qiita-image-store.s3.amazonaws.com/0/104675/aac7afee-07ee-2d9f-69dd-b2ebf1fac318.png)

# 4 おまけ

セマンティックセグメンテーションの結果を描画しても、
何色がどのラベルに対応しているかわかりにくかったので、
ラベルの色とラベル名を対応させた画像を出力する関数も作成しました。
※　パラメータは適当に決め打ちしています。

```python
def create_label_img(label_names, label_colors):
    label_rows = len(label_names) * 30 + 45
    label_cols = 500
    label_img = np.full([label_rows, label_cols, 3],255,dtype=np.uint8)
    cv2.putText(label_img, 'ex) num label : [B, G, R]', (5, 20),
                cv2.FONT_HERSHEY_COMPLEX, 0.7, (0,0,0), 2)
    for t,c in enumerate(label_names):
        p_str = '{:2d} {} : {}'.format(t, c,label_colors[t])
        color = (int(label_colors[t][0]), int(label_colors[t][1]), int(label_colors[t][2]))
        cv2.putText(label_img, p_str, (5, t*30 + 45),
                    cv2.FONT_HERSHEY_COMPLEX, 0.7, color, 2)
    return label_img
```

## 結果

こんな感じの画像が出力されます。
１行目は例として、２行目からは、ラベル番号、ラベル名、設定色が描画されます。

![label.png](https://qiita-image-store.s3.amazonaws.com/0/104675/d8aa7a0b-fcb6-fc6e-b635-2bd794cb9a2b.png)

# 最後に

上記の関数をまとめておきます。

```python:viz_opencv.py
##
# coding:utf-8
##

import numpy as np
import cv2
from chainercv.datasets import voc_bbox_label_names

# Chainer -> OpenCV
def trans_img_cv2(img):
    buf = np.asanyarray(img, dtype=np.uint8).transpose(1, 2, 0)
    dst = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)
    return dst

# OpenCV -> Chainer
def trans_img_chainer(img):
    buf = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    dst = np.asanyarray(buf, dtype=np.float32).transpose(2, 0, 1)
    return dst

# 物体検出のバウンディングボックスとラベル名の描画
def vis_bbox_cv2(img, bbox, label, score, label_names=voc_bbox_label_names):
    dst = trans_img_cv2(img)

    if label.max() >= len(label_names):
        raise ValueError('The values of label exceed the number of classes')

    for c,box in enumerate(bbox):
        cv2.rectangle(dst,(box[1],box[0]),(box[3],box[2]),(0,0,255),2,1)
        cv2.rectangle(dst,(int(box[1]),int(box[0]-25)),(box[3],box[0]),(255,255,255),-1)
        p_str = '{}:{:0.3f}'.format(label_names[label[c]],score[c])
        cv2.putText(dst,p_str, (box[1],int(box[0]-5)),cv2.FONT_HERSHEY_COMPLEX,0.7,(0,0,0),1)

    return dst

# semantic segmentationのラベル名の一覧表
def create_label_img(label_names, label_colors):
    label_rows = len(label_names) * 30 + 45
    label_cols = 500
    label_img = np.full([label_rows, label_cols, 3],255,dtype=np.uint8)
    cv2.putText(label_img, 'ex) num label : [B, G, R]', (5, 20),
                cv2.FONT_HERSHEY_COMPLEX, 0.7, (0,0,0), 2)
    for t,c in enumerate(label_names):
        p_str = '{:2d} {} : {}'.format(t, c,label_colors[t])
        color = (int(label_colors[t][0]), int(label_colors[t][1]), int(label_colors[t][2]))
        cv2.putText(label_img, p_str, (5, t*30 + 45),
                    cv2.FONT_HERSHEY_COMPLEX, 0.7, color, 2)
    return label_img

# semantic segmentationのラベル描画用
def vis_semantic_segmentation_cv2(img, label, label_colors, alpha=1.0):
    img_list = []
    for rows in label:
        buf = [label_colors[col] for col in rows]
        img_list.append(buf)

    dst = np.array(img_list, dtype=np.uint8)

    if img is not None and alpha < 1.0:
        buf_img = trans_img_cv2(img)
        beta = 1.0 - alpha
        dst = cv2.addWeighted(dst,alpha,buf_img,beta,1.0)

    return dst
```