بعد تدريب النموذج الخاص بك باستخدام AutoML Vision Edge ، يمكنك استخدامه في تطبيقك لتسمية الصور.
هناك طريقتان لدمج النماذج التي تم تدريبها من AutoML Vision Edge: يمكنك تجميع النموذج عن طريق وضعه داخل مجلد أصول التطبيق الخاص بك، أو يمكنك تنزيله ديناميكيًا من Firebase.
خيارات تجميع النماذج | |
---|---|
المجمعة في التطبيق الخاص بك |
|
مستضاف مع Firebase |
|
قبل ان تبدأ
أضف تبعيات مكتبات ML Kit Android إلى ملف gradle على مستوى التطبيق الخاص بوحدتك، والذي يكون عادةً
app/build.gradle
:لتجميع نموذج مع تطبيقك:
dependencies { // ... // Image labeling feature with bundled automl model implementation 'com.google.mlkit:image-labeling-custom:16.3.1' }
لتنزيل نموذج ديناميكيًا من Firebase، قم بإضافة تبعية
linkFirebase
:dependencies { // ... // Image labeling feature with automl model downloaded // from firebase implementation 'com.google.mlkit:image-labeling-custom:16.3.1' implementation 'com.google.mlkit:linkfirebase:16.1.0' }
إذا كنت تريد تنزيل نموذج ، فتأكد من إضافة Firebase إلى مشروع Android الخاص بك ، إذا لم تكن قد قمت بذلك بالفعل. هذا غير مطلوب عند تجميع النموذج.
1. قم بتحميل النموذج
تكوين مصدر نموذج محلي
لتجميع النموذج مع تطبيقك:
قم باستخراج النموذج وبياناته الوصفية من الأرشيف المضغوط الذي قمت بتنزيله من وحدة تحكم Firebase. ننصحك باستخدام الملفات كما قمت بتنزيلها، دون تعديل (بما في ذلك أسماء الملفات).
قم بتضمين النموذج الخاص بك وملفات البيانات التعريفية الخاصة به في حزمة تطبيقك:
- إذا لم يكن لديك مجلد أصول في مشروعك، فقم بإنشاء واحد عن طريق النقر بزر الماوس الأيمن فوق
app/
المجلد، ثم النقر فوق جديد > مجلد > مجلد الأصول . - قم بإنشاء مجلد فرعي ضمن مجلد الأصول ليحتوي على ملفات النموذج.
- انسخ الملفات
model.tflite
وdict.txt
وmanifest.json
إلى المجلد الفرعي (يجب أن تكون الملفات الثلاثة جميعها في نفس المجلد).
- إذا لم يكن لديك مجلد أصول في مشروعك، فقم بإنشاء واحد عن طريق النقر بزر الماوس الأيمن فوق
أضف ما يلي إلى ملف
build.gradle
الخاص بالتطبيق الخاص بك للتأكد من أن Gradle لا يقوم بضغط ملف النموذج عند إنشاء التطبيق:android { // ... aaptOptions { noCompress "tflite" } }
سيتم تضمين ملف النموذج في حزمة التطبيق وسيكون متاحًا لـ ML Kit كأصل أولي.
قم بإنشاء كائن
LocalModel
، مع تحديد المسار إلى ملف بيان النموذج:جافا
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();
كوتلين
val localModel = LocalModel.Builder() .setAssetManifestFilePath("manifest.json") // or .setAbsoluteManifestFilePath(absolute file path to manifest file) .build()
قم بتكوين مصدر نموذج مستضاف على Firebase
لاستخدام النموذج المستضاف عن بعد، قم بإنشاء كائن CustomRemoteModel
، مع تحديد الاسم الذي قمت بتعيينه للنموذج عند نشره:
جافا
// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
new CustomRemoteModel.Builder(firebaseModelSource).build();
كوتلين
// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
.build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).build()
بعد ذلك، ابدأ مهمة تنزيل النموذج، مع تحديد الشروط التي تريد السماح بالتنزيل بموجبها. إذا لم يكن النموذج موجودًا على الجهاز، أو إذا كان إصدار أحدث من النموذج متاحًا، فستقوم المهمة بتنزيل النموذج بشكل غير متزامن من Firebase:
جافا
DownloadConditions downloadConditions = new DownloadConditions.Builder()
.requireWifi()
.build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(@NonNull Task<Void> task) {
// Success.
}
});
كوتلين
val downloadConditions = DownloadConditions.Builder()
.requireWifi()
.build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener {
// Success.
}
تبدأ العديد من التطبيقات مهمة التنزيل في رمز التهيئة الخاص بها، ولكن يمكنك القيام بذلك في أي وقت قبل أن تحتاج إلى استخدام النموذج.
قم بإنشاء ملصق صورة من النموذج الخاص بك
بعد تكوين مصادر النموذج الخاص بك، قم بإنشاء كائن ImageLabeler
من أحد هذه المصادر.
إذا كان لديك نموذج مُجمَّع محليًا فقط، فما عليك سوى إنشاء مُلصق من كائن CustomImageLabelerOptions
الخاص بك وتكوين حد درجة الثقة الذي تريد طلبه (راجع تقييم النموذج الخاص بك ):
جافا
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
كوتلين
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)
إذا كان لديك نموذج مستضاف عن بعد، فسيتعين عليك التحقق من تنزيله قبل تشغيله. يمكنك التحقق من حالة مهمة تنزيل النموذج باستخدام طريقة isModelDownloaded()
الخاصة بمدير النموذج.
على الرغم من أنه يتعين عليك فقط تأكيد ذلك قبل تشغيل أداة التسمية، إذا كان لديك نموذج مستضاف عن بعد ونموذج مجمع محليًا، فقد يكون من المنطقي إجراء هذا التحقق عند إنشاء وحدة تسمية الصورة: قم بإنشاء أداة تسمية من النموذج البعيد إذا تم تنزيله، ومن النموذج المحلي خلاف ذلك.
جافا
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean isDownloaded) {
CustomImageLabelerOptions.Builder optionsBuilder;
if (isDownloaded) {
optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel);
} else {
optionsBuilder = new CustomImageLabelerOptions.Builder(localModel);
}
CustomImageLabelerOptions options = optionsBuilder
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate threshold.
.build();
ImageLabeler labeler = ImageLabeling.getClient(options);
}
});
كوتلين
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
CustomImageLabelerOptions.Builder(remoteModel)
} else {
CustomImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Cloud console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = ImageLabeling.getClient(options)
}
إذا كان لديك نموذج مستضاف عن بعد فقط، فيجب عليك تعطيل الوظائف المرتبطة بالنموذج - على سبيل المثال، اللون الرمادي أو إخفاء جزء من واجهة المستخدم الخاصة بك - حتى تتأكد من تنزيل النموذج. يمكنك القيام بذلك عن طريق إرفاق مستمع بطريقة download()
الخاصة بمدير النموذج:
جافا
RemoteModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void v) {
// Download complete. Depending on your app, you could enable
// the ML feature, or switch from the local model to the remote
// model, etc.
}
});
كوتلين
RemoteModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener {
// Download complete. Depending on your app, you could enable the ML
// feature, or switch from the local model to the remote model, etc.
}
2. قم بإعداد صورة الإدخال
بعد ذلك، لكل صورة تريد تصنيفها، قم بإنشاء كائن InputImage
من صورتك. يعمل مُلصق الصور بشكل أسرع عند استخدام Bitmap
، أو، إذا كنت تستخدم واجهة برمجة تطبيقات Camera2، YUV_420_888 media.Image
، والتي يوصى بها عندما يكون ذلك ممكنًا.
يمكنك إنشاء InputImage
من مصادر مختلفة، كل منها موضح أدناه.
باستخدام media.Image
لإنشاء كائن InputImage
من كائن media.Image
، كما هو الحال عند التقاط صورة من كاميرا الجهاز، قم بتمرير كائن media.Image
وتدوير الصورة إلى InputImage.fromMediaImage()
.
إذا كنت تستخدم مكتبة CameraX ، فستقوم فئتا OnImageCapturedListener
و ImageAnalysis.Analyzer
بحساب قيمة التدوير نيابةً عنك.
Kotlin+KTX
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) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees); // Pass image to an ML Kit Vision API // ... } }
إذا كنت لا تستخدم مكتبة الكاميرا التي تعطيك درجة دوران الصورة، يمكنك حسابها من درجة دوران الجهاز واتجاه مستشعر الكاميرا في الجهاز:
Kotlin+KTX
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * 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, context: Context): 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) // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360 // Return the corresponding FirebaseVisionImageMetadata rotation value. val result: Int when (rotationCompensation) { 0 -> result = FirebaseVisionImageMetadata.ROTATION_0 90 -> result = FirebaseVisionImageMetadata.ROTATION_90 180 -> result = FirebaseVisionImageMetadata.ROTATION_180 270 -> result = FirebaseVisionImageMetadata.ROTATION_270 else -> { result = FirebaseVisionImageMetadata.ROTATION_0 Log.e(TAG, "Bad rotation value: $rotationCompensation") } } return result }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 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, Context context) 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); // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360; // Return the corresponding FirebaseVisionImageMetadata rotation value. int result; switch (rotationCompensation) { case 0: result = FirebaseVisionImageMetadata.ROTATION_0; break; case 90: result = FirebaseVisionImageMetadata.ROTATION_90; break; case 180: result = FirebaseVisionImageMetadata.ROTATION_180; break; case 270: result = FirebaseVisionImageMetadata.ROTATION_270; break; default: result = FirebaseVisionImageMetadata.ROTATION_0; Log.e(TAG, "Bad rotation value: " + rotationCompensation); } return result; }
بعد ذلك، قم بتمرير كائن media.Image
وقيمة درجة التدوير إلى InputImage.fromMediaImage()
:
Kotlin+KTX
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
باستخدام ملف URI
لإنشاء كائن InputImage
من ملف URI، قم بتمرير سياق التطبيق وملف URI إلى InputImage.fromFilePath()
. يعد هذا مفيدًا عند استخدام هدف ACTION_GET_CONTENT
لمطالبة المستخدم بتحديد صورة من تطبيق المعرض الخاص به.
Kotlin+KTX
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+KTX
val image = InputImage.fromByteBuffer( byteBuffer, /* 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 );
باستخدام Bitmap
لإنشاء كائن InputImage
من كائن Bitmap
، قم بالإعلان التالي:
Kotlin+KTX
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
يتم تمثيل الصورة بواسطة كائن Bitmap
مع درجات التدوير.
3. قم بتشغيل أداة تسمية الصورة
لتسمية كائنات في صورة ما، قم بتمرير كائن image
إلى طريقة process()
الخاصة بـ ImageLabeler
.
جافا
labeler.process(image)
.addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
@Override
public void onSuccess(List<ImageLabel> labels) {
// Task completed successfully
// ...
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Task failed with an exception
// ...
}
});
كوتلين
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
4. احصل على معلومات حول الكائنات ذات العلامات
إذا نجحت عملية وضع العلامات على الصور، فسيتم تمرير قائمة كائنات ImageLabel
إلى مستمع النجاح. يمثل كل كائن ImageLabel
شيئًا تم تصنيفه في الصورة. يمكنك الحصول على الوصف النصي لكل تصنيف ودرجة الثقة للمطابقة وفهرس المباراة. على سبيل المثال:
جافا
for (ImageLabel label : labels) {
String text = label.getText();
float confidence = label.getConfidence();
int index = label.getIndex();
}
كوتلين
for (label in labels) {
val text = label.text
val confidence = label.confidence
val index = label.index
}
نصائح لتحسين الأداء في الوقت الحقيقي
إذا كنت تريد تصنيف الصور في تطبيق في الوقت الفعلي، فاتبع هذه الإرشادات لتحقيق أفضل معدلات الإطارات:
- خنق المكالمات إلى ملصق الصورة. إذا أصبح إطار فيديو جديد متاحًا أثناء تشغيل أداة تسمية الصورة، فقم بإسقاط الإطار. راجع فئة
VisionProcessorBase
في نموذج تطبيق التشغيل السريع للحصول على مثال. - إذا كنت تستخدم مخرجات أداة تسمية الصور لتراكب الرسومات على الصورة المدخلة، فاحصل أولاً على النتيجة، ثم اعرض الصورة والتراكب في خطوة واحدة. من خلال القيام بذلك، يمكنك العرض على سطح العرض مرة واحدة فقط لكل إطار إدخال. راجع فئتي
CameraSourcePreview
وGraphicOverlay
في نموذج التطبيق السريع للحصول على مثال. إذا كنت تستخدم Camera2 API، فالتقط الصور بتنسيق
ImageFormat.YUV_420_888
.إذا كنت تستخدم واجهة برمجة تطبيقات الكاميرا الأقدم، فالتقط الصور بتنسيق
ImageFormat.NV21
.