SpringBoot + KotlinでGoogleのVisionAIを使う

今趣味でつくっているWebサービスでGoogleCloudのVisionAIを使用したので備忘録として書いておきます。

目次

  • 実行環境
  • Vision API
  • VisionAPIをSpringで使用する
    • 準備
    • 実装
  • 使ってみた感想

実行環境

言語: Kotlin
Framework: SpringBoot 2.3.1 RELEASE
ビルドツール: Maven
プロジェクトでMysqlも使っているので Docker上で実行しています
Java Image: openjdk:14-jdk-alpine)

Vision AI

VisionAIGoogleの提供する画像解析ツールです。今回はAPIとして提供されているVision APIを使用します。

VIsionAPI: Google Cloud の Vision APIREST API や RPC API を介して強力な事前トレーニング済み機械学習モデルを提供します。画像にラベルを割り当てることで、事前定義済みの数百万のカテゴリに画像を高速に分類できます。オブジェクトや顔を検出し、印刷テキストや手書き入力を読み取り、有用なメタデータを画像カタログに作成します。

参考: Cloud VisionAPI REST API Document https://cloud.google.com/vision/docs/reference/rest?hl=ja

参考: Spring FrameworkVisionを使用すr https://cloud.google.com/vision/docs/adding-spring?hl=ja

VisionAPIをSpringで使用する

準備

1. 依存情報を追加(pom.xml)

dependencyManagementとdependencyの両方に追記する必要があります。

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-gcp-dependencies</artifactId>
                <version>1.2.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
</dependency>

GCP プロジェクトの作成・サービスアカウントのSpringプロジェクトへの適用

記事が多いので省略します。

  1. GCPプロジェクトを作成する(アカウント登録・プロジェクト登録など)

  2. サービスアカウントの作成

下記リンクを参考に。

https://cloud.google.com/docs/authentication/getting-started?hl=ja

3.サービスアカウントキーをプロジェクトの環境変数に設定します。

export GOOGLE_APPLICATION_CREDENTIALS="path/credentials.json"

実装

Controller, Service, Repositoryを書きます。
今回は簡単のためInterfaceは挟みません。
今回実装するAPI応答時間は開発環境で1.3sぐらいです。
VisionAPIのレスポンス速度が早くてびっくり。

Controller

今回はクライアント側から次のようなRequestBodyを受け取ることにします。 画像をBase64エンコードした文字列をPostします。

VisionOcrBody

data class VisionOcrBody (

        /** IMG -> Decode from Base64 */
        @field:NotNull(message = "dataは必須項目です")
        val data: String
)

VisionController.kt

import project.backend.model.requests.vision.VisionOcrBody
import project.backend.model.responses.ResponseVision
import project.backend.service.VisionService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@RestController
@RequestMapping("/vision")
class VisionController {

    @Autowired
    lateinit var visionService: VisionService

    @GetMapping("ocr")
    fun ocr(@Valid @RequestBody visionOcrBody: VisionOcrBody): ResponseVision {
        return visionService.ocr(
                data = visionOcrBody.data)
    }
}

Service

今回は例として書いているのでほとんど記載はないですが、取得したデータの加工は主にService Layerで行います。(今回の場合だとextract function)

VisionService.kt

import project.backend.model.responses.ResponseVision
import project.backend.repository.VisionRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class VisionService {
    @Autowired
    lateinit var visionRepository: VisionRepository

    fun ocr(extension: String, data: String): ResponseVision {
        val result: ArrayList<String> = 
                 visionRepository.analyzeByBase64String(base64String = data)
        return extract(result)
    }

    fun extract(result: ArrayList<String>): ResponseVision {
        // 取得したデータの加工
    }

}

Repository

cloudVisionTemplateを使用してVisionAPIを叩きます。 具体的には、下記の部分です。

        cloudVisionTemplate.analyzeImage(
                resource, Feature.Type.TEXT_DETECTION
        )

Resourceの実装クラスとresource画像解析のTypeを指定します。

今回は文字抽出をしたかったのでFeature.Type.TEXT_DETECTIONを使用しました。

実施する画像解析によって引数が変わります。

参考: https://cloud.google.com/vision/docs/features-list?hl=ja

VisionRepository.kt

import com.google.cloud.vision.v1.AnnotateImageResponse
import com.google.cloud.vision.v1.Feature
import org.apache.commons.codec.binary.Base64
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.gcp.vision.CloudVisionTemplate
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.stereotype.Repository

@Repository
class VisionRepository {
    
    @Autowired
    lateinit var cloudVisionTemplate: CloudVisionTemplate
    
    fun analyzeByBase64String(base64String: String): List<String> {
        val byteArrayData: ByteArray = Base64.decodeBase64(base64String)
        var resource: Resource = ByteArrayResource(byteArrayData)
        val response: AnnotateImageResponse = cloudVisionTemplate.analyzeImage(
                resource, Feature.Type.TEXT_DETECTION
        )
        val ret = ArrayList<String>()
        for(item in response.textAnnotationsList){
            ret.add(item.description.toString()) 
            // item.boundingPoly.toString()
        }
        return ret
    }
}

Response VisionAPIのResponseBodyは次のようなものです。 response.textAnnotationList: 検出した文字列の一覧 response.textAnnotationList[index].description: 検出された文字 response.textAnnotationList[index].boundingPoly: 検出した文字の位置情報など

GoogleのRepositoryサンプル

VisionAPIのチュートリアルのコードではURLを指定し、画像を取得 -> VisionAPIにリクエストという流れでしたが、レスポンスは10sぐらいかかりました。

(画像サイズにもよりますが、resourceLoader.getResource(url)で相当時間かかってそう)

fun analyzeByUrl(url: String): List<String> {
        val resource: Resource = resourceLoader.getResource(url)
        val response: AnnotateImageResponse = cloudVisionTemplate.analyzeImage(
                resource, Feature.Type.TEXT_DETECTION
        )
        val ret = ArrayList<String>()
        for(item in response.textAnnotationsList){
            ret.add(item.description.toString()) // description
//            println(item.boundingPoly) // position
        }
        return ret
}

使ってみた感想

実装もかなり簡単なので、凄く使いやすいと感じました。

ただし、APIで文字列が抽出できて終わり!のようなプロジェクトでは別ですが、今回は抽出した文字一覧のうち必要な情報を抽出し、クライアントに返さないと行けなかったのでその部分が少し大変でした。

クライアント側に必要な情報だけを提供するテクニックを3つ紹介します。

  • 正規表現: シンプル
  • boundingPoly(位置座標)から推定
  • ある程度同じフォーマットの画像なら、返ってくるListに含まれる文字列の順序(検知順)が同じなのでListのIndexで推測