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

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

امتحانش کن.

قبل از اینکه شروع کنی

۱. در فایل build.gradle در سطح پروژه، مطمئن شوید که مخزن Maven گوگل را هم در بخش‌های buildscript و هم allprojects خود وارد کرده‌اید.

۲. وابستگی‌های کتابخانه‌های اندروید ML Kit را به فایل 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 را به پروژه اندروید خود اضافه کرده‌اید ، البته اگر قبلاً این کار را نکرده‌اید. این کار هنگام بسته‌بندی مدل لازم نیست.

۱. مدل را بارگذاری کنید

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

برای اتصال مدل به برنامه خود:

۱. مدل و متادیتای آن را از آرشیو زیپی که از کنسول فایربیس دانلود کرده‌اید، استخراج کنید. توصیه می‌کنیم از فایل‌ها به همان شکلی که دانلود کرده‌اید، بدون تغییر (از جمله نام فایل‌ها) استفاده کنید.

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

الف) اگر پوشه assets در پروژه خود ندارید، با کلیک راست روی app/ folder و سپس کلیک روی New > Folder > Assets Folder ، یکی ایجاد کنید.

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

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

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

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

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

کاتلین

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 ایجاد کنید و نامی را که هنگام انتشار مدل به آن اختصاص داده‌اید، مشخص کنید:

کاتلین

// 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 دانلود می‌کند:

کاتلین

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 خود ایجاد کنید و آستانه امتیاز اطمینان مورد نیاز خود را پیکربندی کنید (به بخش ارزیابی مدل خود مراجعه کنید).

کاتلین

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() در model manager بررسی کنید.

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

کاتلین

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() در model manager انجام دهید:

کاتلین

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 مقدار چرخش را برای شما محاسبه می‌کنند.

کاتلین

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

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

کاتلین

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() ارسال کنید:

کاتلین

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

استفاده از یک URI فایل

برای ایجاد یک شیء InputImage از یک URI فایل، متن برنامه و URI فایل را به InputImage.fromFilePath() ارسال کنید. این زمانی مفید است که از یک ACTION_GET_CONTENT برای وادار کردن کاربر به انتخاب یک تصویر از برنامه گالری خود استفاده می‌کنید.

کاتلین

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 را با بافر یا آرایه، به همراه ارتفاع، عرض، فرمت کدگذاری رنگ و درجه چرخش تصویر ایجاد کنید:

کاتلین

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 ، تعریف زیر را انجام دهید:

کاتلین

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

تصویر توسط یک شیء Bitmap به همراه درجه چرخش نمایش داده می‌شود.

۳. برچسب‌گذار تصویر را اجرا کنید

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

کاتلین

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 نشان‌دهنده‌ی چیزی است که در تصویر برچسب‌گذاری شده است. می‌توانید توضیحات متنی هر برچسب، امتیاز اطمینان تطابق و اندیس تطابق را دریافت کنید. برای مثال:

کاتلین

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();
}

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

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

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