تقسیم‌بندی سوژه با ML Kit برای Android

از «کیت یادگیری ماشین» استفاده کنید تا به‌راحتی ویژگی‌های بخش‌بندی سوژه را به برنامه‌تان اضافه کنید.

ویژگی جزئیات
نام کیت توسعه نرم‌افزار play-services-mlkit-subject-segmentation
پیاده‌سازی غیربسته‌ای: مدل به‌طور پویا بااستفاده از خدمات Google Play بارگیری می‌شود.
تأثیر اندازه برنامه افزایش اندازه حدود ۲۰۰ کیلوبایت.
زمان مقداردهی اولیه ممکن است کاربران مجبور شوند قبل‌از اولین استفاده، منتظر بمانند تا مدل بارگیری شود.

امتحان کنید

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

قبل‌از شروع

  1. در فایل build.gradle سطح پروژه، حتماً مخزن Maven متعلق به Google را در هر دو بخش buildscript و allprojects اضافه کنید.
  2. وابستگی کتابخانه بخش‌بندی سوژه ML Kit را به فایل gradle سطح برنامه ماژولتان اضافه کنید، که معمولاً app/build.gradle است:
dependencies {
   implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}

همان‌طور که در بالا ذکر شد، این مدل توسط «خدمات Google Play» ارائه می‌شود. می‌توانید برنامه‌تان را به‌گونه‌ای پیکربندی کنید که پس‌از نصب شدن برنامه از «فروشگاه Play»، مدل را به‌طور خودکار در دستگاه بارگیری کند. برای انجام این کار، اعلان زیر را به فایل AndroidManifest.xml برنامه‌تان اضافه کنید:

<application ...>
      ...
      <meta-data
          android:name="com.google.mlkit.vision.DEPENDENCIES"
          android:value="subject_segment" >
      <!-- To use multiple models: android:value="subject_segment,model2,model3" -->
</application>

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

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

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

برای انجام بخش‌بندی روی تصویر، یک شیء InputImage از Bitmap، media.Image، ByteBuffer، آرایه بایت، یا فایلی در دستگاه ایجاد کنید.

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

۲. ایجاد نمونه‌ای از SubjectSegmenter

تعریف کردن گزینه‌های بخش‌بندی‌کننده

برای بخش‌بندی تصویر، ابتدا نمونه‌ای از SubjectSegmenterOptions را به‌صورت زیر ایجاد کنید:

Kotlin

val options = SubjectSegmenterOptions.Builder()
       // enable options
       .build()

جاوا

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        // enable options
        .build();

جزئیات هر گزینه در اینجا آمده است:

ماسک اطمینان پیش‌زمینه

ماسک اطمینان پیش‌نما به شما امکان می‌دهد سوژه پیش‌نما را از پس‌زمینه تشخیص دهید.

فراخوانی enableForegroundConfidenceMask() در گزینه‌ها به شما امکان می‌دهد بعداً با فراخوانی getForegroundMask() در شیء SubjectSegmentationResult که پس‌از پردازش تصویر برگردانده می‌شود، ماسک پیش‌زمینه را بازیابی کنید.

Kotlin

val options = SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build()

جاوا

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build();
بیت‌مپ پیش‌زمینه

به‌طور مشابه، می‌توانید نگاشت بیتی سوژه پیش‌زمینه را نیز دریافت کنید.

فراخوانی enableForegroundBitmap() در گزینه‌ها به شما امکان می‌دهد بعداً با فراخوانی getForegroundBitmap() در شیء SubjectSegmentationResult که پس‌از پردازش تصویر برگردانده می‌شود، بیت‌مپ پیش‌زمینه را بازیابی کنید.

Kotlin

val options = SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build()

جاوا

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build();
ماسک اطمینان چندموضوعی

مانند گزینه‌های پیش‌زمینه، می‌توانید از SubjectResultOptions برای فعال کردن ماسک اطمینان برای هر سوژه پیش‌زمینه به‌صورت زیر استفاده کنید:

Kotlin

val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
    .enableConfidenceMask()
    .build()

val options = SubjectSegmenterOptions.Builder()
    .enableMultipleSubjects(subjectResultOptions)
    .build()

جاوا

SubjectResultOptions subjectResultOptions =
        new SubjectSegmenterOptions.SubjectResultOptions.Builder()
            .enableConfidenceMask()
            .build()

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
      .enableMultipleSubjects(subjectResultOptions)
      .build()
بیت‌مپ چندموضوعی

و به‌طور مشابه، می‌توانید نقشه بیتی را برای هر سوژه فعال کنید:

Kotlin

val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
    .enableSubjectBitmap()
    .build()

val options = SubjectSegmenterOptions.Builder()
    .enableMultipleSubjects(subjectResultOptions)
    .build()

جاوا

SubjectResultOptions subjectResultOptions =
      new SubjectSegmenterOptions.SubjectResultOptions.Builder()
        .enableSubjectBitmap()
        .build()

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
      .enableMultipleSubjects(subjectResultOptions)
      .build()

ایجاد بخش‌بندی‌کننده سوژه

پس‌از مشخص کردن گزینه‌های SubjectSegmenterOptions، یک نمونه SubjectSegmenter ایجاد کنید که getClient() را فراخوانی کند و گزینه‌ها را به‌عنوان پارامتر ارسال کند:

Kotlin

val segmenter = SubjectSegmentation.getClient(options)

جاوا

SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

۳. پردازش تصویر

شیء InputImage آماده‌شده را به روش process مربوط به SubjectSegmenter ارسال کنید:

Kotlin

segmenter.process(inputImage)
    .addOnSuccessListener { result ->
        // Task completed successfully
        // ...
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }

جاوا

segmenter.process(inputImage)
    .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(SubjectSegmentationResult result) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

۴. دریافت نتیجه بخش‌بندی سوژه

بازیابی ماسک‌های پیش‌زمینه و نقشه‌های بیتی

پس‌از پردازش، می‌توانید ماسک پیش‌زمینه را برای تصویرتان با فراخوانی getForegroundConfidenceMask() به‌صورت زیر بازیابی کنید:

Kotlin

val colors = IntArray(image.width * image.height)

val foregroundMask = result.foregroundConfidenceMask
for (i in 0 until image.width * image.height) {
  if (foregroundMask[i] > 0.5f) {
    colors[i] = Color.argb(128, 255, 0, 255)
  }
}

val bitmapMask = Bitmap.createBitmap(
  colors, image.width, image.height, Bitmap.Config.ARGB_8888
)

جاوا

int[] colors = new int[image.getWidth() * image.getHeight()];

FloatBuffer foregroundMask = result.getForegroundConfidenceMask();
for (int i = 0; i < image.getWidth() * image.getHeight(); i++) {
  if (foregroundMask.get() > 0.5f) {
    colors[i] = Color.argb(128, 255, 0, 255);
  }
}

Bitmap bitmapMask = Bitmap.createBitmap(
      colors, image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888
);

همچنین می‌توانید با فراخوانی getForegroundBitmap()، نقشه بیتی پیش‌زمینه تصویر را بازیابی کنید:

Kotlin

val foregroundBitmap = result.foregroundBitmap

جاوا

Bitmap foregroundBitmap = result.getForegroundBitmap();

بازیابی ماسک‌ها و نقشه‌های بیتی برای هر سوژه

به‌طور مشابه، می‌توانید با فراخوانی getConfidenceMask() روی هر سوژه به‌صورت زیر، ماسک سوژه‌های بخش‌بندی‌شده را بازیابی کنید:

Kotlin

val subjects = result.subjects

val colors = IntArray(image.width * image.height)
for (subject in subjects) {
  val mask = subject.confidenceMask
  for (i in 0 until subject.width * subject.height) {
    val confidence = mask[i]
    if (confidence > 0.5f) {
      colors[image.width * (subject.startY - 1) + subject.startX] =
          Color.argb(128, 255, 0, 255)
    }
  }
}

val bitmapMask = Bitmap.createBitmap(
  colors, image.width, image.height, Bitmap.Config.ARGB_8888
)

جاوا

List subjects = result.getSubjects();

int[] colors = new int[image.getWidth() * image.getHeight()];
for (Subject subject : subjects) {
  FloatBuffer mask = subject.getConfidenceMask();
  for (int i = 0; i < subject.getWidth() * subject.getHeight(); i++) {
    float confidence = mask.get();
    if (confidence > 0.5f) {
      colors[width * (subject.getStartY() - 1) + subject.getStartX()]
          = Color.argb(128, 255, 0, 255);
    }
  }
}

Bitmap bitmapMask = Bitmap.createBitmap(
  colors, image.width, image.height, Bitmap.Config.ARGB_8888
);

همچنین می‌توانید به نقشه بیتی هر سوژه بخش‌بندی‌شده به‌صورت زیر دسترسی پیدا کنید:

Kotlin

val bitmaps = mutableListOf()
for (subject in subjects) {
  bitmaps.add(subject.bitmap)
}

جاوا

List bitmaps = new ArrayList<>();
for (Subject subject : subjects) {
  bitmaps.add(subject.getBitmap());
}

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

برای هر جلسه برنامه، اولین استنتاج اغلب به‌دلیل مقداردهی اولیه مدل کندتر از استنتاج‌های بعدی است. اگر تأخیر کم حیاتی است، فراخوانی یک استنتاج «ساختگی» را ازقبل درنظر بگیرید.

کیفیت نتایج شما به کیفیت تصویر ورودی بستگی دارد:

  • برای اینکه «کیت یادگیری ماشین» نتیجه بخش‌بندی دقیقی دریافت کند، تصویر باید حداقل ۵۱۲×۵۱۲ پیکسل باشد.
  • تمرکز ضعیف تصویر نیز می‌تواند بر دقت تأثیر بگذارد. اگر نتایج قابل‌قبولی دریافت نکردید، از کاربر بخواهید تصویر را دوباره ضبط کند.