تقسیم بندی موضوع با ML Kit برای اندروید

از کیت ML برای افزودن آسان ویژگی‌های تقسیم‌بندی موضوعی به برنامه خود استفاده کنید.

ویژگی جزئیات
نام SDK تقسیم‌بندی موضوعی سرویس‌های بازی mlkit
پیاده‌سازی غیرمتمرکز: مدل به صورت پویا با استفاده از سرویس‌های گوگل پلی دانلود می‌شود.
تأثیر اندازه برنامه افزایش حجم حدود ۲۰۰ کیلوبایت.
زمان اولیه سازی کاربران ممکن است مجبور باشند قبل از اولین استفاده، منتظر دانلود مدل بمانند.

امتحانش کن.

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

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

همانطور که در بالا ذکر شد، این مدل توسط سرویس‌های گوگل پلی ارائه می‌شود. می‌توانید برنامه خود را طوری پیکربندی کنید که پس از نصب برنامه از فروشگاه گوگل پلی، مدل را به طور خودکار روی دستگاه دانلود کند. برای انجام این کار، عبارت زیر را به فایل 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>

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

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

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

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

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

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

گزینه‌های تقسیم‌بندی را تعریف کنید

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

کاتلین

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

جاوا

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

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

ماسک اعتماد به نفس در پیش زمینه

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

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

کاتلین

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

جاوا

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

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

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

کاتلین

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

جاوا

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

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

کاتلین

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()
بیت‌مپ چند موضوعی

و به طور مشابه، می‌توانید بیت‌مپ را برای هر موضوع فعال کنید:

کاتلین

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() ایجاد کنید و گزینه‌ها را به عنوان پارامتر ارسال کنید:

کاتلین

val segmenter = SubjectSegmentation.getClient(options)

جاوا

SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

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

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

کاتلین

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() به صورت زیر بازیابی کنید:

کاتلین

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() ‎ یک بیت‌مپ از پیش‌زمینه تصویر بازیابی کنید:

کاتلین

val foregroundBitmap = result.foregroundBitmap

جاوا

Bitmap foregroundBitmap = result.getForegroundBitmap();

بازیابی ماسک‌ها و بیت‌مپ‌ها برای هر موضوع

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

کاتلین

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

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

کاتلین

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

جاوا

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

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

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

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

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