Mengenali teks pada Android Things dengan ABBYY RTR SDK dan Django

Hai Nama saya Azat Kalmykov, saya adalah mahasiswa tahun kedua dari Fakultas Matematika dan Ilmu Komputer Terapan Ilmu Komputer di Sekolah Tinggi Ekonomi dan magang di departemen pengembangan ponsel ABBYY. Dalam artikel ini saya akan berbicara tentang proyek kecil saya, yang dilakukan sebagai bagian dari magang musim panas.



Bayangkan konveyor kecil. Barang atau detail menyertainya, yang penting untuk mengenali teks (mungkin ini adalah pengidentifikasi unik, atau mungkin sesuatu yang lebih menarik). Paket adalah contoh yang bagus. Pengoperasian conveyor dikendalikan dari jarak jauh oleh operator yang memonitor kerusakan dan dalam hal ini memecahkan masalah. Apa yang bisa membantunya dengan ini? Perangkat berbasis platform Android Things bisa menjadi solusi yang bagus: perangkat bergerak, mudah dikonfigurasikan, dan dapat bekerja melalui Wi-Fi. Kami memutuskan untuk mencoba menggunakan teknologi ABBYY dan mencari tahu bagaimana mereka cocok untuk situasi seperti itu - pengenalan teks dalam aliran pada "perangkat non-standar" dari kategori Internet of Things. Kami akan dengan mudah menyederhanakan banyak hal, karena kami hanya membangun sebuah konsep. Jika Anda tertarik, selamat datang di kucing.


Android Things


Suatu hal yang luar biasa yang disebut Android Things Starter Kit datang ke kantor ABBYY kami dari konferensi Google I / O. Kebaikan tidak hilang, dan kami ingin bermain dengannya mencari berbagai skenario untuk menggunakan perpustakaan pengenalan kami. Pertama, Anda perlu merakit perangkat kami, dan kemudian jalankan. Tidak sulit melakukan ini, cukup ikuti instruksi dari pabriknya.


Baca lebih lanjut tentang platform di sini dan di sini .


Apa yang datang ke tangan saya
gambar
Dan di akhir posting saya akan menunjukkan kepada Anda seperti apa perangkat yang dirakit itu


Apa yang kita lakukan


Kami akan menulis aplikasi untuk platform Android Things yang akan memproses gambar dari kamera, mengirimkan teks yang dikenali dan (secara berkala) frame ke server kami sehingga operator bersyarat dapat memahami apa yang terjadi pada pipa. Server akan ditulis dalam Django.


Saya segera mencatat bahwa untuk pelaksanaan proyek ini Anda tidak akan memerlukan investasi apa pun, serta pendaftaran dan SMS (oke, di AWS Anda masih perlu mendaftar dan mendapatkan akun gratis).


Kami meluncurkan roket ke luar angkasa server


Kami akan menganggap bahwa Anda sudah memiliki akun AWS gratis. Kami akan mengikat kartu kami sehingga Amazon yang jahat, dalam kasus kecerobohan kami, mengurangi beberapa syikal dari kami. Kami akan menggunakan AWS EC2 dan membuat mesin virtual di Ubuntu Server 18.04 LTS (HVM) dengan Tipe Volume SSD. Untuk OS ini dan saat menggunakan akun gratis, hanya satu konfigurasi yang tersedia, pilih (jangan khawatir, satu gigabyte RAM sudah cukup untuk kami). Kami akan membuat kunci ssh (atau menggunakan yang sudah siap) dan mencoba terhubung ke mesin kami. Kami juga akan membuat IP Elastis (sesuatu seperti IP statis) dan segera mengikatnya ke mesin kami. Perhatikan bahwa IP Elastis, yang tidak terikat pada mesin virtual mana pun, akan dikenakan biaya uang selama pengembangan.


Kami terhubung ke server. Kami menginstal toolkit yang diperlukan pada mesin.


Versi ketiga Python sudah diinstal sebelumnya. Masalahnya diserahkan kepada yang kecil.


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

Instal buruh pelabuhan, kita akan membutuhkannya nanti.


 $ sudo apt-get install docker.io 

Anda juga perlu membuka port 8000. Kami akan menggunakannya saat menggunakan layanan web. Port 22 untuk ssh terbuka secara default.


Hore! Sekarang kami memiliki komputer jarak jauh untuk menjalankan aplikasi kami. Kami akan menulis kode langsung di server.


Django (+ saluran)


Saya memutuskan untuk menggunakan Django, karena ini akan memungkinkan Anda dengan cepat dan mudah membuat layanan web kecil. Pustaka saluran Django tambahan akan memberi kita kesempatan untuk bekerja dengan soket web (yaitu, untuk melakukannya kruk disiarkan dengan mentransfer gambar tanpa menyegarkan halaman).


Buat direktori tempat meletakkan proyek. Instal Django bersama dengan saluran Django, tanpa menyimpang dari instruksi dalam dokumentasi .


 $ 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 

Kami membuat proyek. Kami akan memiliki 3 subdirektori. Yang utama akan memiliki nama yang sama - situs saya (dibuat secara otomatis), dua lainnya - streaming dan mengunggah. Yang pertama akan bertanggung jawab untuk menampilkan informasi pada halaman web, dan yang kedua - untuk mengunduhnya melalui 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 

Konfigurasikan saluran Django. Komentari baris dengan WSGI_APPLICATION dan tambahkan yang baru dengan ASGI_APPLICATION. Sekarang aplikasi kita akan bekerja secara tidak sinkron.


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

Kami juga memperbarui nilai daftar 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', ] # ... 

Arsitektur


Kami akan menulis kode berdasarkan tutorial saluran Django resmi . Struktur layanan kecil kami akan terlihat seperti ini:


MYIP: 8000 / frame - halaman web yang akan menampilkan hasilnya, secara kondisional, halaman yang dilihat operator
MYIP: 8000 / upload / upload_text / - alamat untuk permintaan POST, mengirim teks yang dikenal
MYIP: 8000 / unggah / unggah_image / - alamat untuk permintaan PUT, kirim masing-masing gambar


Penting untuk mendaftarkan logika ini di file urls.py dari direktori yang sesuai.


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

API SISA


Kami beralih ke deskripsi logika API kami.


 # 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'}) 

Halaman web


Semua informasi akan muat pada satu halaman, jadi logikanya akan sederhana.


 # 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', {}) 

Kita perlu menulis dokumen html kecil untuk menampilkan hasilnya. Ini akan memiliki skrip bawaan untuk menghubungkan ke soket web dan mengisinya dengan konten.


 <!-- 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> 

Konfigurasikan perutean, soket


Apa terjemahan terbaik dari kata routing ke bahasa Rusia? Mari kita coba mengeluarkan pertanyaan ini dari pikiran kita dan mengaturnya (atau dia).


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

Sekarang Anda perlu mendaftarkan logika "penerusan" (file routing.py mirip dengan file urls.py, hanya sekarang untuk soket 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), ] 

Dan sekarang kami mengimplementasikan FrameConsumer sendiri di 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'] })) 

Dan akhirnya, dengan telapak tangan yang berkeringat kami luncurkan.


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

Dan sekarang tentang Android


Kami akan menggunakan RTR SDK dari ABBYY untuk pengenalan teks. Paket Ultimate RTR SDK untuk keperluan kita dapat diunduh di sini . Kami menerapkan antarmuka yang cukup sederhana untuk memproses bingkai, aplikasi kami akan didasarkan pada sampel dari arsip yang diunduh dari tautan sebelumnya (/ sample-textcapture). Kami akan membuang kelebihan komponen dari aplikasi, sedikit memolesnya untuk bekerja secara khusus dengan Android Things, dan mengimplementasikan komunikasi dengan server.


File perpustakaan .aar terletak di direktori libs dari arsip yang diunduh, kami impor. Kami menyalin isi direktori aset arsip (pada dasarnya ada file yang diperlukan untuk proses pengakuan itu sendiri) ke dalam aset proyek kami. Di sana kami menyalin file lisensi dari arsip, tanpa itu aplikasi tidak akan memulai.


Untuk mengimplementasikan fungsi ABBYY RTR SDK yang kita butuhkan, kita perlu membuat objek bertipe Engine, dan menggunakannya sudah menjadi objek bertipe ITextCaptureService, yang akan kita luncurkan nanti.


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

Dalam hal ini, Anda harus melewati objek bertipe ITextCaptureService.Callback, buat langsung di kelas MainActivity kami, ia harus menerapkan 3 metode.


 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) { //    } }; 

Kami telah mendelegasikan penerimaan bingkai ke objek kamera. Saya akan menunjukkan apa yang terjadi di dalam.


 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); } }; 

Untuk mengirim pesan, kami menulis beberapa kelas, yang pada gilirannya akan mendelegasikan pekerjaan mereka ke objek kelas Pengunggah.


 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; } } // ... } 

Komunikasi dengan jaringan itu sendiri di kelas Pengunggah diimplementasikan menggunakan pustaka OkHttp3 yang nyaman. Ini memungkinkan Anda untuk sangat menyederhanakan interaksi dengan server.


Hasil


Kami mendapatkan aplikasi client-server yang berfungsi dengan pengakuan dari ABBYY, dibangun di Internet of Things - bukankah itu keren?


Perangkat yang dikumpulkan dan iklan lokal kecil dari perusahaan saya
gambar


Teks dikenali
gambar


Panorama selfie dengan gambaran umum beberapa perangkat
gambar


Vidos, bagaimana semua itu bisa terlihat dalam kehidupan nyata



Repositori di github:


β†’ AndroidThingsTextRecognition-Backend
β†’ AndroidThingsTextRecognition-Android


Ambil dan gunakan!

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


All Articles