Google App Engine(Java)+Spring Boot+Vuetifyから、Google Cloud Vision APIを呼び出しOCRを実現
ファイルのアップロードができるようになったので、Google Cloud Vision APIを呼び出し、アップロードした画像をOCR解析してみる。
1.Google Cloud APIクライアントライブラリを使用
以下のURLの手順にしたがって、チュートリアルをこなし、疎通を確認。
https://cloud.google.com/vision/docs/libraries?hl=ja#client-libraries-install-java
さて、上記チュートリアルでは、サービスアカウントKEYをローカルで管理するのだが、GAEに上げるときにはどうするのだろう?
調べるのが面倒なので、いったん、デプロイ、API呼び出しを実行してみて、エラーログを確認してみる。
URLにアクセスして、APIを有効化しなさいと。
[b~favophrase/1.417820383360958412].<stderr>: com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException:
PERMISSION_DENIED: Cloud Vision API has not been used in project 512734593206 before or it is disabled.
Enable it by visiting https://console.developers.google.com/apis/api/vision.googleapis.com/overview?project=99999999999 then retry.
If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
アクセスすると、あぁ、この画面ね。確かに。
ということで有効化する。
2.GAE Java(Spring Boot)側の実装
2.1 CloudVIsionService
Google Cloud VIsion API を呼び出すユーティリティサービスを、Spring サービスとして作成。
上記チュートリアルのサンプルコードに、OCRのドキュメントを見ながら、少し手を加える。
テキストが、画像上に表示されている位置情報などもあるのだが、とりあえず、解析結果”description” だけ取得するようにする。
動かしてい見ると、最初に解析結果全体が与えられて、以降、ブロックごとに取得できるようなので、とりあえず先頭のみ抜き出すようにコードを書いておく。
package info.typea.google.api.cloudvision; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.springframework.stereotype.Service; import com.google.cloud.vision.v1.AnnotateImageRequest; import com.google.cloud.vision.v1.AnnotateImageResponse; import com.google.cloud.vision.v1.BatchAnnotateImagesResponse; import com.google.cloud.vision.v1.EntityAnnotation; import com.google.cloud.vision.v1.Feature; import com.google.cloud.vision.v1.Feature.Type; import com.google.cloud.vision.v1.Image; import com.google.cloud.vision.v1.ImageAnnotatorClient; import com.google.cloud.vision.v1.WebDetection; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.FieldDescriptor; @Service public class CloudVisionService { private static final Logger logger = Logger.getLogger(CloudVisionService.class.getName()); public List<String> getOcrDocumentText(InputStream in) { Map<String, Object> annotateMap = annotateImage(in, Type.DOCUMENT_TEXT_DETECTION); @SuppressWarnings("unchecked") List<String> result = (List<String>)annotateMap.get("description"); return result; } public Map<String, Object> annotateImage(InputStream in, Type type) { Map<String, Object> result = new HashMap<>(); try( DataInputStream reader = new DataInputStream(in); ByteArrayOutputStream writer = new ByteArrayOutputStream(); ImageAnnotatorClient vision = ImageAnnotatorClient.create()) { byte[] buf = new byte[1024]; while(reader.read(buf) >= 0) { writer.write(buf); } byte[] data = writer.toByteArray(); ByteString imgBytes = ByteString.copyFrom(data); List<AnnotateImageRequest> requests = new ArrayList<>(); Image img = Image.newBuilder().setContent(imgBytes).build(); Feature feat = Feature.newBuilder().setType(type).build(); AnnotateImageRequest request = AnnotateImageRequest.newBuilder() .addFeatures(feat) .setImage(img) .build(); requests.add(request); BatchAnnotateImagesResponse response = vision.batchAnnotateImages(requests); List<AnnotateImageResponse> responses = response.getResponsesList(); for(AnnotateImageResponse res : responses) { if (res.hasError()) { System.out.printf("Error:%s\n", res.getError().getMessage()); } switch(type) { case TEXT_DETECTION: case DOCUMENT_TEXT_DETECTION: for (EntityAnnotation annotation : res.getTextAnnotationsList()) { annotation.getAllFields().forEach((k,v) -> { // TODO とりあえず、OCRの解析結果のみ保持。先頭にすべて、以降にブロック?ごとの解析結果が格納されている。 if ("description".equals(k.getName())) { List<String> discriptions = (List<String>)result.get("description"); if (discriptions == null) { discriptions = new ArrayList<>(); } discriptions.add(v.toString()); result.put(k.getName(), discriptions); } }); } break; // TODO 他のタイプ処理 } } } catch(Exception e) { e.printStackTrace(); } return result; } }
2.2 アプリケーションAPI
ファイルのアップロードを受け付けるアプリケーションのAPIから、上記のCloudVisionService を呼び出す。@Autowired アノテーション付与したメンバーとして宣言しておく。
前回JSONで返すためのMapに”text”エントリーを追加する。
@PostMapping("/api/upload-file") public ResponseEntity<Map<String,String>> uploadFile( @RequestHeader(name="Origin", required=false) String origin, @RequestParam("file") MultipartFile file, @RequestParam("fileName") String fileName) { HttpHeaders headers = new HttpHeaders(); headers.add("Access-Control-Allow-Origin", origin); List<String> discriptionList = null; try { discriptionList = cloudVisionService.getOcrDocumentText(file.getInputStream()); } catch(Exception e) { } Map<String,String> result = new HashMap<>(); result.put("message", "file uploaded success."); result.put("fileName", fileName); result.put("text", ((discriptionList == null || discriptionList.isEmpty())?"":discriptionList.get(0))); return new ResponseEntity<>(result, headers, HttpStatus.OK); }
3.Vuetify側の実装
3.1 OCR結果出力領域
UI側で、{{ text }} に画像ファイルをアップロードしOCRをかました結果のテキストを表示させる、エリアを用意。
<pre>{{text}}</pre>
3.2 AxiosによるAjax通信
- UIとバインドする、”text” を、data() に定義
- axios.post(…).then((response) => { …} ) にて、this.text に結果オブジェクトのtext要素をセットする
<script> import axios from 'axios'; export default { name: 'FavoPhrase', data() { return { file: '', fileName: '', text: 'initial annotate.', } }, methods: { sampleAjax : function() { axios.get(process.env.VUE_APP_API_BASE_URL + "/api/hello") .then((res) => { alert("status=" + res.status + ":" + res.data); }); }, handleFileUpload: function(e) { this.file = this.$refs.file.files[0]; if (this.file) { this.fileName = this.file.name; } }, submitFile: function() { let formData = new FormData(); formData.append('file', this.file); formData.append('fileName', this.fileName); console.log(formData); axios.post(process.env.VUE_APP_API_BASE_URL + "/api/upload-file", formData, { headers: { 'Content-Type': 'multipart/formdata' } } ).then((response) => { this.text = response.data.text; console.log(response.data); }).catch((response) => { alert("Faiure!\n" + response); }); } } } </script>
3.動作確認
このブログの先頭部分をキャプチャした、以下の画像を読み込ませてみる。
text.png
おぉ、結果が表示された!
かなり使えそうな結果!