برچسب گذاری تصاویر با یک مدل سفارشی در اندروید

شما می توانید از کیت ML برای شناسایی موجودیت ها در یک تصویر و برچسب گذاری آنها استفاده کنید. این API از طیف گسترده ای از مدل های طبقه بندی تصاویر سفارشی پشتیبانی می کند. لطفاً برای راهنمایی در مورد الزامات سازگاری مدل، مکان یافتن مدل های از پیش آموزش دیده و نحوه آموزش مدل های خود به مدل های سفارشی با کیت ML مراجعه کنید.

دو راه برای ادغام برچسب‌گذاری تصویر با مدل‌های سفارشی وجود دارد: با بسته‌بندی خط لوله به‌عنوان بخشی از برنامه خود، یا با استفاده از خط لوله جداشده که به خدمات Google Play بستگی دارد. اگر خط لوله جداشده را انتخاب کنید، برنامه شما کوچکتر خواهد شد. برای جزئیات به جدول زیر مراجعه کنید.

همراه تفکیک شده
نام کتابخانه com.google.mlkit:image-labeling-custom com.google.android.gms:play-services-mlkit-image-labeling-custom
پیاده سازی Pipeline به صورت ایستا به برنامه شما در زمان ساخت مرتبط است. Pipeline به صورت پویا از طریق خدمات Google Play دانلود می شود.
اندازه برنامه حدود 3.8 مگابایت افزایش حجم. افزایش حجم حدود 200 کیلوبایت
زمان اولیه سازی خط لوله بلافاصله در دسترس است. ممکن است قبل از استفاده برای اولین بار منتظر بمانید تا خط لوله بارگیری شود.
مرحله چرخه عمر API در دسترس بودن عمومی (GA) بتا

دو راه برای ادغام یک مدل سفارشی وجود دارد: مدل را با قرار دادن آن در پوشه دارایی برنامه خود دسته بندی کنید یا به صورت پویا آن را از Firebase دانلود کنید. جدول زیر این دو گزینه را با هم مقایسه می کند.

مدل همراه مدل میزبانی شده
این مدل بخشی از APK برنامه شما است که اندازه آن را افزایش می دهد. مدل بخشی از APK شما نیست. با آپلود در Firebase Machine Learning میزبانی می شود.
این مدل بلافاصله در دسترس است، حتی زمانی که دستگاه اندروید آفلاین است مدل در صورت تقاضا دانلود می شود
بدون نیاز به پروژه Firebase به پروژه Firebase نیاز دارد
برای به‌روزرسانی مدل، باید برنامه خود را دوباره منتشر کنید به روز رسانی مدل را بدون انتشار مجدد برنامه خود فشار دهید
بدون تست A/B داخلی تست آسان A/B با Firebase Remote Config

آن را امتحان کنید

قبل از شروع

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

  2. وابستگی های کتابخانه های اندروید ML Kit را به فایل gradle سطح برنامه ماژول خود اضافه کنید، که معمولا app/build.gradle است. یکی از وابستگی های زیر را بر اساس نیاز خود انتخاب کنید:

    برای بسته بندی خط لوله با برنامه شما:

    dependencies {
      // ...
      // Use this dependency to bundle the pipeline with your app
      implementation 'com.google.mlkit:image-labeling-custom:17.0.3'
    }
    

    برای استفاده از خط لوله در خدمات Google Play:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded pipeline in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5'
    }
    
  3. اگر انتخاب کردید از خط لوله در خدمات Google Play استفاده کنید ، می‌توانید برنامه خود را طوری پیکربندی کنید که پس از نصب برنامه از فروشگاه Play، خط لوله را به‌طور خودکار در دستگاه دانلود کنید. برای انجام این کار، اعلان زیر را به فایل AndroidManifest.xml برنامه خود اضافه کنید:

    <application ...>
        ...
        <meta-data
            android:name="com.google.mlkit.vision.DEPENDENCIES"
            android:value="custom_ica" />
        <!-- To use multiple downloads: android:value="custom_ica,download2,download3" -->
    </application>
    

    همچنین می‌توانید صریحاً در دسترس بودن خط لوله را بررسی کنید و از طریق سرویس‌های Google Play ModuleInstallClient API درخواست دانلود کنید.

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

  4. اگر می‌خواهید مدلی را به صورت پویا از Firebase دانلود کنید، وابستگی linkFirebase را اضافه کنید:

    برای دانلود پویا یک مدل از Firebase، وابستگی linkFirebase را اضافه کنید:

    dependencies {
      // ...
      // Image labeling feature with model downloaded from Firebase
      implementation 'com.google.mlkit:image-labeling-custom:17.0.3'
      // Or use the dynamically downloaded pipeline in Google Play Services
      // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5'
      implementation 'com.google.mlkit:linkfirebase:17.0.0'
    }
    
  5. اگر می خواهید مدلی را دانلود کنید ، مطمئن شوید که Firebase را به پروژه اندروید خود اضافه کرده اید ، اگر قبلاً این کار را انجام نداده اید. هنگامی که مدل را بسته بندی می کنید، این مورد نیاز نیست.

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

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

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

  1. فایل مدل (معمولاً به .tflite یا .lite ختم می شود) در assets/ پوشه برنامه خود کپی کنید. (شاید لازم باشد ابتدا با کلیک راست بر روی app/ پوشه، سپس روی New > Folder > Assets Folder، پوشه را ایجاد کنید.)

  2. سپس موارد زیر را به فایل build.gradle برنامه خود اضافه کنید تا مطمئن شوید که Gradle فایل مدل را هنگام ساخت برنامه فشرده نمی کند:

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
            // or noCompress "lite"
        }
    }
    

    فایل مدل در بسته برنامه گنجانده شده و به عنوان دارایی خام در اختیار ML Kit قرار خواهد گرفت.

  3. شی LocalModel را ایجاد کنید، مسیر فایل مدل را مشخص کنید:

    کاتلین

    val localModel = LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build()

    جاوا

    LocalModel localModel =
        new LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build();

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

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

کاتلین

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

جاوا

// Specify the name you assigned in the Firebase console.
CustomRemoteModel remoteModel =
    new CustomRemoteModel
        .Builder(new FirebaseModelSource.Builder("your_model_name").build())
        .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 از یکی از آنها ایجاد کنید.

گزینه های زیر در دسترس هستند:

گزینه ها
confidenceThreshold

حداقل امتیاز اطمینان برچسب های شناسایی شده اگر تنظیم نشود، از هر آستانه طبقه‌بندی‌کننده‌ای که توسط فراداده مدل مشخص شده است استفاده می‌شود. اگر مدل حاوی هیچ ابرداده ای نباشد یا ابرداده آستانه طبقه بندی کننده را مشخص نکرده باشد، از آستانه پیش فرض 0.0 استفاده می شود.

maxResultCount

حداکثر تعداد برچسب برای بازگشت. اگر تنظیم نشود، مقدار پیش فرض 10 استفاده می شود.

اگر فقط یک مدل باندل محلی دارید، فقط یک برچسب از شی LocalModel خود ایجاد کنید:

کاتلین

val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.5f)
    .setMaxResultCount(5)
    .build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)

جاوا

CustomImageLabelerOptions customImageLabelerOptions =
        new CustomImageLabelerOptions.Builder(localModel)
            .setConfidenceThreshold(0.5f)
            .setMaxResultCount(5)
            .build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);

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

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

کاتلین

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded ->
    val optionsBuilder =
        if (isDownloaded) {
            CustomImageLabelerOptions.Builder(remoteModel)
        } else {
            CustomImageLabelerOptions.Builder(localModel)
        }
    val options = optionsBuilder
                  .setConfidenceThreshold(0.5f)
                  .setMaxResultCount(5)
                  .build()
    val labeler = ImageLabeling.getClient(options)
}

جاوا

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @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.5f)
                    .setMaxResultCount(5)
                    .build();
                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

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

کاتلین

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

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

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

شما می توانید یک شی InputImage از منابع مختلف ایجاد کنید که هر کدام در زیر توضیح داده شده است.

استفاده از یک media.Image

برای ایجاد یک شیء InputImage از یک شیء media.Image ، مانند زمانی که تصویری را از دوربین دستگاه می‌گیرید، شیء media.Image 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 همراه با درجه چرخش نمایش داده می شود.

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

برای برچسب گذاری اشیاء در یک تصویر، شی 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
                // ...
            }
        });

4. اطلاعاتی در مورد نهادهای دارای برچسب دریافت کنید

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

کاتلین

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 API استفاده می‌کنید، دریچه گاز با برچسب تصویر تماس می‌گیرد. اگر در حالی که برچسب‌دهنده تصویر در حال اجرا است، فریم ویدیویی جدیدی در دسترس است، قاب را رها کنید. برای مثال، کلاس VisionProcessorBase را در برنامه نمونه سریع شروع کنید.
  • اگر از CameraX API استفاده می‌کنید، مطمئن شوید که استراتژی فشار برگشتی روی مقدار پیش‌فرض ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST تنظیم شده است.STRATEGY_KEEP_ONLY_LATEST. این تضمین می کند که هر بار فقط یک تصویر برای تجزیه و تحلیل تحویل داده می شود. اگر در زمانی که آنالایزر مشغول است، تصاویر بیشتری تولید شود، به طور خودکار حذف می شوند و برای تحویل در صف قرار نمی گیرند. هنگامی که تصویر مورد تجزیه و تحلیل با فراخوانی ImageProxy.close بسته شد، آخرین تصویر بعدی تحویل داده می شود.
  • اگر از خروجی برچسب تصویر برای همپوشانی گرافیک روی تصویر ورودی استفاده می کنید، ابتدا نتیجه را از کیت ML دریافت کنید، سپس تصویر را رندر کنید و در یک مرحله همپوشانی کنید. این تنها یک بار برای هر فریم ورودی به سطح نمایشگر نمایش داده می شود. برای مثال، کلاس‌های CameraSourcePreview و GraphicOverlay را در برنامه نمونه شروع سریع ببینید.
  • اگر از Camera2 API استفاده می کنید، تصاویر را با فرمت ImageFormat.YUV_420_888 بگیرید. اگر از دوربین قدیمی‌تر API استفاده می‌کنید، تصاویر را با فرمت ImageFormat.NV21 بگیرید.