برچسب‌گذاری تصاویر با مدل آموزش‌دیده AutoML در Android

پس‌از اینکه مدل خودتان را بااستفاده از AutoML Vision Edge آموزش دادید، می‌توانید از آن در برنامه‌تان برای برچسب‌گذاری تصاویر استفاده کنید. دو روش برای ادغام مدل‌های آموزش‌دیده از AutoML Vision Edge وجود دارد: می‌توانید مدل را با قرار دادن آن در پوشه دارایی برنامه‌تان بسته‌بندی کنید، یا می‌توانید آن را به‌صورت پویا از Firebase بارگیری کنید.
گزینه‌های بسته‌بندی مدل
در برنامه‌تان دسته‌بندی شده است
  • مدل بخشی از APK برنامه‌تان است
  • مدل فوراً دردسترس قرار می‌گیرد، حتی وقتی دستگاه Android آفلاین باشد
  • نیازی به پروژه Firebase نیست
میزبانی‌شده با Firebase
  • مدل را با بارگذاری در Firebase Machine Learning میزبانی کنید
  • اندازه APK را کاهش می‌دهد
  • مدل به‌درخواست بارگیری می‌شود
  • به‌روزرسانی‌های مدل را بدون انتشار مجدد برنامه‌تان ارسال کنید
  • آزمایش آ/ب آسان با پیکربندی از راه دور Firebase
  • به پروژه Firebase نیاز دارد

امتحان کنید

  • با برنامه نمونه کار کنید تا نمونه‌ای از کاربرد این API را ببینید.

قبل‌از شروع

۱. در فایل build.gradle سطح پروژه، حتماً مخزن Maven متعلق به Google را در هر دو بخش buildscript و allprojects اضافه کنید.

۲. وابستگی‌های کتابخانه‌های ML Kit Android را به فایل gradle سطح برنامه ماژولتان اضافه کنید که معمولاً app/build.gradle است: برای بسته‌بندی مدل با برنامه‌تان:
    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
    }
    
برای بارگیری پویا مدل از Firebase، وابستگی linkFirebase را اضافه کنید:
    dependencies {
      // ...
      // Image labeling feature with automl model downloaded
      // from firebase
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
      implementation 'com.google.mlkit:linkfirebase:16.0.1'
    }
    
‫۳. اگر می‌خواهید مدلی را بارگیری کنید، مطمئن شوید که Firebase را به پروژه Android خود اضافه کرده‌اید، اگر قبلاً این کار را نکرده‌اید. این کار هنگام بسته‌بندی مدل لازم نیست.

۱. بار کردن مدل

پیکربندی منبع مدل محلی

برای بسته‌بندی مدل با برنامه‌تان:

۱. مدل و فراداده آن را از بایگانی zip که از کنسول Firebase بارگیری کرده‌اید استخراج کنید. توصیه می‌کنیم از فایل‌ها همان‌طور که بارگیری کرده‌اید استفاده کنید، بدون اینکه آن‌ها را اصلاح کنید (ازجمله نام فایل‌ها).

۲. مدل و فایل‌های فراداده آن را در بسته برنامه‌تان قرار دهید:

الف. اگر پوشه دارایی‌ها را در پروژه‌تان ندارید، با کلیک راست کردن روی پوشه app/ و سپس کلیک کردن روی جدید > پوشه > پوشه دارایی‌ها، آن را ایجاد کنید.

ب. زیرپوشه‌ای در پوشه دارایی‌ها ایجاد کنید تا فایل‌های مدل را دربرگیرد.

ج. فایل‌های model.tflite، dict.txt، و manifest.json را در زیرپوشه کپی کنید (هر سه فایل باید در یک پوشه باشند).

‫۳. برای اطمینان از اینکه Gradle هنگام ساختن برنامه، فایل مدل را فشرده نمی‌کند، موارد زیر را به فایل build.gradle برنامه‌تان اضافه کنید:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
فایل مدل در بسته برنامه گنجانده خواهد شد و به‌عنوان دارایی خام برای ML Kit دردسترس خواهد بود.

توجه: از نسخه ۴.۱ افزایه Android Gradle،‏ tflite. به‌طور پیش‌فرض به فهرست noCompress اضافه خواهد شد و دیگر به موارد بالا نیازی نیست.

‫۴. شیء LocalModel را ایجاد کنید و مسیر فایل مانیفست مدل را مشخص کنید:

Kotlin

val localModel = AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build()

جاوا

AutoMLImageLabelerLocalModel localModel =
    new AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build();

پیکربندی منبع مدل میزبانی‌شده در Firebase

برای استفاده از مدل میزبانی‌شده ازراه‌دور، یک شیء RemoteModel ایجاد کنید، و نامی را که هنگام انتشار مدل به آن اختصاص داده‌اید مشخص کنید:

Kotlin

// Specify the name you assigned in the Firebase console.
val remoteModel =
    AutoMLImageLabelerRemoteModel.Builder("your_model_name").build()

جاوا

// Specify the name you assigned in the Firebase console.
AutoMLImageLabelerRemoteModel remoteModel =
    new AutoMLImageLabelerRemoteModel.Builder("your_model_name").build();

سپس، تکلیف بارگیری مدل را شروع کنید و شرایطی را که می‌خواهید بارگیری تحت آن مجاز باشد مشخص کنید. اگر مدل در دستگاه نباشد، یا اگر نسخه جدیدتری از مدل دردسترس باشد، تکلیف به‌صورت ناهمگام مدل را از Firebase بارگیری خواهد کرد:

Kotlin

val downloadConditions = DownloadConditions.Builder()
    .requireWifi()
    .build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
    .addOnSuccessListener {
        // Success.
    }

جاوا

DownloadConditions downloadConditions = new DownloadConditions.Builder()
        .requireWifi()
        .build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(@NonNull Task task) {
                // Success.
            }
        });

بسیاری از برنامه‌ها تکلیف بارگیری را در کد مقداردهی اولیه خود شروع می‌کنند، اما شما می‌توانید این کار را در هر نقطه‌ای قبل‌از نیاز به استفاده از مدل انجام دهید.

ایجاد برچسب‌زن تصویر از مدل شما

پس‌از پیکربندی منابع مدل، یک شیء ImageLabeler از یکی از آن‌ها ایجاد کنید.

اگر فقط مدل بسته‌بندی‌شده محلی دارید، کافی است برچسب‌زن را از شیء AutoMLImageLabelerLocalModel خود ایجاد کنید و آستانه امتیاز اطمینان موردنظرتان را پیکربندی کنید (به ارزیابی مدل مراجعه کنید):

Kotlin

val autoMLImageLabelerOptions = AutoMLImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0)  // Evaluate your model in the Firebase console
                                // to determine an appropriate value.
    .build()
val labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)

جاوا

AutoMLImageLabelerOptions autoMLImageLabelerOptions =
        new AutoMLImageLabelerOptions.Builder(localModel)
                .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                               // to determine an appropriate value.
                .build();
ImageLabeler labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)

اگر مدل با میزبانی از راه دور دارید، باید قبل‌از اجرای آن بررسی کنید که بارگیری شده باشد. می‌توانید وضعیت تکلیف بارگیری مدل را بااستفاده از روش isModelDownloaded() مدیر مدل بررسی کنید.

Although you only have to confirm this before running the labeler, if you have both a remotely-hosted model and a locally-bundled model, it might make sense to perform this check when instantiating the image labeler: create a labeler from the remote model if it's been downloaded, and from the local model otherwise.

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded -> 
    val optionsBuilder =
        if (isDownloaded) {
            AutoMLImageLabelerOptions.Builder(remoteModel)
        } else {
            AutoMLImageLabelerOptions.Builder(localModel)
        }
    // Evaluate your model in the Firebase console to determine an appropriate threshold.
    val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
    val labeler = ImageLabeling.getClient(options)
}

جاوا

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                AutoMLImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new AutoMLImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new AutoMLImageLabelerOptions.Builder(localModel);
                }
                AutoMLImageLabelerOptions options = optionsBuilder
                        .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                       // to determine an appropriate threshold.
                        .build();

                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

اگر فقط مدل میزبانی‌شده از راه دور دارید، باید عملکرد مربوط به مدل را غیرفعال کنید—برای مثال، بخشی از میانای کاربر را خاکستری یا پنهان کنید—تا زمانی که تأیید کنید مدل بارگیری شده است. می‌توانید این کار را با پیوست کردن شنونده به روش download() مدیر مدل انجام دهید:

Kotlin

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

جاوا

RemoteModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener() {
            @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.
            }
        });

۲. آماده کردن تصویر ورودی

سپس، برای هر تصویری که می‌خواهید برچسب‌گذاری کنید، یک شیء InputImage از تصویرتان ایجاد کنید. برچسب‌زن تصویر زمانی سریع‌ترین عملکرد را دارد که از Bitmap یا، اگر از camera2 API استفاده می‌کنید، از YUV_420_888 media.Image استفاده کنید که درصورت امکان توصیه می‌شود.

می‌توانید شیء 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
            // ...
        }
    }
}

جاوا

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
}

جاوا

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 intent برای درخواست از کاربر برای انتخاب تصویری از برنامه گالری‌اش استفاده می‌کنید.

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
)

جاوا

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 به‌همراه درجه چرخش نشان داده می‌شود.

۳. اجرای برچسب‌زن تصویر

برای برچسب‌گذاری اشیاء در تصویر، شیء image را به روش process() مربوط به ImageLabeler ارسال کنید.

Kotlin

labeler.process(image)
        .addOnSuccessListener { labels ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

جاوا

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

۴. دریافت اطلاعات درباره اشیای برچسب‌گذاری‌شده

اگر عملیات برچسب‌گذاری تصویر موفقیت‌آمیز باشد، فهرستی از اشیاء ImageLabel به شنونده موفقیت ارسال می‌شود. هر شیء ImageLabel نشان‌دهنده چیزی است که در تصویر برچسب‌گذاری شده است. می‌توانید شرح نوشتاری هر برچسب، امتیاز اطمینان مطابقت، و نمایه مطابقت را دریافت کنید. برای مثال:

Kotlin

for (label in labels) {
    val text = label.text
    val confidence = label.confidence
    val index = label.index
}

جاوا

for (ImageLabel label : labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
    int index = label.getIndex();
}

نکاتی برای بهبود عملکرد هم‌زمان

اگر می‌خواهید تصاویر را در یک برنامه هم‌زمان برچسب‌گذاری کنید، این دستورالعمل‌ها را دنبال کنید تا به بهترین نرخ فریم دست یابید:

  • اگر از میانای برنامه‌سازی کاربردی Camera یا camera2 استفاده می‌کنید، فراخوانی‌های برچسب‌زن تصویر را محدود کنید. اگر فریم ویدیو جدیدی در حین اجرای برچسب‌زن تصویر دردسترس قرار گرفت، فریم را رها کنید. برای دیدن نمونه، کلاس VisionProcessorBase را در برنامه نمونه شروع سریع ببینید.
  • اگر از CameraX API استفاده می‌کنید، مطمئن شوید که استراتژی فشار برگشتی روی مقدار پیش‌فرض آن ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST تنظیم شده باشد. این کار تضمین می‌کند که در هر بار فقط یک تصویر برای تجزیه‌وتحلیل ارسال شود. اگر وقتی تحلیل‌گر مشغول است تصاویر بیشتری تولید شود، این تصاویر به‌طور خودکار حذف می‌شوند و برای تحویل در صف قرار نمی‌گیرند. وقتی تصویری که درحال تجزیه‌وتحلیل است با فراخوانی ImageProxy.close()‎ بسته شود، تصویر جدید بعدی ارائه خواهد شد.
  • اگر از خروجی برچسب‌زن تصویر برای هم‌پوشانی گرافیک روی تصویر ورودی استفاده می‌کنید، ابتدا نتیجه را از ML Kit دریافت کنید، سپس تصویر و هم‌پوشانی را در یک مرحله رندر کنید. این مورد فقط یک‌بار برای هر فریم ورودی در سطح نمایشگر پرداز می‌شود. برای دیدن نمونه، کلاس‌های CameraSourcePreview و GraphicOverlay را در برنامه نمونه شروع سریع ببینید.
  • اگر از Camera2 API استفاده می‌کنید، تصاویر را در قالب ImageFormat.YUV_420_888 ضبط کنید. اگر از Camera API قدیمی‌تر استفاده می‌کنید، تصاویر را در قالب ImageFormat.NV21 ضبط کنید.