OpenCVの顕著性マップを試してみた。

# 1.はじめに

OpenCVのcontribに入っている顕著性マップを動かしてみました。
動かすまでに苦労したので、ちょっとまとめておきます。
※元論文は読み込んでいないので、詳しいアルゴリズムの解説はしていません。すみません。

OpenCVに実装されている顕著性マップのクラスは4つです。

| No | 関数名                                   | 入力 | 元論文  |
|:--:|:----------------------------------------|:-----|:-----:|
|  1 | StaticSaliencySpectralResidual_create() | 画像 | [[1]](https://www.researchgate.net/publication/221364530_Saliency_Detection_A_Spectral_Residual_Approach) |
|  2 | StaticSaliencyFineGrained_create()      | 画像 | [[2]](https://www.sciencedirect.com/science/article/pii/S0262885609001371) |
|  3 | ObjectnessBING_create()                 | 画像 | [[3]](https://mmcheng.net/bing/) |
|  4 | MotionSaliencyBinWangApr2014_create()   | 動画 | [[4]](https://ieeexplore.ieee.org/document/6910012) |

1,2の関数は顕著性マップを出力しますが、3のBINGに関しては物体がありそうな場所を
バウンディングボックスで囲うというアルゴリズムなので、バウンディングボックスの情報が出力されます。
4のアルゴリズムは、動画の中で静止背景と動物体を切り分けるアルゴリズムなので、入力が動画になります。

これらを一つづ動かしていきました。
なお、動作環境は以下のとおりです。

### 1.1.動作環境
+ Python 3.5
+ OpenCV 3.4.3

# 2.Saliency

上記の表1,2の顕著性マップを画像として出力するプログラムを作成します。
1,2のどちらのクラスを使用して処理するかはコマンドライン引数で選択できるようにしています。

## 2.1.プログラム

```python
##
# cording:utf-8
##

import argparse
import cv2

def argparser():
    parser = argparse.ArgumentParser()
    parser.add_argument("image", type=str, help="input image")
    parser.add_argument("--func", type=str, choices=['SR', 'FG'], default='SR')
    return parser.parse_args()

def main():

    args = argparser()

    img = cv2.imread(args.image)
    if img is None:
        exit()

    saliency = None
    if args.func == 'SR':
        saliency = cv2.saliency.StaticSaliencySpectralResidual_create()
    elif args.func == 'FG':
        saliency = cv2.saliency.StaticSaliencyFineGrained_create()

    if saliency is None:
        exit()

    (success, saliencyMap) = saliency.computeSaliency(img)
    saliencyMap = (saliencyMap * 255).astype("uint8")

    cv2.imshow('img',img)
    cv2.imshow('srn',saliencyMap)
    cv2.waitKey()

if __name__=='__main__':
    main()
```

## 2.2.結果

画像は,Pascal VOC2012のデータセットからいただきました。

### 2.2.1.原画像

![2008_005398.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/dc007356-6496-60ba-24b5-2a9348887a68.jpeg)


### 2.2.2.SpectralResidual

![output.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/ba5341c5-b220-96f5-456b-5e6e5bb54fa9.jpeg)

ぼんやりした像ですが、物体が浮き出ています。

### 2.2.3.FineGrained

![output.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/3d8bd3b4-1545-5863-557f-0176e2d33f04.jpeg)

こちらは、エッジが出ていますが、雲なども浮き出ています。

# 3.BING

物体らしい場所のバウンディングボックスを出力するBINGのプログラムです。
こちらは、予め学習したフィルタのパラメータがOpenCVのフォルダ内に格納されているため、
そのフィルタを使用してバウンディングボックスを画像に描画して出力します。

学習済みのフィルタは、以下に格納されているのでコピーして使用するか、
このフォルダまでのパスをプログラムに記述してあげることで使用できます。

```
$ opencv_contrib-3.4.3/modules/saliency/samples/ObjectnessTrainedModel
```

## 3.1.プログラム

```python
##
# cording:utf-8
##

import numpy as np
import cv2

def main():

    # 出力するバウンディングボックスの最小数
    bbox_num = 20

    img = cv2.imread('test.jpg')

    saliency = cv2.saliency.ObjectnessBING_create()

    # パラメータの格納されているディレクトリを指定する
    saliency.setTrainingPath("hogehoge/opencv_contrib- \
                              3.4.3/modules/saliency/samples/ \
                              ObjectnessTrainedModel")
    # 処理結果のバウンディングボックスの情報を保存するディレクトリを設定
    saliency.setBBResDir( "Results" )

    (success, saliencyMap) = saliency.computeSaliency(img)
    numDetections = saliencyMap.shape[0]

    # バウンディングボックスの描画処理
    output = img.copy()
    for i in range(0, min(numDetections, bbox_num)):
        (startX, startY, endX, endY) = saliencyMap[i].flatten()

        color = np.random.randint(0, 255, size=(3,))
        color = [int(c) for c in color]
        cv2.rectangle(output, (startX, startY), (endX, endY), color, 2)

    # 結果表示
    cv2.imshow("Image", output)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

if __name__=='__main__':
    main()
```
## 3.2.結果

saliency.setTrainingPathに指定したディレクトリに３つのフィルタを入れておくと、３つのフィルタの結果を
すべて出力します。

```shell
$ Average time for predicting an image (MAXBGR) is 0.020916s
$ Average time for predicting an image (HSV) is 0.018478s
$ Average time for predicting an image (I) is 0.017292s
```

### 3.2.1.20bbox

`bbox_num`の値を20に設定した時の結果です。

![output_bing.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/540b1bcf-9012-7a93-a276-3bb8856ca993.jpeg)

### 3.2.2.100bbox

`bbox_num`の値を100に設定した時の結果です。

![output_bing100.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/d04de25a-0dce-6893-e1d8-2b999bd3debb.jpeg)

### 3.2.3.目視で確認

`bbox_num`の値を100に設定した時の結果から目視で、うまく検出できていたものを
１つだけ描画しました。

![testt.jpg](https://qiita-image-store.s3.amazonaws.com/0/104675/73de953b-49bd-22a4-5272-d540c1f9a42c.jpeg)

# 4.Motion

## 4.1.プログラム

```python
##
# cording:utf-8
##

import cv2

def main():

    cap = cv2.VideoCapture('vtest.avi')
    if not cap.isOpened():
        exit()

    saliency = None

    # 結果動画の保存
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    writer = cv2.VideoWriter('output.avi',fourcc, 10.0, (1536,576))

    while True:
        ret, img = cap.read()
        if not ret:
            break  

        # 初期化
        if saliency is None:
            saliency = cv2.saliency.MotionSaliencyBinWangApr2014_create()
            saliency.setImagesize(img.shape[1], img.shape[0])
            saliency.init()

        # 入力はグレースケールのみ
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        (success, saliencyMap) = saliency.computeSaliency(gray)
        saliencyMap = (saliencyMap * 255).astype("uint8")

        viewSaliency = cv2.merge([saliencyMap, saliencyMap, saliencyMap])
        
        view = None
        output = cv2.drawMatches(img, [], viewSaliency, [], [], view)

        writer.write(output)

        cv2.imshow("Frame", output)
        #cv2.imshow("Map", saliencyMap)
        if cv2.waitKey(1) & 0xFF == 27:
            break

    writer.release()
    cv2.destroyAllWindows()
    

if __name__=='__main__':
    main()
```

## 4.2.結果

動いている箇所が抜き出されます（背景差分との違いはなんだろう・・・）。
最初の数フレームが白飛びしているのは、初期化に使っているのか・・・
いろいろ疑問が出てきたので論文読んでみます。

![circleAnimationMuvie2.gif](https://qiita-image-store.s3.amazonaws.com/0/104675/bf2121d7-11c4-d925-1927-366dbe5c3580.gif)

# まとめ

ちゃんとアルゴリズムの仕組みを知らずにとりあえず動かしてみた感しかない・・・OTL
論文をこれから読んで仕組みがわかったらしっかり書き直したいと思います。

[5] [OpenCV Saliency Detection](https://www.pyimagesearch.com/2018/07/16/opencv-saliency-detection/)
[6] [OpenCVのVideoWriterを使って画像から動画を作る。](http://shibafu3.hatenablog.com/entry/2016/11/13/151118)
[7] [OpenCV:featurematching![undefined]()](https://docs.opencv.org/3.4.3/dc/dc3/tutorial_py_matcher.html)
[8] [GithubやQiitaに載せるgif形式の動画を作成する方法](https://qiita.com/syamaoka/items/4d4054cd75df00460925)