ããã«ã¡ã¯ ç§ã®ååã¯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
ãããžã§ã¯ããäœæããŸãã 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ãœã±ããã«æ¥ç¶ããŠã³ã³ãã³ããå
¥åããããã®çµã¿èŸŒã¿ã¹ã¯ãªãããå«ãŸããŸãã
<!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) {
ãã¬ãŒã ã®åä¿¡ãã«ã¡ã©ãªããžã§ã¯ãã«å§ä»»ããŸããã å
éšã§äœãèµ·ãã£ãŠãããã瀺ããŸãã
private Camera.PreviewCallback cameraPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) {
ã¡ãã»ãŒãžãéä¿¡ããããã«ãããã€ãã®ã¯ã©ã¹ãäœæãããããã®ã¯ã©ã¹ã®äœæ¥ã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
åã£ãŠäœ¿çšããŠãã ããïŒ