スターバックスの黒板アートを生成系AIを使って描かせてみたら...。

## 作ろうと思ったきっかけ
私はスタバが好きで週6～8日、スタバに行くのですが、私がスタバに行く楽しみの一つが黒板アートです。黒板アートというのは、新作のドリンクの紹介などで、入口近くやメニューの横に掲げられている手書きの看板です。手書きゆえに個性や温かさを感じることができ、ついいつも見惚れてしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217319/33dcc1aa-e6a0-4af3-78ca-fe6eb6d40b0e.png)

本当は店員さんの書いた黒板アートが好きなのですが、今、ちょうど画像生成系AIの学習をしていたこともあって、もしかしたらこの黒板アート。AIで描けるかもと思いました。(私の心の中では、店員さんが書いた方が、心の温かさとかいっぱいつまってるから絶対店員さんの絵を推したい人なのですが）、とはいえ私も技術者の端くれですので、今の画像生成AIの実力がどの程度か確認しておかねばという想いも一方でありました。

そこで今回、DALL-Eという画像生成AIを使って黒板アートを描かせてみて実力を確認してみました。よりいつでも試せるようにLINEMessagingAPIを組み合わせてドリンク名を入力したら、黒板アート風の絵を返してくれるBotで試しました。


## 今回作ったもの
https://youtube.com/shorts/4gh8VS9lL8E


## システム構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217319/21903b7b-5b53-146e-ff05-77f21c07e302.png)

## 開発環境、ライブラリ
・Github codespaces
・node.js (ver 19.9.0)
・axios (ver 1.0.0)
・line/bot-sdk　(ver 7.5.2)
・openai(ver 3.2.1)
・huggingface/inference　


## コード
<details><summary>コードはコチラ (DALL-E版LINE Bot) </summary>

```javascript
'use strict';
const dotenv = require('dotenv');
const express = require('express');
const line = require('@line/bot-sdk');
const { Configuration, OpenAIApi } = require('openai');
dotenv.config();

const PORT = process.env.PORT || 3000;

// Messaging APIを利用するための鍵を設定します。
const config = {
  channelSecret: process.env.CHANNEL_SECRET,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
};

const client = new line.Client(config);

// openAI のパラメータ設定
const aiConfig = {
    apiKey: process.env.OPENAI_API_KEY
};
const configuration = new Configuration(aiConfig)
const openai = new OpenAIApi(configuration);

const generateImageUrl = async (prompt) => {
    const res = await openai.createImage({
        //prompt: prompt,
        prompt:`picture of starbucks blackboard art-style of ${prompt}.`,
        n: 1,
        size: "1024x1024"
    })
    return res.data.data[0].url;
}

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  const { userId } = event.source;
  const { text } = event.message;

  // ユーザーにリプライメッセージを送ります。
  const res = await client.replyMessage(event.replyToken, {
    type: 'text', // テキストメッセージ
    text: 'now drawing ...'
  });
  console.log(res);
  const imageUrl = await generateImageUrl(text);

  return await client.pushMessage(userId, {
    type: 'image',
    originalContentUrl: imageUrl,
    previewImageUrl: imageUrl
  })
}

const app = express();
app.get('/', (_, res) => res.send('Hello LINE BOT! (HTTP GET)'));
app.post('/webhook', line.middleware(config), (req, res) => {
  
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)');
    console.log('検証イベントを受信しました！');
    return;
  } else {
    console.log('受信しました:', req.body.events);
  }
  
  Promise.all(req.body.events.map(handleEvent))
  .then((result) => res.json(result))
  .catch((err) => {
    console.error(err.originalError.response.data);
    res.status(500).end();
  });
});

app.listen(PORT, () => {
  console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
});



```
</details>


## 出来た画像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217319/8f83986a-3045-2c57-a967-1dfab2833348.png)



## AIに黒板アートを描かせてみて思ったこと
文字はちょいちょい間違えはしていますが、絵心というかセンスという意味では、意外とセンスあるなと思いました。ゆずシトラスティーとか独創性のある絵だしかなりクオリティ高いなと思います。。抹茶ティーラテはラテアートがグリーンになっていて、抹茶感出てるって思いました。ガブリピーチフラペチーノも色の感じがそれっぽい！ホットココアの雪の結晶みたいなマーク、ココアの色はいい感じだなと思いました。イングリッシュブレックファストティーは茶葉のティーパックで紅茶を表現しててなかなか斬新って思いました。そういう発想思いつかなかった。でもいいなって思いました。スチームミルクはなんとなくミルクな感じがします。

一流のGAHAKUには及ばないにしても少なくとも私以上に上手い黒板アートを作ってくるなと思いました。


## 同じく生成AIであるStable Diffusion × LINE Bot版も作ってみました！
　ここは躓きポイントがあるので、躓いたところを書き残しておきます。
　APIを叩いて帰ってくるレスポンスはDall-Eでは生成画像のURLを返してくれていましたが、
Stable Diffusionでは直画像を返すので、コードをそのまま使うだけと、下記
エラーが出てしまいます。

```Shell
$ node stableDiffusionLinebot.js 
ポート3000番でExpressサーバーを実行中です…
受信しました: [
  {
    type: 'message',
    message: { type: 'text', id: '464958●●●●●●●●●●●', text: 'Yuzu Citrus Tea' },
    webhookEventId: '01H5WZ4HPWW4TW●●●●●●●●●●●',
    deliveryContext: { isRedelivery: false },
    timestamp: 1689968067822,
    source: { type: 'user', userId: 'U4aa601ee47f8db9c81dcb●●●●●●●●●●●' },
    replyToken: '84a05c8b14d0409d86●●●●●●●●●●●',
    mode: 'active'
  }
]
{ 'x-line-request-id': '4bde9e31-8409-41ca-●●●●-●●●●●●●●●●●' }
Error: Internal Server Error
    at request (/workspaces/po08-scbc1167/node_modules/@huggingface/inference/dist/index.js:206:15)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async textToImage (/workspaces/po08-scbc1167/node_modules/@huggingface/inference/dist/index.js:500:15)
    at async generateImageUrl (/workspaces/po08-scbc1167/boot/work/0901_work3/stableDiffusionLinebot.js:29:17)
    at async handleEvent (/workspaces/po08-scbc1167/boot/work/0901_work3/stableDiffusionLinebot.js:53:20)
    at async Promise.all (index 0)

```


ChatGPTに相談したり、色々な人に相談したり、手を動かしたりしてレスポンスをみたりしながら、
システム構成を見直しました。Hugging Faceのときはリンクではなく画像が返ってくるので、
AWSに画像を一旦保存し、保存した画像のリンクを返すように修正しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217319/50da7836-5ff9-5fc0-d954-4e60e7e01357.png)


<details><summary>コードはコチラ (Stable Diffusion　× LINE BOT) </summary>

```javascript
'use strict';
const dotenv = require('dotenv');
const express = require('express');
const line = require('@line/bot-sdk');
const fs = require('fs')
const hugging = require('@huggingface/inference');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3Client = new S3Client({ region: 'us-east-1' });

dotenv.config();

const PORT = process.env.PORT || 3000;

const model = 'stabilityai/stable-diffusion-2' // モデル
const nPrompt = 'word' //ネガティブプロンプト

// Messaging APIを利用するための鍵を設定します。
const config = {
  channelSecret: process.env.CHANNEL_SECRET,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
};

const client = new line.Client(config);

// Hugging Face のパラメータ設定
const hf = new hugging.HfInference(process.env.API_TOKEN);

const generateImageUrl = async (model, text, nPrompt) => {
  const res = await hf.textToImage({
      inputs: `Drawing of starbucks blackboard art-style of ${text}`,
      model: model,
      parameters: {
          negative_prompt: nPrompt,
      }
  });

    // レスポンスをBufferとして扱う
    const buffer = Buffer.from(await res.arrayBuffer());
    const fileName = `${Date.now()}.jpeg`;

    // S3に画像をアップロード
    const params = {
        Bucket: '230618-onsei-yoyo',
        Key: fileName,
        Body: buffer,
        ContentType: 'image/jpeg',

    };

    await s3Client.send(new PutObjectCommand(params));

    // 画像のURL
    const url = `https://${params.Bucket}.s3.amazonaws.com/${params.Key}`;
    console.log(url);
    return url;
};
  

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  const { userId } = event.source;
  const { text } = event.message;

  // ユーザーにリプライメッセージを送ります。
  const res = await client.replyMessage(event.replyToken, {
    type: 'text', // テキストメッセージ
    text: 'now drawing ...'
  });
  console.log(res);
  const imageUrl = await generateImageUrl(model, text, nPrompt);

  return await client.pushMessage(userId, {
    type: 'image',
    originalContentUrl: imageUrl,
    previewImageUrl: imageUrl
  })
}

const app = express();
app.get('/', (_, res) => res.send('Hello LINE BOT! (HTTP GET)'));
app.post('/webhook', line.middleware(config), (req, res) => {
  
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)');
    console.log('検証イベントを受信しました！');
    return;
  } else {
    console.log('受信しました:', req.body.events);
  }
  
  Promise.all(req.body.events.map(handleEvent))
  .then((result) => res.json(result))
  .catch((err) => {
    if (err.originalError && err.originalError.response && err.originalError.response.data) {
      console.error(err.originalError.response.data);
    } else {
      console.error(err);
    }
    res.status(500).end();
  });
});

app.listen(PORT, () => {
  console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
});

```
</details>

Stable Diffusionで生成した画像はこちらになります。
Dall-Eとはまた違った雰囲気の黒板アートを返してくれました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217319/e892e5c6-2d18-1bce-a403-50fa1c91fb9f.png)

ゆずシトラスティーは柑橘系の感じで積極的に描かれていてかなり表現力高くてレベル高いなと思いました。抹茶ティーラテ、Gaburi Peach Frappuccino、Hot Cocoaは色で雰囲気を伝えている感じ。ざっくりした絵だけど、でも、色で共感できるって思いました。ホワイトモカはホイップクリーム、そしてやっぱりモカの感じも出ててそれっぽい。スチームミルクは、いかにもミルクって感じの容器にはいっていたり、白を積極的に使っている。English Breakfast Teaは絵めちゃめちゃうまいけど、白だけで表現しているので本当にEnglish Break Fast teaかちょっとあやしい。でも、絵はめちゃめちゃうまいなって思いました。

Dall-Eと似てるところもあるけど、ちょっと感じちがってるところもあって面白いなって思いました。