【TF2】[実験] VGG16単体で、円の中心位置と直径を検出する

ディープラーニング実験企画
スポンサーリンク

CNN自体はよく画像のクラス分類に使われますが、同時に「人間の目のよう」とも称されます。
では、座標の位置判定にも使えるのでは?
という疑問の解消のためにやってみました。

 

はじめに

VGGやMobileNetなどのCNNモデルは、
分類を行う際の常套手段ではありますが、
出力層を変えるだけで、位置推定に使えるのでは?
と思い、実験した
ものです。

結論から言うと、実験はうまくいき、
SSDなどの物体検出器を使わず、
VGG16単体で、おおよその円の中心と半径を検出できました。

↑ 赤の× が検出した中心位置、赤の○が円の位置です。
そこそこの精度が出ています。

ソースコード

こちらにありますので、実際に動作させたい方はこちらを。
以下の説明は、このソースコードの前提で書いています。

GitHub - MaxiParadise/Circle_Detection_by_CNN
Contribute to MaxiParadise/Circle_Detection_by_CNN development by creating an account on GitHub.

準備編

まずはじめに、
学習用の円画像データを生成します。

data_generator.py でランダム生成できるように作ってあります。

Trainデータ1000枚とValidデータ200枚、
テスト用に100枚生成
します。

$ python data_generator.py

以下のフォルダにデータが生成されます。

dataset/image/train  学習データ 円画像
dataset/image/valid  学習データ 円画像(validation)
dataset/image/test  テストデータ 円画像

dataset/annotation/train.csv  学習データ 円画像の正解データ 座標と半径
dataset/annotation/valid.csv  学習データ(validation) 円画像の正解データ 座標と半径
dataset/annotation/test.csv  テストデータ 円画像の正解データ 座標と半径

・円画像

・正解データ

 

この円の、
中心位置(x,y) と、半径を検出するように学習させます。

出力層を、位置と直径用に設定する

train_circle_position_radius.py が、
学習用スクリプトです。

位置座標と半径を検出するよう、VGG16モデルの設定を行います。

よく使われる分類器では、
出力がNクラスあれば、
N次元のベクターデータ(確度)が出力値となります。

今回は、位置(x,y)と半径rなので、出力は3次元です。

    OUTPUT_PARAMS = 3 # x, y, radius
    # Base Model 作成
    base_model = VGG16(include_top=False, input_tensor=input_tensor)

    # Output layer 作成
    x = base_model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    # 
    predictions = Dense(OUTPUT_PARAMS, activation='linear')(x)
    # 全体Model作成
    model = Model(inputs=base_model.input, outputs=predictions)

また、出力層の活性関数は、
分類のように確度を扱うのであれば、SoftmaxやSigmoidを使うのが常套ですが、
今回の座標の場合は、変な偏りが無いように直線の関数にします。
(座標にSoftmaxなんて使うと位置が激しくずれる)
そして負値がありうるのでLinearにしています。

ちなみに、
今回のように範囲内に全部の円が入っているデータばかりだと、
ReLUでも問題ないとは思います(負にならないため)。
はみ出てる円画像も認識させたいなどに備えて、負値になっても良いようLinearにしてます。

Loss関数を、位置検出用にする

例えば分類器だと、Loss関数はクロスエントロピーですが、
位置座標の場合は、
平均二乗平方根誤差(MSE)あるいは平均絶対値誤差(MAE、L1Loss)、
もしくは、それらを合体させたSmoothL1Lossてのも使われます。
(SSDの位置検出で使われている関数)

精度自体はSmoothL1>MSE>MAE だと思っていますが、
今回はTensorflowで最初から使えるMSEで実装しました。

    # Optimizer 選択
    model.compile(optimizer=Adam(lr=LEARN_RATE), loss='mse', metrics=['accuracy'])

OptimizerはとりあえずのAdam。
ここは特に何も考えてません。

余談ですが、
画像処理でも「平均絶対値誤差」のキーワードはボカし技術の一貫で出てきます。
ディープラーニングでも、MAEはMSEに比べて
出力結果が「ぼやける」傾向にあります。

なので、DLでも結果を故意にぼやかしたい場合は、
MAEをうまく利用することもあるのではと考えてます。

 

あとは学習させる

$ python train_circle_position_radius.py

で、学習を開始します。

 

    # モデル訓練実行
    history = model.fit(
        X_train, Y_train,
        batch_size=BATCH_SIZE,
        epochs=MAX_EPOCH,
        verbose=1,
        validation_data=(X_valid, Y_valid),
        callbacks=cb_funcs
    )

なお今回は単純にmodel.fit で実装しています。

このコード、何のAugmentationもしていません。

とにかくシンプルなサンプルを目指したので、
今回はテキトー方式で学習させています。

そのためもあってか、
このコードだけだとVGG16だと精度が出ますが、
MobileNetV2だとろくな精度が出ません。

おそらく、
ランダムノイズやランダムコントラストなど加えれば、
MobileNetや8層くらいのネットワークでも精度が出ると思っています。

学習結果のテスト

学習したモデルのテストは、

$ python test.py checkpoint/xxx.hdf5

で実施できます。

100枚の検出エラー平均が出ます。
Average Error x:0.73, y:0.82, r:0.41
という表示。

224ピクセルにおける検出精度が1ピクセル未満に収まっています。

また、test_result フォルダに検出結果を図示した画像が出力されます。

これが冒頭の画像になります。
結構な精度で、位置と半径を検出できていることが分かりました。

 

ユースケース

SSDなどの、物体検出ほど大がかりなものでなく、
たとえばコインの画像から、
その中心位置を推定するようなユースケースで、

ごくごくカンタンな処理をしたいときなどに使えるのでは無いでしょうか。

 

さいごに

これぐらいであれば画像処理アルゴリズムでの抽出の方が楽ですが、
将来的には
ディープラーニングの方が、
速度的にもコスト的にも上回る可能性あるんじゃないかなー
と思ってます。
その時にでも使えれば。

あるいは社内での勉強会とかでも取り扱ったり、
なにかの参考になれば幸いでございます。

ではまたー

 

最後まで読んでいただき、ありがとうございます!

ブックマーク登録、
ツイッターフォロー、
よろしくお願いいたします!🙇‍♂️🙇‍♂️

 

タイトルとURLをコピーしました