ABBYY RTR SDKとdjangoを使用したAndroid Things上のテキストの認識

こんにちは 私の名前はAzat Kalmykovです。私は、高等経済学部のコンピューターサイエンス学部の応用数学およびコンピューターサイエンスの 2年生で、ABBYYのモバイル開発部門のインターンです。 この記事では、夏のインターンシップの一環として完了した私の小さなプロジェクトについてお話します。









小さなコンベアを想像してください。 商品や詳細がそれに沿って移動します。その上で、テキストを認識することが重要です(おそらく、これは何らかの一意の識別子であるか、おそらくもっと興味深いものです)。 区画は良い例です。 コンベヤーの動作は、誤動作を監視し、その場合に問題を解決するオペレーターによって遠隔制御されます。 これで彼を助けることができるものは何ですか? Android Thingsプラットフォームに基づくデバイスは、優れたソリューションです。モバイルであり、構成が簡単で、Wi-Fi経由で動作します。 ABBYYテクノロジーを使用して、それらがそのような状況に適しているかどうかを調べることにしました。 モノのインターネットのカテゴリからの「非標準デバイス」のストリーム内のテキストの認識です。 概念を構築しているだけなので、多くのことを意図的に単純化します。 興味があれば、猫にようこそ。







Android Things



Android Things Starter Kitという素晴らしいものが、 Google I / OカンファレンスからABBYYオフィスに届きました。 良さが消えることはありませんでした。 認識ライブラリを使用するためのさまざまなシナリオを探して試してみました 。 まず、デバイスを組み立ててから実行する必要があります。 これを行うのは難しくありません。メーカーの指示に厳密に従うだけで十分です。







プラットフォームの詳細については、 こちらこちらをご覧ください







私の手に入ったもの

画像

そして、投稿の最後に、組み立てられたデバイスがどのように見えるかを示します







何してるの?



カメラからの画像を処理し、認識されたテキストと(定期的に)フレームをサーバーに送信するAndroid Thingsプラットフォーム用のアプリケーションを作成して、条件演算子がパイプラインで何が起こっているかを理解できるようにします。 サーバーはdjangoで作成されます。







このプロジェクトの実装には、登録とSMSだけでなく投資も必要ないことに注意してください(AWSでは、登録して無料アカウントを取得する必要があります)。







打ち上げ 宇宙へのロケット サーバー



すでに無料のAWSアカウントを持っていると仮定します。 邪悪なアマゾンが私たちの無謀さの場合に、私たちから数シェケルを差し引くようにカードを結びます。 AWS EC2を使用し、SSDボリュームタイプを使用してUbuntu Server 18.04 LTS(HVM)上に仮想マシンを作成します。 このOSの場合、無料のアカウントを使用する場合、使用できる構成は1つだけなので、それを選択します(心配しないで、1ギガバイトのRAMで十分です)。 sshキーを作成(または既製のキーを使用)し、マシンへの接続を試みます。 また、Elastic IP(静的IPのようなもの)を作成し、すぐにマシンにバインドします。 仮想マシンに関連付けられていないElastic IPは、開発中に費用がかかります。







サーバーに接続しています。 必要なツールキットをマシンにインストールします。







Pythonの第3バージョンがプリインストールされています。 問題は小さなものに任されています。







$ sudo apt-get update $ sudo apt-get install python3-pip $ sudo pip3 install virtualenv
      
      





ドッカーをインストールします。後で必要になります。







 $ sudo apt-get install docker.io
      
      





また、ポート8000​​を開く必要があります。Webサービスを使用するときに使用します。 sshのポート22はデフォルトで開いています。







やった! これで、アプリケーションを実行するリモートコンピューターができました。 サーバー上でコードを直接記述します。







Django(+チャネル)



djangoを使用することにしました。これにより、小さなWebサービスをすばやく簡単に作成できるようになります。 追加のdjangoチャネルライブラリにより、Webソケットを操作する機会が与えられます(つまり、 松葉杖 ページを更新せずに画像を転送することでブロードキャストします)。







プロジェクトを配置するディレクトリを作成します。 ドキュメントの指示から逸脱することなく、djangoをdjangoチャンネルと一緒にインストールします







 $ mkdir Project $ cd Project $ virtualenv venv $ source venv/bin/activate $ pip install -U channels #       django $ pip install channels_redis #    Redis $ pip install djangorestframework $ django-admin startproject mysite $ cd mysite
      
      





プロジェクトを作成します。 3つのサブディレクトリがあります。 メインの名前は同じ名前-mysite(自動的に作成)、他の2つはストリーミングとアップロードになります。 1つ目はWebページに情報を表示し、2つ目はREST APIを介して情報をダウンロードします。







 $ python3 manage.py startapp streaming $ cd streaming $ rm -r migrations admin.py apps.py models.py tests.py $ cd .. $ python3 manage.py startapp uploading $ cd uploading $ rm -r migrations admin.py apps.py models.py tests.py
      
      





djangoチャンネルを設定します。 WSGI_APPLICATIONの行をコメント化し、ASGI_APPLICATIONの新しい行を追加します。 これで、アプリケーションは非同期に動作します。







 # mysite/settings.py # ... # WSGI_APPLICATION = ... ASGI_APPLICATION = 'mysite.routing.application' # ...
      
      





INSTALLED_APPSリストの値も更新します。







 # mysite/settings.py # ... INSTALLED_APPS = [ 'channels', 'streaming', 'uploading', 'rest_framework', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] # ...
      
      





建築



公式の djangoチャンネルチュートリアルに基づいてコードを記述します。 小さなサービスの構造は次のようになります。







MYIP:8000 /フレーム-結果を、条件付きで、オペレーターが見ているページを表示するWebページ

MYIP:8000 / upload / upload_text /-POSTリクエストのアドレス、認識されたテキストの送信

MYIP:8000 / upload / upload_image /-個々の画像を送信するPUTリクエストのアドレス







このロジックを、対応するディレクトリのurls.pyファイルに登録する必要があります。







 # mysite/mysite/urls.py from django.contrib import admin from django.conf.urls import include, url urlpatterns = [ url(r'^frame/', include('streaming.urls')), url(r'^upload/', include('uploading.urls')), ]
      
      





REST API



APIのロジックの説明に戻ります。







 # mysite/uploading/urls.py from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns from . import views urlpatterns = [ url(r'^upload_text/$', views.UploadTextView.as_view()), url(r'^upload_image/$', views.UploadImageView.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns)
      
      





 # mysite/uploading/views.py from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from channels.layers import get_channel_layer from rest_framework.parsers import FileUploadParser from asgiref.sync import async_to_sync import base64 # Create your views here. class UploadTextView(APIView): def post(self, request, format=None): message = request.query_params['message'] if not message: raise ParseError("Empty content") channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)("chat", { "type": "chat.message", "message": message, }) return Response({'status': 'ok'}) class UploadImageView(APIView): parser_class = (FileUploadParser,) def put(self, request, format=None): if 'file' not in request.data: raise ParseError("Empty content") f = request.data['file'] channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)("chat", { "type": "chat.message", "image64": base64.b64encode(f.read()).decode("ascii"), }) return Response({'status': 'ok'})
      
      





ウェブページ



すべての情報は1ページに収まるため、ロジックは単純です。







 # mysite/streaming/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^', views.index, name='index'), ]
      
      





 # mysite/streaming/views.py from django.shortcuts import render from django.utils.safestring import mark_safe import json # Create your views here. def index(request): return render(request, 'index.html', {})
      
      





結果を表示するには、小さなHTMLドキュメントを作成する必要があります。 Webソケットに接続してコンテンツを入力するための組み込みスクリプトが含まれます。







 <!-- mysite/streaming/templates/index.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Live from Android Things</title> </head> <body> <textarea id="chat-log" cols="100" rows="20"></textarea><br/> <img id="frame"> </body> <script> var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; var image64 = data['image64']; if (image64) { document.querySelector('#frame').setAttribute( 'src', 'data:image/png;base64,' + image64 ); } else if (message) { document.querySelector('#chat-log').value += (message + '\n'); } }; chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; </script> </html>
      
      





ルーティング、ソケットを構成する



ロシア語への単語ルーティングの最良の翻訳は何ですか? この質問を頭から出して、設定するだけです(または彼女)。







 # mysite/mysite/settings.py # ... ALLOWED_HOSTS = ['*'] #  []  ['*'],    # ... CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('127.0.0.1', 6379)], }, }, }
      
      





ここで、「転送」ロジックを登録する必要があります(routing.pyファイルはurls.pyファイルに似ていますが、現在はWebソケットのみ)。







 # mysite/mysite/routing.py from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import streaming.routing application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( streaming.routing.websocket_urlpatterns ) ), })
      
      





 # mysite/streaming/routing.py from django.conf.urls import url from . import consumers websocket_urlpatterns = [ url(r'^ws/chat/$', consumers.FrameConsumer), ]
      
      





そして今、FrameConsumer自体をconsumer.pyに実装します







 # mysite/streaming/consumers.py from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer, JsonWebsocketConsumer import json class FrameConsumer(WebsocketConsumer): def connect(self): self.room_group_name = 'chat' # Join room group async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name ) self.accept() def disconnect(self, close_code): # Leave room group async_to_sync(self.channel_layer.group_discard)( self.room_group_name, self.channel_name ) # Receive message from WebSocket def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] # Send message to room group async_to_sync(self.channel_layer.group_send)( self.room_group_name, { 'type': 'chat_message', 'message': message } ) # Receive message from room group def chat_message(self, event): if 'message' in event: # Send message to WebSocket self.send(text_data=json.dumps({ 'message': event['message'] })) elif 'image64' in event: self.send(text_data=json.dumps({ 'image64': event['image64'] }))
      
      





そして最後に、汗ばんだ手のひらで打ち上げます。







 $ docker run -p 6379:6379 -d redis:2.8 $ python manage.py runserver 0.0.0.0:8000
      
      





そして今、Androidについて



テキスト認識にはABTRYY RTR SDKを使用します。 私たちの目的のためのUltimate pack RTR SDKはここからダウンロードできます 。 フレームを処理するためのかなりシンプルなインターフェイスを実装します。アプリケーションは、前のリンク(/ sample-textcapture)からダウンロードしたアーカイブのサンプルに基づいています。 アプリケーションから余分な部分を取り除き、Android Thingsで特に動作するように少し磨き、サーバーとの通信を実装します。







ライブラリの.aarファイルは、ダウンロードしたアーカイブのlibsディレクトリにあり、インポートします。 アーカイブのアセットディレクトリの内容(基本的に認識プロセス自体に必要なファイル)をプロジェクトのアセットにコピーします。 そこで、アーカイブからライセンスファイルをコピーします。コピーしないと、アプリケーションは起動しません。







必要なABBYY RTR SDK機能を実装するには、Engineタイプのオブジェクトを作成し、それをすでにITextCaptureServiceタイプのオブジェクトとして使用する必要があります。これは後で起動します。







 try { mEngine = Engine.load(this, LICENSE_FILE_NAME); mTextCaptureService = mEngine.createTextCaptureService(textCaptureCallback); return true; } // ...
      
      





この場合、ITextCaptureService.Callback型のオブジェクトを渡し、MainActivityクラスで直接作成する必要があります。3つのメソッドを実装する必要があります。







 private ITextCaptureService.Callback textCaptureCallback = new ITextCaptureService.Callback() { @Override public void onRequestLatestFrame(byte[] buffer) { //  ,       . //    . mCamera.addCallbackBuffer(buffer); } @Override public void onFrameProcessed( ITextCaptureService.TextLine[] lines, ITextCaptureService.ResultStabilityStatus resultStatus, ITextCaptureService.Warning warning) { //      ,    if (resultStatus.ordinal() >= 3) { //   ,     mSurfaceViewWithOverlay.setLines(lines, resultStatus); } else { //  ,     mSurfaceViewWithOverlay.setLines(null, ITextCaptureService.ResultStabilityStatus.NotReady); } //  warnings // ... } @Override public void onError(Exception e) { //    } };
      
      





フレームの受信をカメラオブジェクトに委任しました。 内部で何が起こっているかを示します。







 private Camera.PreviewCallback cameraPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { //     (    ) if (!mIsUploading) { mIsUploading = true; //    new UploadImageTask(mCameraPreviewSize.width, mCameraPreviewSize.height).execute(data); } //     mTextCaptureService.submitRequestedFrame(data); } };
      
      





メッセージを送信するために、いくつかのクラスを作成し、それらのクラスの作業をUploaderクラスのオブジェクトに委任します。







 public static class UploadTextTask extends AsyncTask<String, Void, Void> { @Override protected Void doInBackground(String... params) { mUploader.uploadText(params[0]); return null; } } public static class UploadImageTask extends AsyncTask<byte[], Void, Void> { private int mCameraPreviewWidth; private int mCameraPreviewHeight; public UploadImageTask(int width, int height) { mCameraPreviewWidth = width; mCameraPreviewHeight = height; } @Override protected Void doInBackground(final byte[]... params) { byte[] jpegBytes = convertToJpegBytes(params[0]); if (jpegBytes != null) { mUploader.uploadImage(jpegBytes); } return null; } private byte[] convertToJpegBytes(byte[] rawBytes) { YuvImage yuvImage = new YuvImage( rawBytes, ImageFormat.NV21, mCameraPreviewWidth, mCameraPreviewHeight, null ); try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { yuvImage.compressToJpeg( new Rect(0, 0, mCameraPreviewWidth, mCameraPreviewHeight), 40, os ); return os.toByteArray(); } catch (IOException e) { Log.d(TAG, "compress error"); return null; } } // ... }
      
      





Uploaderクラスのネットワークとの通信は、便利なOkHttp3ライブラリを使用して実装されます。 サーバーとの対話を大幅に簡素化できます。







結果



モノのインターネットに組み込まれたABBYYの認識機能を備えた動作するクライアントサーバーアプリケーションを取得します。







組み立てられたデバイスと私の雇用者の小さなネイティブ広告

画像







認識されたテキスト

画像







いくつかのデバイスの概要を含むSelfieパノラマ

画像







Vidos、実生活でどのように見えるか









githubのリポジトリ:







AndroidThingsTextRecognition-Backend

AndroidThingsTextRecognition-Android







取って使用してください!








All Articles