يمكنك استخدام مجموعة أدوات تعلُّم الآلة للتعرّف على الرموز الشريطية وفك ترميزها.
الميزة | غير مجمعة | مُجمَّعة |
---|---|---|
التنفيذ | يتم تنزيل النموذج ديناميكيًا من خلال "خدمات Google Play". | يكون النموذج مرتبطًا بشكلٍ ثابت بتطبيقك في وقت الإصدار. |
حجم التطبيق | زيادة في الحجم بمقدار 200 كيلوبايت تقريبًا. | زيادة الحجم بمقدار 2.4 ميغابايت تقريبًا. |
وقت الإعداد | قد تضطر إلى الانتظار حتى يتم تنزيل النموذج قبل الاستخدام لأول مرة. | يتوفّر الطراز على الفور. |
جرّبه الآن
- يمكنك تجربة نموذج التطبيق من أجل يمكنك الاطّلاع على مثال حول استخدام واجهة برمجة التطبيقات هذه.
- الاطلاع على واجهة عرض التصميم المتعدد الأبعاد app لتفعيل واجهة برمجة التطبيقات هذه بشكل شامل.
قبل البدء
في ملف
build.gradle
على مستوى المشروع، تأكد من تضمين مستودع Maven في كلٍّ من القسمَينbuildscript
وallprojects
.أضِف ملحقات مكتبات ML Kit على Android إلى ملف Gradle على مستوى التطبيق، ويكون عادةً
app/build.gradle
. يُرجى تحديد أحد الخيارات التالية: التبعيات التالية بناءً على احتياجاتك:لدمج النموذج مع تطبيقك:
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.3.0' }
لاستخدام النموذج في "خدمات Google Play":
dependencies { // ... // Use this dependency to use the dynamically downloaded model in Google Play Services implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1' }
إذا اخترت استخدام النموذج في خدمات Google Play، يمكنك ضبط لتطبيقك تلقائيًا لتنزيل النموذج على الجهاز بعد التطبيقات المثبّتة من متجر Play. لإجراء ذلك، أضِف البيان التالي إلى ملف
AndroidManifest.xml
لتطبيقك:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode" > <!-- To use multiple models: android:value="barcode,model2,model3" --> </application>
يمكنك أيضًا التحقق بشكل صريح من مدى توفّر النموذج وطلب التنزيل من خلال ModuleInstallClient API في "خدمات Google Play"
في حال عدم تفعيل عمليات تنزيل نموذج وقت التثبيت أو طلب تنزيل واضح، يتم تنزيل النموذج في المرة الأولى التي تُشغِّل فيها الماسح الضوئي. الطلبات التي تقدّمها قبل اكتمال التنزيل لا ينتج عنها أي نتائج.
إرشادات إدخال الصور
-
لكي تتمكن أدوات تعلّم الآلة من قراءة الرموز الشريطية بدقة، يجب أن تحتوي الصور المدخلة على الرموز الشريطية التي يتم تمثيلها ببيانات بكسل كافية.
تعتمد المتطلبات المحددة لبيانات البكسل على كل من نوع الرمز الشريطي ومقدار البيانات المشفرة فيه، نظرًا لأن العديد من الرموز الشريطية تدعم حمولة بيانات ذات حجم متغير. وبشكل عام، تُعد أصغر معنى يجب ألا يقل عرض وحدة الرمز الشريطي عن 2 بكسل رموز ثنائية الأبعاد، بطول 2 بكسل
على سبيل المثال، تتكون الرموز الشريطية EAN-13 من الأشرطة والمسافات التي تبلغ 1، بعرض 2 أو 3 أو 4 وحدات، لذا من المفترض أن تحتوي صورة الرمز الشريطي EAN-13 على أشرطة المساحات التي لا يقل عرضها عن 2 و4 و6 و8 بكسل. لأنّ رقم EAN-13 يبلغ عرض الرمز الشريطي 95 وحدة، ويجب ألا يقل عرض الرمز الشريطي عن 190 وحدة عرض البكسل.
تحتاج التنسيقات الأكثر كثافة، مثل PDF417، إلى أبعاد بكسل أكبر تكنولوجيا تعلُّم الآلة لقراءتها بشكلٍ موثوق. على سبيل المثال، يمكن أن يتضمن رمز PDF417 ما يصل إلى "كلمة" بعرض 34 وحدة 17 في صف واحد، والذي من المفترض أن يكون على الأقل عرض 1156 بكسل
-
يمكن أن يؤثر التركيز الضعيف للصورة على دقة المسح الضوئي. إذا لم يحصل تطبيقك على نتائج مقبولة، اطلب من المستخدم تلخيص الصورة.
-
بالنسبة إلى التطبيقات النموذجية، يوصى بتوفير مستوى أعلى من صورة بدرجة دقة، مثل 1280x720 أو 1920x1080، مما يجعل الرموز الشريطية يمكن مسحه ضوئيًا من مسافة أكبر بعيدًا عن الكاميرا.
ولكن في التطبيقات التي يكون فيها وقت الاستجابة مهمًا، يمكنك تحسين الأداء من خلال التقاط الصور بدقة أقل، ولكن يتطلب ذلك يشكّل الرمز الشريطي غالبية الصورة التي تم إدخالها. راجع أيضًا نصائح لتحسين الأداء في الوقت الفعلي.
1. إعداد الماسح الضوئي للرموز الشريطية
إذا عرفت تنسيقات الرمز الشريطي التي تتوقّع قراءتها، يمكنك تحسين سرعة لكشف الرمز الشريطي من خلال إعداده لاكتشاف تلك التنسيقات فقط.على سبيل المثال، لاكتشاف رمز Aztec ورموز الاستجابة السريعة فقط، يمكنك إنشاء
BarcodeScannerOptions
كما في المثال التالي:
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC) .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC) .build();
التنسيقات التالية متاحة:
- الرمز 128 (
FORMAT_CODE_128
) - الرمز 39 (
FORMAT_CODE_39
) - الرمز 93 (
FORMAT_CODE_93
) - الكودابار (
FORMAT_CODABAR
) - رقم EAN-13 (
FORMAT_EAN_13
) - رقم EAN-8 (
FORMAT_EAN_8
) - ITF (
FORMAT_ITF
) - الرمز العالمي للمنتج (UPC)-A (
FORMAT_UPC_A
) - الرمز العالمي للمنتج (UPC)-E (
FORMAT_UPC_E
) - رمز الاستجابة السريعة (
FORMAT_QR_CODE
) - PDF417 (
FORMAT_PDF417
) - أزتيك (
FORMAT_AZTEC
) - مصفوفة البيانات (
FORMAT_DATA_MATRIX
)
بدءًا من النموذج المجمّع 17.1.0 والنموذج غير المجمّع 18.2.0، يمكنك أيضًا الاتصال
enableAllPotentialBarcodes()
لعرض كل الرموز الشريطية المحتمَلة حتى في حال كانت
. يمكن استخدام ذلك لتسهيل رصد المزيد من الأنشطة، مثلاً
من خلال تكبير الصورة للحصول على صورة أوضح لأي رمز شريطي في الصورة المعروضة
المربع المحيط.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build();
Further on, starting from bundled library 17.2.0 and unbundled library 18.3.0, a new feature called auto-zoom has been introduced to further enhance the barcode scanning experience. With this feature enabled, the app is notified when all barcodes within the view are too distant for decoding. As a result, the app can effortlessly adjust the camera's zoom ratio to the recommended setting provided by the library, ensuring optimal focus and readability. This feature will significantly enhance the accuracy and success rate of barcode scanning, making it easier for apps to capture information precisely.
To enable auto-zooming and customize the experience, you can utilize the
setZoomSuggestionOptions()
method along with your
own ZoomCallback
handler and desired maximum zoom
ratio, as demonstrated in the code below.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .setZoomSuggestionOptions( new ZoomSuggestionOptions.Builder(zoomCallback) .setMaxSupportedZoomRatio(maxSupportedZoomRatio) .build()) // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .setZoomSuggestionOptions( new ZoomSuggestionOptions.Builder(zoomCallback) .setMaxSupportedZoomRatio(maxSupportedZoomRatio) .build()) // Optional .build();
zoomCallback
is required to be provided to handle whenever the library
suggests a zoom should be performed and this callback will always be called on
the main thread.
The following code snippet shows an example of defining a simple callback.
Kotlin
fun setZoom(ZoomRatio: Float): Boolean { if (camera.isClosed()) return false camera.getCameraControl().setZoomRatio(zoomRatio) return true }
Java
boolean setZoom(float zoomRatio) { if (camera.isClosed()) { return false; } camera.getCameraControl().setZoomRatio(zoomRatio); return true; }
maxSupportedZoomRatio
is related to the camera hardware, and different camera
libraries have different ways to fetch it (see the javadoc of the setter
method). In case this is not provided, an
unbounded zoom ratio might be produced by the library which might not be
supported. Refer to the
setMaxSupportedZoomRatio()
method
introduction to see how to get the max supported zoom ratio with different
Camera libraries.
When auto-zooming is enabled and no barcodes are successfully decoded within
the view, BarcodeScanner
triggers your zoomCallback
with the requested
zoomRatio
. If the callback correctly adjusts the camera to this zoomRatio
,
it is highly probable that the most centered potential barcode will be decoded
and returned.
A barcode may remain undecodable even after a successful zoom-in. In such cases,
BarcodeScanner
may either invoke the callback for another round of zoom-in
until the maxSupportedZoomRatio
is reached, or provide an empty list (or a
list containing potential barcodes that were not decoded, if
enableAllPotentialBarcodes()
was called) to the OnSuccessListener
(which
will be defined in step 4. Process the image).
2. Prepare the input image
To recognize barcodes in an image, create anInputImage
object
from either a Bitmap
, media.Image
, ByteBuffer
, byte array, or a file on
the device. Then, pass the InputImage
object to the
BarcodeScanner
's process
method.
You can create an InputImage
object from different sources, each is explained below.
Using a media.Image
To create an InputImage
object from a media.Image
object, such as when you capture an image from a
device's camera, pass the media.Image
object and the image's
rotation to InputImage.fromMediaImage()
.
If you use the
CameraX library, the OnImageCapturedListener
and
ImageAnalysis.Analyzer
classes calculate the rotation value
for you.
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);
استخدام معرف موارد منتظم (URI) لملف
لإنشاء 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- الحصول على نسخة افتراضية من BarcodeScanner
Kotlin
val scanner = BarcodeScanning.getClient() // Or, to specify the formats to recognize: // val scanner = BarcodeScanning.getClient(options)
Java
BarcodeScanner scanner = BarcodeScanning.getClient(); // Or, to specify the formats to recognize: // BarcodeScanner scanner = BarcodeScanning.getClient(options);
4. معالجة الصورة
تمرير الصورة إلى طريقةprocess
:
Kotlin
val result = scanner.process(image) .addOnSuccessListener { barcodes -> // Task completed successfully // ... } .addOnFailureListener { // Task failed with an exception // ... }
Java
Task<List<Barcode>> result = scanner.process(image) .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() { @Override public void onSuccess(List<Barcode> barcodes) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
5- الحصول على معلومات من الرموز الشريطية
إذا نجحت عملية التعرّف على الرمز الشريطي، ستظهر لك قائمةBarcode
.
يتم تمرير الكائنات إلى مستمع النجاح. يمثل كل عنصر Barcode
رمز شريطي تم رصده في الصورة لكل رمز شريطي، يمكنك الحصول على
إحداثيات الحدود في صورة الإدخال، بالإضافة إلى البيانات الأولية التي تم تشفيرها بواسطة
الرمز الشريطي. أيضًا، إذا تمكن الماسح الضوئي للرموز الشريطية من تحديد نوع البيانات
بترميز الرمز الشريطي، يمكنك الحصول على كائن يحتوي على بيانات محللة.
على سبيل المثال:
Kotlin
for (barcode in barcodes) { val bounds = barcode.boundingBox val corners = barcode.cornerPoints val rawValue = barcode.rawValue val valueType = barcode.valueType // See API reference for complete list of supported types when (valueType) { Barcode.TYPE_WIFI -> { val ssid = barcode.wifi!!.ssid val password = barcode.wifi!!.password val type = barcode.wifi!!.encryptionType } Barcode.TYPE_URL -> { val title = barcode.url!!.title val url = barcode.url!!.url } } }
Java
for (Barcode barcode: barcodes) { Rect bounds = barcode.getBoundingBox(); Point[] corners = barcode.getCornerPoints(); String rawValue = barcode.getRawValue(); int valueType = barcode.getValueType(); // See API reference for complete list of supported types switch (valueType) { case Barcode.TYPE_WIFI: String ssid = barcode.getWifi().getSsid(); String password = barcode.getWifi().getPassword(); int type = barcode.getWifi().getEncryptionType(); break; case Barcode.TYPE_URL: String title = barcode.getUrl().getTitle(); String url = barcode.getUrl().getUrl(); break; } }
نصائح لتحسين الأداء في الوقت الفعلي
إذا أردت مسح الرموز الشريطية ضوئيًا في تطبيق في الوقت الفعلي، اتّبِع الخطوات التالية: الإرشادات لتحقيق أفضل معدلات عرض الإطارات:
-
لا تلتقط مدخلاً بدرجة الدقة الأصلية للكاميرا. في بعض الأجهزة، ينتج عن التقاط المدخلات بالدقة الأصلية كمية كبيرة جدًا (أكثر من 10 ميغابكسل)، وهو ما ينتج عنه وقت استجابة ضعيف جدًا بدون الاستفادة ودقتها. بدلاً من ذلك، اطلب فقط المقاس المطلوب من الكاميرا. لاكتشاف الرمز الشريطي، والذي لا يزيد عادةً عن 2 ميغابكسل.
إذا كانت سرعة المسح الضوئي مهمة، يمكنك تقليل التقاط الصورة أكثر الحل. مع ذلك، يجب الانتباه إلى الحدّ الأدنى لمتطلبات حجم الرمز الشريطي. الموضحة أعلاه.
إذا كنت تحاول التعرّف على الرموز الشريطية من سلسلة عمليات بث إطارات الفيديو، فقد ينتج عن أداة التعرف نتائج مختلفة من إطار إلى آخر الإطار. يجب عليك الانتظار حتى تحصل على سلسلة متتالية من نفس قيمة لتكون واثقًا من أنك تعرض نتيجة جيدة.
لا يمكن استخدام رقم المجموع الاختباري مع ITF وCODE-39.
- إذا كنت تستخدم
Camera
أوcamera2
واجهة برمجة التطبيقات، تقييد المكالمات الواردة إلى أداة الكشف. إذا ظهر فيديو جديد يصبح الإطار متاحًا أثناء تشغيل أداة الكشف، لذا أفلِت الإطار. يمكنك الاطّلاع على صف واحد (VisionProcessorBase
) في نموذج تطبيق Quickstart كمثال. - في حال استخدام
CameraX
API: تأكَّد من ضبط استراتيجية الضغط العكسي على قيمتها التلقائيةImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
وهذا يضمن تسليم صورة واحدة فقط للتحليل في كل مرة. إذا كانت المزيد من الصور يتم إنتاجها عندما يكون المحلل مشغولاً، فسيتم إسقاطها تلقائيًا ولن يتم وضعها في قائمة الانتظار التسليم. بمجرد إغلاق الصورة التي يتم تحليلها عن طريق استدعاء ImageProxy.Close()، سيتم تسليم الصورة التالية الأحدث. - إذا استخدمت مخرجات أداة الكشف لتراكب الرسومات على
الصورة المدخلة، والحصول أولاً على النتيجة من ML Kit، ثم عرض الصورة
وتراكبها في خطوة واحدة. يتم عرض هذا المحتوى على سطح الشاشة.
مرة واحدة فقط لكل إطار إدخال يمكنك الاطّلاع على
CameraSourcePreview
وGraphicOverlay
صفًا في نموذج تطبيق Quickstart كمثال. - في حال استخدام واجهة برمجة التطبيقات Camera2 API، يمكنك التقاط الصور في
تنسيق
ImageFormat.YUV_420_888
إذا كنت تستخدم واجهة برمجة التطبيقات للكاميرا القديمة، يمكنك التقاط الصور في تنسيقImageFormat.NV21
إنّ محتوى هذه الصفحة مرخّص بموجب ترخيص Creative Commons Attribution 4.0 ما لم يُنصّ على خلاف ذلك، ونماذج الرموز مرخّصة بموجب ترخيص Apache 2.0. للاطّلاع على التفاصيل، يُرجى مراجعة سياسات موقع Google Developers. إنّ Java هي علامة تجارية مسجَّلة لشركة Oracle و/أو شركائها التابعين.
تاريخ التعديل الأخير: 2024-08-20 (حسب التوقيت العالمي المتفَّق عليه)