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


取っお䜿甚しおください



Source: https://habr.com/ru/post/J432514/


All Articles