TECH PLAY

電通総研

電通総研 の技術ブログ

834

電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、 美少女アニメ 画の呪文です。 v2.1 美少女アニメ画 の記事も書きました。よろしければご覧ください。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 japanese anime of a beautiful girl pixiv, light novel, digital painting fantasy costume, fantasy background, beautiful composition, cinematic lighting extremely detailed, sharp focus, ray tracing, 8k, cinematic postprocessing まとめ 仲間募集 Stable Diffusionの過去コンテンツ japanese anime of a beautiful girl 画像のタイプは、japanese anime。anime単独より、japanseseをつけた方が、出力が安定します。 サブジ ェクト(描画対象)は、a beautiful girl。 kawaii よりもbeautifulの方が出力が安定している気がします。 まずは、シンプルなPromptから始めましょう。 japanese anime of a beautiful girl 出力はこちら。 コレジャナイ感が出てますね。 pixiv, light novel, digital painting 画像のクオリティを上げるためには、作風をStable Diffusionに伝える必要があります。ゲーム名、映画名、絵師などを指定する部分です。いろいろ試してみましたが、 美少女アニメ 画には、pixiv, light novel, digital paintingが、経験上、出力が安定しています。ネットでは、Makoto ShinkaiをいれるPromptもよく見かけるのですが、僕が試した限りは、出力が安定しませんでした。 今回のPromptはこちら。 japanese anime of a beaultiful girl, pixiv, light novel, digital painting 出力はこちら。 結構それっぽくなってきましたが、ちょっとチープ感があります。 fantasy costume, fantasy background, beautiful composition, cinematic lighting 画像のクオリティをさらに上げるには、服装、背景、構図、ライティングが重要です。 fantasy costumeで、服装を指定します。 fantasy backgroundで、背景を指定します。 beautiful compositionで、構図を指定します。これを指定することで、 サブジ ェクトがいい感じで配置されます。 cinematic lightingで、ライティングを指定します。ライティングにより、 サブジ ェクトが輝いて見えます。ライティングの仕方は色々ありますが、cinematic lightingは、外れることがほとんどないので、よく使っています。 今回のPromptはこちら。 japanese anime of a beaultiful girl, fantasy costume, fantasy background, beautiful composition, cinematic lighting, pixiv, light novel, digital painting 出力はこちら。 ぐっとクオリティが上がりました。これで十分だと思われる方もいるかも知れません。 extremely detailed, sharp focus, ray tracing, 8k, cinematic postprocessing ここから仕上げです。 extremely detailed, sharp focusで、 サブジ ェクトを詳細に描画し、ピントを当てます。 ray tracing, 8kで、より現実に近い映像を作り出します。 cinematic postprocessingで、映画レベルまでクオリティをあげます。 今回のPromptはこちら。 japanese anime of a beaultiful girl, fantasy costume, fantasy background, beautiful composition, cinematic lighting, pixiv, light novel, digital painting, extremely detailed, sharp focus, ray tracing, 8k, cinematic postprocessing 出力はこちら。 映画の中の1シーンのようですね。 まとめ 美少女アニメ 画は、作風、服装、背景、構図、ライティングをしっかり指定することで、クオリティが上がることが理解できたと思います。 次回は、 美少女写真編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマは、画像タイプ。色々な画像を見ていくよ。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 a ball-point pen art a pencil sketch 3D photorealistic pencil drawing a crayon painting an acrylic painting a watercolor painting an oil painting an ukiyo-e painting airbrush caricature a low poly illustration japanese anime, pixiv, unreal engine, 4k まとめ 仲間募集 Stable Diffusionの過去コンテンツ a ball-point pen art ボールペンアート。 Promptはこちら。 a ball-point pen art of a cat wearing sunglasses, award-winning 出力された画像はこちら。 a pencil sketch 鉛筆によるスケッチ。 Promptはこちら。 a pencil sketch of a young boy playing hockey, award-winning 出力された画像はこちら。 3D photorealistic pencil drawing 3Dのリアルな写真的な鉛筆画。 Promptはこちら。 3D photorealistic pencil drawing of a young boy, award-winning 出力された画像はこちら。 a crayon painting クレヨン画。 Promptはこちら。 a crayon painting of a camel on the desert 出力された画像はこちら。 an acrylic painting アクリル画 。水彩画と同様に絵の具を水に溶かして使うが、水彩画のほうが劣化しやすいらしい。 Promptはこちら。 an acrylic painting of a young boy, award-winning 出力された画像はこちら。 a watercolor painting 水彩画。紙に書かれることがほとんどらしい。 Promptはこちら。 a watercolor painting of cherry blossoms, award-winning 出力された画像はこちら。 an oil painting 油彩画。 Promptはこちら。 an oil painting of sunflowers, award-winning 出力された画像はこちら。 an ukiyo-e painting 浮世絵。 Promptはこちら。 an ukiyo-e painting of Mt. Fuji in Katsushika Hokusai style, award-winning in 画家の名前 styleで指定した画家のスタイルを取り入れることができます。 出力された画像はこちら。 airbrush caricature ある人物の性格や特徴を大げさに表現した人物画。 Promptはこちら。 an airbrush caricature of a famous actor 出力された画像はこちら。 a low poly illustration 粗いポリゴンのイラスト。 Promptはこちら。 a low poly illustration of two kids, award-winning 出力された画像はこちら。 japanese anime, pixiv, unreal engine , 4k 日本のアニメ。それっぽい画像を出力するのはけっこう大変でした。 特定のアニメ、ゲーム、絵師の指定はなしで挑戦しました。 著作権 が絡んでくるリスクがあるので。 特に深い理由はありませんが、 kawaii というキーワードも意図的に使いませんでした。 Promptはこちら。 japanese anime of a beautiful girl, beautiful face, long black hair, cute eyes, long eyelashes, cute mouth, fantasy costume, overheard sunlight, fantasy background, pixiv, unreal engine, 4k 特定の絵師の名前は入れていませんが、pixivはあったほうが出力が安定しました。 overhead sunlightは、頭に太陽の光があたっている効果が出ます。 顔のパーツを色々指定していますが、指定したほうが安定します。 unreal engine , 4kがないと、チープな感じになってしまいます。 出力された画像はこちら。 まとめ 今回は、いろいろな画像タイプを紹介しました。 人物写真編 、 レンズ編 とあわせてこなしていれば、StableDiffusionの基礎はもう固まっていると思います。 次回は、 美少女アニメ画編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマは、画像タイプ。色々な画像を見ていくよ。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 a ball-point pen art a pencil sketch 3D photorealistic pencil drawing a crayon painting an acrylic painting a watercolor painting an oil painting an ukiyo-e painting airbrush caricature a low poly illustration japanese anime, pixiv, unreal engine, 4k まとめ 仲間募集 Stable Diffusionの過去コンテンツ a ball-point pen art ボールペンアート。 Promptはこちら。 a ball-point pen art of a cat wearing sunglasses, award-winning 出力された画像はこちら。 a pencil sketch 鉛筆によるスケッチ。 Promptはこちら。 a pencil sketch of a young boy playing hockey, award-winning 出力された画像はこちら。 3D photorealistic pencil drawing 3Dのリアルな写真的な鉛筆画。 Promptはこちら。 3D photorealistic pencil drawing of a young boy, award-winning 出力された画像はこちら。 a crayon painting クレヨン画。 Promptはこちら。 a crayon painting of a camel on the desert 出力された画像はこちら。 an acrylic painting アクリル画 。水彩画と同様に絵の具を水に溶かして使うが、水彩画のほうが劣化しやすいらしい。 Promptはこちら。 an acrylic painting of a young boy, award-winning 出力された画像はこちら。 a watercolor painting 水彩画。紙に書かれることがほとんどらしい。 Promptはこちら。 a watercolor painting of cherry blossoms, award-winning 出力された画像はこちら。 an oil painting 油彩画。 Promptはこちら。 an oil painting of sunflowers, award-winning 出力された画像はこちら。 an ukiyo-e painting 浮世絵。 Promptはこちら。 an ukiyo-e painting of Mt. Fuji in Katsushika Hokusai style, award-winning in 画家の名前 styleで指定した画家のスタイルを取り入れることができます。 出力された画像はこちら。 airbrush caricature ある人物の性格や特徴を大げさに表現した人物画。 Promptはこちら。 an airbrush caricature of a famous actor 出力された画像はこちら。 a low poly illustration 粗いポリゴンのイラスト。 Promptはこちら。 a low poly illustration of two kids, award-winning 出力された画像はこちら。 japanese anime, pixiv, unreal engine , 4k 日本のアニメ。それっぽい画像を出力するのはけっこう大変でした。 特定のアニメ、ゲーム、絵師の指定はなしで挑戦しました。 著作権 が絡んでくるリスクがあるので。 特に深い理由はありませんが、 kawaii というキーワードも意図的に使いませんでした。 Promptはこちら。 japanese anime of a beautiful girl, beautiful face, long black hair, cute eyes, long eyelashes, cute mouth, fantasy costume, overheard sunlight, fantasy background, pixiv, unreal engine, 4k 特定の絵師の名前は入れていませんが、pixivはあったほうが出力が安定しました。 overhead sunlightは、頭に太陽の光があたっている効果が出ます。 顔のパーツを色々指定していますが、指定したほうが安定します。 unreal engine , 4kがないと、チープな感じになってしまいます。 出力された画像はこちら。 まとめ 今回は、いろいろな画像タイプを紹介しました。 人物写真編 、 レンズ編 とあわせてこなしていれば、StableDiffusionの基礎はもう固まっていると思います。 次回は、 美少女アニメ画編 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 本日は最新のGradle(2022/08現在)を使いこなしながらKotlinで Java のアプリケーションをビルドする スクリプト を書く際に、知っておくと便利なノウハウをまとめてご紹介します。 はじめに 記事の執筆環境 scoopのセットアップ Javaのセットアップ Gradleのセットアップ サンプルアプリケーションについて ルートプロジェクトの実装 ウェブアプリケーションプロジェクトの実装 ビルドスクリプトの作成 サンプルアプリケーションの実装 バージョニング その他のバージョニングプラグイン バッチプロジェクトの実装 バッチアプリケーションの実装 Fat/Uber Jarの作り方 ビルドにおける共通処理の切り出し ローカルプラグインの作り方 ローカルプラグインの実装 ローカルプラグインの使い方 ローカルプラグインにGradleプラグインを組み込む プラグインで変数や関数を定義する前に Extensionの実装 まとめ はじめに この記事では、 ソースコード をユーザーが利用できる環境にデプロイできるアプリケーションに変換する工程をビルドプロセスと呼ぶこととします。 Java で実装されたアプリケーションをビルドするためのツールとしては、古くはmakeから始まり、Antを経由して現代では Maven やGradleといったツールが広く利用されています。 この記事では、自分がコン トロール できる範囲では宣言的に記述でき、必要に応じて アドホック なコードを書いていけるGradleを使ったビルドプロセスにおけるノウハウを紹介します。 記事の執筆環境 この記事が前提とする環境について軽く説明しておきましょう。 まず、OSはWindows10 Proで、バージョンは21H2です。 Windows 環境でアプリケーションをインストールするために、パッケージマネージャーとして scoop を使っています。 そして、 Java 17を使います。 記事の主題はGradleの使い方ですので、 Windows 以外のOSでも有効なものです。 Windows 以外の環境をお使いなら次に続く環境のセットアップ手順については読み飛ばしてください。 scoopのセットアップ scoopは powershell だけで様々なツールをインストールできるパッケージマネージャーの一種です。 この記事で紹介するツールはscoopを使ってインストールしますので、もし未導入なら導入をお願いします。 まずは、 PowerShell の実行ポリシーを変えます。 scoopのインストールのためには、インターネット上にあるサーバから シェルスクリプト をダウンロードしてきて実行できるようにします。cf. about_Execution_Policies PowerShell を起動して以下のコマンドを実行します。 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser 次にインストール スクリプト をダウンロードしつつ実行します。 irm get.scoop.sh | iex これでscoopのインストールは終わりです。 Java のセットアップ scoopで Java をインストールする前に バケット を追加します。 scoop bucket add java この バケット には様々なOpenJDKのビルドが入っているのですけども、ここではopenjdk17を使います。 scoop install openjdk17 openjdkを指定すると、記事執筆時点では Java 18がインストールされましたのでメジャーバージョンを明示的に指定しています。 Gradleのセットアップ 次はGradleをインストールしましょう。 scoop instlal gradle Gradleのインストールが完了したらセットアップは終了です。 サンプルアプリケーションについて この記事では ユーザーインターフェース のある ウェブアプリケーション と、 ユーザーインターフェース のないバッチアプリケーションが、単一のgit リポジトリ 内に共存しているものを想定しています。 典型的な構造ですが、様々なやり方でビルド スクリプト を記述できます。この記事では私が実施しているプロジェクトにおける構成において得られた知見をもとに説明します。 ルートプロジェクトの実装 まずは、プロジェクト全体を格納するための ディレクト リを作成しましょう。 ここからは、この記事内でシェルコマンドを実行するよう説明している部分では、必ずこのルート ディレクト リで実行してください。 作るプロジェクトは、説明のために demo-project とします。作ったdemo-project ディレクト リの中で、以下のコマンドを実行して最小限のプロジェクトを作成します。 gradle init --type basic --dsl kotlin --project-name demo-project --incubating 最小限とはいえ、Gradle Wrapperとなる シェルスクリプト やgit用の設定ファイルが生成されていますね。 その中から、不要な build.gradle.kts を削除してください。 ウェブアプリケーション プロジェクトの実装 次はSpring Bootを使った ウェブアプリケーション をビルドする環境を作ってみましょう。 ビルド スクリプト の作成 demo-project ディレクト リに web ディレクト リを作成します。次に、以下の内容で build.gradle.kts を作成します。 plugins { // 1. id("java") id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" } group = "com.example" // 2. version = "0.0.1-SNAPSHOT" // 3. repositories.mavenCentral() // 4. dependencies { // 5. implementation("org.springframework.boot:spring-boot-starter") } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) // 6. tasks.withType<JavaCompile>().configureEach { // 7. options.encoding = "UTF-8" } このビルド スクリプト について説明しておきましょう。 このプロジェクトで利用する プラグイン を列挙しています。 ウェブアプリケーション は Java を使って記述する想定なので java プラグイン を指定しています。 Spring Bootを使ったアプリケーションを作るための プラグイン が org.springframework.boot です。 io.spring.dependency-management は、Gradleでも Maven のように依存性を宣言するための プラグイン です。 ビルド成果物として出力される アーティファクト (JARファイル)のグループIDとなる部分の宣言です。基本的には会社の ドメイン 名を逆にしたものを使っておきましょう。 ビルド成果物のバージョン番号を記述しています。バージョン番号の管理についてはこの後、もう少し詳しく説明します。 依存ライブラリをダウンロードする先を宣言しています。ここでは Maven のデフォルト アーティファクト リポジトリ である Maven Central Repository を指定しています。 このプロジェクトが依存するライブラリを列挙しています。 io.spring.dependency-management によって機能性が拡張されているため、バージョン番号の指定がありません。 org.springframework.boot:spring-boot-starter がSpring Bootの本体です。 このビルド スクリプト で利用する コンパイラ やランタイムのバージョン番号を指定しています。 これは、Gradleを実行している Java ランタイムのバージョンが開発者ごとにズレていても、 コンパイル やテストに使う Java ランタイムは統一できるということです。ビルドの再現性が高まりますので必ず設定しましょう。 この機能を使うと、必要に応じてGradleがビルド済みの JDK を自動的にダウンロードしてくれます。 デフォルトでは Adoptium を使います。 このビルド スクリプト 内に定義されているタスクを型指定で検索しています。 ここでは、検索によって得られたJavaCompile型の インスタンス に文字 エンコーディング として UTF-8 を設定しています。 このビルド スクリプト がルートプロジェクトの一部であることを記述しておきましょう。 demo-project直下にある settings.gradle.kts に include("web") を追記します。 その結果以下のようになるでしょう。 /* * This file was generated by the Gradle 'init' task. * * The settings file is used to specify which projects to include in your build. * * Detailed information about configuring a multi-project build in Gradle can be found * in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html * This project uses @Incubating APIs which are subject to change. */ rootProject.name = "demo-project" include("web") // ルートプロジェクトに含めるために追記した部分 コメントアウト されている部分に意味はないので消してしまって構いません。 ビルド スクリプト が正しく記述できているか確認しておきましょう。以下のコマンドを実行します。 gradle tasks --group=application Application tasksとして bootRun が出力されていればビルド スクリプト は上手く構成されています。 サンプルアプリケーションの実装 ビルド スクリプト を配置したら ソースコード を格納するための ディレクト リを作成しましょう。以下のコマンドを実行します。 mkdir web/src/main/java/com/example/web 以下の内容で demo-project/web/src/main/java/com/example/web ディレクト リに Main.java を作成します。 package com.example.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } } 詳細に説明することはしませんが、これはSpring Bootアプリケーションを起動するコードです。 アプリケーションをビルドして アーティファクト を生成してみましょう。以下のコマンドを実行します。 gradle build ビルドが成功したら demo-project/web/build/libs ディレクト リの中に web-0.0.1-SNAPSHOT.jar が出力されます。 バージョニング ビルドプロセスを検討するにあたって、生成物に対してバージョン番号をどのように付与するのかは悩ましい課題です。 開発者のローカル環境でビルドしているなら、リリース作業をする前に build.gradle.kts にある version の値をエディタで書き換えてからビルドすればいいでしょう。作業が成功したら変更差分としてコミットしておけば問題ありません。 しかし、再現性のあるビルドを行うなら GitHub ActionsのようなCIサーバ上でビルドするのが望ましいでしょう。そうした時、 build.gradle.kts にある version の値を書き換えるのは少々やっかいです。 僕が好んで使っているやり方は、gitのタグをプッシュして、その名前をバージョン番号を決める際のパラメータとする方法です。 このやり方をサポートしてくれるGradleの プラグイン が me.qoomon.git-versioning です。 この プラグイン を使うとタグ名やブランチ名をパラメータにしてバージョン番号を簡単に決められます。具体例をお見せしましょう。 plugins { id("java") id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" id("me.qoomon.git-versioning") version "6.3.0" // 1. } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { // 2. refs { considerTagsOnBranches = true // 3. tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { // 4. version = "\${ref.version}" // 5. } } } repositories.mavenCentral() dependencies { implementation("org.springframework.boot:spring-boot-starter") } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } 先ほどのコードに対してバージョニングするための プラグイン を組み込んでいます。 me.qoomon.git-versioning がバージョニングするための プラグイン IDです。 バージョン番号を決める version 変数の直後に、gitVersioning.applyブロックを宣言しています。 これによって、バージョニング プラグイン が動作しなかった時は、最初に定義したバージョン番号が利用されます。 considerTagsOnBranches をtrueにすることで常にタグを考慮してバージョン番号を決定します。 ここで指定されている 正規表現 にタグがマッチしている場合にタグをバージョン番号として使います。 バージョン番号をマッチするための 正規表現 の中にある名前を使って具体的にバージョン番号として採用する範囲を決めています。 ref.version では専用の式言語を使って 正規表現 と一致した部分を取り出しています。 $ 記号の前に \ があるのは、Kotlinの文字列テンプレートとしては解釈されないようにするためです。 プラグイン が正しく構成されているか確認してみましょう。以下のコマンドを実行します。 gradle web:version この時点ではタグを打っていないので、version変数に代入されている 0.0.1-SNAPSHOT がバージョン番号として出力されます。 では、以下のコマンドを実行してタグを打った上で、同じようにバージョン番号を出力してみましょう。 git tag v0.1.2 gradle web:version バージョン番号として 0.1.2 が出力されましたね。 その他のバージョニング プラグイン タグプッシュを起点にビルドする以外の方式でバージョン番号を決めたい場合には、以下の プラグイン について検討するといいでしょう。 https://github.com/dipien/semantic-version-gradle-plugin https://github.com/nemerosa/versioning バッチプロジェクトの実装 次はバッチアプリケーションを作ってみましょう。 demo-project ディレクト リに batch ディレクト リを作成します。次に、以下の内容で build.gradle.kts を作成します。 plugins { id("application") // 1. id("me.qoomon.git-versioning") version "6.3.0" } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { // 2. refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } repositories.mavenCentral() dependencies { implementation("com.google.guava:guava:30.1.1-jre") } application { mainClass.set("com.example.batch.Main") // 3. } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } ウェブアプリケーション のものと似ていますが、このビルド スクリプト 固有の部分について説明しておきましょう。 application プラグイン は、 Java で CLI アプリケーションを実装するための定義を自動的に実施します。 ウェブアプリケーション と同じバージョニングポリシーを採用するので、 me.qoomon.git-versioning を導入します。 ここでは、 CLI アプリケーションとして起動する際に使うmainメソッドのあるクラスを指定しています。 demo-project直下にある settings.gradle.kts に include("batch") を追記します。 その結果以下のようになるでしょう。 rootProject.name = "demo-project" include("web") include("batch") // ルートプロジェクトに含めるために追記した部分 バッチアプリケーションの実装 ビルド スクリプト を配置したら ソースコード を格納するための ディレクト リを作成しましょう。以下のコマンドを実行します。 mkdir batch/src/main/java/com/example/batch 以下の内容で demo-project/batch/src/main/java/com/example/batch ディレクト リに Main.java を作成します。 package com.example.batch; import com.google.common.base.Strings; public class Main { public static void main(String[] args) { System.out.println(Strings.repeat("Hello, ", 2) + "World."); } } 詳細に説明することはしませんが、 CLI アプリケーションとして起動するコードです。 この後で説明するFat Jarの動作を確認できるようにGuavaのメソッドを使っています。 Fat/ Uber Jarの作り方 Java の実行バイナリの形式として、依存ライブラリを全て一つのJARファイルに含めてしまう形式の事を、Fat Jar や Uber Jarと呼びます。 ビルド生成物を単一のファイルにすると、その後の取り回しが単 純化 するのでデプロイプロセスの安定性が高まります。 Gradleでは Gradle Shadow プラグイン を使うと簡単にFat/ Uber Jarがつくれます。 既に書いたバッチアプリケーションのビルド スクリプト に、この プラグイン を導入してみましょう。 plugins { id("application") id("me.qoomon.git-versioning") version "6.3.0" id("com.github.johnrengelman.shadow") version "7.1.2" // プラグイン導入 } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } repositories.mavenCentral() dependencies { implementation("com.google.guava:guava:30.1.1-jre") } application { mainClass.set("com.example.batch.Main") } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } このビルド スクリプト では、既にapplication プラグイン が導入されているので、shadow プラグイン はそれに相乗りする形で動作します。つまり、 プラグイン の導入を宣言するだけでFat/ Uber Jarが生成されるようになるのです。 では、以下のコマンドでビルドしてみましょう。 gradle batch:build ビルドが正常終了したら、以下のコマンドを実行して生成された成果物を確認してみましょう。 ls batch/build/libs 大きく膨らんだ batch-0.0.1-SNAPSHOT-all.jar がFat/ Uber Jarです。今回は、この中にGuavaおよびその依存ライブラリが全て含まれています。 ビルドにおける共通処理の切り出し ここまで、 ウェブアプリケーション とバッチアプリケーションのビルド スクリプト を作ってきたわけですが、かなりの重複があることにお気づきでしょうか? オレンジ色の枠で囲った部分が二つのビルド スクリプト における重複部分です。 これらはビルド対象プロジェクトが増えるたびに重複していくでしょう。それは、望ましくありません。 ここからは、Gradleのローカル プラグイン を使ってビルド スクリプト の共通部分を切り出す方法について説明します。 共通部分を プラグイン 化することによって、それぞれのビルド スクリプト はより小さくて分かり易いものになります。また、プロジェクトにおけるバージョニングなどのポリシーを一か所に書くことで、ポリシーの変更があった際に抜け漏れなく対応できます。 ローカル プラグイン の作り方 それでは、ローカル プラグイン の作り方を説明しましょう。 demo-project ディレクト リに buildSrc ディレクト リを作成します。次に、以下の内容で build.gradle.kts を作成します。 plugins { `kotlin-dsl` // 1. } repositories.gradlePluginPortal() // 2. Gradleはルートプロジェクト直下にある buildSrc ディレクト リをローカル プラグイン が実装されているプロジェクトであるとみなします。少し奇妙だと感じるかもしれませんが、慣れてしまいましょう。 このプロジェクトは kotlin-dsl を使って プラグイン を記述します。 このプロジェクトは プラグイン プロジェクトになるので、依存ライブラリを Gradle - Plugins からダウンロードします。 ローカル プラグイン の実装 ビルド スクリプト を配置したら ソースコード を格納するための ディレクト リを作成しましょう。以下のコマンドを実行します。 mkdir buildSrc/src/main/kotlin 以下の内容で demo-project/buildSrc/src/main/kotlin ディレクト リに com.example.commons.gradle.kts を作成します。 plugins { id("java") } group = "com.example" version = "0.0.1-SNAPSHOT" repositories.mavenCentral() java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } ここでは、Gradleの標準機能だけを使って共 通化 しています。 これで、 com.example.commons を プラグイン IDとして参照できるローカル プラグイン の実装は終わりです。非常に簡単ですね。 ローカル プラグイン の使い方 では、ローカル プラグイン を使って ウェブアプリケーション のビルド スクリプト を簡略化してみましょう。 まずは、 ウェブアプリケーション のビルド スクリプト にローカル プラグイン を適用します。 plugins { id("com.example.commons") // ローカルプラグインの導入 id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" id("me.qoomon.git-versioning") version "6.3.0" } gitVersioning.apply { refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } dependencies { implementation("org.springframework.boot:spring-boot-starter") } 差分を確認してみましょう。 かなり小さくなりましたね。同じことをバッチアプリケーションのビルド スクリプト でもやっておきましょう。 ローカル プラグイン にGradle プラグイン を組み込む 次は、バージョニング プラグイン の適用もローカル プラグイン の中でやってみましょう。 まずは、誤った例をお見せします。 この状態でローカル プラグイン がビルドされると以下のようなエラーが出力されます。 Invalid plugin request [id: 'me.qoomon.git-versioning', version: '6.3.0']. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'me.qoomon.git-versioning' is an implementation dependency of project ':buildSrc'. これは、ローカル プラグイン の実装で プラグイン を指定する場合には、冒頭のpluginsブロックの中でバージョン番号を指定できないというエラーです。このローカル プラグイン からはバージョン番号を削除した上で、buildSrcプロジェクトにおける依存性として プラグイン を宣言するように指示されています。 具体的にどうするのかというと、buildSrc ディレクト リ内にあるbuild.gradle.ktsを以下のように修正します。 plugins { `kotlin-dsl` } repositories.gradlePluginPortal() dependencies { implementation("me.qoomon:gradle-git-versioning-plugin:6.3.0") // 依存性を追加 } ここで追加する依存性は アーティファクト ID( me.qoomon:gradle-git-versioning-plugin:6.3.0 )を指定します。 プラグイン ID( me.qoomon.git-versioning )ではありませんのでご注意ください。 次は、ローカル プラグイン 内にバージョニング プラグイン を適用します。 plugins { id("java") id("me.qoomon.git-versioning") // 1. } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { // 2. refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } repositories.mavenCentral() java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } 注意深く見てほしい2か所について説明します。 ここでは、バージョン番号を明記しない形で プラグイン の導入を宣言します。 プラグイン が導入されているので、それを使ったバージョン番号の採番処理を記述しています。内容は以前に説明したものと同じですね。 ウェブアプリケーション のビルド スクリプト からバージョン番号の採番を消せました。これで、さらに小さくて読みやすくなりましたね。 plugins { id("com.example.commons") id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" } dependencies { implementation("org.springframework.boot:spring-boot-starter") } プラグイン で変数や関数を定義する前に 最後は複数のプロジェクトで使いまわせる関数や変数をローカル プラグイン で定義してみましょう。 定義の前にGradleにおける プラグイン の動作と変数スコープについて少し説明させてください。 まず、Gradleのビルド スクリプト における一番外側のスコープ、つまり暗黙の インスタンス は Project です。 plugins や dependencies といったコードブロックは、 Project クラスの インスタンス に定義されたメソッドをレシーバを指定せずに呼び出しているのです。 そして、Gradleの プラグイン はProject インスタンス のメソッドを呼び出したり、動的にメンバを追加することで機能を実現しています。 つまり、複数のプロジェクトで使いまわせる関数や変数をローカル プラグイン で定義するというのは、動的なメンバをProject インスタンス に追加するという事になります。もちろん、 dependencies や tasks といったスコープに変数や関数を追加することも出来ます。 そして、Gradle プラグイン では動的にメンバを編集する仕組みをExtensionと呼んでいます。 Extensionの実装 それでは、Extensionを使ってプロジェクト固有の変数を追加してみましょう。 以下の内容で demo-project/buildSrc/src/main/kotlin ディレクト リに Extension.kt を作成します。 import org.gradle.api.Project val Project.env: String get() = if (System.getenv("ENV") != null) { System.getenv("ENV").toLowerCase() } else "dev" Extensionというファイル名はGradle プラグイン における 命名規則 によるものです。 プラグイン 名が自明な場合には、 プラグイン 名の サフィックス として Extension を指定するのが一般的なようです。 今回は明示的な プラグイン 名がないので、単に Extension としています。 ここでは、プロジェクトスコープに env という変数を追加してみました。 これは 環境変数 の ENV を読み取った上で中身があれば、その値を返します。それが無ければ dev を返すという処理になっています。 これをバッチアプリケーションのビルド スクリプト で使ってみましょう。 plugins { id("com.example.commons") id("application") id("com.github.johnrengelman.shadow") version "7.1.2" } dependencies { implementation("com.google.guava:guava:30.1.1-jre") } application { mainClass.set("com.example.batch.Main") } tasks { shadowJar { archiveClassifier.set("all-" + env) // ファイル名のサフィックスとしてenvの値を使う。 } } ここでは、shadow プラグイン によって出力される アーティファクト 名の一部として、envの値を使うように変更しました。 これによって、ファイル名の一部として 環境変数 であるENVの値を部分的に使うようになります。 この状態でビルドすると batch/build/libs/batch-0.0.1-SNAPSHOT-all-dev.jar というJARファイルが出力されるのを確認できます。 環境変数 ENVの中身を prod に変えてビルドしてください。ビルド結果として batch/build/libs/batch-0.0.1-SNAPSHOT-all-prod.jar というJARファイルが出力されるのを確認できます。 まとめ このエントリでは、Kotlinを使ってGradleのビルド スクリプト を書く際に知っていると便利なTipsを紹介してきました。ここで紹介したGradleの標準機能や、いくつかの プラグイン は私がビルド スクリプト を書く際には必ず利用しているものです。 また、ローカル プラグイン に プラグイン を追加する際のエラーについては 検索エンジン にインデックスされるよう少し冗長に書いています。冗長に書いた理由は、同じエラーに遭遇した誰かに届いてほしいからです。私自身は、このエラーを上手く解決できなくてかなり苦しみましたので、そういう方が一人でも減ってほしいという気持ちがあります。 この長文を最後まで読んでいただき本当にありがとうございます。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @higa ( Shodo で執筆されました )
みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 本日は最新のGradle(2022/08現在)を使いこなしながらKotlinで Java のアプリケーションをビルドする スクリプト を書く際に、知っておくと便利なノウハウをまとめてご紹介します。 はじめに 記事の執筆環境 scoopのセットアップ Javaのセットアップ Gradleのセットアップ サンプルアプリケーションについて ルートプロジェクトの実装 ウェブアプリケーションプロジェクトの実装 ビルドスクリプトの作成 サンプルアプリケーションの実装 バージョニング その他のバージョニングプラグイン バッチプロジェクトの実装 バッチアプリケーションの実装 Fat/Uber Jarの作り方 ビルドにおける共通処理の切り出し ローカルプラグインの作り方 ローカルプラグインの実装 ローカルプラグインの使い方 ローカルプラグインにGradleプラグインを組み込む プラグインで変数や関数を定義する前に Extensionの実装 まとめ はじめに この記事では、 ソースコード をユーザーが利用できる環境にデプロイできるアプリケーションに変換する工程をビルドプロセスと呼ぶこととします。 Java で実装されたアプリケーションをビルドするためのツールとしては、古くはmakeから始まり、Antを経由して現代では Maven やGradleといったツールが広く利用されています。 この記事では、自分がコン トロール できる範囲では宣言的に記述でき、必要に応じて アドホック なコードを書いていけるGradleを使ったビルドプロセスにおけるノウハウを紹介します。 記事の執筆環境 この記事が前提とする環境について軽く説明しておきましょう。 まず、OSはWindows10 Proで、バージョンは21H2です。 Windows 環境でアプリケーションをインストールするために、パッケージマネージャーとして scoop を使っています。 そして、 Java 17を使います。 記事の主題はGradleの使い方ですので、 Windows 以外のOSでも有効なものです。 Windows 以外の環境をお使いなら次に続く環境のセットアップ手順については読み飛ばしてください。 scoopのセットアップ scoopは powershell だけで様々なツールをインストールできるパッケージマネージャーの一種です。 この記事で紹介するツールはscoopを使ってインストールしますので、もし未導入なら導入をお願いします。 まずは、 PowerShell の実行ポリシーを変えます。 scoopのインストールのためには、インターネット上にあるサーバから シェルスクリプト をダウンロードしてきて実行できるようにします。cf. about_Execution_Policies PowerShell を起動して以下のコマンドを実行します。 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser 次にインストール スクリプト をダウンロードしつつ実行します。 irm get.scoop.sh | iex これでscoopのインストールは終わりです。 Java のセットアップ scoopで Java をインストールする前に バケット を追加します。 scoop bucket add java この バケット には様々なOpenJDKのビルドが入っているのですけども、ここではopenjdk17を使います。 scoop install openjdk17 openjdkを指定すると、記事執筆時点では Java 18がインストールされましたのでメジャーバージョンを明示的に指定しています。 Gradleのセットアップ 次はGradleをインストールしましょう。 scoop instlal gradle Gradleのインストールが完了したらセットアップは終了です。 サンプルアプリケーションについて この記事では ユーザーインターフェース のある ウェブアプリケーション と、 ユーザーインターフェース のないバッチアプリケーションが、単一のgit リポジトリ 内に共存しているものを想定しています。 典型的な構造ですが、様々なやり方でビルド スクリプト を記述できます。この記事では私が実施しているプロジェクトにおける構成において得られた知見をもとに説明します。 ルートプロジェクトの実装 まずは、プロジェクト全体を格納するための ディレクト リを作成しましょう。 ここからは、この記事内でシェルコマンドを実行するよう説明している部分では、必ずこのルート ディレクト リで実行してください。 作るプロジェクトは、説明のために demo-project とします。作ったdemo-project ディレクト リの中で、以下のコマンドを実行して最小限のプロジェクトを作成します。 gradle init --type basic --dsl kotlin --project-name demo-project --incubating 最小限とはいえ、Gradle Wrapperとなる シェルスクリプト やgit用の設定ファイルが生成されていますね。 その中から、不要な build.gradle.kts を削除してください。 ウェブアプリケーション プロジェクトの実装 次はSpring Bootを使った ウェブアプリケーション をビルドする環境を作ってみましょう。 ビルド スクリプト の作成 demo-project ディレクト リに web ディレクト リを作成します。次に、以下の内容で build.gradle.kts を作成します。 plugins { // 1. id("java") id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" } group = "com.example" // 2. version = "0.0.1-SNAPSHOT" // 3. repositories.mavenCentral() // 4. dependencies { // 5. implementation("org.springframework.boot:spring-boot-starter") } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) // 6. tasks.withType<JavaCompile>().configureEach { // 7. options.encoding = "UTF-8" } このビルド スクリプト について説明しておきましょう。 このプロジェクトで利用する プラグイン を列挙しています。 ウェブアプリケーション は Java を使って記述する想定なので java プラグイン を指定しています。 Spring Bootを使ったアプリケーションを作るための プラグイン が org.springframework.boot です。 io.spring.dependency-management は、Gradleでも Maven のように依存性を宣言するための プラグイン です。 ビルド成果物として出力される アーティファクト (JARファイル)のグループIDとなる部分の宣言です。基本的には会社の ドメイン 名を逆にしたものを使っておきましょう。 ビルド成果物のバージョン番号を記述しています。バージョン番号の管理についてはこの後、もう少し詳しく説明します。 依存ライブラリをダウンロードする先を宣言しています。ここでは Maven のデフォルト アーティファクト リポジトリ である Maven Central Repository を指定しています。 このプロジェクトが依存するライブラリを列挙しています。 io.spring.dependency-management によって機能性が拡張されているため、バージョン番号の指定がありません。 org.springframework.boot:spring-boot-starter がSpring Bootの本体です。 このビルド スクリプト で利用する コンパイラ やランタイムのバージョン番号を指定しています。 これは、Gradleを実行している Java ランタイムのバージョンが開発者ごとにズレていても、 コンパイル やテストに使う Java ランタイムは統一できるということです。ビルドの再現性が高まりますので必ず設定しましょう。 この機能を使うと、必要に応じてGradleがビルド済みの JDK を自動的にダウンロードしてくれます。 デフォルトでは Adoptium を使います。 このビルド スクリプト 内に定義されているタスクを型指定で検索しています。 ここでは、検索によって得られたJavaCompile型の インスタンス に文字 エンコーディング として UTF-8 を設定しています。 このビルド スクリプト がルートプロジェクトの一部であることを記述しておきましょう。 demo-project直下にある settings.gradle.kts に include("web") を追記します。 その結果以下のようになるでしょう。 /* * This file was generated by the Gradle 'init' task. * * The settings file is used to specify which projects to include in your build. * * Detailed information about configuring a multi-project build in Gradle can be found * in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html * This project uses @Incubating APIs which are subject to change. */ rootProject.name = "demo-project" include("web") // ルートプロジェクトに含めるために追記した部分 コメントアウト されている部分に意味はないので消してしまって構いません。 ビルド スクリプト が正しく記述できているか確認しておきましょう。以下のコマンドを実行します。 gradle tasks --group=application Application tasksとして bootRun が出力されていればビルド スクリプト は上手く構成されています。 サンプルアプリケーションの実装 ビルド スクリプト を配置したら ソースコード を格納するための ディレクト リを作成しましょう。以下のコマンドを実行します。 mkdir web/src/main/java/com/example/web 以下の内容で demo-project/web/src/main/java/com/example/web ディレクト リに Main.java を作成します。 package com.example.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } } 詳細に説明することはしませんが、これはSpring Bootアプリケーションを起動するコードです。 アプリケーションをビルドして アーティファクト を生成してみましょう。以下のコマンドを実行します。 gradle build ビルドが成功したら demo-project/web/build/libs ディレクト リの中に web-0.0.1-SNAPSHOT.jar が出力されます。 バージョニング ビルドプロセスを検討するにあたって、生成物に対してバージョン番号をどのように付与するのかは悩ましい課題です。 開発者のローカル環境でビルドしているなら、リリース作業をする前に build.gradle.kts にある version の値をエディタで書き換えてからビルドすればいいでしょう。作業が成功したら変更差分としてコミットしておけば問題ありません。 しかし、再現性のあるビルドを行うなら GitHub ActionsのようなCIサーバ上でビルドするのが望ましいでしょう。そうした時、 build.gradle.kts にある version の値を書き換えるのは少々やっかいです。 僕が好んで使っているやり方は、gitのタグをプッシュして、その名前をバージョン番号を決める際のパラメータとする方法です。 このやり方をサポートしてくれるGradleの プラグイン が me.qoomon.git-versioning です。 この プラグイン を使うとタグ名やブランチ名をパラメータにしてバージョン番号を簡単に決められます。具体例をお見せしましょう。 plugins { id("java") id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" id("me.qoomon.git-versioning") version "6.3.0" // 1. } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { // 2. refs { considerTagsOnBranches = true // 3. tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { // 4. version = "\${ref.version}" // 5. } } } repositories.mavenCentral() dependencies { implementation("org.springframework.boot:spring-boot-starter") } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } 先ほどのコードに対してバージョニングするための プラグイン を組み込んでいます。 me.qoomon.git-versioning がバージョニングするための プラグイン IDです。 バージョン番号を決める version 変数の直後に、gitVersioning.applyブロックを宣言しています。 これによって、バージョニング プラグイン が動作しなかった時は、最初に定義したバージョン番号が利用されます。 considerTagsOnBranches をtrueにすることで常にタグを考慮してバージョン番号を決定します。 ここで指定されている 正規表現 にタグがマッチしている場合にタグをバージョン番号として使います。 バージョン番号をマッチするための 正規表現 の中にある名前を使って具体的にバージョン番号として採用する範囲を決めています。 ref.version では専用の式言語を使って 正規表現 と一致した部分を取り出しています。 $ 記号の前に \ があるのは、Kotlinの文字列テンプレートとしては解釈されないようにするためです。 プラグイン が正しく構成されているか確認してみましょう。以下のコマンドを実行します。 gradle web:version この時点ではタグを打っていないので、version変数に代入されている 0.0.1-SNAPSHOT がバージョン番号として出力されます。 では、以下のコマンドを実行してタグを打った上で、同じようにバージョン番号を出力してみましょう。 git tag v0.1.2 gradle web:version バージョン番号として 0.1.2 が出力されましたね。 その他のバージョニング プラグイン タグプッシュを起点にビルドする以外の方式でバージョン番号を決めたい場合には、以下の プラグイン について検討するといいでしょう。 https://github.com/dipien/semantic-version-gradle-plugin https://github.com/nemerosa/versioning バッチプロジェクトの実装 次はバッチアプリケーションを作ってみましょう。 demo-project ディレクト リに batch ディレクト リを作成します。次に、以下の内容で build.gradle.kts を作成します。 plugins { id("application") // 1. id("me.qoomon.git-versioning") version "6.3.0" } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { // 2. refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } repositories.mavenCentral() dependencies { implementation("com.google.guava:guava:30.1.1-jre") } application { mainClass.set("com.example.batch.Main") // 3. } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } ウェブアプリケーション のものと似ていますが、このビルド スクリプト 固有の部分について説明しておきましょう。 application プラグイン は、 Java で CLI アプリケーションを実装するための定義を自動的に実施します。 ウェブアプリケーション と同じバージョニングポリシーを採用するので、 me.qoomon.git-versioning を導入します。 ここでは、 CLI アプリケーションとして起動する際に使うmainメソッドのあるクラスを指定しています。 demo-project直下にある settings.gradle.kts に include("batch") を追記します。 その結果以下のようになるでしょう。 rootProject.name = "demo-project" include("web") include("batch") // ルートプロジェクトに含めるために追記した部分 バッチアプリケーションの実装 ビルド スクリプト を配置したら ソースコード を格納するための ディレクト リを作成しましょう。以下のコマンドを実行します。 mkdir batch/src/main/java/com/example/batch 以下の内容で demo-project/batch/src/main/java/com/example/batch ディレクト リに Main.java を作成します。 package com.example.batch; import com.google.common.base.Strings; public class Main { public static void main(String[] args) { System.out.println(Strings.repeat("Hello, ", 2) + "World."); } } 詳細に説明することはしませんが、 CLI アプリケーションとして起動するコードです。 この後で説明するFat Jarの動作を確認できるようにGuavaのメソッドを使っています。 Fat/ Uber Jarの作り方 Java の実行バイナリの形式として、依存ライブラリを全て一つのJARファイルに含めてしまう形式の事を、Fat Jar や Uber Jarと呼びます。 ビルド生成物を単一のファイルにすると、その後の取り回しが単 純化 するのでデプロイプロセスの安定性が高まります。 Gradleでは Gradle Shadow プラグイン を使うと簡単にFat/ Uber Jarがつくれます。 既に書いたバッチアプリケーションのビルド スクリプト に、この プラグイン を導入してみましょう。 plugins { id("application") id("me.qoomon.git-versioning") version "6.3.0" id("com.github.johnrengelman.shadow") version "7.1.2" // プラグイン導入 } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } repositories.mavenCentral() dependencies { implementation("com.google.guava:guava:30.1.1-jre") } application { mainClass.set("com.example.batch.Main") } java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } このビルド スクリプト では、既にapplication プラグイン が導入されているので、shadow プラグイン はそれに相乗りする形で動作します。つまり、 プラグイン の導入を宣言するだけでFat/ Uber Jarが生成されるようになるのです。 では、以下のコマンドでビルドしてみましょう。 gradle batch:build ビルドが正常終了したら、以下のコマンドを実行して生成された成果物を確認してみましょう。 ls batch/build/libs 大きく膨らんだ batch-0.0.1-SNAPSHOT-all.jar がFat/ Uber Jarです。今回は、この中にGuavaおよびその依存ライブラリが全て含まれています。 ビルドにおける共通処理の切り出し ここまで、 ウェブアプリケーション とバッチアプリケーションのビルド スクリプト を作ってきたわけですが、かなりの重複があることにお気づきでしょうか? オレンジ色の枠で囲った部分が二つのビルド スクリプト における重複部分です。 これらはビルド対象プロジェクトが増えるたびに重複していくでしょう。それは、望ましくありません。 ここからは、Gradleのローカル プラグイン を使ってビルド スクリプト の共通部分を切り出す方法について説明します。 共通部分を プラグイン 化することによって、それぞれのビルド スクリプト はより小さくて分かり易いものになります。また、プロジェクトにおけるバージョニングなどのポリシーを一か所に書くことで、ポリシーの変更があった際に抜け漏れなく対応できます。 ローカル プラグイン の作り方 それでは、ローカル プラグイン の作り方を説明しましょう。 demo-project ディレクト リに buildSrc ディレクト リを作成します。次に、以下の内容で build.gradle.kts を作成します。 plugins { `kotlin-dsl` // 1. } repositories.gradlePluginPortal() // 2. Gradleはルートプロジェクト直下にある buildSrc ディレクト リをローカル プラグイン が実装されているプロジェクトであるとみなします。少し奇妙だと感じるかもしれませんが、慣れてしまいましょう。 このプロジェクトは kotlin-dsl を使って プラグイン を記述します。 このプロジェクトは プラグイン プロジェクトになるので、依存ライブラリを Gradle - Plugins からダウンロードします。 ローカル プラグイン の実装 ビルド スクリプト を配置したら ソースコード を格納するための ディレクト リを作成しましょう。以下のコマンドを実行します。 mkdir buildSrc/src/main/kotlin 以下の内容で demo-project/buildSrc/src/main/kotlin ディレクト リに com.example.commons.gradle.kts を作成します。 plugins { id("java") } group = "com.example" version = "0.0.1-SNAPSHOT" repositories.mavenCentral() java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } ここでは、Gradleの標準機能だけを使って共 通化 しています。 これで、 com.example.commons を プラグイン IDとして参照できるローカル プラグイン の実装は終わりです。非常に簡単ですね。 ローカル プラグイン の使い方 では、ローカル プラグイン を使って ウェブアプリケーション のビルド スクリプト を簡略化してみましょう。 まずは、 ウェブアプリケーション のビルド スクリプト にローカル プラグイン を適用します。 plugins { id("com.example.commons") // ローカルプラグインの導入 id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" id("me.qoomon.git-versioning") version "6.3.0" } gitVersioning.apply { refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } dependencies { implementation("org.springframework.boot:spring-boot-starter") } 差分を確認してみましょう。 かなり小さくなりましたね。同じことをバッチアプリケーションのビルド スクリプト でもやっておきましょう。 ローカル プラグイン にGradle プラグイン を組み込む 次は、バージョニング プラグイン の適用もローカル プラグイン の中でやってみましょう。 まずは、誤った例をお見せします。 この状態でローカル プラグイン がビルドされると以下のようなエラーが出力されます。 Invalid plugin request [id: 'me.qoomon.git-versioning', version: '6.3.0']. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'me.qoomon.git-versioning' is an implementation dependency of project ':buildSrc'. これは、ローカル プラグイン の実装で プラグイン を指定する場合には、冒頭のpluginsブロックの中でバージョン番号を指定できないというエラーです。このローカル プラグイン からはバージョン番号を削除した上で、buildSrcプロジェクトにおける依存性として プラグイン を宣言するように指示されています。 具体的にどうするのかというと、buildSrc ディレクト リ内にあるbuild.gradle.ktsを以下のように修正します。 plugins { `kotlin-dsl` } repositories.gradlePluginPortal() dependencies { implementation("me.qoomon:gradle-git-versioning-plugin:6.3.0") // 依存性を追加 } ここで追加する依存性は アーティファクト ID( me.qoomon:gradle-git-versioning-plugin:6.3.0 )を指定します。 プラグイン ID( me.qoomon.git-versioning )ではありませんのでご注意ください。 次は、ローカル プラグイン 内にバージョニング プラグイン を適用します。 plugins { id("java") id("me.qoomon.git-versioning") // 1. } group = "com.example" version = "0.0.1-SNAPSHOT" gitVersioning.apply { // 2. refs { considerTagsOnBranches = true tag("v(?<version>\\d+[.]\\d+[.]\\d+)") { version = "\${ref.version}" } } } repositories.mavenCentral() java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" } 注意深く見てほしい2か所について説明します。 ここでは、バージョン番号を明記しない形で プラグイン の導入を宣言します。 プラグイン が導入されているので、それを使ったバージョン番号の採番処理を記述しています。内容は以前に説明したものと同じですね。 ウェブアプリケーション のビルド スクリプト からバージョン番号の採番を消せました。これで、さらに小さくて読みやすくなりましたね。 plugins { id("com.example.commons") id("org.springframework.boot") version "2.7.2" id("io.spring.dependency-management") version "1.0.13.RELEASE" } dependencies { implementation("org.springframework.boot:spring-boot-starter") } プラグイン で変数や関数を定義する前に 最後は複数のプロジェクトで使いまわせる関数や変数をローカル プラグイン で定義してみましょう。 定義の前にGradleにおける プラグイン の動作と変数スコープについて少し説明させてください。 まず、Gradleのビルド スクリプト における一番外側のスコープ、つまり暗黙の インスタンス は Project です。 plugins や dependencies といったコードブロックは、 Project クラスの インスタンス に定義されたメソッドをレシーバを指定せずに呼び出しているのです。 そして、Gradleの プラグイン はProject インスタンス のメソッドを呼び出したり、動的にメンバを追加することで機能を実現しています。 つまり、複数のプロジェクトで使いまわせる関数や変数をローカル プラグイン で定義するというのは、動的なメンバをProject インスタンス に追加するという事になります。もちろん、 dependencies や tasks といったスコープに変数や関数を追加することも出来ます。 そして、Gradle プラグイン では動的にメンバを編集する仕組みをExtensionと呼んでいます。 Extensionの実装 それでは、Extensionを使ってプロジェクト固有の変数を追加してみましょう。 以下の内容で demo-project/buildSrc/src/main/kotlin ディレクト リに Extension.kt を作成します。 import org.gradle.api.Project val Project.env: String get() = if (System.getenv("ENV") != null) { System.getenv("ENV").toLowerCase() } else "dev" Extensionというファイル名はGradle プラグイン における 命名規則 によるものです。 プラグイン 名が自明な場合には、 プラグイン 名の サフィックス として Extension を指定するのが一般的なようです。 今回は明示的な プラグイン 名がないので、単に Extension としています。 ここでは、プロジェクトスコープに env という変数を追加してみました。 これは 環境変数 の ENV を読み取った上で中身があれば、その値を返します。それが無ければ dev を返すという処理になっています。 これをバッチアプリケーションのビルド スクリプト で使ってみましょう。 plugins { id("com.example.commons") id("application") id("com.github.johnrengelman.shadow") version "7.1.2" } dependencies { implementation("com.google.guava:guava:30.1.1-jre") } application { mainClass.set("com.example.batch.Main") } tasks { shadowJar { archiveClassifier.set("all-" + env) // ファイル名のサフィックスとしてenvの値を使う。 } } ここでは、shadow プラグイン によって出力される アーティファクト 名の一部として、envの値を使うように変更しました。 これによって、ファイル名の一部として 環境変数 であるENVの値を部分的に使うようになります。 この状態でビルドすると batch/build/libs/batch-0.0.1-SNAPSHOT-all-dev.jar というJARファイルが出力されるのを確認できます。 環境変数 ENVの中身を prod に変えてビルドしてください。ビルド結果として batch/build/libs/batch-0.0.1-SNAPSHOT-all-prod.jar というJARファイルが出力されるのを確認できます。 まとめ このエントリでは、Kotlinを使ってGradleのビルド スクリプト を書く際に知っていると便利なTipsを紹介してきました。ここで紹介したGradleの標準機能や、いくつかの プラグイン は私がビルド スクリプト を書く際には必ず利用しているものです。 また、ローカル プラグイン に プラグイン を追加する際のエラーについては 検索エンジン にインデックスされるよう少し冗長に書いています。冗長に書いた理由は、同じエラーに遭遇した誰かに届いてほしいからです。私自身は、このエラーを上手く解決できなくてかなり苦しみましたので、そういう方が一人でも減ってほしいという気持ちがあります。 この長文を最後まで読んでいただき本当にありがとうございます。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @higa ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマはレンズです。 Stable DiffusionはAIカメラなので、どのようなレンズを使うかどうかはとても重要です。カメラの知識が必要ですが、基礎から丁寧に説明するので大丈夫です。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 レンズの属性 焦点距離 F値 SIGMA SIGMA 85 mm F/1.4 SIGMA 24 mm F/1.4 SIGMA 300 mm F/2.8 SIGMA 105 mm F/5.6 シャッタースピード 1/1000 sec shutter ocean, 5 sec shutter cars with lights, blue hour, 1/10 sec speed panning shot, 1/20 sec shutter まとめ 仲間募集 Stable Diffusionの過去コンテンツ レンズの属性 レンズは、 名前 焦点距離 F値 のような属性を持っています。例えば、 SIGMA 85 mm F/1.4 の場合、名前は SIGMA 、 焦点距離 は85 mm、 F値 は1.4になります。 SIGMA は、レンズ名というより、メーカー名です。レンズ名だと SIGMA Art Lensのようになるのですが、レンズ名まで指定しても影響はあまりないようです。 焦点距離 焦点距離 とは、レンズの中心から イメージセンサー までの距離です。 イメージセンサー とは、アナログフィルムのデジタル版だと思えば大体あっています。 焦点距離 が短くなるほど、広い範囲を写せます。窓に近づくほど、広い範囲の外の景色が見えるようになるのと一緒です。 焦点距離 が長くなるほど、見える範囲は狭くなりますが、遠くのものを大きく写せます。 この講座では、次の4つの 焦点距離 を使います。 24 mm: 風景を広範囲に撮ったり、寄って撮影するときに使う。 85 mm: 基本はこれ。 105 mm: マクロ撮影(小さいものを拡大して撮影)するときに使う。 300 mm: 遠くにあるものを撮るときに使う。 Promptで使う場合、数字とmmの間は、ブランクを開けてください。ブランクがなくても動きますが、ブランクがあったほうが再現性が高くなります。 F値 F値 (の最小値) = 焦点距離 ÷ レンズの口径です。 焦点距離 が同じ場合、レンズの口径が大きくなるほど、 F値 は小さくなります。レンズの口径が大きくなるほど、取り込める光の量が増えます。 F値 の小さいレンズを明るいレンズといいます。 大抵のレンズには、絞りがついていて、取り込む光の量を調節できます。絞るとは、取り込む光の量を減らすことです。レンズの口径を小さくするのに似ています。つまり、 F値 は大きくなります。 F値 が小さくなるほど、ボケは大きくなります。ボケとは、ピントの合っている部分以外がボケて見えることです。 犬の背景がボケていますね。 この講座では、次の3つの F値 を使います。 1.4: 一番良く使う F値 。 2.8: 焦点距離 300 mmのときに使う。 5.6: マクロ撮影(小さいものを拡大して撮影)のときに使う。 Promptで使う場合、F/数字にしましょう。F数字でも大丈夫です。 SIGMA いろいろ試した中で、 SIGMA のレンズが最も再現性が高く感じたので、このレンズを使って説明します。お気に入りのレンズがあれば SIGMA の部分を置き換えてください。 SIGMA 85 mm F/1.4 今回の講座の標準レンズ。 Promptはこちら。 a photo of a dog in a park, SIGMA 85 mm F/1.4, golden hour, award-winning golden hourは夜明け直後か日暮れ直前の、太陽の光が柔らかく赤みを帯びている時間帯のことです。外で撮る写真は、golden hourに撮るといい感じに見えます。 award-winningをつけるとなんとなく、見栄えのより写真になる気がします。 出力された画像はこちら。 SIGMA 24 mm F/1.4 広角(視野範囲が広い)で風景などを撮るか、対象に少し寄って撮るレンズ。今回はちょっと対象に寄って撮ります。 Promptはこちら。 a photo of a dog in a park, SIGMA 24 mm F/1.4, golden hour, award-winning 85を24に変えただけです。 出力された画像はこちら。 24 mmは、広角レンズに分類されますが、広角レンズ特有の歪みも少なく、使いやすいレンズです。 SIGMA 300 mm F/2.8 遠くにあるものを拡大して撮影する望遠レンズです。 今回は、空を飛んでいる鳥を撮影してみましょう。 Promptはこちら。 a photo of a bird flying in the sky, highly detailed, SIGMA 300 mm F/2.8, golden hour, award-winning なにかを大きく撮影したい場合は、highly detailedを指定します。 出力された画像はこちら。 SIGMA 105 mm F/5.6 小さいものを撮影する マクロレンズ です。 蝶を撮影してみましょう。 Promptはこちら。 a photo of a beautiful butterfly on a flower, highly detailed, SIGMA 105 mm F/5.6, award-winning 出力された画像はこちら。 4つのレンズを試しました。状況によってどのレンズを使えばいいか、なんとなくでもつかめましたか? シャッタースピード シャッタースピード を調整することで、写真を生き生きとしてモノにできます。 シャッタースピード を速くするには、短時間で撮影に必要な一 定量 の光を集めることが必要です。 光を短時間で集めるには、明るいレンズ( F値 の低いレンズ)で絞りを開ける、 ISO感度 を上げるという方法があります。 ISO感度 とは、光を電子的に増幅させる性能です。 ISO感度 を2倍にすると シャッタースピード を二倍速くできます。 ISO感度 の基本は100になります。 ISO感度 を上げるとノイズも増幅されてしまうので、単に上げればよいというものではありません。 1/1000 sec shutter 瞬間を切り取りたいときは、1/1000 sec shutterで シャッタースピード を1/1000秒にするのが良いでしょう。今回は、F/1.4の明るいレンズを使います。 ISO感度 は、時間帯など状況に合わせて調整してください。 波しぶきを撮影してみましょう。 今回のPromptはこちら。 a photo of beautiful ocean spray, golden hour, SIGMA 85 mm F/1.4, 1/1000 sec shutter, ISO 400, award-winning 時間帯をgolden hourにしたので、 ISO感度 を400にしました。 出力された画像はこちら。 ocean, 5 sec shutter 海など、動きがゆっくりのものを シャッタースピード を遅くして撮影すると、幻想的な写真が取れることがあります。 今回のPromptはこちら。 a photo of beautiful ocean, golden hour, SIGMA 24 mm F/1.4, 5 sec shutter, award-winning 出力された画像はこちら。 cars with lights, blue hour, 1/10 sec speed 暗くなった時間帯に、ライトを付けた車を シャッタースピード を落として撮影すると、美しい光の残像を撮ることができます。 今回のPromptはこちら。 a photo of cars with lights speeding on highway, blue hour, SIGMA 85 mm F/1.4, 1/10 sec shutter, award-winning blue hourは、日の出前と日の入り後に発生する空が濃い青色に染まる時間帯のことです。 出力された画像はこちら。 panning shot, 1/20 sec shutter パン撮影は、動く被写体と一緒にカメラを動かし、被写体にピントを合わせて背景をブレさせる手法です。panning shotのキーワードを使いましょう。 シャッタースピード は少しだけ落とします。この撮影法は、再現性が低いので、うまくいくまで何度も試してください。 今回のPromptはこちら。 a panning shot of a car, SIGMA 300 mm F/2.8, 1/20 sec shutter 望遠レンズを使ったほうが再現性は高くなります。panning shotというキーワードを見つけるのに、100回以上、 トライアンドエラー を繰り返しました。motion blur というキーワードもあるのですが、ほとんどの場合、被写体もブレてしまいます。 背景だけブレさせるというのが難しいのです。 出力された画像はこちら。 かなり疾走感のある画像になっていますね。 まとめ 今回は、レンズといろいろな撮影テクニックについて解説しました。 次は、 画像タイプ について解説します。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマはレンズです。 Stable DiffusionはAIカメラなので、どのようなレンズを使うかどうかはとても重要です。カメラの知識が必要ですが、基礎から丁寧に説明するので大丈夫です。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 レンズの属性 焦点距離 F値 SIGMA SIGMA 85 mm F/1.4 SIGMA 24 mm F/1.4 SIGMA 300 mm F/2.8 SIGMA 105 mm F/5.6 シャッタースピード 1/1000 sec shutter ocean, 5 sec shutter cars with lights, blue hour, 1/10 sec speed panning shot, 1/20 sec shutter まとめ 仲間募集 Stable Diffusionの過去コンテンツ レンズの属性 レンズは、 名前 焦点距離 F値 のような属性を持っています。例えば、 SIGMA 85 mm F/1.4 の場合、名前は SIGMA 、 焦点距離 は85 mm、 F値 は1.4になります。 SIGMA は、レンズ名というより、メーカー名です。レンズ名だと SIGMA Art Lensのようになるのですが、レンズ名まで指定しても影響はあまりないようです。 焦点距離 焦点距離 とは、レンズの中心から イメージセンサー までの距離です。 イメージセンサー とは、アナログフィルムのデジタル版だと思えば大体あっています。 焦点距離 が短くなるほど、広い範囲を写せます。窓に近づくほど、広い範囲の外の景色が見えるようになるのと一緒です。 焦点距離 が長くなるほど、見える範囲は狭くなりますが、遠くのものを大きく写せます。 この講座では、次の4つの 焦点距離 を使います。 24 mm: 風景を広範囲に撮ったり、寄って撮影するときに使う。 85 mm: 基本はこれ。 105 mm: マクロ撮影(小さいものを拡大して撮影)するときに使う。 300 mm: 遠くにあるものを撮るときに使う。 Promptで使う場合、数字とmmの間は、ブランクを開けてください。ブランクがなくても動きますが、ブランクがあったほうが再現性が高くなります。 F値 F値 (の最小値) = 焦点距離 ÷ レンズの口径です。 焦点距離 が同じ場合、レンズの口径が大きくなるほど、 F値 は小さくなります。レンズの口径が大きくなるほど、取り込める光の量が増えます。 F値 の小さいレンズを明るいレンズといいます。 大抵のレンズには、絞りがついていて、取り込む光の量を調節できます。絞るとは、取り込む光の量を減らすことです。レンズの口径を小さくするのに似ています。つまり、 F値 は大きくなります。 F値 が小さくなるほど、ボケは大きくなります。ボケとは、ピントの合っている部分以外がボケて見えることです。 犬の背景がボケていますね。 この講座では、次の3つの F値 を使います。 1.4: 一番良く使う F値 。 2.8: 焦点距離 300 mmのときに使う。 5.6: マクロ撮影(小さいものを拡大して撮影)のときに使う。 Promptで使う場合、F/数字にしましょう。F数字でも大丈夫です。 SIGMA いろいろ試した中で、 SIGMA のレンズが最も再現性が高く感じたので、このレンズを使って説明します。お気に入りのレンズがあれば SIGMA の部分を置き換えてください。 SIGMA 85 mm F/1.4 今回の講座の標準レンズ。 Promptはこちら。 a photo of a dog in a park, SIGMA 85 mm F/1.4, golden hour, award-winning golden hourは夜明け直後か日暮れ直前の、太陽の光が柔らかく赤みを帯びている時間帯のことです。外で撮る写真は、golden hourに撮るといい感じに見えます。 award-winningをつけるとなんとなく、見栄えのより写真になる気がします。 出力された画像はこちら。 SIGMA 24 mm F/1.4 広角(視野範囲が広い)で風景などを撮るか、対象に少し寄って撮るレンズ。今回はちょっと対象に寄って撮ります。 Promptはこちら。 a photo of a dog in a park, SIGMA 24 mm F/1.4, golden hour, award-winning 85を24に変えただけです。 出力された画像はこちら。 24 mmは、広角レンズに分類されますが、広角レンズ特有の歪みも少なく、使いやすいレンズです。 SIGMA 300 mm F/2.8 遠くにあるものを拡大して撮影する望遠レンズです。 今回は、空を飛んでいる鳥を撮影してみましょう。 Promptはこちら。 a photo of a bird flying in the sky, highly detailed, SIGMA 300 mm F/2.8, golden hour, award-winning なにかを大きく撮影したい場合は、highly detailedを指定します。 出力された画像はこちら。 SIGMA 105 mm F/5.6 小さいものを撮影する マクロレンズ です。 蝶を撮影してみましょう。 Promptはこちら。 a photo of a beautiful butterfly on a flower, highly detailed, SIGMA 105 mm F/5.6, award-winning 出力された画像はこちら。 4つのレンズを試しました。状況によってどのレンズを使えばいいか、なんとなくでもつかめましたか? シャッタースピード シャッタースピード を調整することで、写真を生き生きとしてモノにできます。 シャッタースピード を速くするには、短時間で撮影に必要な一 定量 の光を集めることが必要です。 光を短時間で集めるには、明るいレンズ( F値 の低いレンズ)で絞りを開ける、 ISO感度 を上げるという方法があります。 ISO感度 とは、光を電子的に増幅させる性能です。 ISO感度 を2倍にすると シャッタースピード を二倍速くできます。 ISO感度 の基本は100になります。 ISO感度 を上げるとノイズも増幅されてしまうので、単に上げればよいというものではありません。 1/1000 sec shutter 瞬間を切り取りたいときは、1/1000 sec shutterで シャッタースピード を1/1000秒にするのが良いでしょう。今回は、F/1.4の明るいレンズを使います。 ISO感度 は、時間帯など状況に合わせて調整してください。 波しぶきを撮影してみましょう。 今回のPromptはこちら。 a photo of beautiful ocean spray, golden hour, SIGMA 85 mm F/1.4, 1/1000 sec shutter, ISO 400, award-winning 時間帯をgolden hourにしたので、 ISO感度 を400にしました。 出力された画像はこちら。 ocean, 5 sec shutter 海など、動きがゆっくりのものを シャッタースピード を遅くして撮影すると、幻想的な写真が取れることがあります。 今回のPromptはこちら。 a photo of beautiful ocean, golden hour, SIGMA 24 mm F/1.4, 5 sec shutter, award-winning 出力された画像はこちら。 cars with lights, blue hour, 1/10 sec speed 暗くなった時間帯に、ライトを付けた車を シャッタースピード を落として撮影すると、美しい光の残像を撮ることができます。 今回のPromptはこちら。 a photo of cars with lights speeding on highway, blue hour, SIGMA 85 mm F/1.4, 1/10 sec shutter, award-winning blue hourは、日の出前と日の入り後に発生する空が濃い青色に染まる時間帯のことです。 出力された画像はこちら。 panning shot, 1/20 sec shutter パン撮影は、動く被写体と一緒にカメラを動かし、被写体にピントを合わせて背景をブレさせる手法です。panning shotのキーワードを使いましょう。 シャッタースピード は少しだけ落とします。この撮影法は、再現性が低いので、うまくいくまで何度も試してください。 今回のPromptはこちら。 a panning shot of a car, SIGMA 300 mm F/2.8, 1/20 sec shutter 望遠レンズを使ったほうが再現性は高くなります。panning shotというキーワードを見つけるのに、100回以上、 トライアンドエラー を繰り返しました。motion blur というキーワードもあるのですが、ほとんどの場合、被写体もブレてしまいます。 背景だけブレさせるというのが難しいのです。 出力された画像はこちら。 かなり疾走感のある画像になっていますね。 まとめ 今回は、レンズといろいろな撮影テクニックについて解説しました。 次は、 画像タイプ について解説します。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 これから、Stable Diffusionについてシリーズで書いていきたいと思います。 基本的な方針は、Promptの仕組みを体系的に理解することです。元ネタとしては、 DALL·E 2 Prompt Book を使います。 DALL·E 2 Prompt Bookの内容を試し、僕なりに消化したことを説明していきたいと思います。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 実行環境 Promptとは 人物写真 a close-up of face shot a close-up of eyes shot a head shot an upper shot a full body shot shot from above shot from below まとめ 仲間募集 Stable Diffusionの過去コンテンツ 実行環境 実行環境は、Webで試すことのできる Dream Studio が最も手軽です。ログインすると画面の真ん中下の方にDreamというボタンがあります。その左のエリアがPromptを入力する部分です。 Promptとは Promptとは、StableDiffusionにこういう画像を作ってほしいと依頼する文のことです。 StableDiffusionというのは、Promptを理解できるAIカメラだと捉えましょう。 Promptの書き方に特に決まりはないのですが、次のフォーマットで入力することをおすすめします。 画像のタイプ of サブジェクト, 追加の文, 追加の分 画像のタイプは、photoやoil paintingなどいろいろあります。 サブジ ェクトは、描画したい対象です。 人物写真 最初は、人物写真を撮ってみましょう。基本のPromptは、次のようになります。 a xxx photo of サブジェクト with yyy eyes, a zzz shot, dramatic backlighting a close-up of face shot xxxは好きな修飾語を入れていいのですが、人物写真の場合は、black and whiteがおすすめです。 サブジ ェクトは、今回は、gorgeous human femaleにしましょう。humanをつけないとたまに、ライオンのメスなどが対象になるときがあります。 with yyy eyesは、入れたほうが描画が安定します。今回は、yyyにfriendlyを指定します。 zzzは、体のどの部分を描画するのかを指定します。今回は、a close-up of face shotを指定します。 最終的にPromptは下記のようになりました。 a black and white photo of a gorgeous human female with friendly eyes, a close-up of face shot, dramatic backlighting 出力された画像は下記のようになりました。 a close-up of eyes shot 今度は、a close-up of face shotの部分をa close-up of eyes shotに変更しましょう。 Promptは下記のようになります。 a black and white photo of a gorgeous human female with friendly eyes, a close-up of eyes shot, dramatic backlighting 出力された画像は、下記のようになりました。 目がより強調されましたね。 a head shot a close-up of face shotよりも、若干引いて、肩が写るときもあれば、写らないときもあるのが、a head shotです。 Promptは下記のようになります。 a black and white photo of a gorgeous human female with friendly eyes, a head shot, dramatic backlighting 出力された画像は、下記のようになりました。 肩を確実に出力するために、a head and shoulder shotというのもありますが、肩が不自然に強調されることがあり、出力が安定しません。 an upper shot 胸より上、つまり上半身を写すのが、an upper shotです。胸は写らないことのほうが多いです。 Promptは下記のようになります。 a black and white photo of a gorgeous human female with friendly eyes, an upper shot, dramatic backlighting 出力された画像は下記のようになりました。 a medium shotというものもあるのですが、出力が安定しません。腰から上を出力する a waist shotというものもありますが、こちらも出力が安定しません。 a full body shot 体全体を写すのが、a full body shotですが、このshotは扱いが難しいです。 まず、次のPromptを出力してみてください。 a black and white photo of a gorgeous human female with friendly eyes, a full body shot, dramatic backlighting 何度か試すと、Dream Studioで使っている人には、ぼかされた画像が表示されたはずです。Dream Studio以外を使っている人には、真っ黒な画像が表示されたはずです。これは、NSFWコンテンツといって、職場や学校などのフォーマルな環境下での閲覧に注意を促すためのものです。 full bodyという言葉から生成される画像は、NSFWコンテンツになりやすいということです。これを解決するために、shot from afarという文を追加しましょう。離れたところから撮影するという意味です。これで、だいぶ、NSFWコンテンツに指定される確率が減ります。 a black and white photo of a gorgeous human female with friendly eyes, shot from afar, a full body shot, dramatic backlighting これでも、NSFWコンテンツに何度かに一回は指定されるし、指定されなかったとしても、体のラインを強調したような画像が出力されることが多いでしょう。これを解決するために、gorgeousをmysteriousに変更します。gorgeousとfull bodyの相性が悪かったわけです。 a black and white photo of mysterious human female, shot from afar, a full body shot, dramatic backlighting 出力された画像は下記のようになりました。 shot from above 上から写真を取るには、shot from aboveを使います。 a black and white photo of a gorgeous human female with friendly eyes, shot from above, dramatic backlighting 出力された画像は下記のようになりました。 shot from below 下から写真を取るには、shot from belowを使います。 a black and white photo of a gorgeous human female with friendly eyes, shot from below, dramatic backlighting 出力された画像は下記のようになりました。 まとめ 今回は、人物写真を説明しました。shotキーワードでStableDiffusionに対して、どう撮影したいのかを伝える方法が、理解できたと思います。 次回は、 レンズ を説明します。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 これから、Stable Diffusionについてシリーズで書いていきたいと思います。 基本的な方針は、Promptの仕組みを体系的に理解することです。元ネタとしては、 DALL·E 2 Prompt Book を使います。 DALL·E 2 Prompt Bookの内容を試し、僕なりに消化したことを説明していきたいと思います。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 魅惑的な女アニメ画(トゥーンレンダリング)編 長い呪文は切り捨てられる編 実行環境 Promptとは 人物写真 a close-up of face shot a close-up of eyes shot a head shot an upper shot a full body shot shot from above shot from below まとめ 仲間募集 Stable Diffusionの過去コンテンツ 実行環境 実行環境は、Webで試すことのできる Dream Studio が最も手軽です。ログインすると画面の真ん中下の方にDreamというボタンがあります。その左のエリアがPromptを入力する部分です。 Promptとは Promptとは、StableDiffusionにこういう画像を作ってほしいと依頼する文のことです。 StableDiffusionというのは、Promptを理解できるAIカメラだと捉えましょう。 Promptの書き方に特に決まりはないのですが、次のフォーマットで入力することをおすすめします。 画像のタイプ of サブジェクト, 追加の文, 追加の分 画像のタイプは、photoやoil paintingなどいろいろあります。 サブジ ェクトは、描画したい対象です。 人物写真 最初は、人物写真を撮ってみましょう。基本のPromptは、次のようになります。 a xxx photo of サブジェクト with yyy eyes, a zzz shot, dramatic backlighting a close-up of face shot xxxは好きな修飾語を入れていいのですが、人物写真の場合は、black and whiteがおすすめです。 サブジ ェクトは、今回は、gorgeous human femaleにしましょう。humanをつけないとたまに、ライオンのメスなどが対象になるときがあります。 with yyy eyesは、入れたほうが描画が安定します。今回は、yyyにfriendlyを指定します。 zzzは、体のどの部分を描画するのかを指定します。今回は、a close-up of face shotを指定します。 最終的にPromptは下記のようになりました。 a black and white photo of a gorgeous human female with friendly eyes, a close-up of face shot, dramatic backlighting 出力された画像は下記のようになりました。 a close-up of eyes shot 今度は、a close-up of face shotの部分をa close-up of eyes shotに変更しましょう。 Promptは下記のようになります。 a black and white photo of a gorgeous human female with friendly eyes, a close-up of eyes shot, dramatic backlighting 出力された画像は、下記のようになりました。 目がより強調されましたね。 a head shot a close-up of face shotよりも、若干引いて、肩が写るときもあれば、写らないときもあるのが、a head shotです。 Promptは下記のようになります。 a black and white photo of a gorgeous human female with friendly eyes, a head shot, dramatic backlighting 出力された画像は、下記のようになりました。 肩を確実に出力するために、a head and shoulder shotというのもありますが、肩が不自然に強調されることがあり、出力が安定しません。 an upper shot 胸より上、つまり上半身を写すのが、an upper shotです。胸は写らないことのほうが多いです。 Promptは下記のようになります。 a black and white photo of a gorgeous human female with friendly eyes, an upper shot, dramatic backlighting 出力された画像は下記のようになりました。 a medium shotというものもあるのですが、出力が安定しません。腰から上を出力する a waist shotというものもありますが、こちらも出力が安定しません。 a full body shot 体全体を写すのが、a full body shotですが、このshotは扱いが難しいです。 まず、次のPromptを出力してみてください。 a black and white photo of a gorgeous human female with friendly eyes, a full body shot, dramatic backlighting 何度か試すと、Dream Studioで使っている人には、ぼかされた画像が表示されたはずです。Dream Studio以外を使っている人には、真っ黒な画像が表示されたはずです。これは、NSFWコンテンツといって、職場や学校などのフォーマルな環境下での閲覧に注意を促すためのものです。 full bodyという言葉から生成される画像は、NSFWコンテンツになりやすいということです。これを解決するために、shot from afarという文を追加しましょう。離れたところから撮影するという意味です。これで、だいぶ、NSFWコンテンツに指定される確率が減ります。 a black and white photo of a gorgeous human female with friendly eyes, shot from afar, a full body shot, dramatic backlighting これでも、NSFWコンテンツに何度かに一回は指定されるし、指定されなかったとしても、体のラインを強調したような画像が出力されることが多いでしょう。これを解決するために、gorgeousをmysteriousに変更します。gorgeousとfull bodyの相性が悪かったわけです。 a black and white photo of mysterious human female, shot from afar, a full body shot, dramatic backlighting 出力された画像は下記のようになりました。 shot from above 上から写真を取るには、shot from aboveを使います。 a black and white photo of a gorgeous human female with friendly eyes, shot from above, dramatic backlighting 出力された画像は下記のようになりました。 shot from below 下から写真を取るには、shot from belowを使います。 a black and white photo of a gorgeous human female with friendly eyes, shot from below, dramatic backlighting 出力された画像は下記のようになりました。 まとめ 今回は、人物写真を説明しました。shotキーワードでStableDiffusionに対して、どう撮影したいのかを伝える方法が、理解できたと思います。 次回は、 レンズ を説明します。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの過去コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa ( Shodo で執筆されました )
はじめに Cypress 導入、操作手順 テストコードの実装方法 お気に入りポイント TypeScript対応 Custom Commands cypress/support/commands.ts cypress/support/e2e.ts fixture cypress/fixtures/users.json cypress/support/e2e.ts ハマりポイント iframe ドラッグ&ドロップ おわりに はじめに こんにちは。X イノベーション 本部の稲丸です。 みなさんは、E2Eテストをどのように作成・実施されていますか? 人力で実施している、 Selenium で自動化している、などさまざまな方法が考えられますが、最近はCypressを利用されている方も多いのではないでしょうか。 ISIDでは、 Ci*X Workflow という汎用ワークフローシステムの開発において、Cypressを用いてE2Eテストを作成・自動化しています。 今回は、E2Eテスト フレームワーク Cypress をご紹介します。 Cypress Cypressは、 JavaScript で実装された オープンソース のE2Eテスト フレームワーク です。 多くのE2Eテスト フレームワーク と異なり、 Selenium に依存していない点が特徴の1つです。 また、テストの実装に必要なライブラリの多くをバンドルしており、基本的なテストであればCypress以外のライブラリをインストールすることなく実装できます。 導入、操作手順 Cypressはnpmパッケージとして提供されているため、Node.jsがインストールされた環境下で下記のコマンドを実行するとインストールできます。 npm install cypress --save-dev Cypressには、 GUI ツールと CLI ツールがそれぞれ用意されています。 下記のコマンドで、 GUI ツール(管理画面)を立ち上げることができます。 npx cypress open 初回起動時など、プロジェクト内にテストコードが存在しない場合は、管理画面上からテンプレートプロジェクトを作成できます。 プロジェクト内にテストコードが存在する場合は、管理画面から作成したテストを実行できます。 また、 CLI ツールを用いて、作成したテストをコマンドで実行することもできます。 (下記の例では、実行ブラウザとして Chrome を指定しています) npx cypress run --browser chrome テストの設定は、 cypress.config.js(.ts) という設定ファイルに記述します。 import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://localhost:3000", screenshotOnRunFailure: false, video: false, specPattern: [ "cypress/e2e/spec.cy.ts", "cypress/e2e/another-spec.cy.ts" ], }, }); 通常、specPatternは実行するテストファイル名のパターンを記述します(デフォルト値は cypress/e2e/**/*.cy.{js,jsx,ts,tsx} )。一方、上記の例のように配列形式でテストファイル名を記述することで、テストの実行順を制御できます。 他にも多くのパラメータが設定可能なので、詳細は こちら をご参照ください。 テストコードの実装方法 まず、 cypress/e2e/ 配下にテストファイルを作成します。管理画面上から、「New Spec」→「Create new empty spec」とクリックして作成することもできます。 次に、describe-itメソッドでテストメソッドを実装します。 describe("Nuxt Application", () => { it("Rendering Welcome Page", () => { cy.visit("/"); cy.get("h2").should("include.text", "Welcome to your Nuxt Application"); }); }); DOM要素の取得や操作は、Cypressの ビルトインAPI を使用します。 また、 アサーション は Chai の API を用いて記述します。 お気に入りポイント Cypressを使用してみて、個人的に良いと感じた点(機能)をご紹介します。 TypeScript対応 CypressはTypeScriptに対応しています。 型定義が提供されているだけでなく、TypeScriptで記述したテストコードを JavaScript にビルドすることなくそのまま実行できます。 (プロジェクト内にTypeScriptをインストールする必要があります) Custom Commands Custom Commands という機能を使うことで、ビルトイン API を拡張したり、独自の API を追加できます。 共通処理をCustom Commandsとして登録することで、テストコードの可読性を向上させることができます。 下記の例では、ログイン処理をCustom Commandsとして登録し、各テストメソッドの実行前にログイン処理を実行しています。 cypress/support/commands.ts // ログイン処理(IDとパスワードを入力してログインボタンをクリックする) Cypress.Commands.add("login", (id: string, password: string) => { cy.get("input[type='text']").type(id); cy.get("input[type='password']").type(password); cy.get("button").contains("ログイン").click(); }); cypress/support/e2e.ts import "./commands"; beforeEach(() => { // ログイン画面にアクセス cy.visit("/login"); // テスト用のユーザーアカウントでログインする cy.login("test1", "password"); }); fixture fixture という機能を使うことで、プロジェクト内に用意したテストデータを読み込むことができます。 これによって、テストコードとテストデータを分離して管理できます。 下記の例では、ユーザーアカウント情報をテストデータとして作成し、そのデータを使ってログイン処理を実行しています。 cypress/fixtures/users. json [ { "id": "test1", "password": "password", "admin": true }, { "id": "test2", "password": "password", "admin": false } ] cypress/support/e2e.ts require("./commands"); interface User { id: string; password: string; admin: boolean; } beforeEach(() => { // ログイン画面にアクセス cy.visit("/login"); // テスト用の管理者アカウントでログインする cy.fixture("users.json").then((users: User[]) => { const admin = users.find((user) => user.admin); cy.login(admin.id, admin.password); }); }); ハマりポイント Cypressを使用してみて、少しハマった点をご紹介します。 iframe テストを実行すると指定したブラウザが起動しますが、テスト対象のアプリケーションの画面はiframe内に描画されます。 テストコードを実装する際は、基本的にビルトイン API を使用してDOM要素の取得や操作を行うので、iframeを意識することはありません。 しかし、例えばアプリケーションの画面でさらにiframeを使用している場合などは、iframeを意識する必要があります。 つまり、アプリケーション画面内のiframeの読み込みが完了するのを待ち、 iframe > 対象の要素 という構造を意識してDOM要素の取得や操作を行います。 working-with-iframes-in-cypress ドラッグ&ドロップ Cypressには、 ドラッグ&ドロップ 操作を行う API が提供されていません。 HTML標準の ドラッグ&ドロップ イベントをトリガーすることで実装できますが、 cypress-drag-drop という プラグイン を導入すると、より簡潔に実装できます。 ドラッグ&ドロップ に限らず、多くの サードパーティ製プラグイン が存在するので、困った場合は独自実装する前に プラグイン を探すと良いかと思います。 おわりに 以上、簡単ではありますが、Cypressについてご紹介しました。 いかがでしたか?Cypressには本記事で言及した機能以外にもさまざまな機能があります。 また、現在も頻繁に更新されており、今後ますます使いやすくなることが期待されます。 E2Eテストの作成・自動化を検討されている方や、Cypressに興味がある方にとって、本記事の内容が少しでもご参考になれば幸いです。 私たちは同じチームで働いてくれる仲間を探しています。プロダクト開発に興味がある方のご応募をお待ちしています。 プロダクトプラットフォーム開発エンジニア/新規プロダクト開発エンジニア 執筆: @inamaru.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
はじめに Cypress 導入、操作手順 テストコードの実装方法 お気に入りポイント TypeScript対応 Custom Commands cypress/support/commands.ts cypress/support/e2e.ts fixture cypress/fixtures/users.json cypress/support/e2e.ts ハマりポイント iframe ドラッグ&ドロップ おわりに はじめに こんにちは。X イノベーション 本部の稲丸です。 みなさんは、E2Eテストをどのように作成・実施されていますか? 人力で実施している、 Selenium で自動化している、などさまざまな方法が考えられますが、最近はCypressを利用されている方も多いのではないでしょうか。 ISIDでは、 Ci*X Workflow という汎用ワークフローシステムの開発において、Cypressを用いてE2Eテストを作成・自動化しています。 今回は、E2Eテスト フレームワーク Cypress をご紹介します。 Cypress Cypressは、 JavaScript で実装された オープンソース のE2Eテスト フレームワーク です。 多くのE2Eテスト フレームワーク と異なり、 Selenium に依存していない点が特徴の1つです。 また、テストの実装に必要なライブラリの多くをバンドルしており、基本的なテストであればCypress以外のライブラリをインストールすることなく実装できます。 導入、操作手順 Cypressはnpmパッケージとして提供されているため、Node.jsがインストールされた環境下で下記のコマンドを実行するとインストールできます。 npm install cypress --save-dev Cypressには、 GUI ツールと CLI ツールがそれぞれ用意されています。 下記のコマンドで、 GUI ツール(管理画面)を立ち上げることができます。 npx cypress open 初回起動時など、プロジェクト内にテストコードが存在しない場合は、管理画面上からテンプレートプロジェクトを作成できます。 プロジェクト内にテストコードが存在する場合は、管理画面から作成したテストを実行できます。 また、 CLI ツールを用いて、作成したテストをコマンドで実行することもできます。 (下記の例では、実行ブラウザとして Chrome を指定しています) npx cypress run --browser chrome テストの設定は、 cypress.config.js(.ts) という設定ファイルに記述します。 import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://localhost:3000", screenshotOnRunFailure: false, video: false, specPattern: [ "cypress/e2e/spec.cy.ts", "cypress/e2e/another-spec.cy.ts" ], }, }); 通常、specPatternは実行するテストファイル名のパターンを記述します(デフォルト値は cypress/e2e/**/*.cy.{js,jsx,ts,tsx} )。一方、上記の例のように配列形式でテストファイル名を記述することで、テストの実行順を制御できます。 他にも多くのパラメータが設定可能なので、詳細は こちら をご参照ください。 テストコードの実装方法 まず、 cypress/e2e/ 配下にテストファイルを作成します。管理画面上から、「New Spec」→「Create new empty spec」とクリックして作成することもできます。 次に、describe-itメソッドでテストメソッドを実装します。 describe("Nuxt Application", () => { it("Rendering Welcome Page", () => { cy.visit("/"); cy.get("h2").should("include.text", "Welcome to your Nuxt Application"); }); }); DOM要素の取得や操作は、Cypressの ビルトインAPI を使用します。 また、 アサーション は Chai の API を用いて記述します。 お気に入りポイント Cypressを使用してみて、個人的に良いと感じた点(機能)をご紹介します。 TypeScript対応 CypressはTypeScriptに対応しています。 型定義が提供されているだけでなく、TypeScriptで記述したテストコードを JavaScript にビルドすることなくそのまま実行できます。 (プロジェクト内にTypeScriptをインストールする必要があります) Custom Commands Custom Commands という機能を使うことで、ビルトイン API を拡張したり、独自の API を追加できます。 共通処理をCustom Commandsとして登録することで、テストコードの可読性を向上させることができます。 下記の例では、ログイン処理をCustom Commandsとして登録し、各テストメソッドの実行前にログイン処理を実行しています。 cypress/support/commands.ts // ログイン処理(IDとパスワードを入力してログインボタンをクリックする) Cypress.Commands.add("login", (id: string, password: string) => { cy.get("input[type='text']").type(id); cy.get("input[type='password']").type(password); cy.get("button").contains("ログイン").click(); }); cypress/support/e2e.ts import "./commands"; beforeEach(() => { // ログイン画面にアクセス cy.visit("/login"); // テスト用のユーザーアカウントでログインする cy.login("test1", "password"); }); fixture fixture という機能を使うことで、プロジェクト内に用意したテストデータを読み込むことができます。 これによって、テストコードとテストデータを分離して管理できます。 下記の例では、ユーザーアカウント情報をテストデータとして作成し、そのデータを使ってログイン処理を実行しています。 cypress/fixtures/users. json [ { "id": "test1", "password": "password", "admin": true }, { "id": "test2", "password": "password", "admin": false } ] cypress/support/e2e.ts require("./commands"); interface User { id: string; password: string; admin: boolean; } beforeEach(() => { // ログイン画面にアクセス cy.visit("/login"); // テスト用の管理者アカウントでログインする cy.fixture("users.json").then((users: User[]) => { const admin = users.find((user) => user.admin); cy.login(admin.id, admin.password); }); }); ハマりポイント Cypressを使用してみて、少しハマった点をご紹介します。 iframe テストを実行すると指定したブラウザが起動しますが、テスト対象のアプリケーションの画面はiframe内に描画されます。 テストコードを実装する際は、基本的にビルトイン API を使用してDOM要素の取得や操作を行うので、iframeを意識することはありません。 しかし、例えばアプリケーションの画面でさらにiframeを使用している場合などは、iframeを意識する必要があります。 つまり、アプリケーション画面内のiframeの読み込みが完了するのを待ち、 iframe > 対象の要素 という構造を意識してDOM要素の取得や操作を行います。 working-with-iframes-in-cypress ドラッグ&ドロップ Cypressには、 ドラッグ&ドロップ 操作を行う API が提供されていません。 HTML標準の ドラッグ&ドロップ イベントをトリガーすることで実装できますが、 cypress-drag-drop という プラグイン を導入すると、より簡潔に実装できます。 ドラッグ&ドロップ に限らず、多くの サードパーティ製プラグイン が存在するので、困った場合は独自実装する前に プラグイン を探すと良いかと思います。 おわりに 以上、簡単ではありますが、Cypressについてご紹介しました。 いかがでしたか?Cypressには本記事で言及した機能以外にもさまざまな機能があります。 また、現在も頻繁に更新されており、今後ますます使いやすくなることが期待されます。 E2Eテストの作成・自動化を検討されている方や、Cypressに興味がある方にとって、本記事の内容が少しでもご参考になれば幸いです。 私たちは同じチームで働いてくれる仲間を探しています。プロダクト開発に興味がある方のご応募をお待ちしています。 プロダクトプラットフォーム開発エンジニア/新規プロダクト開発エンジニア 執筆: @inamaru.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 前回の 「バーチャルヒューマンをリアルタイムフェイスリグで動かす」 の記事に引き続き、最新の3DCG技術を紹介します。 本記事では、特に映画/ゲーム業界等で VFX に活用されている Houdini を使って、トップ画像のようなフォトリアルな雲を レンダリング します。 雲のような3次元空間上に密度分布を持った現象をシミュレーションする為に、 オープンソース の OpenVDB ライブラリを利用します。 前提知識 1. Voxel 3次元データを表す最小単位です。Voxelが集まったデータをVolumeと呼びます。 座標以外にも様々なデータ(濃度、温度、速度)を持たせることで、水、火、煙、雲などの物理シミュレーションにも活用されています。 2. OpenVDB (画像: https://www.openvdb.org ) 3次元データを操作する為のデータ規格およびモジュールライブラリです。今回用いるHoudiniや、 Blender など様々なCGソフトに採用されています。 Academy Software Foundation が オープンソース として管理しており、2014年に アカデミー賞 (科学技術部門)も受賞しています。 NVIDIA を中心に活発に開発が進められており、直近では以下の新機能が追加されています: NanoVDB (2021):データ構造の改善により、 GPU 処理が可能になりました。これまで計算負荷の高かったボリュームのシミュレーションが高速に可能になったことで、去年ごろからリアルタイムにボリュームデータを レンダリング する ユースケース も増えました。 NeuralVDB (2022): GPU 上の処理に 機械学習 を取り入れることで、メモリ使用量を1/100に削減!現時点で3DCGツールへの導入は限定的だが、既に NVIDIA Omniverse では導入されている模様です。 3. Houdini (画像: https://www.sidefx.com/ja/products/houdini) ) Side Effects Software社が開発する3DCGソフトウェアです。ノードベー スプログ ラミングを採用しており、プロシージャル(手続き的)に可変的なシミュレーションが可能です。(VEXコードというテキストベースの プログラミング言語 も利用可能) 映画、ゲーム、科学などの業界で、20年以上前から複雑なシミュレーションに活用されており、その功績として2018年に アカデミー賞 (科学技術部門)も受賞しています。 Houdiniでボリュームを扱う際には、Houdini独自VolumeとOpenVBDの2種類の処理方式が利用可能です。 4. Redshift (画像: https://docs.redshift3d.com/display/redshift3d/redshift3d+Home ) Redshift Rendering Technologies社(2019年にMaxon社が買収)が開発する3DCG レンダリング ソフトウェアです。高速処理が可能になる GPU レンダリング を2014年に世界で始めて商用化しました。 Maxon社の提供する3DCGツールであるCinema4Dに加えて、Autodesk社のMayaなどでも利用可能です。 元は NVIDIA GPU のみに対応しておりましたが、2021年には macOS にも対応しました。 シミュレーションの実施 実行環境 macOS 12.1 Monterey( Apple M1 Max) Houdini FX 19.5.303 Redshift4houdini 3.0.53 手順 1. 雲の元となるポリゴンを作成 2. ポリゴンをボリュームに変換した後、ノイズをかけてディテールを付与 3. マテリアル、ライトを設定して、Redshiftで レンダリング HoudiniやRedshiftのインストール手順は割愛します。 それでは、始めていきます。 手順1. 雲の元となるポリゴンを作成 ここでは、3つのノードを使用します。 1-1. 球体の作成 最初に、 Sphere ノードで球体を作成します。 主な設定値は、以下です。 Primitive Type:「Polygon」 Frequency:24 ポリゴンの数が少ないとこの後かけるノイズが荒くなってしまうので、適当な数に上げています。 Houdiniの場合、最後になってから増やすのも簡単にできるので、パラメータチューニングは最後に行っても大丈夫です。 1-2. ノイズの付与 次に、Attribute Noise(旧名:mountain)ノードでノイズをかけていきます。 このノードでは様々な種類のノイズを、任意のデータに対してかけることができます。 今回は、先ほど作成したポリゴンの各座標Pに対して、「Sparse Convolution」というノイズをかけています。 主な設定値は、以下です。 Noise Value Amplitude:0.61 Nise Pattern Noise Type:「Sparse Convolution」 Element Size:0.37 Offset:4.42 ジオメトリのデータを参照するViewであるGeometry Spreadsheetを見ると、各ポイントの座標が変化しているのが分かります。 変換前:3-1. sphere ノードの値 変換後:3-2. mountainノードの値 1-3. Y軸方向に変形 雲のような形にする為、Y軸方向を0.5倍にスケールして押し潰します。 Transformノード内に「Scale」という項目があるので、Y軸の値を0.5に設定します。 これで、雲の元となるポリゴンの作成は完了です。 手順2. ポリゴンをボリュームに変換した後、ノイズをかけてディテールを付与 次に、作成したポリゴンをボリュームデータ(OpenVDB)に変換します。 以下3種類のノードを使用します。 2-1. Cloudノードでボリュームに変換 ポリゴンのボリュームへの変換は、Cloudノードを使用します。 主な設定値は、以下です。 Source:「Polygon Model」 Uniform Smpling Divs:「100」 ここで重要なのが、項目「Uniform Smpling Divs」です。この数値によって、Voxelの分割数が決まります。正方形のポリゴンであれば、10倍にすると単純計算では千から100万にVoxelが増える為、適切な数を設定しましょう。 例えばデフォルトの10のままだと、かなりぼやっとした感じになるのが分かります。 CloudノードはOpenVDBを採用しておりますが、Houdiniでは独自のVolumeノードも用意しております。 試しに、両者を比較してみましょう。 先ほどのTransformノードのアウトプットを新しく作成したVolumeノードにも繋ぎます。 それぞれのデータを見てみす。 VolumeノードではVoxel数が40万程度にあるのに対して、 CloudノードではVoxel数が14万強と、大幅に削減されているのが分かります。 Houdini独自のVolumeは、3次元格子内の全てのVoxelデータを 保有 する為にVoxel数が大きくなっています。 このノードが残っている理由としては、Volume表面の精緻な検出に用いる用途などが挙げられます。 2-2. Cloud Noiseノードで、ノイズを付与 次に、Cloud Noiseノードでノイズをかけていきます。 主な設定値は、以下です。 Amplitude:0.129 Element Size:0.6 様々な設定値によって結果が大きく変わるので、自分の気に入る形になるまで調整を繰り返します。 2-3. Volume VOPノードでさらに細かいノイズを部分的に付与 最後に、Volume VOPノードで最終的なアウトプットを出力します。 houdiniでは、ビジュアルプログラミングが可能になるVOPというノードも提供されています。 この中では、値の変換処理や条件分岐など、少し複雑な処理も簡単に記述できます。 Cloudノードによって、雲の濃さを表すdensityという値(Float型)が作成されているので、この値に対して処理を行います。 VOP内の処理詳細は割愛しますが、変換前はほぼ一律になっていたdensityの値を、Perlinノイズを用いて自然なグラデーションに変換します。また外周部分の厚みが薄い部分の値を、条件分岐を用いて0にしています。 実際に結果を確認する為に、Volume Sliceというノードに接続して確認します。 Volumeは、先に挙げたGeometrySpreadsheetのような2次元データとは異なる3次元データですので、 デバッグ には断面を確認する手法が便利です。 今回はXY平面にスライスして確認します。 各Voxel内でdensityの値は数値(float型)として保持されているので、濃い部分を白く、薄い部分を暗くしたグレイスケールで可視化してみます。 変換前:densityがほぼ一定になっており、全体的に色の濃さが均一になっています。 変換後:densityにPerlinノイズによるばらつきが加わった為、色の濃さも自然にまばらになっています。 以上で、雲のVolumeデータが完成しました。 完成したデータはopenVDBファイルとしてアウトプットします。これでHoudini以外の3DCGソフト( Blender など)へimport可能です。 前回記事にて用いた Unreal Engine では執筆時点(2022年8月)でopenVDBの対応はされていないようですが、 オープンソース で プラグイン が開発されておりそちらを利用して利用も可能です。 手順3. マテリアル、ライトを設定して、Redshiftで レンダリング 最後に、カメラ、マテリアル、 レンダリング の設定を行い完成画像を出力します。 3DCGソフトに不慣れな方は戸惑うかもしれませんが、3Dモデルを実際に2次元の画像や動画に変換する処理は レンダリング と呼ばれ、 モデリング やアニメーションとは別のソフトを用いることが一般的です。 HoudiniにもMantraというデフォルトの レンダリング 機能が用意されていますが、これは GPU レンダリング に対応していない為、 GPU レンダリング 対応のレンダラーを用います。 HoudiniではRedshift以外にも様々な レンダリング ソフトに対応しており、OTOY社の Octane Render なども有名です。 レンダリング の設定を行う際に注意すべき点としては、ライト、マテリアルに関しては レンダリング エンジン固有のモジュールを用いる必要がある、という点です。 設定内容が膨大になるので、詳細は省き主要な設定内容のみ記載します。 3-1. カメラ Houdiniのカメラを使用します。解像度やFocul Lengthの設定など、様々な可能です。 Enable Photogrametric Exposure:ON Enable Depth of Field:ON 3-2. ライト 今回は、Redshiftの提供するRSLightDomeというライトを用います。 ライトには点(Point light)、面(Area light)、太陽光(Directional light)など様々な種類のライトがあります。 今回は、360°で撮影したHDRIテクスチャ画像を光源として使用できるRSLightDomeノードを使用します。 光源として使用するテクスチャ画像は、CC0の Poly Heaven を使用しました。 3-3.テクスチャ 雲の質感を設定します。 RS Volumeノードで、拡散(Scatter)、吸収(Absorption)、発光(Emission)などの値を設定します。 3-4. レンダラー レンダリング に関する値を設定します。 Global Illumination: Enabled:ON Primary GI Engine:Brute Force Trace Depth:3 Sample Filtering Filter:「Gauss」 Size:4 Redshift → Advanced → Bucket Size:512 今回はBucketRenderingを使用しました。 バケット サイズの設定をしないと処理速度が20倍くらい変わるため注意しましょう。 完成イメージ 完成画像です(LightDomeの背景画像もそのまま残しています)。 私の環境( Apple M1 Max)では、 Bucket Renderingで1~10sec程度、ProgressiveRenderingだと1~2分ほどかかりました。 終わりに いかがでしょうか? 今回はvbdを作成した後にHoudini内で レンダリング まで行いましたが、 Blender などnanoVDBやneuralVDBに対応している3DCGソフト上であればどこでも扱えます。複数シーケンスとして出力することで、アニメーションがついた表現も可能ですし、OpenVDBはデータ規格だけでなく処理ライブラリも提供されている為、ダイナミックな物理シミュレーションも可能です。 これまで特に映画産業では、雲や炎、水や霧などボリュームデータを用いたシミュレーションは膨大な時間とコンピューティングリソースをかけてプリ レンダリング されてきました。 が、OpenVDBの登場により今後はゲームや メタバース などのリアルタイムインタ ラク ションが求められる環境でも処理が可能になってくると思われます。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 OpenVDB nanoVDB Houdini: What’s new Pyro and FLIP fluids 執筆: @yamashita.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 前回の 「バーチャルヒューマンをリアルタイムフェイスリグで動かす」 の記事に引き続き、最新の3DCG技術を紹介します。 本記事では、特に映画/ゲーム業界等で VFX に活用されている Houdini を使って、トップ画像のようなフォトリアルな雲を レンダリング します。 雲のような3次元空間上に密度分布を持った現象をシミュレーションする為に、 オープンソース の OpenVDB ライブラリを利用します。 前提知識 1. Voxel 3次元データを表す最小単位です。Voxelが集まったデータをVolumeと呼びます。 座標以外にも様々なデータ(濃度、温度、速度)を持たせることで、水、火、煙、雲などの物理シミュレーションにも活用されています。 2. OpenVDB (画像: https://www.openvdb.org ) 3次元データを操作する為のデータ規格およびモジュールライブラリです。今回用いるHoudiniや、 Blender など様々なCGソフトに採用されています。 Academy Software Foundation が オープンソース として管理しており、2014年に アカデミー賞 (科学技術部門)も受賞しています。 NVIDIA を中心に活発に開発が進められており、直近では以下の新機能が追加されています: NanoVDB (2021):データ構造の改善により、 GPU 処理が可能になりました。これまで計算負荷の高かったボリュームのシミュレーションが高速に可能になったことで、去年ごろからリアルタイムにボリュームデータを レンダリング する ユースケース も増えました。 NeuralVDB (2022): GPU 上の処理に 機械学習 を取り入れることで、メモリ使用量を1/100に削減!現時点で3DCGツールへの導入は限定的だが、既に NVIDIA Omniverse では導入されている模様です。 3. Houdini (画像: https://www.sidefx.com/ja/products/houdini) ) Side Effects Software社が開発する3DCGソフトウェアです。ノードベー スプログ ラミングを採用しており、プロシージャル(手続き的)に可変的なシミュレーションが可能です。(VEXコードというテキストベースの プログラミング言語 も利用可能) 映画、ゲーム、科学などの業界で、20年以上前から複雑なシミュレーションに活用されており、その功績として2018年に アカデミー賞 (科学技術部門)も受賞しています。 Houdiniでボリュームを扱う際には、Houdini独自VolumeとOpenVBDの2種類の処理方式が利用可能です。 4. Redshift (画像: https://docs.redshift3d.com/display/redshift3d/redshift3d+Home ) Redshift Rendering Technologies社(2019年にMaxon社が買収)が開発する3DCG レンダリング ソフトウェアです。高速処理が可能になる GPU レンダリング を2014年に世界で始めて商用化しました。 Maxon社の提供する3DCGツールであるCinema4Dに加えて、Autodesk社のMayaなどでも利用可能です。 元は NVIDIA GPU のみに対応しておりましたが、2021年には macOS にも対応しました。 シミュレーションの実施 実行環境 macOS 12.1 Monterey( Apple M1 Max) Houdini FX 19.5.303 Redshift4houdini 3.0.53 手順 1. 雲の元となるポリゴンを作成 2. ポリゴンをボリュームに変換した後、ノイズをかけてディテールを付与 3. マテリアル、ライトを設定して、Redshiftで レンダリング HoudiniやRedshiftのインストール手順は割愛します。 それでは、始めていきます。 手順1. 雲の元となるポリゴンを作成 ここでは、3つのノードを使用します。 1-1. 球体の作成 最初に、 Sphere ノードで球体を作成します。 主な設定値は、以下です。 Primitive Type:「Polygon」 Frequency:24 ポリゴンの数が少ないとこの後かけるノイズが荒くなってしまうので、適当な数に上げています。 Houdiniの場合、最後になってから増やすのも簡単にできるので、パラメータチューニングは最後に行っても大丈夫です。 1-2. ノイズの付与 次に、Attribute Noise(旧名:mountain)ノードでノイズをかけていきます。 このノードでは様々な種類のノイズを、任意のデータに対してかけることができます。 今回は、先ほど作成したポリゴンの各座標Pに対して、「Sparse Convolution」というノイズをかけています。 主な設定値は、以下です。 Noise Value Amplitude:0.61 Nise Pattern Noise Type:「Sparse Convolution」 Element Size:0.37 Offset:4.42 ジオメトリのデータを参照するViewであるGeometry Spreadsheetを見ると、各ポイントの座標が変化しているのが分かります。 変換前:3-1. sphere ノードの値 変換後:3-2. mountainノードの値 1-3. Y軸方向に変形 雲のような形にする為、Y軸方向を0.5倍にスケールして押し潰します。 Transformノード内に「Scale」という項目があるので、Y軸の値を0.5に設定します。 これで、雲の元となるポリゴンの作成は完了です。 手順2. ポリゴンをボリュームに変換した後、ノイズをかけてディテールを付与 次に、作成したポリゴンをボリュームデータ(OpenVDB)に変換します。 以下3種類のノードを使用します。 2-1. Cloudノードでボリュームに変換 ポリゴンのボリュームへの変換は、Cloudノードを使用します。 主な設定値は、以下です。 Source:「Polygon Model」 Uniform Smpling Divs:「100」 ここで重要なのが、項目「Uniform Smpling Divs」です。この数値によって、Voxelの分割数が決まります。正方形のポリゴンであれば、10倍にすると単純計算では千から100万にVoxelが増える為、適切な数を設定しましょう。 例えばデフォルトの10のままだと、かなりぼやっとした感じになるのが分かります。 CloudノードはOpenVDBを採用しておりますが、Houdiniでは独自のVolumeノードも用意しております。 試しに、両者を比較してみましょう。 先ほどのTransformノードのアウトプットを新しく作成したVolumeノードにも繋ぎます。 それぞれのデータを見てみす。 VolumeノードではVoxel数が40万程度にあるのに対して、 CloudノードではVoxel数が14万強と、大幅に削減されているのが分かります。 Houdini独自のVolumeは、3次元格子内の全てのVoxelデータを 保有 する為にVoxel数が大きくなっています。 このノードが残っている理由としては、Volume表面の精緻な検出に用いる用途などが挙げられます。 2-2. Cloud Noiseノードで、ノイズを付与 次に、Cloud Noiseノードでノイズをかけていきます。 主な設定値は、以下です。 Amplitude:0.129 Element Size:0.6 様々な設定値によって結果が大きく変わるので、自分の気に入る形になるまで調整を繰り返します。 2-3. Volume VOPノードでさらに細かいノイズを部分的に付与 最後に、Volume VOPノードで最終的なアウトプットを出力します。 houdiniでは、ビジュアルプログラミングが可能になるVOPというノードも提供されています。 この中では、値の変換処理や条件分岐など、少し複雑な処理も簡単に記述できます。 Cloudノードによって、雲の濃さを表すdensityという値(Float型)が作成されているので、この値に対して処理を行います。 VOP内の処理詳細は割愛しますが、変換前はほぼ一律になっていたdensityの値を、Perlinノイズを用いて自然なグラデーションに変換します。また外周部分の厚みが薄い部分の値を、条件分岐を用いて0にしています。 実際に結果を確認する為に、Volume Sliceというノードに接続して確認します。 Volumeは、先に挙げたGeometrySpreadsheetのような2次元データとは異なる3次元データですので、 デバッグ には断面を確認する手法が便利です。 今回はXY平面にスライスして確認します。 各Voxel内でdensityの値は数値(float型)として保持されているので、濃い部分を白く、薄い部分を暗くしたグレイスケールで可視化してみます。 変換前:densityがほぼ一定になっており、全体的に色の濃さが均一になっています。 変換後:densityにPerlinノイズによるばらつきが加わった為、色の濃さも自然にまばらになっています。 以上で、雲のVolumeデータが完成しました。 完成したデータはopenVDBファイルとしてアウトプットします。これでHoudini以外の3DCGソフト( Blender など)へimport可能です。 前回記事にて用いた Unreal Engine では執筆時点(2022年8月)でopenVDBの対応はされていないようですが、 オープンソース で プラグイン が開発されておりそちらを利用して利用も可能です。 手順3. マテリアル、ライトを設定して、Redshiftで レンダリング 最後に、カメラ、マテリアル、 レンダリング の設定を行い完成画像を出力します。 3DCGソフトに不慣れな方は戸惑うかもしれませんが、3Dモデルを実際に2次元の画像や動画に変換する処理は レンダリング と呼ばれ、 モデリング やアニメーションとは別のソフトを用いることが一般的です。 HoudiniにもMantraというデフォルトの レンダリング 機能が用意されていますが、これは GPU レンダリング に対応していない為、 GPU レンダリング 対応のレンダラーを用います。 HoudiniではRedshift以外にも様々な レンダリング ソフトに対応しており、OTOY社の Octane Render なども有名です。 レンダリング の設定を行う際に注意すべき点としては、ライト、マテリアルに関しては レンダリング エンジン固有のモジュールを用いる必要がある、という点です。 設定内容が膨大になるので、詳細は省き主要な設定内容のみ記載します。 3-1. カメラ Houdiniのカメラを使用します。解像度やFocul Lengthの設定など、様々な可能です。 Enable Photogrametric Exposure:ON Enable Depth of Field:ON 3-2. ライト 今回は、Redshiftの提供するRSLightDomeというライトを用います。 ライトには点(Point light)、面(Area light)、太陽光(Directional light)など様々な種類のライトがあります。 今回は、360°で撮影したHDRIテクスチャ画像を光源として使用できるRSLightDomeノードを使用します。 光源として使用するテクスチャ画像は、CC0の Poly Heaven を使用しました。 3-3.テクスチャ 雲の質感を設定します。 RS Volumeノードで、拡散(Scatter)、吸収(Absorption)、発光(Emission)などの値を設定します。 3-4. レンダラー レンダリング に関する値を設定します。 Global Illumination: Enabled:ON Primary GI Engine:Brute Force Trace Depth:3 Sample Filtering Filter:「Gauss」 Size:4 Redshift → Advanced → Bucket Size:512 今回はBucketRenderingを使用しました。 バケット サイズの設定をしないと処理速度が20倍くらい変わるため注意しましょう。 完成イメージ 完成画像です(LightDomeの背景画像もそのまま残しています)。 私の環境( Apple M1 Max)では、 Bucket Renderingで1~10sec程度、ProgressiveRenderingだと1~2分ほどかかりました。 終わりに いかがでしょうか? 今回はvbdを作成した後にHoudini内で レンダリング まで行いましたが、 Blender などnanoVDBやneuralVDBに対応している3DCGソフト上であればどこでも扱えます。複数シーケンスとして出力することで、アニメーションがついた表現も可能ですし、OpenVDBはデータ規格だけでなく処理ライブラリも提供されている為、ダイナミックな物理シミュレーションも可能です。 これまで特に映画産業では、雲や炎、水や霧などボリュームデータを用いたシミュレーションは膨大な時間とコンピューティングリソースをかけてプリ レンダリング されてきました。 が、OpenVDBの登場により今後はゲームや メタバース などのリアルタイムインタ ラク ションが求められる環境でも処理が可能になってくると思われます。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 OpenVDB nanoVDB Houdini: What’s new Pyro and FLIP fluids 執筆: @yamashita.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは! ISID X(クロス) イノベーション 本部 オープン イノベーション ラボの奥野正寛です。 Unityを使ったARアプリの開発において基本となるAR Foundationと SDK の1つであるVuforia Engineの組み合わせについて紹介します。 はじめに 環境 Vuforia EngineとAR Foudationのターゲット トラッキングに利用するターゲット AR Foundationの構成 Vuforia Engineの構成 動かしてみた!! Vuforia Engineのターゲットへオクルージョン反映 完成! おまけ 最後に・・ はじめに 基本的にUnityのAR アプリ開発 では AR Foundation もしくは Vuforia Engine 等の SDK のどちらかだけを使います。 平面ト ラッキング や画像ト ラッキング などはどちらにも備わっていますが、AR FoundationのオクルージョンやVuforia Engineの Area Target といったそれぞれ独自の機能があり、組み合わせて使いたいことがあります。 今回はVuforia EngineとAR Foundationの組み合わせについて以下の構成を試しました。 Vuforia EngineとAR Foundationそれぞれのターゲットを組み合わせる Vuforia Engineのターゲットで表示したオブジェクトにAR Foundationのオクルージョンを適用 ※本記事ではVuforia EngineとAR Foudationの組み合わせについて記載し、環境構築については割愛します。 環境 PC: macOS Monterey 12.5 Unity 2020.3.37 Vuforia Engine 10.9.3 AR Foundation 4.1.10 ARKit XR Plugin 4.1.10 mobile: iPad Pro (LiDAR搭載) iOS 15.6 Vuforia EngineとAR Foudationのターゲット ト ラッキング に利用するターゲット Vuforia Developer Libraryのドキュメント を参考に、3つのターゲットを組み合わせました。 Vuforia Engine : Area Target , Image Target AR Foundation : Image ト ラッキング 用の画像として、本の表紙を利用させていただきました。(どちらもUnity勉強にオススメです) Unityの教科書 Unity2019 Unityではじめる ROS・人工知能 ロボットプログラミング実践入門 AR Foundationの構成 以下手順でAR Foundationによる画像ト ラッキング を構成します。 ヒエラルキー にXR -> AR Session OriginとXR -> AR Sessionをそれぞれ追加 AR Session Originへ コンポーネント 「AR Tracked Image Manager」を追加 プロジェクトのアセットより作成 -> XR -> Reference Image Library Reference Image Libraryへ ト ラッキング 用画像を登録(事前に画像ファイルはUnityへインポートしておきます) Physical Sizeより実物のサイズを入力 表示させたいオブジェクトのPrefabを作成(今回はCubeという名のPrefabを作成) AR Tracked Image Managerへ以下を登録 Serialized LibraryにReference Image Library Tracked Image PrefabにオブジェクトのPrefab Vuforia Engineの構成 事前に今回利用するImage TargetとArea Targetのデータをインポートしておきます。 Image Targetは Vuforia Engine developer portal より、 Area Targetは Vuforia Area Target Generator でデータ作成できます。 以下手順でVuforia Engineを構成します。 ヒエラルキー よりそれぞれのターゲットオブジェクトをAR Foundatinoの構成で追加した AR Session Originの子として追加。ここで大事なのは、本来Vuforai Engineの構成で追加する Vuforia EngineのAR Cameraは追加しない ことです。 それぞれのターゲットでインポートしておいたターゲットデータを登録 シーン上にArea Targetによる空間データ、Image Targetによる画像データが表示されれればOKです。 Area Targetのデータとして筆者の部屋を、 iPad のArea Target Generatorでスキャンしたものを使いました。 ヒエラルキー よりそれぞれのターゲットで表示させるオブジェクトを ターゲットの子として追加。今回はArea Tagertに緑色のキューブ、Image Targetに青色のキューブを追加しました。 動かしてみた!! それぞれのターゲットに紐づくオブジェクトが表示されました。 Vuforia EngineとAR Foundationのターゲットが一緒に動くことを確認できました。 Vuforia Area Target:緑色キューブ Vuforia Image Target:青色キューブ AR Foundation Image:赤色キューブ Vuforia Engineのターゲットへオクルージョン反映 現実に存在する人や物体によってARオブジェクトが隠れるように見せるため、AR Foudationのオクルージョンを使用します。 AR Session Originの子であるAR Cameraへ コンポーネント 「AR Occlusion Manager」を追加して、それぞれの設定を変更します。今回は全て最適としました。 完成! 全てのオブジェクトに対して人(動画では手)と物体(動画ではポーチ)にオクルージョンが効いています。 Vuforia Engineでト ラッキング している緑色、青色のキューブにもAR Foudationのオクルージョンが反映されることを確認できました。 おまけ Vuforia EngineのAR Cameraを使った構成にして、AR FoundationのAR Occlusion Manager コンポーネント を追加しても、オクルージョンが反映されるのではないか?と思い試してみましたが、反映されない結果でした。AR Occlusion ManagerはAR FoudationのAR Cameraのみに使えるようです。 最後に・・ 今回はVuforia EngineとAR Foundationを組み合わせたAR アプリ開発 について紹介いたしました。 開発プラットフォームを組み合わせることで、より高度な アプリ開発 が可能となりそうですね。 ただし Vuforia Developer Library に書かれている通りオーバーヘッドや レイテンシー 増加の可能性があるため、両方の機能が必要でない限り1つのシーン内ではどちらかのみ利用することが推奨されます。 最後までお読みいただき、ありがとうございました!! 執筆: @okuno.takahiro 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは! ISID X(クロス) イノベーション 本部 オープン イノベーション ラボの奥野正寛です。 Unityを使ったARアプリの開発において基本となるAR Foundationと SDK の1つであるVuforia Engineの組み合わせについて紹介します。 はじめに 環境 Vuforia EngineとAR Foudationのターゲット トラッキングに利用するターゲット AR Foundationの構成 Vuforia Engineの構成 動かしてみた!! Vuforia Engineのターゲットへオクルージョン反映 完成! おまけ 最後に・・ はじめに 基本的にUnityのAR アプリ開発 では AR Foundation もしくは Vuforia Engine 等の SDK のどちらかだけを使います。 平面ト ラッキング や画像ト ラッキング などはどちらにも備わっていますが、AR FoundationのオクルージョンやVuforia Engineの Area Target といったそれぞれ独自の機能があり、組み合わせて使いたいことがあります。 今回はVuforia EngineとAR Foundationの組み合わせについて以下の構成を試しました。 Vuforia EngineとAR Foundationそれぞれのターゲットを組み合わせる Vuforia Engineのターゲットで表示したオブジェクトにAR Foundationのオクルージョンを適用 ※本記事ではVuforia EngineとAR Foudationの組み合わせについて記載し、環境構築については割愛します。 環境 PC: macOS Monterey 12.5 Unity 2020.3.37 Vuforia Engine 10.9.3 AR Foundation 4.1.10 ARKit XR Plugin 4.1.10 mobile: iPad Pro (LiDAR搭載) iOS 15.6 Vuforia EngineとAR Foudationのターゲット ト ラッキング に利用するターゲット Vuforia Developer Libraryのドキュメント を参考に、3つのターゲットを組み合わせました。 Vuforia Engine : Area Target , Image Target AR Foundation : Image ト ラッキング 用の画像として、本の表紙を利用させていただきました。(どちらもUnity勉強にオススメです) Unityの教科書 Unity2019 Unityではじめる ROS・人工知能 ロボットプログラミング実践入門 AR Foundationの構成 以下手順でAR Foundationによる画像ト ラッキング を構成します。 ヒエラルキー にXR -> AR Session OriginとXR -> AR Sessionをそれぞれ追加 AR Session Originへ コンポーネント 「AR Tracked Image Manager」を追加 プロジェクトのアセットより作成 -> XR -> Reference Image Library Reference Image Libraryへ ト ラッキング 用画像を登録(事前に画像ファイルはUnityへインポートしておきます) Physical Sizeより実物のサイズを入力 表示させたいオブジェクトのPrefabを作成(今回はCubeという名のPrefabを作成) AR Tracked Image Managerへ以下を登録 Serialized LibraryにReference Image Library Tracked Image PrefabにオブジェクトのPrefab Vuforia Engineの構成 事前に今回利用するImage TargetとArea Targetのデータをインポートしておきます。 Image Targetは Vuforia Engine developer portal より、 Area Targetは Vuforia Area Target Generator でデータ作成できます。 以下手順でVuforia Engineを構成します。 ヒエラルキー よりそれぞれのターゲットオブジェクトをAR Foundatinoの構成で追加した AR Session Originの子として追加。ここで大事なのは、本来Vuforai Engineの構成で追加する Vuforia EngineのAR Cameraは追加しない ことです。 それぞれのターゲットでインポートしておいたターゲットデータを登録 シーン上にArea Targetによる空間データ、Image Targetによる画像データが表示されれればOKです。 Area Targetのデータとして筆者の部屋を、 iPad のArea Target Generatorでスキャンしたものを使いました。 ヒエラルキー よりそれぞれのターゲットで表示させるオブジェクトを ターゲットの子として追加。今回はArea Tagertに緑色のキューブ、Image Targetに青色のキューブを追加しました。 動かしてみた!! それぞれのターゲットに紐づくオブジェクトが表示されました。 Vuforia EngineとAR Foundationのターゲットが一緒に動くことを確認できました。 Vuforia Area Target:緑色キューブ Vuforia Image Target:青色キューブ AR Foundation Image:赤色キューブ Vuforia Engineのターゲットへオクルージョン反映 現実に存在する人や物体によってARオブジェクトが隠れるように見せるため、AR Foudationのオクルージョンを使用します。 AR Session Originの子であるAR Cameraへ コンポーネント 「AR Occlusion Manager」を追加して、それぞれの設定を変更します。今回は全て最適としました。 完成! 全てのオブジェクトに対して人(動画では手)と物体(動画ではポーチ)にオクルージョンが効いています。 Vuforia Engineでト ラッキング している緑色、青色のキューブにもAR Foudationのオクルージョンが反映されることを確認できました。 おまけ Vuforia EngineのAR Cameraを使った構成にして、AR FoundationのAR Occlusion Manager コンポーネント を追加しても、オクルージョンが反映されるのではないか?と思い試してみましたが、反映されない結果でした。AR Occlusion ManagerはAR FoudationのAR Cameraのみに使えるようです。 最後に・・ 今回はVuforia EngineとAR Foundationを組み合わせたAR アプリ開発 について紹介いたしました。 開発プラットフォームを組み合わせることで、より高度な アプリ開発 が可能となりそうですね。 ただし Vuforia Developer Library に書かれている通りオーバーヘッドや レイテンシー 増加の可能性があるため、両方の機能が必要でない限り1つのシーン内ではどちらかのみ利用することが推奨されます。 最後までお読みいただき、ありがとうございました!! 執筆: @okuno.takahiro 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは。 XI 本部 AI トランスフォーメンションセンター 所属の山田です。 今回は、 Python の Web アプリケーション開発環境において Poetry を利用し始めたことについて紹介します。 背景 私たちのチームではデータ分析、 システム開発 ともに Python を使用しています。 これまで、 Python を業務で使う際にはパッケージ管理ツールに pip を利用してきました。 しかしながら、継続的に開発・更新を繰り返すことを見越した システム開発 ではたびたび問題を引き起こすことがあり、見直しを図ることにしました。 pip で感じていた課題 まず pip を利用時に感じていた課題を整理します。 pip install パッケージ名 で導入した際に requirements.txt への記載漏れというヒューマンエラーが発生する。 requirements.txt においてパッケージバージョンの記載漏れというヒューマンエラーが発生する。 pip でのパッケージ間の依存関係解決は弱く、インストールの順序関係によってはエラーが発生する。 pip freeze によるバージョン固定ファイルを手動で実施する必要があり、開発者間で異なるバージョンがインストールされ、環境差異が発生する。 開発環境特有のパッケージ管理問題。 requirements-dev.txt のように別ファイルで管理するコストが辛い。 上記の課題の多くは、パッケージ管理ツールを見直すことで解決可能であると判断し、見直しを実施しました。 Poetry そして結果として、Poetry を使うことにしました。 Poetry の公式ドキュメントと GItHub リポジトリ へのリンクを載せておきます。 公式ドキュメント、 https://python-poetry.org/ GitHub リポジトリ 、 https://github.com/python-poetry/poetry Poetry を採用した経緯としては、もともとチーム内でも関心があったツールであり、他社でも導入事例 1 や利点 2 が紹介されていたことが大きいです。 また開発ロードマップが示されており、今後のアップデートに伴って拡充される機能が魅力的でもありました。 Poetry - Feature roadmap、 https://github.com/python-poetry/poetry/issues/1856 pipとPoetryの比較 pip と Poetry の比較を表にまとめたものが以下になります。 # pip poetry 設定ファイル requirements.txt pyproject.toml 設定ファイルのサンプル django==4.0.1 [tool.poetry] name = "sample" version = "0.1.0" description = "" authors = ["username <*****@*******>"] [tool.poetry.dependencies] python = ">=3.8,<3.9" Django = "4.0.1" [tool.poetry.dev-dependencies] django-stubs = "^1.9.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" 設定ファイルに基づく依存解決 pip install -r requirements.txt poetry install パッケージの追加方法 pip install <package-name> requirements.txt には手動で追記。 poetry add <package-name> pyproject.toml には自動で追記。 パッケージのlockファイル requirements.lock pip freeze コマンドでの手動生成 poetry.lock 自動生成。 poetry lock コマンドでの手動生成・更新も可能。 開発環境向けのパッケージ管理 requirements-dev.txt などで管理。 pyproject.toml 内の [tool.poetry.dev-dependencies] で管理。 開発環境向けのパッケージ追加 pip install -r requirements-dev.txt requirements-dev.txt などに手動で追記する必要あり。 poetry add -D <package-name> pyproject.toml には自動で追記。 Poetry 導入のメリット もともと pip で感じていた課題と対応する形でメリットについて整理します。 pip install パッケージ名 で導入した際に requirements.txt への記載漏れというヒューマンエラーが発生する。 requirements.txt においてパッケージバージョンの記載漏れというヒューマンエラーが発生する。 上記2つの課題については、Poetry によってインストール時のパッケージとバージョンが自動で追記されるため、ヒューマンエラーの発生を防げるようになりました。 pip でのパッケージ間の依存関係解決は弱く、インストールの順序関係によってはエラーが発生する。 これまで依存関係解決について触れませんでしたが、Poetry は pip と異なるより強固な依存関係リ ゾル バが採用されており、順序関係によるような依存関係解決のエラーは発生しにくいようになっています。 これは Poetry の README でも強みとして記載されています。 Poetry - Why?、 https://github.com/python-poetry/poetry#why pip freeze によるバージョン固定ファイルを手動で実施する必要があり、開発者間で異なるバージョンがインストールされ、環境差異が発生する。 Poetry では poetry.lock が自動生成されます。 また poetry.lock が存在すれば poetry install 時は優先して読み込まれるため、開発者簡で異なるバージョンがインストールされる事象を防ぐことができます。 開発環境特有のパッケージ管理問題。 requirements-dev.txt のように別ファイルで管理するコストが辛い。 Poetryでは、 pyproject.toml 内の [tool.poetry.dev-dependencies] で開発環境固有のパッケージを管理できます。 これにより、開発環境固有のパッケージも同一ファイルで管理でき、コストを削減できます。 pip から Poetry に移行するに当たって 実際に pip から Poetry へ移行するに当たって考慮すべきポイントをまとめておきます。 requirements.txt から pyproject.toml への移行について まず requirements.txt から pyproject.toml に移行する必要があります。 現時点では、Poetry 側では依存関係を既存の requirements.txt から取り入れる方法はサポートされていません。 そのため、既存の開発環境で Poetry を適用する際には、移行作業に充てる時間を多く見積もっておいた方が良いでしょう。 シェルスクリプト を使うことである程度、自動化は図れますが、 pyproject.toml を準備するコストがかかると認識しておいて損はないでしょう。 参考までに既存プロジェクトにおける Poetry への移行方法について、 Stack Overflow での質問リンクを載せておきます。 How to import requirements.txt from an existing project using Poetry、 https://stackoverflow.com/questions/62764148/how-to-import-requirements-txt-from-an-existing-project-using-poetry チームメンバーの開発環境の整備 チームで使えるように開発環境構築のドキュメントなどを整え、設定などを共有する必要があります。 例えば、私たちのチームでは Poetry によって作成される仮想環境の venv がプロジェクト内になるように設定するよう周知しています。 # プロジェクト内に`.venv`を置くように設定変更 poetry config virtualenvs.in-project true 新しいツールを導入する際は、このあたりの設定の意図がチームメンバーに伝わるようにすることは開発をスムーズに進める上でも重要でしょう。 CI/CD環境の更新 すでに CI/CD パイプラインがあるプロジェクトでは、Poetry を利用する方式に変更する必要があります。 GitHub Actions 上では Poetry の Actions が公開されているのでそちらを利用するのが良いでしょう。 Python Poetry Action、 https://github.com/marketplace/actions/python-poetry-action 以下は、 GitHub Actions において Poetry を使って依存関係解決を行う際の設定ファイルの記述例になります。 - name: Setup Poetry uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: "1.1.14" - name: Install dependencies run: poetry install まとめ 今回は、私たちのチームで Python のパッケージ管理ツールとして Poetry を利用し始めたことについて紹介しました。 似たような課題をお持ちの方はぜひ Poetry の導入を検討してみてはいかがでしょうか。 私たちは同じチームで働いてくれる仲間を探しています。AI 製品開発に興味がある方のご応募をお待ちしております。 AI製品エンジニアリーダー AIエンジニア/コンサルタント 執筆: @yamada.y 、レビュー: @sato.taichi ( Shodo で執筆されました ) サーバー アプリ開発 環境( Python /FastAPI)、 https://future-architect.github.io/articles/20210611a/ ↩ pipとpipenvとpoetryの技術的・歴史的背景とその展望、 https://vaaaaaanquish.hatenablog.com/entry/2021/03/29/221715 ↩
こんにちは。 XI 本部 AI トランスフォーメンションセンター 所属の山田です。 今回は、 Python の Web アプリケーション開発環境において Poetry を利用し始めたことについて紹介します。 背景 私たちのチームではデータ分析、 システム開発 ともに Python を使用しています。 これまで、 Python を業務で使う際にはパッケージ管理ツールに pip を利用してきました。 しかしながら、継続的に開発・更新を繰り返すことを見越した システム開発 ではたびたび問題を引き起こすことがあり、見直しを図ることにしました。 pip で感じていた課題 まず pip を利用時に感じていた課題を整理します。 pip install パッケージ名 で導入した際に requirements.txt への記載漏れというヒューマンエラーが発生する。 requirements.txt においてパッケージバージョンの記載漏れというヒューマンエラーが発生する。 pip でのパッケージ間の依存関係解決は弱く、インストールの順序関係によってはエラーが発生する。 pip freeze によるバージョン固定ファイルを手動で実施する必要があり、開発者間で異なるバージョンがインストールされ、環境差異が発生する。 開発環境特有のパッケージ管理問題。 requirements-dev.txt のように別ファイルで管理するコストが辛い。 上記の課題の多くは、パッケージ管理ツールを見直すことで解決可能であると判断し、見直しを実施しました。 Poetry そして結果として、Poetry を使うことにしました。 Poetry の公式ドキュメントと GItHub リポジトリ へのリンクを載せておきます。 公式ドキュメント、 https://python-poetry.org/ GitHub リポジトリ 、 https://github.com/python-poetry/poetry Poetry を採用した経緯としては、もともとチーム内でも関心があったツールであり、他社でも導入事例 1 や利点 2 が紹介されていたことが大きいです。 また開発ロードマップが示されており、今後のアップデートに伴って拡充される機能が魅力的でもありました。 Poetry - Feature roadmap、 https://github.com/python-poetry/poetry/issues/1856 pipとPoetryの比較 pip と Poetry の比較を表にまとめたものが以下になります。 # pip poetry 設定ファイル requirements.txt pyproject.toml 設定ファイルのサンプル django==4.0.1 [tool.poetry] name = "sample" version = "0.1.0" description = "" authors = ["username <*****@*******>"] [tool.poetry.dependencies] python = ">=3.8,<3.9" Django = "4.0.1" [tool.poetry.dev-dependencies] django-stubs = "^1.9.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" 設定ファイルに基づく依存解決 pip install -r requirements.txt poetry install パッケージの追加方法 pip install <package-name> requirements.txt には手動で追記。 poetry add <package-name> pyproject.toml には自動で追記。 パッケージのlockファイル requirements.lock pip freeze コマンドでの手動生成 poetry.lock 自動生成。 poetry lock コマンドでの手動生成・更新も可能。 開発環境向けのパッケージ管理 requirements-dev.txt などで管理。 pyproject.toml 内の [tool.poetry.dev-dependencies] で管理。 開発環境向けのパッケージ追加 pip install -r requirements-dev.txt requirements-dev.txt などに手動で追記する必要あり。 poetry add -D <package-name> pyproject.toml には自動で追記。 Poetry 導入のメリット もともと pip で感じていた課題と対応する形でメリットについて整理します。 pip install パッケージ名 で導入した際に requirements.txt への記載漏れというヒューマンエラーが発生する。 requirements.txt においてパッケージバージョンの記載漏れというヒューマンエラーが発生する。 上記2つの課題については、Poetry によってインストール時のパッケージとバージョンが自動で追記されるため、ヒューマンエラーの発生を防げるようになりました。 pip でのパッケージ間の依存関係解決は弱く、インストールの順序関係によってはエラーが発生する。 これまで依存関係解決について触れませんでしたが、Poetry は pip と異なるより強固な依存関係リ ゾル バが採用されており、順序関係によるような依存関係解決のエラーは発生しにくいようになっています。 これは Poetry の README でも強みとして記載されています。 Poetry - Why?、 https://github.com/python-poetry/poetry#why pip freeze によるバージョン固定ファイルを手動で実施する必要があり、開発者間で異なるバージョンがインストールされ、環境差異が発生する。 Poetry では poetry.lock が自動生成されます。 また poetry.lock が存在すれば poetry install 時は優先して読み込まれるため、開発者簡で異なるバージョンがインストールされる事象を防ぐことができます。 開発環境特有のパッケージ管理問題。 requirements-dev.txt のように別ファイルで管理するコストが辛い。 Poetryでは、 pyproject.toml 内の [tool.poetry.dev-dependencies] で開発環境固有のパッケージを管理できます。 これにより、開発環境固有のパッケージも同一ファイルで管理でき、コストを削減できます。 pip から Poetry に移行するに当たって 実際に pip から Poetry へ移行するに当たって考慮すべきポイントをまとめておきます。 requirements.txt から pyproject.toml への移行について まず requirements.txt から pyproject.toml に移行する必要があります。 現時点では、Poetry 側では依存関係を既存の requirements.txt から取り入れる方法はサポートされていません。 そのため、既存の開発環境で Poetry を適用する際には、移行作業に充てる時間を多く見積もっておいた方が良いでしょう。 シェルスクリプト を使うことである程度、自動化は図れますが、 pyproject.toml を準備するコストがかかると認識しておいて損はないでしょう。 参考までに既存プロジェクトにおける Poetry への移行方法について、 Stack Overflow での質問リンクを載せておきます。 How to import requirements.txt from an existing project using Poetry、 https://stackoverflow.com/questions/62764148/how-to-import-requirements-txt-from-an-existing-project-using-poetry チームメンバーの開発環境の整備 チームで使えるように開発環境構築のドキュメントなどを整え、設定などを共有する必要があります。 例えば、私たちのチームでは Poetry によって作成される仮想環境の venv がプロジェクト内になるように設定するよう周知しています。 # プロジェクト内に`.venv`を置くように設定変更 poetry config virtualenvs.in-project true 新しいツールを導入する際は、このあたりの設定の意図がチームメンバーに伝わるようにすることは開発をスムーズに進める上でも重要でしょう。 CI/CD環境の更新 すでに CI/CD パイプラインがあるプロジェクトでは、Poetry を利用する方式に変更する必要があります。 GitHub Actions 上では Poetry の Actions が公開されているのでそちらを利用するのが良いでしょう。 Python Poetry Action、 https://github.com/marketplace/actions/python-poetry-action 以下は、 GitHub Actions において Poetry を使って依存関係解決を行う際の設定ファイルの記述例になります。 - name: Setup Poetry uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: "1.1.14" - name: Install dependencies run: poetry install まとめ 今回は、私たちのチームで Python のパッケージ管理ツールとして Poetry を利用し始めたことについて紹介しました。 似たような課題をお持ちの方はぜひ Poetry の導入を検討してみてはいかがでしょうか。 私たちは同じチームで働いてくれる仲間を探しています。AI 製品開発に興味がある方のご応募をお待ちしております。 AI製品エンジニアリーダー AIエンジニア/コンサルタント 執筆: @yamada.y 、レビュー: @sato.taichi ( Shodo で執筆されました ) サーバー アプリ開発 環境( Python /FastAPI)、 https://future-architect.github.io/articles/20210611a/ ↩ pipとpipenvとpoetryの技術的・歴史的背景とその展望、 https://vaaaaaanquish.hatenablog.com/entry/2021/03/29/221715 ↩
こんにちは。ISID 金融ソリューション事業部の若本です。 1ヶ月ほど前にOura Ringという スマートデバイス を購入しまして、生体データを色々取り溜めていました。せっかくデータを取るなら何かに使いたいということで、 機械学習 モデルと組み合わせて就寝時間をレコメンドしてみます。 Oura Ringとは? Oura Ring は 指輪型の健康トラッカー です。多種のセンサーを内蔵しており、運動や睡眠、心拍数に関するデータを日々蓄積しています。完全に個人の感想ですが、充電持ちがよく着用の違和感もないので重宝しています。 なお、蓄積したデータは API を使ってアクセスできます。 API は2種類ありますが、今回は Oura API V1 のデータのみを使いました。より詳細なデータを取得できる Oura API V2 もありますが、こちらは運動や心拍のデータがメインとなっており、コンディションや睡眠に関するデータは記事執筆時点で提供されていません。今回は API V1で取得できるデータのうち、 機械学習 モデルへの入力として「前日の睡眠時間・起床時間・就寝時間」を、出力として「翌日のReadinessスコア」を使用しました。 ※V1の機能はV2に順次移行していくようですので、仕様は変わる可能性があります。適宜 API リファレンスをご参照ください。 Oura Ringでやりたいこと Oura Ringでは平均的な就寝時間の範囲を把握することはできますが、就寝時間を変えることによって体調がどれほど変化するのかはわかりません。そこで、Oura Ringのデータから「何時に寝ればいいのか」「それによってどれくらい次の日のコンディションがよくなるのか」を教えてくれるAIモデルを作成します。 Oura Ringでは体が準備できているかを表す指標としてReadinessスコアを算出しており、これを疑似的にコンディションとみなすことができそうです。このReadinessスコアを高くするために、 前日の睡眠実績から「今日は何時に寝るべきか」を推論 することがゴールです。 機械学習 モデルによる予測 Oura Ringで取得している睡眠実績データを基に、同じくOura Ringで算出されるReadinessスコアを予測します。取り貯めた1ヶ月のデータをOura API V1で取得して得られた前日の睡眠時間、起床時間、就寝時間を基に、翌日のReadinessスコアを予測する 機械学習 モデルを作成しましょう。 ここで、Oura API で取得したデータについて前処理を行います。取得した起床時間/就寝時間は タイムゾーン なのでそのまま 機械学習 モデルで扱うことができません。floatへ型変換を行いましょう。具体的には、「何時何分か」の情報を数値情報に変換します。一番簡単なのは1日を0~1の数値に落とし込むことですが、単純に24で除算してしまうと24時以降の値が不連続になってしまうので、14時までのデータは24を足すことで連続するようにしました。睡眠時間とスコアについても適当にスケーリングします。 def time_to_float_converter (x): time = pd.to_datetime(x) hour = (time.hour + 24 if time.hour < 14 else time.hour) / 24 - 0.5 minute = time.minute / ( 24 * 60 ) return (hour+minute) info_df[ 'sleep_bedtime_start' ] = info_df[ 'sleep_bedtime_start' ].apply( lambda x: time_to_float_converter(x)) info_df[ 'sleep_bedtime_end' ] = info_df[ 'sleep_bedtime_end' ].apply( lambda x: time_to_float_converter(x)) info_df[ 'sleep_duration' ] = info_df[ 'sleep_duration' ] / 3600 / 12 info_df[ 'readiness_score' ] /= 100 前処理後のデータは以下のようになります。全てのデータをfloat型に落とし込めていることが確認できました。 Readinessスコアの説明 によると、Readinessスコアは運動・睡眠・心拍数の長期的な推移で算出されているそうですが、今回は睡眠データの一部しか使わないため、正確にReadinessスコアを予測することは不可能です。さらに期間も短いため、外れ値がモデルの学習に影響を及ぼしやすい条件になっています。そこで、予測モデルは外れ値の影響を受けづらい Huber Regressor を選定しました。なお、モデルの作成時には PyCaret でチューニングしたパラメータを用いています。 X_train = info_df.drop([ 'readiness_score' ], axis= 1 ) y_train = info_df[ 'readiness_score' ] model = HuberRegressor(**params) model.fit(X_train, y_train) DiCEで就寝時間をレコメンド 上記のモデルで翌日のReadinessスコアを予測したい場合、就寝時間の入力を使うことができません。なぜなら、予測時点でまだ就寝時間が決まっていないからです。 なので、モデルの理想の出力に合わせて就寝時間を計算し、就寝時間をレコメンドします。反実仮想説明(CE: Counterfactual Explanation)に用いられるDiCE 1 でこれを実現します。 DiCEの詳細な説明は省きますが、DiCEでは「もしも~の入力とその予測結果」(反実仮想サンプル)を生成します。詳細は 原著論文 をご参照ください。DiCEを使用することで、予測を変えるために入力をどれだけ変えるべきかがわかります。 まず先ほど学習済みの 機械学習 モデルをDiCEに取り込みます。 d = dice_ml.Data(dataframe=info_df, continuous_features = list (info_df.drop([ 'readiness_score' ], axis= 1 ).columns), # 連続変数の指定(今回はすべて該当) outcome_name = 'readiness_score' ) m = dice_ml.Model(model=model, backend= "sklearn" , model_type= 'regressor' ) exp = dice_ml.Dice(d, m, method= "random" ) 次にDiCEを用いて、学習データから反実仮想サンプルを生成してみます。今回は2つ生成してみましょう。21時~27時の間に就寝するという制約を付け、Readinessスコアが80点以上となるようなサンプルを生成します。 index = 0 counterfactuals_num = 2 conf = exp.generate_counterfactuals(X_train.iloc[index:index+ 1 , :], total_CFs=counterfactuals_num, features_to_vary=[ "sleep_bedtime_start" ], permitted_range={ 'sleep_bedtime_start' : [ 0.417 , 0.625 ]}, # 21時~27時の間 desired_range=[ 0.8 , 1.0 ]) # スコアが80~100点ならOK conf.visualize_as_dataframe(show_only_changes= True ) 上記のような結果が得られました。sleep_duration(前日の睡眠時間)やsleep_bedtime_end(起床時間)は変わらず、sleep_bedtime_start(就寝時間)のみ変化していることが分かります。最後に、得られた反実仮想サンプルを時刻に戻して出力してみます。 def float_to_time_converter (x: float ): time_tmp = (x + 0.5 ) * 24 f, i = math.modf(time_tmp) pred_hour = int (i) pred_minites = int (f * 60 ) return pred_hour, pred_minites def messenger (df): for i, row in df.iterrows(): pred_hour, pred_minites = float_to_time_converter(row[ 'sleep_bedtime_start' ]) score = row[ 'readiness_score' ] print (f '<候補{i+1}> {pred_hour}:{pred_minites}に就寝すると{score*100:.1f}点のReadinessスコアが見込めます。' ) conf_df = conf.cf_examples_list[ 0 ].final_cfs_df_sparse # DiCEの結果をpandasに変換 messenger(conf_df) 前日の睡眠状態から、「24時過ぎに寝ること」をレコメンドされました。現実的な数値なので納得感はあります。23時過ぎの就寝もレコメンドされていますが、1時間早く寝ても翌日の予測Readinessスコアは0.2点しか上がらないようです。 思ったよりも就寝時間の差による影響が少なかった印象ですが、より長い期間のデータを基にモデルを作ると顕著な傾向が見られるかもしれません。もしくは就寝時間だけでなく、今回使用しなかった運動データなどについてもレコメンド対象とすれば、より大幅にReadinessスコアをコン トロール できそうです。 おわりに 今回は、Oura Ringで取得したデータを用いた就寝時間のレコメンドについて紹介しました。今回は簡易的な検証として睡眠データとReadinessスコアのみを用いましたが、好みに合わせて入力を変えるのも面白そうです。予測したい変数についても1日の総タイピング数、Gitのcommit行数などに変えてもいいかもしれません。 さらに、 LINE Notify や Heroku などのサービスを組み合わせることで就寝時間を毎日通知してくれるようにもできます。この指輪1つで色々と面白いことができそうですね。 おまけ 今回使用しているコードは こちら に公開しています。Oura Ringをお持ちの方はぜひお試しください。 執筆: @wakamoto.ryosuke 、レビュー: @higa ( Shodo で執筆されました ) DiCE: Diverse Counterfactual Explanations( https://github.com/interpretml/DiCE ) ↩
こんにちは。ISID 金融ソリューション事業部の若本です。 1ヶ月ほど前にOura Ringという スマートデバイス を購入しまして、生体データを色々取り溜めていました。せっかくデータを取るなら何かに使いたいということで、 機械学習 モデルと組み合わせて就寝時間をレコメンドしてみます。 Oura Ringとは? Oura Ring は 指輪型の健康トラッカー です。多種のセンサーを内蔵しており、運動や睡眠、心拍数に関するデータを日々蓄積しています。完全に個人の感想ですが、充電持ちがよく着用の違和感もないので重宝しています。 なお、蓄積したデータは API を使ってアクセスできます。 API は2種類ありますが、今回は Oura API V1 のデータのみを使いました。より詳細なデータを取得できる Oura API V2 もありますが、こちらは運動や心拍のデータがメインとなっており、コンディションや睡眠に関するデータは記事執筆時点で提供されていません。今回は API V1で取得できるデータのうち、 機械学習 モデルへの入力として「前日の睡眠時間・起床時間・就寝時間」を、出力として「翌日のReadinessスコア」を使用しました。 ※V1の機能はV2に順次移行していくようですので、仕様は変わる可能性があります。適宜 API リファレンスをご参照ください。 Oura Ringでやりたいこと Oura Ringでは平均的な就寝時間の範囲を把握することはできますが、就寝時間を変えることによって体調がどれほど変化するのかはわかりません。そこで、Oura Ringのデータから「何時に寝ればいいのか」「それによってどれくらい次の日のコンディションがよくなるのか」を教えてくれるAIモデルを作成します。 Oura Ringでは体が準備できているかを表す指標としてReadinessスコアを算出しており、これを疑似的にコンディションとみなすことができそうです。このReadinessスコアを高くするために、 前日の睡眠実績から「今日は何時に寝るべきか」を推論 することがゴールです。 機械学習 モデルによる予測 Oura Ringで取得している睡眠実績データを基に、同じくOura Ringで算出されるReadinessスコアを予測します。取り貯めた1ヶ月のデータをOura API V1で取得して得られた前日の睡眠時間、起床時間、就寝時間を基に、翌日のReadinessスコアを予測する 機械学習 モデルを作成しましょう。 ここで、Oura API で取得したデータについて前処理を行います。取得した起床時間/就寝時間は タイムゾーン なのでそのまま 機械学習 モデルで扱うことができません。floatへ型変換を行いましょう。具体的には、「何時何分か」の情報を数値情報に変換します。一番簡単なのは1日を0~1の数値に落とし込むことですが、単純に24で除算してしまうと24時以降の値が不連続になってしまうので、14時までのデータは24を足すことで連続するようにしました。睡眠時間とスコアについても適当にスケーリングします。 def time_to_float_converter (x): time = pd.to_datetime(x) hour = (time.hour + 24 if time.hour < 14 else time.hour) / 24 - 0.5 minute = time.minute / ( 24 * 60 ) return (hour+minute) info_df[ 'sleep_bedtime_start' ] = info_df[ 'sleep_bedtime_start' ].apply( lambda x: time_to_float_converter(x)) info_df[ 'sleep_bedtime_end' ] = info_df[ 'sleep_bedtime_end' ].apply( lambda x: time_to_float_converter(x)) info_df[ 'sleep_duration' ] = info_df[ 'sleep_duration' ] / 3600 / 12 info_df[ 'readiness_score' ] /= 100 前処理後のデータは以下のようになります。全てのデータをfloat型に落とし込めていることが確認できました。 Readinessスコアの説明 によると、Readinessスコアは運動・睡眠・心拍数の長期的な推移で算出されているそうですが、今回は睡眠データの一部しか使わないため、正確にReadinessスコアを予測することは不可能です。さらに期間も短いため、外れ値がモデルの学習に影響を及ぼしやすい条件になっています。そこで、予測モデルは外れ値の影響を受けづらい Huber Regressor を選定しました。なお、モデルの作成時には PyCaret でチューニングしたパラメータを用いています。 X_train = info_df.drop([ 'readiness_score' ], axis= 1 ) y_train = info_df[ 'readiness_score' ] model = HuberRegressor(**params) model.fit(X_train, y_train) DiCEで就寝時間をレコメンド 上記のモデルで翌日のReadinessスコアを予測したい場合、就寝時間の入力を使うことができません。なぜなら、予測時点でまだ就寝時間が決まっていないからです。 なので、モデルの理想の出力に合わせて就寝時間を計算し、就寝時間をレコメンドします。反実仮想説明(CE: Counterfactual Explanation)に用いられるDiCE 1 でこれを実現します。 DiCEの詳細な説明は省きますが、DiCEでは「もしも~の入力とその予測結果」(反実仮想サンプル)を生成します。詳細は 原著論文 をご参照ください。DiCEを使用することで、予測を変えるために入力をどれだけ変えるべきかがわかります。 まず先ほど学習済みの 機械学習 モデルをDiCEに取り込みます。 d = dice_ml.Data(dataframe=info_df, continuous_features = list (info_df.drop([ 'readiness_score' ], axis= 1 ).columns), # 連続変数の指定(今回はすべて該当) outcome_name = 'readiness_score' ) m = dice_ml.Model(model=model, backend= "sklearn" , model_type= 'regressor' ) exp = dice_ml.Dice(d, m, method= "random" ) 次にDiCEを用いて、学習データから反実仮想サンプルを生成してみます。今回は2つ生成してみましょう。21時~27時の間に就寝するという制約を付け、Readinessスコアが80点以上となるようなサンプルを生成します。 index = 0 counterfactuals_num = 2 conf = exp.generate_counterfactuals(X_train.iloc[index:index+ 1 , :], total_CFs=counterfactuals_num, features_to_vary=[ "sleep_bedtime_start" ], permitted_range={ 'sleep_bedtime_start' : [ 0.417 , 0.625 ]}, # 21時~27時の間 desired_range=[ 0.8 , 1.0 ]) # スコアが80~100点ならOK conf.visualize_as_dataframe(show_only_changes= True ) 上記のような結果が得られました。sleep_duration(前日の睡眠時間)やsleep_bedtime_end(起床時間)は変わらず、sleep_bedtime_start(就寝時間)のみ変化していることが分かります。最後に、得られた反実仮想サンプルを時刻に戻して出力してみます。 def float_to_time_converter (x: float ): time_tmp = (x + 0.5 ) * 24 f, i = math.modf(time_tmp) pred_hour = int (i) pred_minites = int (f * 60 ) return pred_hour, pred_minites def messenger (df): for i, row in df.iterrows(): pred_hour, pred_minites = float_to_time_converter(row[ 'sleep_bedtime_start' ]) score = row[ 'readiness_score' ] print (f '<候補{i+1}> {pred_hour}:{pred_minites}に就寝すると{score*100:.1f}点のReadinessスコアが見込めます。' ) conf_df = conf.cf_examples_list[ 0 ].final_cfs_df_sparse # DiCEの結果をpandasに変換 messenger(conf_df) 前日の睡眠状態から、「24時過ぎに寝ること」をレコメンドされました。現実的な数値なので納得感はあります。23時過ぎの就寝もレコメンドされていますが、1時間早く寝ても翌日の予測Readinessスコアは0.2点しか上がらないようです。 思ったよりも就寝時間の差による影響が少なかった印象ですが、より長い期間のデータを基にモデルを作ると顕著な傾向が見られるかもしれません。もしくは就寝時間だけでなく、今回使用しなかった運動データなどについてもレコメンド対象とすれば、より大幅にReadinessスコアをコン トロール できそうです。 おわりに 今回は、Oura Ringで取得したデータを用いた就寝時間のレコメンドについて紹介しました。今回は簡易的な検証として睡眠データとReadinessスコアのみを用いましたが、好みに合わせて入力を変えるのも面白そうです。予測したい変数についても1日の総タイピング数、Gitのcommit行数などに変えてもいいかもしれません。 さらに、 LINE Notify や Heroku などのサービスを組み合わせることで就寝時間を毎日通知してくれるようにもできます。この指輪1つで色々と面白いことができそうですね。 おまけ 今回使用しているコードは こちら に公開しています。Oura Ringをお持ちの方はぜひお試しください。 執筆: @wakamoto.ryosuke 、レビュー: @higa ( Shodo で執筆されました ) DiCE: Diverse Counterfactual Explanations( https://github.com/interpretml/DiCE ) ↩
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 クラウド インフラをIaC化すると、静的セキュリティスキャンができるようになります。インフラをデプロイする前にセキュリティ上の問題や、ベストプ ラク ティスに沿っていない構成を知ることができるため、ぜひスキャンしておきたいところです。 今までのスキャンツールとしては、以下のようなものがありました。 tfsec OSS Terraformに対応 cfn_nag OSS CloudFormationテンプレートに対応 terrascan OSS Terraform、CloudFormationテンプレート、Azure Resource Managerなどに対応 Snyk IaC 有償(無償プランあり) Terraform、CloudFormationテンプレート、Azure Resource Managerなどに対応 最近、 AWS CDKで使いやすい、 AWS 開発の OSS ツール cdk-nag の存在を知り、非常にテンションが上がっているためブログを書いております! 前半では従来のツールでCDKコードをスキャンするときの問題点とcdk-nagの導入方法、後半では実用する時に欠かせない、cdk-nagエラーのサプレス方法を書いていきます。 TL;DR CDKのセキュリティスキャンはcdk-nagが使いやすい 意味のあるCIにするためには正しいサプレスが重要。細かい粒度でサプレスするべき appliesTo でリソースレベルよりもさらに細かい粒度でサプレスできる CDKが勝手に作成するリソースについては、 applyToChildren を true にするか、 addResourceSuppressionsByPath でサプレスできる サプレスはCDKコードの最後にまとめて書くのが良さそう 既存のCDKに導入する時、 コメントアウト して少しずつ対応すると良い TL;DR cfn_nagでCDKをスキャンするときの問題点 問題点1:CDKコードとの関連が分かりにくい 問題点2:サプレスがイケてない cdk-nagセットアップ スキャンしてみる cdk-nagのサプレス方法 なぜサプレスが重要なのか 基本的なサプレス スタック全体で特定のルールをサプレス より細かい粒度のサプレス CDKが作成するリソースのサプレス interfaceEndpoints作成時のWarningのサプレス サプレスはどこに書くべきか まとめ cfn_nagでCDKをスキャンするときの問題点 cdk-nagの存在を知る前までは、cfn_nagとterrascanを GitHub Actionsのワークフローに組み込んで使っていました。どちらのツールも直接CDKをサポートしていませんが、 cdk synth で生成されるCloudFormationテンプレートに対してスキャンをかけることができます。 例として、cfn_nagのスキャン結果は次のようになります(一部抜粋、編集済) ------------------------------------------------------------ cdk.out/MyStack.template.json ------------------------------------------------------------ | WARN W89 | | Resource: ["AWS679f53fac002430cb0da5b7982bd22872D164C4C", "SomeTrivialFunction4CC789DF"] | Line Numbers: [647, 2395] | | Lambda functions should be deployed inside a VPC これには次のような問題があることがわかりました。 問題点1:CDKコードとの関連が分かりにくい スキャン結果では行番号を出力してくれていますが、CloudFormationテンプレート内の行番号なので、CDKで探す際の参考にはなりません。 またリソースの論理IDも出力されますが、こちらもCloudFormationテンプレートに変換した後のIDなので、CDKで記述したIDとは少し異なります。明示的にCDKで定義したリソースならば、IDの先頭の文字列を見ればどのリソースかなんとなく分かりますが、CDKが自動で作成するリソースについてはIDが勝手に割り振られるため、何によって作成されたリソースかは判断つきません。 問題点2:サプレスがイケてない こちらが一番重要です。cfn_nagでは特定のリソースに対して特定のルールをサプレスしたい場合、CloudFromationテンプレートのMetadataに記載することになります。 CDKからMetadataを追加する場合、一度 CloudFormationリソースとして取得する 必要があり、コードが煩雑になります。(CDKが自動で作成するリソースのルールをサプレスする方法は、まだ試していませんがさらに大変そうです) const cfnFunction = someTrivialFunction.node.defaultChild as lambda.CfnFunction ; cfnFunction.cfnOptions.metadata = { cfn_nag: { rules_to_suppress: [{ id: "W89" , reason: "No need to deploy this function inside VPC" }] } , } ; cdk-nagセットアップ ここからはcdk-nagを使っていきます。 AWSのブログ や cdk-nagのREADME に従ってセットアップします。 cdk-nagパッケージをインストールします。 npm install -D cdk-nag v2.15.32時点でルールパックは5種類ありますが、今回は AWS Solutions を利用します。またエラー詳細を出力するように verbose: true を指定します。 import * as cdk from "aws-cdk-lib" ; import { AwsSolutionsChecks } from "cdk-nag" ; import { ExampleStack } from "../lib/example-stack" ; const app = new cdk.App (); new ExampleStack ( app , "ExampleStack" , {} ); cdk.Aspects. of( app ) .add (new AwsSolutionsChecks ( { verbose: true } )); cfn_nagと異なり、cdk-nagはCDKと直接統合されており、CDKの Aspects を利用し、Synthesize前のPrepareの段階でコードを実行する仕組みです。サプレスされていないルール違反がある場合はフローの途中でエラーが返るため、 cdk deploy を実行しても後続のデプロイ処理がされません。CI/CDに組み込みやすいです。 スキャンしてみる まずは簡単なS3 バケット を作って、 cdk synth を実行します。 import { Stack , StackProps } from "aws-cdk-lib" ; import * as s3 from "aws-cdk-lib/aws-s3" ; import { Construct } from "constructs" ; export class ExampleStack extends Stack { constructor( scope: Construct , id: string , props?: StackProps ) { super( scope , id , props ); const myBucket = new s3.Bucket ( this , "MyBucket" , {} ); } } するとエラーが4つ出ました。 [Error at /ExampleStack/MyBucket/Resource] AwsSolutions-S1: The S3 Bucket has server access logs disabled. The bucket should have server access logging enabled to provide detailed records for the requests that are made to the bucket. [Error at /ExampleStack/MyBucket/Resource] AwsSolutions-S2: The S3 Bucket does not have public access restricted and blocked. The bucket should have public access restricted and blocked to prevent unauthorized access. [Error at /ExampleStack/MyBucket/Resource] AwsSolutions-S3: The S3 Bucket does not default encryption enabled. The bucket should minimally have SSE enabled to help protect data-at-rest. [Error at /ExampleStack/MyBucket/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies. 内容は以下のとおりです AwsSolutions-S1:サーバー アクセスログ が有効でない AwsSolutions-S2:ブロックパブリックアクセスが有効でない AwsSolutions-S3:デフォルト暗号化が有効でない AwsSolutions-S10: TLS 通信の強制が有効でない 同時に cdk.out/AwsSolutions-ExampleStack-NagReport.csv にも結果レポートが出力されます。違反のあったルールだけではなく、クリアしたルールとサプレスしたルールについても出力されています。 サーバー アクセスログ 以外の3つのエラーに対応してみます。 const myBucket = new s3.Bucket ( this , "MyBucket" , { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL , encryption: s3.BucketEncryption.S3_MANAGED , enforceSSL: true , } ); 再び cdk synth を実行すると、エラーが1つだけになりました。 [Error at /ExampleStack/MyBucket/Resource] AwsSolutions-S1: The S3 Bucket has server access logs disabled. The bucket should have server access logging enabled to provide detailed records for the requests that are made to the bucket. 今回、この バケット ではサーバー アクセスログ を取得しないものとして、このルールをサプレスしようと思います。 cdk-nagのサプレス方法 なぜサプレスが重要なのか スキャン結果を必要に応じて簡単にサプレスできることは、意味のあるCIをするために非常に重要です。 スキャンルールはあくまでもベストプ ラク ティス集であり、その全てをクリアできないケース(あるいはする必要がないケース)が多いでしょう。不要なルールがサプレスされずに結果に残り続けると、確認する際のノイズになり、差分も分かりにくくなります。CIに組み込んでいる場合、ノイズが多いとそのうち結果を確認しなくなります。 また、なるべく細かい粒度(リソースレベルかそれ以下)でサプレスすることも重要です。プロジェクト全体で絶対に対応しない方針としたものであれば良いのですが、不用意にグローバルでルールをサプレスしてしまうと、リソースを追加した際に気にかけておくべきベストプ ラク ティスを見逃すことになります。 基本的なサプレス サプレスは id にルールIDを渡し、次のように行います。 import { NagSuppressions } from "cdk-nag" ; ... NagSuppressions.addResourceSuppressions ( myBucket , [ { id: "AwsSolutions-S1" , reason: "Bucket storing logs. No need to export logs itself" } , ] ); cdk synth を実行するとエラーがなくなり、CloudFormationテンプレートが出力されました。 cdk-nagでは reason の記載が必須であり、さらには文字数が10文字未満だと次のようなエラーになります。将来読んでも理由がよくわかるように正確に書きましょう。 Error: MyBucket: Error(s) detected in suppression with 'id' AwsSolutions-S1. The suppression must have a 'reason' of 10 characters or more. サプレスのために記載した reason は、出力したCloudFormationテンプレートの Metadata に書き込まれます。 CloudFormationテンプレートに入力されたマルチバイト文字は文字化けしてしまい、 cdk diff として間違って検出されてしまうため、今のところ reason 欄は英語で記載するのが良さそうです。 (2022/9/2追記)cdk-nagに送った PR がマージされ、v2.18.0からは reason に日本語を記載しても問題にならなくなりました! スタック全体で特定のルールをサプレス 特定のルールをスタック全体でサプレスしたい場合は、次のように書けますが、あまり濫用しない方が良いでしょう。 NagSuppressions.addStackSuppressions ( this , [ { id: "AwsSolutions-S1" , reason: "Enabling server access logs is not requiered" } , ] ); より細かい粒度のサプレス S3 バケット へのアクセスを許可するIAMポリシーを作ってみます。 import * as iam from "aws-cdk-lib/aws-iam" ; ... const myIamPolicy = new iam.ManagedPolicy ( this , "MyIAMPolicy" , { statements: [ new iam.PolicyStatement ( { resources: [ myBucket.bucketArn , ` ${ myBucket.bucketArn } /*` ] , actions: [ "s3:Get*" , "s3:ListBucket" ] , effect: iam.Effect.ALLOW , } ), ] , } ); エラーが2つ出ました。 [Error at /ExampleStack/MyIAMPolicy/Resource] AwsSolutions-IAM5[Action::s3:Get*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission. Metadata explaining the evidence (e.g. via supporting links) for wildcard permissions allows for transparency to operators. This is a granular rule that returns individual findings that can be suppressed with 'appliesTo'. The findings are in the format 'Action::<action>' for policy actions and 'Resource::<resource>' for resources. Example: appliesTo: ['Action::s3:*']. [Error at /ExampleStack/MyIAMPolicy/Resource] AwsSolutions-IAM5[Resource::<MyBucketF68F3FF0.Arn>/*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission. Metadata explaining the evidence (e.g. via supporting links) for wildcard permissions allows for transparency to operators. This is a granular rule that returns individual findings that can be suppressed with 'appliesTo'. The findings are in the format 'Action::<action>' for policy actions and 'Resource::<resource>' for resources. Example: appliesTo: ['Action::s3:*']. IAMポリシーに ワイルドカード ( * )が使われており、広いアクセス権限を付与していることに対するエラーです。ActionとResourceの両方で ワイルドカード を使っているため、両方について指摘されています。今回は問題ないこととしてサプレスするとします。 このIAMポリシー全体に対してルール AwsSolutions-IAM5 をサプレスすることも可能ですが、そうすると将来的に追加するあらゆる ワイルドカード も許可してしまいます。そうではなく、特定のActionやResourceに対してのみ ワイルドカード を許可する方が良いでしょう。 エラーをよく見ると、今回はルールIDの後に括弧がついており( [Action::s3:Get*] [Resource::<MyBucketF68F3FF0.Arn>/*] )、問題となった具体的なActionやResourceがわかるようになっています。 appliesTo を使うことで、特定の記述のみをサプレスできます。 NagSuppressions.addResourceSuppressions ( myIamPolicy , [ { id: "AwsSolutions-IAM5" , reason: "Necessary to grant Get access to all objects in the bucket" , appliesTo: [ "Action::s3:Get*" , "Resource::<MyBucketF68F3FF0.Arn>/*" ] , } , ] ); Resourceは前に作成したS3 バケット への参照となっていますが、ルールIDの後の括弧にある Resource::<MyBucketF68F3FF0.Arn>/* をそのまま appliesTo に追加して構いません。これでサプレスされました。 ちなみに、 appliesTo の中に 正規表現 を書くこともできます。 NagSuppressions.addResourceSuppressions ( myIamPolicy , [ { id: "AwsSolutions-IAM5" , reason: "Necessary to grant Get access to all objects in the bucket" , appliesTo: [ "Action::s3:Get*" , { regex: "/^Resource::<MyBucketF68F3FF0.Arn>(.*)$/g" , } , ] , } , ] ); CDKが作成するリソースのサプレス 今度はLambda関数を作ります。 import { Runtime } from "aws-cdk-lib/aws-lambda" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import * as logs from "aws-cdk-lib/aws-logs" ; ... const myTrivialFunction = new lambdaNodejs.NodejsFunction ( this , "MyTrivialFunction" , { entry: "aws-cdk/functions/my-trivial-function.ts" , runtime: Runtime.NODEJS_16_X , logRetention: logs.RetentionDays.SIX_MONTHS , bundling: { forceDockerBundling: false } , } ); 3つのエラーが出ました。まずは1つ目です。 [Error at /ExampleStack/MyTrivialFunction/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies. An AWS managed policy is a standalone policy that is created and administered by AWS. Currently, many AWS managed policies do not restrict resource scope. Replace AWS managed policies with system specific (customer) managed policies.This is a granular rule that returns individual findings that can be suppressed with 'appliesTo'. The findings are in the format 'Policy::<policy>' for AWS managed policies. Example: appliesTo: ['Policy::arn:<AWS::Partition>:iam::aws:policy/foo']. このLambda関数が明示的に実行ロールを指定していないため、実行ロールが自動で作成されており、 AWSLambdaBasicExecutionRole ポリシーが付与されています。 AWS マネージドポリシーはリソースレベルで権限を絞っていないため、独自のポリシーを使うべきという指摘です。今回は問題ないとし、サプレスします。 しかし今までに述べた方法ではうまくいきません。エラーになっているリソースは関数そのものではなく、関数が使用しているロールだからです。この場合、 addResourceSuppressions の3つ目の引数の applyToChildren に true を渡すことでサプレスできます。(合わせ技で appliesTo には具体的なポリシーを指定しています) NagSuppressions.addResourceSuppressions ( myTrivialFunction , [ { id: "AwsSolutions-IAM4" , reason: "OK to use AWS managed AWSLambdaBasicExecutionRole" , appliesTo: [ "Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] , } , ] , true ); 2つ目、3つ目のエラーは次の通りです。 [Error at /ExampleStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies. An AWS managed policy is a standalone policy that is created and administered by AWS. Currently, many AWS managed policies do not restrict resource scope. Replace AWS managed policies with system specific (customer) managed policies.This is a granular rule that returns individual findings that can be suppressed with 'appliesTo'. The findings are in the format 'Policy::<policy>' for AWS managed policies. Example: appliesTo: ['Policy::arn:<AWS::Partition>:iam::aws:policy/foo']. [Error at /ExampleStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource] AwsSolutions-IAM5[Resource::*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission. Metadata explaining the evidence (e.g. via supporting links) for wildcard permissions allows for transparency to operators. This is a granular rule that returns individual findings that can be suppressed with 'appliesTo'. The findings are in the format 'Action::<action>' for policy actions and 'Resource::<resource>' for resources. Example: appliesTo: ['Action::s3:*']. リソースパスが長くランダム風の文字列になっており、CDKが自動で作成したリソースであることがわかります。Lambda関数を作る時に logRetention を指定した場合、ログリテンションポリシーを設定するためのLambda関数が1つ作られ、その実行ロールに対する指摘です。 明示的にCDKに記述したLambda関 数の子 リソースではないため、 applyToChildren を true にしてもサプレスできません。エラーメッセージに出力されているリソースパスを使い、 addResourceSuppressionsByPath でスタックからこのリソースを見つけてもらいます。 NagSuppressions.addResourceSuppressionsByPath ( this , "/ExampleStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource" , [ { id: "AwsSolutions-IAM4" , reason: "CDK managed resource" , appliesTo: [ "Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] , } , ] ); NagSuppressions.addResourceSuppressionsByPath ( this , "/ExampleStack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource" , [ { id: "AwsSolutions-IAM5" , reason: "CDK managed resource" , appliesTo: [ "Resource::*" ] , } , ] ); ちなみにCDKでカスタムリソースを作成する場合もLambda関数が作られますが、そのサプレスにもこの方法を利用できます。 interfaceEndpoints作成時のWarningのサプレス VPC とセキュリティグループ、 VPC エンドポイントを作ります。 import * as ec2 from "aws-cdk-lib/aws-ec2" ; ... const vpc = new ec2.Vpc ( this , "VPC" , { flowLogs: { s3: { destination: ec2.FlowLogDestination.toS3 ( myBucket , "vpc" ), trafficType: ec2.FlowLogTrafficType.ALL , } , } , } ); const appSecurityGroup = new ec2.SecurityGroup ( this , "appSecurityGroup" , { vpc: vpc , allowAllOutbound: true , } ); const httpsSecurityGroup = new ec2.SecurityGroup ( this , "httpsSecurityGroup" , { vpc: vpc , allowAllOutbound: true , } ); httpsSecurityGroup.addIngressRule ( appSecurityGroup , ec2.Port.tcp ( 443 )); vpc.addInterfaceEndpoint ( "SSMEndpoint" , { service: ec2.InterfaceVpcEndpointAwsService.SSM , subnets: { subnets: vpc.publicSubnets } , securityGroups: [ httpsSecurityGroup ] , privateDnsEnabled: true , } ); Cloudformationテンプレートは問題なく生成されますが、実はチェック失敗のWarningが出力されています。 [Warning at /ExampleStack/httpsSecurityGroup/Resource] CdkNagValidationFailure: 'AwsSolutions-EC23' threw an error during validation. This is generally caused by a parameter referencing an intrinsic function. For more details enable verbose logging.' The parameter resolved to to a non-primitive value "{"Fn::GetAtt":["VPCB9E5F0B4","CidrBlock"]}", therefore the rule could not be validated. こちらのIssue に記載がある通り、想定通りの挙動でサプレスして良いとのことなので、次のように id に CdkNagValidationFailure を渡すことでサプレスできます。 NagSuppressions.addResourceSuppressions ( httpsSecurityGroup , [ { id: "CdkNagValidationFailure" , reason: "https://github.com/cdklabs/cdk-nag/issues/817" } , ] ); サプレスはどこに書くべきか プロジェクトの対応方針によっては大量にサプレスが発生する場合があります。cdk-nagはCDKコード内にサプレスを記載するため、どこに書くべきかについて考えてみます。2つのパターンが考えられます。 エラーが発生したリソース付近に書く サプレスのみをまとめて書く 「1. エラーが発生したリソース付近に書く」場合、どのリソースに対するサプレスかがわかりやすい一方で、インフラを定義するコードとセキュリティスキャンのサ プレスコ ードが入り混じるため、見通しの悪いコードになってしまう印象があります。IaCコードを読む時は、サプレス済みのセキュリティスキャンルールは意識の外に置きたいです。 そこで個人的には「2. サプレスのみをまとめて書く」のが良いのではないかと思います。スタックの最後にサプレスをまとめて記述すれば、コードを読む際の邪魔にはなりません。それでもリソースの数が増えるとどこで何をサプレスしているのか探しにくくなるので、CDKを書く際には1つのスタックにリソースを詰め込みすぎず、適度な長さに分割するのが良いでしょう。 まとめ さまざまなケースを取り上げてサプレスする方法を説明しました。ハードルは下がったと思うので、ぜひcdk-nagを導入してセキュリティチェックをしていきましょう。 既存のCDKコードにcdk-nagを入れる場合、たくさんのエラーが出る可能性があります。まずはコードを全て コメントアウト した上で少しずつコメントを外し、 cdk synth でエラーをステップ・バイ・ステップで対応していくのが良いと思います。 今回は AWS Solutionsルールパックのみでしたが、そのうち他のルールパックについても試してみたいと思います。ここまで読んでいただきありがとうございました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 - セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo2 、レビュー: @kou.kinyo2 ( Shodo で執筆されました )