يمكنك استخدام مجموعة أدوات تعلُّم الآلة للتعرّف على النص في الصور أو الفيديو، مثل نصوص لافتة الشارع. في ما يلي الخصائص الرئيسية لهذه الميزة:
الميزة | غير مجمّعة | مُجمَّعة |
---|---|---|
اسم المكتبة | com.google.android.gms:play-services-mlkit-text-recognition
com.google.android.gms:play-services-mlkit-text-recognition-chinese com.google.android.gms:play-services-mlkit-text-recognition-devanagari com.google.android.gms:play-services-mlkit-text-recognition-japanese com.google.android.gms:play-services-mlkit-text-recognition-korean |
com.google.mlkit:text-recognition
com.google.mlkit:text-recognition-chinese com.google.mlkit:text-recognition-devanagari com.google.mlkit:text-recognition-japanese com.google.mlkit:text-recognition-korean |
التنفيذ | يتم تنزيل النموذج ديناميكيًا من خلال "خدمات Google Play". | يتم ربط النموذج بتطبيقك بشكل ثابت في وقت الإنشاء. |
حجم التطبيق | زيادة حجم تبلغ 260 كيلوبايت تقريبًا لكل بنية نص برمجي | زيادة حجم تبلغ 4 ميغابايت تقريبًا لكل نص برمجي لكل بنية |
وقت الإعداد | قد تحتاج إلى الانتظار إلى أن يتم تنزيل النموذج قبل استخدامه لأول مرة. | يتوفّر الطراز على الفور. |
الأداء | عرض فوري على معظم الأجهزة لمكتبة النصوص اللاتينية، ويكون أبطأ بالنسبة إلى الأجهزة الأخرى | يتم عرض مكتبة الأحرف اللاتينية في الوقت الفعلي على معظم الأجهزة، ولكن ببطء أكبر على الأجهزة الأخرى. |
جرّبه الآن
- يمكنك استخدام نموذج التطبيق للاطّلاع على مثال على استخدام واجهة برمجة التطبيقات هذه.
- جرب الرمز بنفسك باستخدام درس تطبيقي حول الترميز.
قبل البدء
- في ملف
build.gradle
على مستوى المشروع، احرص على تضمين مستودع Maven من Google في كلّ من القسمَينbuildscript
وallprojects
. أضِف الملحقات لمكتبات ML Kit لنظام التشغيل Android إلى ملف Gradle على مستوى التطبيق الخاص بالوحدة، والذي يكون عادةً
app/build.gradle
:لتجميع النموذج مع تطبيقك:
dependencies { // To recognize Latin script implementation 'com.google.mlkit:text-recognition:16.0.1' // To recognize Chinese script implementation 'com.google.mlkit:text-recognition-chinese:16.0.1' // To recognize Devanagari script implementation 'com.google.mlkit:text-recognition-devanagari:16.0.1' // To recognize Japanese script implementation 'com.google.mlkit:text-recognition-japanese:16.0.1' // To recognize Korean script implementation 'com.google.mlkit:text-recognition-korean:16.0.1' }
لاستخدام النموذج في "خدمات Google Play":
dependencies { // To recognize Latin script implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.1' // To recognize Chinese script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.1' // To recognize Devanagari script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-devanagari:16.0.1' // To recognize Japanese script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.1' // To recognize Korean script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.1' }
إذا اخترت استخدام النموذج في "خدمات Google Play"، يمكنك ضبط تطبيقك لتنزيل النموذج تلقائيًا على الجهاز بعد تثبيت تطبيقك من "متجر Play". لإجراء ذلك، أضِف ما يلي: بيان في ملف
AndroidManifest.xml
في تطبيقك:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="ocr" > <!-- To use multiple models: android:value="ocr,ocr_chinese,ocr_devanagari,ocr_japanese,ocr_korean,..." --> </application>
يمكنك أيضًا التحقّق صراحةً من مدى توفّر النموذج وطلب تنزيل التطبيق. من خلال ModuleInstallClient API في "خدمات Google Play". إذا لم تفعِّل تنزيلات النموذج في وقت التثبيت أو طلبت تنزيلًا صريحًا، يتم تنزيل النموذج في المرة الأولى التي تشغّل فيها الماسح الضوئي. لا تؤدي الطلبات التي تقدّمها قبل اكتمال عملية التنزيل إلى أي نتائج.
1. إنشاء مثيل من TextRecognizer
أنشئ مثيلًا من TextRecognizer
، مع تمرير الخيارات
المرتبطة بالمكتبة التي أعلنت تبعيتك لها أعلاه:
Kotlin
// When using Latin script library val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) // When using Chinese script library val recognizer = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build()) // When using Devanagari script library val recognizer = TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build()) // When using Japanese script library val recognizer = TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build()) // When using Korean script library val recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
Java
// When using Latin script library TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS); // When using Chinese script library TextRecognizer recognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build()); // When using Devanagari script library TextRecognizer recognizer = TextRecognition.getClient(new DevanagariTextRecognizerOptions.Builder().build()); // When using Japanese script library TextRecognizer recognizer = TextRecognition.getClient(new JapaneseTextRecognizerOptions.Builder().build()); // When using Korean script library TextRecognizer recognizer = TextRecognition.getClient(new KoreanTextRecognizerOptions.Builder().build());
2. تجهيز صورة الإدخال
للتعرّف على نص في صورة، أنشئ عنصر InputImage
من
Bitmap
أو media.Image
أو ByteBuffer
أو صفيف بايت أو ملف على
الجهاز. بعد ذلك، نقْل عنصر InputImage
إلى الطريقة processImage
في ملف
TextRecognizer
.
يمكنك إنشاء عنصر InputImage
من مصادر مختلفة، وسيتم شرح كل مصدر أدناه.
استخدام media.Image
لإنشاء InputImage
كائن من كائن media.Image
، مثلاً عند التقاط صورة من
كاميرا الجهاز، فما عليك سوى تمرير الكائن media.Image
تدوير إلى InputImage.fromMediaImage()
.
إذا كنت تستخدِم مكتبة
CameraX، تحتسِب فئتَا OnImageCapturedListener
و
ImageAnalysis.Analyzer
قيمة التدوير
نيابةً عنك.
Kotlin
private class YourImageAnalyzer : ImageAnalysis.Analyzer { override fun analyze(imageProxy: ImageProxy) { val mediaImage = imageProxy.image if (mediaImage != null) { val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) // Pass image to an ML Kit Vision API // ... } } }
Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { @Override public void analyze(ImageProxy imageProxy) { Image mediaImage = imageProxy.getImage(); if (mediaImage != null) { InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees()); // Pass image to an ML Kit Vision API // ... } } }
إذا كنت لا تستخدم مكتبة كاميرا تمنحك درجة دوران الصورة، يمكنك احتسابها من درجة دوران الجهاز واتجاه كاميرا الاستشعار في الجهاز:
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 0) ORIENTATIONS.append(Surface.ROTATION_90, 90) ORIENTATIONS.append(Surface.ROTATION_180, 180) ORIENTATIONS.append(Surface.ROTATION_270, 270) } /** * Get the angle by which an image must be rotated given the device's current * orientation. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Throws(CameraAccessException::class) private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int { // Get the device's current rotation relative to its "native" orientation. // Then, from the ORIENTATIONS table, look up the angle the image must be // rotated to compensate for the device's rotation. val deviceRotation = activity.windowManager.defaultDisplay.rotation var rotationCompensation = ORIENTATIONS.get(deviceRotation) // Get the device's sensor orientation. val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360 } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360 } return rotationCompensation }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 0); ORIENTATIONS.append(Surface.ROTATION_90, 90); ORIENTATIONS.append(Surface.ROTATION_180, 180); ORIENTATIONS.append(Surface.ROTATION_270, 270); } /** * Get the angle by which an image must be rotated given the device's current * orientation. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing) throws CameraAccessException { // Get the device's current rotation relative to its "native" orientation. // Then, from the ORIENTATIONS table, look up the angle the image must be // rotated to compensate for the device's rotation. int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int rotationCompensation = ORIENTATIONS.get(deviceRotation); // Get the device's sensor orientation. CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360; } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360; } return rotationCompensation; }
بعد ذلك، مرِّر عنصر media.Image
وقيمة
درجة الدوران إلى InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
استخدام عنوان URL للملف
لإنشاء InputImage
من معرف موارد منتظم (URI) لملف، فمرر سياق التطبيق ومعرف الموارد المنتظم (URI) للملف إلى
InputImage.fromFilePath()
يكون ذلك مفيدًا عند
استخدام نية ACTION_GET_CONTENT
لطلب تحديد
صورة من تطبيق معرض الصور.
Kotlin
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
Java
InputImage image; try { image = InputImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
استخدام ByteBuffer
أو ByteArray
لإنشاء عنصر InputImage
من ByteBuffer
أو ByteArray
، يجب أولاً احتساب درجة دوران
الصورة كما هو موضّح سابقًا لإدخال media.Image
.
بعد ذلك، أنشئ عنصر InputImage
باستخدام المخزن المؤقت أو الصفيف، بالإضافة إلى
ارتفاع الصورة وعرضها وتنسيق ترميز الألوان ودرجة دورانها:
Kotlin
val image = InputImage.fromByteBuffer( byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ) // Or: val image = InputImage.fromByteArray( byteArray, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 )
Java
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ); // Or: InputImage image = InputImage.fromByteArray( byteArray, /* image width */480, /* image height */360, rotation, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
استخدام Bitmap
لإنشاء عنصر InputImage
من عنصر Bitmap
، أدخِل التعريف التالي:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
يتم تمثيل الصورة بواسطة كائن Bitmap
مع درجات التدوير.
3- معالجة الصورة
نقْل الصورة إلى طريقة process
:
Kotlin
val result = recognizer.process(image) .addOnSuccessListener { visionText -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
Task<Text> result = recognizer.process(image) .addOnSuccessListener(new OnSuccessListener<Text>() { @Override public void onSuccess(Text visionText) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. استخراج النص من مجموعات النصوص التي تم التعرّف عليها
في حال نجاح عملية التعرّف على النص، يتم تمرير عنصر Text
إلى
مُستمع الحدث الناجح. يحتوي عنصر Text
على النص الكامل الذي تم التعرّف عليه في
الصورة وصفر أو أكثر من عناصر TextBlock
.
يمثّل كل رمز TextBlock
كتلة مستطيلة من النص،
التي تحتوي على صفر أو أكثر من كائنات Line
. على كل
يمثل الكائن Line
سطرًا من النص يحتوي على صفرًا
أو أكثر من كائنات Element
. كل Element
يمثل كائن كلمة أو كيانًا شبيهًا بالكلمة، وتحتوي على صفر أو أكثر
كائنات Symbol
. كل Symbol
يمثل كائنًا حرفًا أو رقمًا أو كيانًا شبيهًا بالكلمة.
لكل TextBlock
وLine
Element
وSymbol
، أنت
يمكننا التعرف على النص في المنطقة، وإحداثيات المحيط
المنطقة والعديد من السمات الأخرى مثل معلومات الدوران ودرجة الثقة
وما إلى ذلك
على سبيل المثال:
Kotlin
val resultText = result.text for (block in result.textBlocks) { val blockText = block.text val blockCornerPoints = block.cornerPoints val blockFrame = block.boundingBox for (line in block.lines) { val lineText = line.text val lineCornerPoints = line.cornerPoints val lineFrame = line.boundingBox for (element in line.elements) { val elementText = element.text val elementCornerPoints = element.cornerPoints val elementFrame = element.boundingBox } } }
Java
String resultText = result.getText(); for (Text.TextBlock block : result.getTextBlocks()) { String blockText = block.getText(); Point[] blockCornerPoints = block.getCornerPoints(); Rect blockFrame = block.getBoundingBox(); for (Text.Line line : block.getLines()) { String lineText = line.getText(); Point[] lineCornerPoints = line.getCornerPoints(); Rect lineFrame = line.getBoundingBox(); for (Text.Element element : line.getElements()) { String elementText = element.getText(); Point[] elementCornerPoints = element.getCornerPoints(); Rect elementFrame = element.getBoundingBox(); for (Text.Symbol symbol : element.getSymbols()) { String symbolText = symbol.getText(); Point[] symbolCornerPoints = symbol.getCornerPoints(); Rect symbolFrame = symbol.getBoundingBox(); } } } }
إرشادات حول إدخال الصور
-
لكي تتمكّن حزمة ML Kit من التعرّف على النص بدقة، يجب أن تحتوي صور الإدخال على نص يمثّله بيانات بكسل كافية. من الناحية المثالية، يجب ألا يقلّ حجم كل حرف عن 16×16 بكسل. ولا يوجد عمومًا على الدقة المحدد للأحرف الأكبر من 24×24 بكسل.
لذلك، على سبيل المثال، قد تعمل صورة بحجم 640×480 جيدًا لمسح بطاقة عمل ضوئيًا تشغل العرض الكامل للصورة لإجراء مسح ضوئي لمستند مطبوع على ورق بحجم حرف، فقد يلزم صورة 720×1280 بكسل.
-
يمكن أن يؤثر التركيز الضعيف للصورة على دقة التعرّف على النص. إذا لم يكن بإمكانك الحصول على نتائج مقبولة، حاوِل أن تطلب من المستخدم إعادة التقاط الصورة.
-
إذا كنت تتعرّف على النص في تطبيق يعمل في الوقت الفعلي، يجب مراعاة الأبعاد العامة للصور المُدخلة. أصغر يمكن معالجة الصور بشكل أسرع. لتقليل وقت الاستجابة، تأكَّد من أنّ النص يشغل أكبر قدر ممكن من الصورة، واختَر دقة أقل عند التقاط الصور (مع مراعاة متطلبات دقة التعرّف المذكورة أعلاه). لمزيد من المعلومات، يُرجى الاطّلاع على نصائح لتحسين الأداء.
نصائح لتحسين الأداء
- إذا كنت تستخدم واجهة برمجة التطبيقات
Camera
أوcamera2
، يمكنك الحد من عدد طلبات البيانات المرسَلة إلى أداة رصد الأداء. إذا توفّر إطار فيديو جديد أثناء تشغيل أداة الكشف، يمكنك إسقاط الإطار. يمكنك الاطّلاع على فئةVisionProcessorBase
في تطبيق نموذج البدء السريع للحصول على مثال. - في حال استخدام
CameraX
API: تأكَّد من ضبط استراتيجية الضغط العكسي على قيمتها التلقائيةImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
يضمن ذلك إرسال صورة واحدة فقط للتحليل في كل مرة. إذا تم إنشاء المزيد من الصور عندما يكون المحلّل مشغولاً، سيتم تجاهلها تلقائيًا ولن يتم وضعها في قائمة الانتظار للإرسال . بمجرد إغلاق الصورة التي يتم تحليلها عن طريق استدعاء ImageProxy. Close()، سيتم تسليم أحدث صورة تالية - إذا كنت تستخدِم ناتج أداة الكشف لوضع الرسومات فوق
صورة الإدخال، يمكنك أولاً الحصول على النتيجة من ML Kit، ثم عرض
الصورة والعنصر المتراكب في خطوة واحدة. يتم عرض هذا المحتوى على سطح الشاشة.
مرة واحدة فقط لكل إطار إدخال يمكنك الاطّلاع على
CameraSourcePreview
وGraphicOverlay
صفًا في نموذج تطبيق Quickstart كمثال. - في حال استخدام واجهة برمجة التطبيقات Camera2 API، يمكنك التقاط الصور في
تنسيق
ImageFormat.YUV_420_888
إذا كنت تستخدم الإصدار القديم من Camera API، يمكنك التقاط الصور بتنسيقImageFormat.NV21
. - ننصحك بالتقاط الصور بدرجة دقة أقل. ومع ذلك، يجب أيضًا مراعاة متطلبات أبعاد الصورة في واجهة برمجة التطبيقات هذه.