بعد تدريب النموذج الخاص بك باستخدام 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 API ، فستعمل media.Image
YUV_420_888.
يمكنك إنشاء 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
في نموذج التطبيق Quickstart للحصول على مثال. - إذا كنت تستخدم إخراج أداة تسمية الصورة لتراكب الرسومات على صورة الإدخال ، فاحصل أولاً على النتيجة ، ثم اعرض الصورة والتراكب في خطوة واحدة. من خلال القيام بذلك ، فإنك تقدم لسطح العرض مرة واحدة فقط لكل إطار إدخال. اطلع على فئات
CameraSourcePreview
وGraphicOverlay
في تطبيق نموذج التشغيل السريع للحصول على مثال. إذا كنت تستخدم Camera2 API ، فقم بالتقاط الصور بتنسيق
ImageFormat.YUV_420_888
.إذا كنت تستخدم Camera API الأقدم ، فقم بالتقاط الصور بتنسيق
ImageFormat.NV21
.