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

بعد از اینکه مدل خود را با استفاده از AutoML Vision Edge آموزش دادید ، می‌توانید از آن در برنامه خود برای برچسب‌گذاری تصاویر استفاده کنید.

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

  1. اگر قبلاً این کار را نکرده‌اید، Firebase را به پروژه Android خود اضافه کنید .
  2. وابستگی های کتابخانه های اندروید ML Kit را به فایل Gradle ماژول (سطح برنامه) خود اضافه کنید (معمولا app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-automl:18.0.5'
    }
    

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

ML Kit مدل های تولید شده توسط AutoML شما را روی دستگاه اجرا می کند. با این حال، می‌توانید ML Kit را برای بارگیری مدل خود از راه دور از Firebase، از ذخیره‌سازی محلی یا هر دو پیکربندی کنید.

با میزبانی مدل در Firebase، می‌توانید مدل را بدون انتشار نسخه جدید برنامه به‌روزرسانی کنید و می‌توانید از Remote Config و A/B Testing برای ارائه پویا مدل‌های مختلف به مجموعه‌های مختلف کاربران استفاده کنید.

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

با بسته‌بندی مدل خود با برنامه‌تان، می‌توانید مطمئن شوید که ویژگی‌های ML برنامه‌تان همچنان در زمانی که مدل میزبانی شده توسط Firebase در دسترس نیست، کار می‌کنند.

یک منبع مدل میزبانی شده توسط Firebase را پیکربندی کنید

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

Java

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

Kotlin+KTX

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

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

Java

FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
        .requireWifi()
        .build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Success.
            }
        });

Kotlin+KTX

val conditions = FirebaseModelDownloadConditions.Builder()
    .requireWifi()
    .build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Success.
    }

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

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

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

  1. مدل و ابرداده آن را از آرشیو فشرده ای که از کنسول Firebase دانلود کرده اید استخراج کنید. توصیه می‌کنیم از فایل‌ها در حین دانلود، بدون تغییر (از جمله نام فایل) استفاده کنید.
  2. مدل خود و فایل های فراداده آن را در بسته برنامه خود قرار دهید:

    1. اگر در پروژه خود پوشه دارایی ندارید، با کلیک راست روی پوشه app/ ، سپس روی New > Folder > Assets Folder کلیک کنید.
    2. یک پوشه فرعی در زیر پوشه assets ایجاد کنید تا حاوی فایل های مدل باشد.
    3. فایل‌های model.tflite ، dict.txt و manifest.json را در پوشه فرعی کپی کنید (هر سه فایل باید در یک پوشه باشند).
  3. موارد زیر را به فایل build.gradle برنامه خود اضافه کنید تا مطمئن شوید که Gradle فایل مدل را هنگام ساخت برنامه فشرده نمی کند:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
    فایل مدل در بسته برنامه گنجانده شده و به عنوان دارایی خام در اختیار ML Kit قرار می گیرد.
  4. یک شی FirebaseAutoMLLocalModel ایجاد کنید و مسیر فایل مانیفست مدل را مشخص کنید:

    Java

    FirebaseAutoMLLocalModel localModel = new FirebaseAutoMLLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            .build();
    

    Kotlin+KTX

    val localModel = FirebaseAutoMLLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            .build()
    

یک برچسب تصویر از مدل خود ایجاد کنید

پس از پیکربندی منابع مدل خود، یک شی FirebaseVisionImageLabeler از یکی از آنها ایجاد کنید.

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

Java

FirebaseVisionImageLabeler labeler;
try {
    FirebaseVisionOnDeviceAutoMLImageLabelerOptions options =
            new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
                    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                   // to determine an appropriate value.
                    .build();
    labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
    // ...
}

Kotlin+KTX

val options = FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0)  // Evaluate your model in the Firebase console
                                // to determine an appropriate value.
    .build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)

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

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

Java

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

                FirebaseVisionImageLabeler labeler;
                try {
                    labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
                } catch (FirebaseMLException e) {
                    // Error.
                }
            }
        });

Kotlin+KTX

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

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

Java

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

Kotlin+KTX

FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.
    }

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

سپس، برای هر تصویری که می خواهید برچسب گذاری کنید، با استفاده از یکی از گزینه های توضیح داده شده در این بخش، یک شی FirebaseVisionImage ایجاد کنید و آن را به نمونه ای از FirebaseVisionImageLabeler (شرح داده شده در بخش بعدی) ارسال کنید.

می‌توانید یک شی FirebaseVisionImage از یک شی media.Image ، یک فایل روی دستگاه، یک آرایه بایت یا یک شی Bitmap ایجاد کنید:

  • برای ایجاد یک شی FirebaseVisionImage از یک شی media.Image ، مانند هنگام گرفتن تصویر از دوربین دستگاه، شی media.Image Image و چرخش تصویر را به FirebaseVisionImage.fromMediaImage() منتقل کنید.

    اگر از کتابخانه CameraX ، کلاس‌های OnImageCapturedListener و ImageAnalysis.Analyzer استفاده می‌کنید، مقدار چرخش را برای شما محاسبه می‌کنند، بنابراین فقط باید قبل از فراخوانی FirebaseVisionImage.fromMediaImage() چرخش را به یکی از ثابت‌های ROTATION_ ML Kit تبدیل کنید:

    Java

    private class YourAnalyzer implements ImageAnalysis.Analyzer {
    
        private int degreesToFirebaseRotation(int degrees) {
            switch (degrees) {
                case 0:
                    return FirebaseVisionImageMetadata.ROTATION_0;
                case 90:
                    return FirebaseVisionImageMetadata.ROTATION_90;
                case 180:
                    return FirebaseVisionImageMetadata.ROTATION_180;
                case 270:
                    return FirebaseVisionImageMetadata.ROTATION_270;
                default:
                    throw new IllegalArgumentException(
                            "Rotation must be 0, 90, 180, or 270.");
            }
        }
    
        @Override
        public void analyze(ImageProxy imageProxy, int degrees) {
            if (imageProxy == null || imageProxy.getImage() == null) {
                return;
            }
            Image mediaImage = imageProxy.getImage();
            int rotation = degreesToFirebaseRotation(degrees);
            FirebaseVisionImage image =
                    FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
    

    Kotlin+KTX

    private class YourImageAnalyzer : ImageAnalysis.Analyzer {
        private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
            0 -> FirebaseVisionImageMetadata.ROTATION_0
            90 -> FirebaseVisionImageMetadata.ROTATION_90
            180 -> FirebaseVisionImageMetadata.ROTATION_180
            270 -> FirebaseVisionImageMetadata.ROTATION_270
            else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
        }
    
        override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
            val mediaImage = imageProxy?.image
            val imageRotation = degreesToFirebaseRotation(degrees)
            if (mediaImage != null) {
                val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                // Pass image to an ML Kit Vision API
                // ...
            }
        }
    }
    

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

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

    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
    }

    سپس، شی media.Image و مقدار چرخش را به FirebaseVisionImage.fromMediaImage() ارسال کنید:

    Java

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

    Kotlin+KTX

    val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
  • برای ایجاد یک شی FirebaseVisionImage از URI فایل، زمینه برنامه و فایل URI را به FirebaseVisionImage.fromFilePath() ارسال کنید. این زمانی مفید است که از یک هدف ACTION_GET_CONTENT استفاده می کنید تا از کاربر بخواهید تصویری را از برنامه گالری خود انتخاب کند.

    Java

    FirebaseVisionImage image;
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri);
    } catch (IOException e) {
        e.printStackTrace();
    }

    Kotlin+KTX

    val image: FirebaseVisionImage
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri)
    } catch (e: IOException) {
        e.printStackTrace()
    }
  • برای ایجاد یک شی FirebaseVisionImage از یک ByteBuffer یا یک آرایه بایت، ابتدا چرخش تصویر را همانطور که در بالا برای ورودی media.Image توضیح داده شد محاسبه کنید.

    سپس، یک شی FirebaseVisionImageMetadata ایجاد کنید که شامل ارتفاع، عرض، فرمت کدگذاری رنگ و چرخش تصویر باشد:

    Java

    FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
            .setWidth(480)   // 480x360 is typically sufficient for
            .setHeight(360)  // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build();

    Kotlin+KTX

    val metadata = FirebaseVisionImageMetadata.Builder()
            .setWidth(480) // 480x360 is typically sufficient for
            .setHeight(360) // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build()

    برای ایجاد یک شی FirebaseVisionImage از بافر یا آرایه و شیء فراداده استفاده کنید:

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
    // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

    Kotlin+KTX

    val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
    // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
  • برای ایجاد یک شی FirebaseVisionImage از یک شی Bitmap :

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

    Kotlin+KTX

    val image = FirebaseVisionImage.fromBitmap(bitmap)
    تصویر نمایش داده شده توسط شی Bitmap باید عمودی باشد، بدون نیاز به چرخش اضافی.

3. برچسب تصویر را اجرا کنید

برای برچسب گذاری اشیاء در یک تصویر، شی FirebaseVisionImage را به متد processImage() FirebaseVisionImageLabeler ارسال کنید.

Java

labeler.processImage(image)
        .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionImageLabel>>() {
            @Override
            public void onSuccess(List<FirebaseVisionImageLabel> labels) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

Kotlin+KTX

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

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

مثلا:

Java

for (FirebaseVisionImageLabel label: labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
}

Kotlin+KTX

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

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

  • دریچه گاز به آشکارساز زنگ می زند. اگر یک قاب ویدیویی جدید در حین کار کردن آشکارساز در دسترس قرار گرفت، قاب را رها کنید.
  • اگر از خروجی آشکارساز برای همپوشانی گرافیک روی تصویر ورودی استفاده می‌کنید، ابتدا نتیجه را از کیت ML دریافت کنید، سپس تصویر را رندر کنید و در یک مرحله همپوشانی کنید. با انجام این کار، برای هر فریم ورودی فقط یک بار به سطح نمایشگر رندر می دهید.
  • اگر از Camera2 API استفاده می کنید، تصاویر را با فرمت ImageFormat.YUV_420_888 بگیرید.

    اگر از دوربین قدیمی‌تر API استفاده می‌کنید، تصاویر را با فرمت ImageFormat.NV21 بگیرید.