使用ABBYY RTR SDK和django识别Android Things上的文本

你好 我的名字叫Azat Kalmykov,我是经济高等学校计算机科学应用数学和计算机科学系的二年级学生,并且是ABBYY移动开发部门的实习生。 在本文中,我将讨论我的小型项目,该项目是暑期实习的一部分。



想象一下一个小的传送带。 货物或一些细节,识别文本很重要(也许这是一些唯一的标识符,或者可能更有趣)。 包裹就是一个很好的例子。 传送带的操作由操作员进行远程控制,该操作员监视故障并在这种情况下解决问题。 有什么可以帮助他的呢? 基于Android Things平台的设备可能是一个很好的解决方案:它可移动,易于配置并且可以通过Wi-Fi运行。 我们决定尝试使用ABBYY技术,并找出它们如何适用于此类情况-识别“非标准设备”中流中的物联网中的文本。 由于我们只是在构建一个概念,因此我们将故意简化许多事情。 如果您有兴趣,欢迎猫。


Android Things


一项名为Android Things Starter Kit的奇妙事物从Google I / O会议来到了我们的ABBYY办公室。 善良并没有消失,我们想在使用识别库的各种情况下进行探索。 首先,您需要组装我们的设备,然后运行。 做到这一点并不困难,而要严格遵循制造商的指示。


此处此处阅读有关平台的更多信息。


什么进入我的手
图片
在文章结尾,我将向您展示组装好的设备的外观


我们在做什么


我们将为Android Things平台编写一个应用程序,该应用程序将处理来自相机的图像,将识别出的文本和(定期)帧发送到我们的服务器,以便有条件的操作员可以了解管道中正在发生的事情。 服务器将以django编写。


我要立即注意,对于该项目的实施,您不需要任何投资,也不需要注册和SMS(好的,在AWS上,您仍然需要注册并获得免费帐户)。


我们启动 火箭进入太空 伺服器


我们将假定您已经有一个免费的AWS账户。 我们将绑好卡片,以便邪恶的亚马逊在万一我们鲁re的情况下从我们身上扣除了几舍客勒。 我们将使用AWS EC2并在具有SSD卷类型的Ubuntu Server 18.04 LTS(HVM)上创建虚拟机。 对于此操作系统,当使用免费帐户时,只有一种配置可用,请选择它(不用担心,一GB的RAM对我们来说足够了)。 我们将创建一个ssh密钥(或使用一个已经准备好的密钥)并尝试连接到我们的计算机。 我们还将创建弹性IP(类似于静态IP)并将其立即绑定到我们的计算机。 请注意,未绑定到任何虚拟机的弹性IP将在开发过程中花费您金钱。


我们已连接到服务器。 我们在计算机上安装了必要的工具包。


预先安装了Python第三版。 这件事留给了小人们。


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

安装docker,稍后我们将需要它。


 $ 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(自动创建),其他两个名称将是流式传输和上载。 第一个负责在网页上显示信息,第二个负责通过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 /帧-一个网页,将显示结果,有条件地显示操作员正在查看的页面
MYIP:8000 / upload / upload_text--POST请求的地址,发送可识别的文本
MYIP:8000 /上传/ 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'}) 

网页


所有信息都将放在一页上,因此逻辑很简单。


 # 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), ] 

现在我们在consumers.py中实现了FrameConsumer本身


 # 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


我们将使用ABBYYRTR 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; } } // ... } 

使用便利的OkHttp3库在Uploader类中与网络本身进行通信。 它使您可以大大简化与服务器的交互。


结果


我们获得了一个工作正常的客户端-服务器应用程序,该应用程序已获得ABBYY的认可,并内置在物联网中-很棒吗?


我的雇主收集的设备和一个小的本地广告
图片


文字识别
图片


自拍照全景图,概述了几种设备
图片


Vidos,现实生活中的一切



GitHub上的存储库:


AndroidThingsTextRecognition-后端
安卓ThingsTextRecognition-Android


随身携带!

Source: https://habr.com/ru/post/zh-CN432514/


All Articles