【UE4】かっこいい構図をAIに決めてもらおう〜外観パース編〜【機械学習】

Pocket
LinkedIn にシェア
LINEで送る

建築×機械学習の企画第二弾。建築AIって試したいことがいっぱいありすぎてどれからやろうか迷いながらの選択でしたが、今回はこのテーマに着手してみました。

AIくんにイケてる外観パースの画角・構図を決めてもらいます。

外観パースと言うのは平たく言うと「建物の外側の見た目」の画像のことです。マンションチラシとか完成予想のCG画像でよく目にすると思います。
ところでですが、はっきり言ってしまえば、カメラアングルの観点だけで言えばだいたい同じなんですよ。細かい仕上げとか、個人個人のクリエーターさんのこだわりはもちろんあります、私だって「機械なんかに負けてらんない」とか思っています。でもアングルはで大して変わらないのですよ。もうこれは認めないわけにはいきません。それが現実です。
というより「外観パースの超個性的なアングル」ってなに? あるの? って感じですもの。そんなものビジネス用途の画像であればボツ一択です。
私は建築写真家ということもやらせてもらっていますが、やっぱりアングルは似てきます。建物毎の独特なディテールをうまく画角に収めたいとか、そういう要件が含まれない限りはだいぶ似てきます。

「似てる」ということは、きっと機械学習による画像処理は相性がいいはずです。ということで実験してみました。

やってみよう

いつものことですが間違っていたら教えてください。

最終的にはAIエンジンをプラグインとかにしてUE4に組み込むのが目標ですが、今回は画像生成と、画像判定を分離して作っていきます。以下の組み合わせでトライ。
  画像生成 :Unreal Engine4 version 4.24
  画像分類 :TensorFlow version 1.15
  学習データ :建築グラビアのギャラリーページの写真。

画像生成:Unreal Engine4

それではアンリアルエンジンの方で画像を作っていきましょう。
今回思いついたのは、カメラアクターは常に家の中心を向かせておいて、一定間隔で移動していって、都度スクリーンショットを撮る。これを自動で繰り返してもらうことで超大量のテスト画像を一気に生成しよう、という方法です。

カメラアクターの変更パラメータは次の通りとしました。

  • 位置 (移動するため)
  • 回転 (建物の中心を見るため)
  • あおり補正 (Camera with Shift Lensプラグインを使用) 

「FoVは入れないの?」と思われたでしょうが、今回は90度(≒フルフレームで焦点距離18mm)で固定とします。
「ひたすらスクショを撮る」というのはこんなイメージです。内部的には1つのCineCameraActorのLocationを一定値で変更してはスクリーンショットを撮る処理を繰り返します。

自動画像生成プログラム作成

今回は、UE4の建築テンプレートCollab Viewerのビルについて、かっこいい構図を決めてもらってみましょうか。

・C++クラスを作成します。
・プラグインを作成します。この記事中では「AutoShoot」とします。
 プラグインの種類は「エディタツールバーのボタン」です。

UE4を再起動するとツールバーにボタンが増えます。これでコマンド実行ボタンの準備が完了。

そしたらVisual Studioを起動してプログラムしていきます。ボタンクリックするとPluginButtonClicked()関数に来ますので、ここにコーディングしていきましょう。

プログラムは今回の主旨ではないので成果物だけ載せます。既存ソースを参考にしながらやれば1時間かからないくらいでしょう。

 

#include "AutoShoot.h"
#include "AutoShootStyle.h"
#include "AutoShootCommands.h"
#include "Misc/MessageDialog.h"
#include "ToolMenus.h"

//// add include.
#include "Engine.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"
#include "LevelEditor.h"
#include "Kismet/KismetMathLibrary.h"
#include "ShiftLensCineCameraActor.h"
#include "ShiftLensCineCameraComponent.h"

static const FName AutoShootTabName("AutoShoot");

#define LOCTEXT_NAMESPACE "FAutoShootModule"

void FAutoShootModule::StartupModule()
{
// ・・・略・・・
}

void FAutoShootModule::ShutdownModule()
{
// ・・・略・・・
}

// ボタンが押されたとき
void FAutoShootModule::PluginButtonClicked()
{
  //
  UWorld* World = nullptr;
  for (const FWorldContext& Context : GEngine->GetWorldContexts())
  {
    UWorld* ThisWorld = Context.World();
    if (!ThisWorld)
    {
      continue;
    }
    else if (Context.WorldType == EWorldType::PIE)
    {
      World = ThisWorld;
      break;
    }
    else if (Context.WorldType == EWorldType::Editor)
    {
      World = ThisWorld;
    }
  }

  //
  //TSubclassOf<ACameraActor> findClass;
  //findClass = ACameraActor::StaticClass();
  TSubclassOf<AShiftLensCineCameraActor> findClass;
  findClass = AShiftLensCineCameraActor::StaticClass();
  TArray<AActor*> Cameras;
  UGameplayStatics::GetAllActorsOfClass(World, findClass, Cameras);

  if (Cameras.Num())
  {
    //ACameraActor* camera = Cast<ACameraActor>(Cameras[0]);
    AShiftLensCineCameraActor* camera = Cast<AShiftLensCineCameraActor>(Cameras[0]);

    FVector orgLocation = camera->GetActorLocation();

    float height = 100;

    //FVector CenterLocation(1200, 0, 500);
    FVector CenterLocation(0, 0, height);
    //FVector CenterLocation(FVector::ZeroVector);

    int startX = -2000;
    int endX = 2000;
    int startY = -200;
    int endY = 2000;
    int startZ = 100;
    int endZ = 1100;
    int step = 500;
    //for (int z = startZ; z <= endZ; z += step) {
    //for (double s = 0.0; s < 1.0; s += 0.3) {
      for (int y = startY; y <= endY; y += step) {
        for (int x = startX; x <= endX; x += step) {

          // 座標を差し替えます
          FVector Location(x, y, height);
          FRotator Rotation = UKismetMathLibrary::FindLookAtRotation(Location, CenterLocation);
          camera->SetActorLocation(Location);
          camera->SetActorRotation(Rotation);
          //UShiftLensCineCameraComponent* cameracompo = Cast<UShiftLensCineCameraComponent>(camera->GetCameraComponent());
          //cameracompo->SetShiftLens(s);

          // ここでスクリーンショットが取れます
          for (FEditorViewportClient* ViewportClient : GEditor->GetAllViewportClients())
          {
            FSceneViewStateInterface* ViewportParentView = ViewportClient->ViewState.GetReference();
            if (ViewportClient->ViewIndex > 0 && ViewportClient->IsPerspective()) {
              // カメラに座標を適用
              ViewportClient->ViewFOV = 90.0f;
              ViewportClient->SetViewLocation(Location);
              //ViewportClient->SetLookAtLocation(CenterLocation, true);
              ViewportClient->SetViewRotation(Rotation);

              //FString PrintString = FString::Printf(TEXT(" %.2f"), Location);
              GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::White, Location.ToString());
              ////
              // Send notification about actors that may have changed
              ULevel::LevelDirtiedEvent.Broadcast();

              // Update the details window with the actors we have just selected
              //GUnrealEd->UpdateFloatingPropertyWindowsFromActorList(SelectedActors);

              // Redraw viewports to show new camera
              GEditor->RedrawAllViewports();
              ////

              FPlatformProcess::Sleep(0.2f);
              ViewportClient->TakeScreenshot(ViewportClient->Viewport, true);
              FPlatformProcess::Sleep(0.2f);
            }
          }
        }
      }
    //}

    // 元の座標に戻す
    camera->SetActorLocation(orgLocation);
  }
}

void FAutoShootModule::RegisterMenus()
{
// ・・・略・・・
}

#undef LOCTEXT_NAMESPACE
  
IMPLEMENT_MODULE(FAutoShootModule, AutoShoot)

 

やっていることの説明です。

  • 最初に現在のレベルを取得して、あおり補正カメラアクターを探します。
  • あとはfor文で位置LocationをX方向、Y方向に5mずつ移動しながら、あおり補正値を0.4ずつ変更していき、スクリーンショットしていきます。
  • 最後にやってることは、カメラアクターの位置、FoV、あおり値をメインカメラにセットしています。それで、メインカメラが持っている設定値の状態でスクリーンショットを撮ります。

・・・ってことです。F9キーを押した時のスクリーンショット機能を活用しました、TakeScreenshot()という関数を呼ぶだけなので簡単です。

UE4エディタに移動して、ShiftLensCineCameraActorを配置します。
・場所はどこでもいいです。
・[Filmback]を[16:9 DSLR]を選択して、焦点距離[Current Focal Length]を18mmくらいにしておきます。

撮影開始

ツールバーの[AutoShoot]ボタンを押してみましょう。
次にプロジェクトフォルダのSaved¥Screenshot¥Windowsフォルダを開いてみましょう。みるみる画像が増えていきます。
今回はX軸Y軸ともに11地点、あおり値が3種類なので、11x11x3=363枚の画像が得られました。

画像分類器:TensorFlow

さてここからが本題ですね。機械学習させます。
今回は画像分類機の応用でアプローチしてみました。一番有名どころにCats and Dogs(その画像は猫?犬?)ってのがありますが、これを改造して
  「Cool or Lame Arch(その建築画像はかっこいい?ダサい?)」
ってやり方にしてみました。このアプローチがイケてるのかどうかってのはかなり分かれるところだと思いますが、はじめの一歩としてこの方法でやってみます。

ということで画像を振り分けましょう。CoolとLameというフォルダを2つ新規作成して振り分けます。

  • 「Cool(かっこいい)」フォルダ
    かっこいいと思っている建築写真を入れていきます。これ自分で言うと恥ずかしいんですけどね。変に個人的な感情のバイアスを入れると良くないかなと思ったので、パッと見て「ああこれはうまく撮れたかな」ってのを入れていきます。
  • 「Lame(ダサい)」フォルダ
    イケてない写真を入れていきます。具体的には、「このアングルは今回は採用しないでほしいな」って写真にしておきます。

 

最終的なフォルダ構成はこんな感じです。([bad]を[lame]だと思ってください)
・学習データはtrail_imagesとvalidation_imagesのcoolとlameフォルダそれぞれに半々ずつ置きます。
・test_imagesに、UE4で出力した363枚を置きます。

プログラムはおおよそCatsAndDogsと同じにしました。最後の方で2つプログラムしておきました。

  • UE4で出力したスクリーンショット363枚について、AIに評価してもらいます。
  • 評価の結果を一覧表としてファイル出力するようにしました。

そして学習させます。成果はこんな感じで75%程度。不安が残ります。
過学習になりそうな兆候はみられないのでデータ不足でしょうか。

結果

さてどんな結果が返ってきたかな?
AIがはじき出した、アンリアルエンジン4の建築テンプレートのビルがかっこよく見える外観パース写真の画角 TOP5は・・・!

5位

94.4%

4位

95.4%

3位

95.8%

2位

96.5%

1位

98.2%

期待していたよりそれっぽいアングルにしてくれましたね! ちょっと嬉しい。
一番興味深いなと感じたのはあおり補正がかかった画像が多い点です。あおり補正が無いよりも、あおりが強めに効いた画像をセレクトしてくれているんです。
学習データは若干少ないかなと思っていたのですが、私があおり補正が良く効いている趣向を反映してくれているのかと思われます。これが特に嬉しかったです。

感想とか課題とか

ああ楽しかった(小並感)。割と本当にそう思いました。
もうちょっと色々試したいです。
  ・学習データ(写真)を変えたらどんな結果になるかな?
  ・UE4側で建物を変えたらどうなるかな?
次は内観パースで試したいです。

あとは、プラグイン化してリリースしたいですね。割と使えると思うので。

ソースコード

christinayan01/demo/AiAutoShoot – GitHub
https://github.com/christinayan01/demo/tree/master/AiAutoShoot

参考サイト

画像分類 – TensorFlowチュートリアル
Camera With Shift Lens – Unreal Engine Marketplace

コメントを残す