😽

OpenAI の Vision API で料理の判別をやってみる

2023/12/16に公開

この記事は mob Advent Calendar 16日目の記事です。

料理判別の機能が欲しくて探してた中で、OpenAI の Vision AI が使えるのか試してみました。

https://platform.openai.com/docs/guides/vision

コードを書く

Kotlin で、また Kotlin Notebook をつかって試していきます。

https://zenn.dev/mobdev/articles/30890bbb780861

まずは openai 周りの依存を追加します。

USE {
    dependencies {
        val ktorVersion = "2.3.2"
        implementation("com.aallam.openai:openai-client:3.6.1")
        implementation("io.ktor:ktor-client-core:$ktorVersion")
        implementation("io.ktor:ktor-client-apache:$ktorVersion")
    }
}

ファイルを読み込みます。問題ないければ Output に ok が表示されます。

import java.io.File

val imgFile = File("dish1.jpg")

if (imgFile.exists()) {
    println("ok")
} else {
    println("ng")
}

Vision API を使うコードを書いてみるとこうなりました。現状 Vision API を使用する時は response_format に対応していないので、 json でのレスポンスは約束はされていない点が注意です。自分が試してる限りは json じゃないレスポンスをすることは一度もありませんでしたが、一応。。。

import com.aallam.openai.api.chat.*
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.client.OpenAI
import kotlinx.coroutines.runBlocking
import java.util.*

val openAI = OpenAI(
    token = "{{API_KEY}}", // <--- ここは自分のを使ってね
)

val base64 = Base64.getEncoder().encodeToString(imgFile.readBytes())

val prompt = """
この画像にある料理を全て教えてください。
アウトプットは次の***に囲まれたフォーマットに従ってください。これ以外の説明などは一切含めないでください。

{
  "items": [
    { "name": "<<料理名1>>", "candidates": ["<<他の候補1>>", ..., "<<他の候補n>>"] },
    ...
    { "name": "<<料理名n>>", "candidates": ["<<他の候補1>>", ..., "<<他の候補n>>"] },
  ]
}

"""

val request = ChatCompletionRequest(
    model = ModelId("gpt-4-vision-preview"),
    messages = listOf(
        ChatMessage(
            role = ChatRole.User,
            messageContent = ListContent(
                listOf(
                    TextPart(
                        text = prompt
                    ),
                    ImagePart(
                        imageUrl = ImagePart.ImageURL(url = "data:image/jpeg;base64,$base64", detail = "high")
                    )
                )
            ),
        )
    ),
    maxTokens = 500
)
var result = ""
runBlocking {
    val response = openAI.chatCompletion(request)
    result = response.choices.firstOrNull()?.message?.content ?: ""
}
print(result)

期待のアウトプットは次のようにしていて、料理のリストもらうようにしています。 name に写真に載ってる料理。 candidates にその料理名以外の候補のリストが入ってくる想定です。

{
  "items": [
    { "name": "<<料理名1>>", "candidates": ["<<他の候補1>>", ..., "<<他の候補n>>"] },
    ...
    { "name": "<<料理名n>>", "candidates": ["<<他の候補1>>", ..., "<<他の候補n>>"] },
  ]
}

試してみる

3つの写真で試してみました。

写真 result note
{ "items": [ { "name": "カレー丼", "candidates": ["牛丼", "カレーライス", "ハヤシライス"] }, { "name": "味噌汁", "candidates": ["豚汁", "スープ", "コンソメ"] }, { "name": "ポテトサラダ", "candidates": ["マカロニサラダ", "コールスロー", "キャベツサラダ"] } ] } Uberで頼んだ牛カレーと漬物と豚汁
{ "items": [ { "name": "味噌ラーメン", "candidates": ["豚骨ラーメン", "塩ラーメン", "醤油ラーメン"] }, { "name": "つけ麺", "candidates": ["冷やし中華", "ラーメン", "油そば"] }, { "name": "煮卵", "candidates": ["温泉卵", "半熟卵", "味玉"] } ] } 三田製麺のつけ麺
{ "items": [ { "name": "焼きそば", "candidates": ["ペヤング", "インスタント焼きそば"] } ] } ぶぶかのカップ麺

candidates も含めれば、どれもわりかし当たっている方で、漬物が見分けられなかったくらいですかね。

結果

これから長期で試してみますが、現状自分目線では十分に優秀な結果を返しているように思えました!

Discussion