はじめに
Power Apps × Power Automate × Computer Vision × Azure OpenAI Service
で、与えられた画像から似たような画像や、
- Japanese Anime
- illusration
- cartoon
- collage
風のイラストを生成してくれるアプリを作成してみましたー
こういったイラストの作成の方が得意っぽいですね
— コルネ (@koruneko32767) 2023年6月1日
でも作成されたどのイラストよりもうちのクリスのほうが可愛い#PowerApps #AzureOpenAI https://t.co/9boqHtxD4D pic.twitter.com/IEvfWRWH6N
今回はこちらの作成方法を簡単にまとめようと思います。
ただし、以下は紹介しておりません。
- Azure OpenAI Serviceの申請方法
- Azure OpenAI Serviceのデプロイ方法
- Computer Visionの作成方法
また、キーなどはめんどいので今回はソリューション化して環境変数に突っ込むみたいなことはしていません。
ちゃんとやるなら、Azure Key Vault シークレットを使用してキーとかは作成したほうがよさそうですね。
構成概要
雑ですがこんな感じ。
Power Appsはフロントの役割しか持たせてないですね。
APIの呼び出しとかは全部Power Automate側。
アプリの実装
画像の指定
まずはPower Appsから似た画像を生成したい元画像を指定します。
Runボタンを押すと、画像ファイルをPower Automateに渡すようにしています。
RunFlowBtn.OnSelect
UpdateContext({isViewSpinner: true}); Set( flowRunResults, '画像分析&生成'.Run(Substitute(JSON(UploadedImage.Image, JSONFormat.IncludeBinaryData), """", "")).result ); Navigate(GenerateScreen)
生成までに少々時間がかかるので、ローディング表示を用意しています。
真ん中のくるくる回っているのは、モダンコントロールのSpinnerで、右下の"Now Creating..."で"..."がアニメーション表示になっているやつはSVGで表現しています。
LoadingImg.Image
"data:image/svg+xml,"& EncodeUrl( "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'> <text x='5%' y='90%' font-size='2em' font-family='Meiryo UI' text-anchor='start' > Now Creating </text> <circle cx='230' cy='90%' r='2.5'> <animate attributeName='fill' values='#000000; #ffffff; #000000' dur='3s' repeatCount='indefinite' /> </circle> <circle cx='240' cy='90%' r='2.5'> <animate attributeName='fill' values='#ffffff; #000000; #000000' dur='3s' repeatCount='indefinite' /> </circle> <circle cx='250' cy='90%' r='2.5'> <animate attributeName='fill' values='#000000; #000000; #ffffff' dur='3s' repeatCount='indefinite' /> </circle> </svg>" )
次は画像を渡されたPower Automate側の実装についてですね。
Power AutomateからComputer Visionを呼び出す
画像を受け取ったPower AutomateはComputer Visionを呼び出します。
なぜComputer Visionを利用しているのか?というと画像生成のプロンプトに利用する画像のタグを抽出したかったからです。
Computer Visionを利用することで、画像のコンテンツ タグを取得できるわけですね。
Power AutomateからComputer Visionを呼び出すのにはHTTPアクションを利用しました。
設定値は以下のような感じ
Method
POST
@{outputs('Computer_Vision_Endpoint')}/vision/v3.2/analyze?visualFeatures=Tags
Headers
{ "Content-Type": "application/octet-stream", "Ocp-Apim-Subscription-Key": "@{outputs('Computer_Vision_Key')}" }
Body
@{dataUriToBinary(triggerBody()['text'])}
keyやEndopointはご自身の環境のものを利用してくださいね。
応答結果をもとに、tagを抽出して、join関数で1行テキストに変換しています。
Promptの作成
@{join(body('選択(tagのみ抽出)'), ',')}
これで画像生成用の元Promptの作成が完了です!
お察しのいい方は既にお気づきでしょうが、ここでのtag生成がうまくいかないような画像はOpenAIでの画像生成も上手くいかないですw
Power AutomateからAzure OpenAI Service(DALL-E)を呼び出す
続いてPower AutomateからAzure OpenAI ServiceのDALL-Eを呼び出すことで画像の生成を行います。
ただし、生成する画像は冒頭でも述べたように
- Original
- Japanese Anime
- illusration
- cartoon
- collage
の5つのパターンで生成させたいです。
なので、上記を含んだ追加Prompを用意して、
このテーブルをApply to eachで回し、その中でAzure OpenAI Serviceを呼び出しています。
Originalには特に追加Promptを用意する必要はないため、空白の文字列を設定しています。
Apply to eachの中身はこんな感じ
変数が出てきますね。
変数は以下を作成しています。
変数名 | 種類 | 値 |
---|---|---|
result | アレイ | |
Operation-Location | 文字列 | |
Operation-Location-2 | 文字列 | |
Retry-after | 整数 | |
Retry-after-2 | 整数 | |
status | 文字列 | |
status-2 | 文字列 |
変数名の最後に"-2"がついているような変数は並列処理を行わせるために同じ役割の変数が必要だったからです。
並列処理を行わせているのは、同じプロンプトで画像を生成してもらう際に少しでも処理時間を短縮させるためです。
だったら生成する画像全部並列で行えば?と思われる方もいらっしゃるかもしれませんが、それするとメンテが非常にめんどくさくなるので、このような作りにする際は(今回の例に限らず)役割毎にすることをおすすめします。
並列処理を行っている箇所は左右どちらも同じ処理をしているので片方(左)だけのロジックを紹介しますね。
まずは、追加Promptを元にPromptを生成して、Azure OpenAI Serviceに投げる用のBodyを作成します。
Prompt
@{concat(item(), ',', outputs('Promptの作成'))}
Body
{ "caption": @{outputs('Prompt')}, "resolution": "1024x1024" }
このBodyをもとにAzure OpenAI Serviceを呼び出します。
Method
POST
@{outputs('Endpoint')}/dalle/text-to-image?api-version=2022-08-03-preview
Headers
{ "Content-Type": "application/json", "api-key": "@{outputs('api-key')}" }
Body
@{outputs('Body')}
ここで若干面倒なのが初回呼び出しでは、結果を返してくれない点ですね。
APIを投げると、実行結果の"headers"に、"operation-location"と"Retry-After"が返ってきますので、これらをもとに再度APIを投げます。
"operation-location"が再度実行するURIで、"Retry-After"がN秒後に再度実行してね。
という時間です。
これを、"status"が"Succeeded"になるまで実行します。
"Succeeded"になれば、画像生成した結果を得ることができます。
だいたい1回実行すれば取れるはずです。
Do until内で実行されたアクションの実行結果をDo until外で参照すると、最後のループで実行されたアクションの実行結果が取得できますので、以下のように作成アクションで実行結果を取っています。(ここら辺の動作どこかでまたまとめますね)
result
@{body('HTTP')}
並列アクションがそれぞれ実行されたら、"results"のアレイ変数に以下を追加します。
配列変数に追加
"Type": "@{if( equals(item(), ''), 'Normal', item() )}", "contentUrls": @{json( concat( '[', join( createArray( concat( '{"Value":"', outputs('result')?['result']?['contentUrl'], '"}' ), concat( '{"Value":"', outputs('result_2')?['result']?['contentUrl'], '"}' ), ']' ), ',' ) ) )}
"Type"には、どういう画像を生成したのか?(Original?illustration?など)を設定しており、
"contentUrls"には、画像の生成結果としては、画像のURLが返ってくるので生成された画像のURLを設定しています。
最後に"results"のアレイ変数の結果をもとにJSONを生成して、その結果をPower Appsに返しています。
return
@{json( concat( '[', concat( '{', join(variables('results'), '},{'), '}' ), ']' ) )}
こういう仕組みにしてあげることで、追加Promptを変更しても後続のロジックを変更する必要がなくていいかな。と思います。
補足
記事作成時に再度Azure Open AI Studioからサンプルコード確認したら、エンドポイントが以下の形式に変わっており、
/openai/images/generations:submit?api-version=2023-06-01-preview
Bodyも以下のようになっていました。
{ "prompt": "USER_PROMPT_GOES_HERE", "n": 1, "size": "1024x1024" }
ただこのエンドポイントとBodyで実行してみましたが、以下のようなエラーが返ってきました。
{ "error": { "code": "invalidPayload", "message": "Invalid payload: Value for property prompt is invalid or missing." } }
わからん!
こちらに関してはまた今度検証し、何かわかれば記載します。
Power Appsで実行結果を表示する
こんな感じで実行結果表示させています。
Power Automateからの実行結果(flowRunResults
)はJSONで返ってきているのでParseJSON 関数を利用しています。
ParseJSON関数の簡単な使い方はこちらをご確認ください。
実行結果としては以下のようなJSON形式で返ってきます。(一部省略しています。)
[ { "Type": "Normal", "contentUrls": [ { "Value": "https://..." }, { "Value": "https://..." } ] }, { "Type": "Japanese Anime", "contentUrls": [ { "Value": "https://..." }, { "Value": "https://..." } ] } ]
これを意識したうえでParseJSONを行っていきましょう。
(解説めんどいので省略すると)結果以下のようになります。
TypeGallery.Items
Table(ParseJSON(flowRunResults))
GenerateImgGallery.Items
Table(ThisItem.Value.contentUrls)
GenerateImg.Image
Text(ThisItem.Value.Value)
ParseJSONの注意事項
ちなみにこんな形式のJSON値だとParseJSONできないので注意(嵌った!!!!)
[ { "Type": "Normal", "contentUrls": [ "https://...", "https://..." ] }, { "Type": "Japanese Anime", "contentUrls": [ "https://...", "https://..." ] } ]
"キー"と"バリュー"のペアが(多分)必ず必要になってくる。
おわりに
OpenAI楽しいですねー
溜まっててかけてなかった記事1つ消化できた!
まだまだたくさんあるが。。。