مرحبا اسمي عزت كالميكوف ، أنا طالبة في السنة الثانية بكلية الرياضيات التطبيقية وعلوم الكمبيوتر لعلوم الكمبيوتر في المدرسة العليا للاقتصاد ومتدربة في قسم تطوير الأجهزة المحمولة في ABBYY. سأتحدث في هذا المقال عن مشروعي الصغير ، الذي تم تنفيذه كجزء من التدريب الصيفي.

تخيل ناقل صغير. تتماشى البضائع أو بعض التفاصيل مع ذلك ، من المهم التعرف على النص (ربما يكون هذا هو المعرف الفريد ، أو ربما شيء أكثر إثارة للاهتمام). الطرود هي مثال جيد. يتم التحكم في تشغيل الناقل عن بعد بواسطة مشغل يراقب الأعطال وفي هذه الحالة يحل المشكلات. ما الذي يمكن أن يساعده في ذلك؟ يمكن أن يكون أي جهاز يستند إلى نظام Android Things حلاً جيدًا: إنه جهاز محمول وسهل التكوين ويمكنه العمل عبر شبكة Wi-Fi. قررنا تجربة استخدام تقنيات ABBYY ومعرفة مدى ملاءمتها لمثل هذه المواقف - التعرف على النص في دفق على "الأجهزة غير القياسية" من فئة Internet of Things. سنقوم بتبسيط العديد من الأشياء عن عمد ، حيث أننا بصدد بناء مفهوم ما. إذا كنت مهتما ، مرحبا بكم في القط.
أشياء Android
جاء شيء رائع يسمى Android Things Starter Kit إلى مكتبنا ABBYY من مؤتمر I / O الخاص بـ Google . لم يختف الخير ، وأردنا اللعب معه بحثًا عن سيناريوهات مختلفة لاستخدام مكتبات التقدير الخاصة بنا. تحتاج أولاً إلى تجميع الجهاز الخاص بنا ، ثم تشغيله. ليس من الصعب القيام بذلك ، بل اتبع إرشادات الشركة المصنعة بدقة.
قراءة المزيد عن النظام الأساسي هنا وهنا .
ما جاء في يدي

وفي نهاية المنشور ، سأريك كيف يبدو الجهاز المجمع
ماذا نفعل؟
سنكتب تطبيقًا لنظام Android Things الذي سيعالج الصورة من الكاميرا ، ويرسل نصًا معترفًا به وإطارات (بشكل دوري) إلى خادمنا حتى يتمكن المشغل الشرطي من فهم ما يحدث على خط الأنابيب. سيتم كتابة الخادم في جانغو.
أسارع إلى ملاحظة أنه لتنفيذ هذا المشروع لن تحتاج إلى أي استثمارات ، بالإضافة إلى التسجيل والرسائل النصية القصيرة (حسناً ، في AWS ما زلت بحاجة إلى التسجيل والحصول على حساب مجاني).
نطلق صاروخ في الفضاء الخادم
سنفترض أن لديك بالفعل حساب AWS مجاني. سنقوم بربط ورقتنا بحيث يقوم الأمازون الشرير ، في حالة تهورنا ، بخصم شيكل منا. سوف نستخدم AWS EC2 وإنشاء جهاز افتراضي على Ubuntu Server 18.04 LTS (HVM) مع نوع وحدة تخزين SSD. بالنسبة لنظام التشغيل هذا ، وعند استخدام حساب مجاني ، يتوفر تكوين واحد فقط ، حدده (لا تقلق ، غيغابايت من ذاكرة الوصول العشوائي كافية لنا). سنقوم بإنشاء مفتاح ssh (أو استخدام واحد جاهز بالفعل) وسنحاول الاتصال بجهازنا. سنقوم أيضًا بإنشاء عنوان IP مرن (يشبه عنوان IP ثابت) ونربطه على الفور مع الجهاز الخاص بنا. لاحظ أن تطبيق IP المرن ، غير المرتبط بأي جهاز ظاهري ، سيكلفك المال أثناء التطوير.
نحن متصلون بالخادم نقوم بتثبيت مجموعة الأدوات اللازمة على الجهاز.
تم تثبيت Python الإصدار الثالث مسبقًا. الأمر متروك للصغير.
$ sudo apt-get update $ sudo apt-get install python3-pip $ sudo pip3 install virtualenv
تثبيت عامل ميناء ، ونحن في حاجة إليها في وقت لاحق.
$ sudo apt-get install docker.io
تحتاج أيضًا إلى فتح المنفذ 8000. سنستخدمه عند استخدام خدمة الويب. المنفذ 22 لـ ssh مفتوح افتراضيًا.
الصيحة! الآن لدينا جهاز كمبيوتر عن بعد لتشغيل تطبيقاتنا. سنكتب الرمز مباشرة على الخادم.
جانغو (+ قنوات)
قررت استخدام django ، لأن هذا سيسمح لك بسرعة وسهولة بإنشاء خدمة ويب صغيرة. ستتيح لنا مكتبة قنوات django إضافية الفرصة للعمل مع مآخذ الويب (أي ، القيام به) عكاز البث عن طريق نقل الصور دون تحديث الصفحة).
إنشاء دليل لوضع المشروع. قم بتثبيت django جنبًا إلى جنب مع قنوات django ، دون الخروج عن الإرشادات الواردة في الوثائق .
$ mkdir Project $ cd Project $ virtualenv venv $ source venv/bin/activate $ pip install -U channels
نخلق مشروع. سيكون لدينا 3 الدلائل الفرعية. سيكون للاسم الرئيسي نفس الاسم - mysite (تم إنشاؤه تلقائيًا) ، والآخر ثنائي التدفق والتحميل. الأول سيكون مسؤولاً عن عرض المعلومات على صفحة ويب ، والثاني - عن تنزيلها من خلال واجهة برمجة تطبيقات REST.
$ 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 / frame - صفحة ويب تعرض النتيجة ، المشروطة ، الصفحة التي ينظر إليها المشغل
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'})
صفحة الويب
سيتم احتواء جميع المعلومات في صفحة واحدة ، وبالتالي فإن المنطق سيكون بسيطًا.
# 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 صغير لعرض النتائج. سيكون به برنامج نصي مضمن للاتصال بمقبس ويب وملئه بالمحتوى.
<!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 ، والآن فقط لمآخذ توصيل الويب).
# 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 نفسه في مستهلكي
# 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
سنستخدم RTR SDK من ABBYY للتعرف على النص. يمكن تنزيل حزمة 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) {
لإرسال رسائل ، نكتب بضع فصول ، والتي بدورها ستفوض عملها إلى كائن من فئة برنامج التحميل.
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 ، مدمج في Internet of Things - أليس كذلك؟
جهاز تم جمعه وإعلان محلي صغير لصاحب العمل

النص المعترف بها

بانوراما صورة شخصية مع لمحة عامة عن العديد من الأجهزة

فيدوس ، كيف يمكن أن تبدو جميع في الحياة الحقيقية
مستودعات على جيثب:
→ AndroidThingsTextRecognition-Backend
→ AndroidThingsTextRecognition-Android
خذ واستخدم!