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

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

ویژگی جزئیات
نام Sdk play-services-mlkit-subject-segmentation
پیاده سازی Unbundled: مدل به صورت پویا با استفاده از خدمات Google Play دانلود می شود.
تاثیر اندازه برنامه 200 کیلوبایت افزایش حجم
زمان اولیه سازی کاربران ممکن است قبل از اولین استفاده منتظر باشند تا مدل دانلود شود.

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

قبل از شروع

  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 Store، مدل را به طور خودکار در دستگاه دانلود کند. برای انجام این کار، اعلان زیر را به فایل 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 با ModuleInstallClient API درخواست دانلود کنید.

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

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

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

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

2. یک نمونه از 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);

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

آبجکت 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
                // ...
            }
        });

4. نتیجه بخش بندی موضوع را دریافت کنید

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

پس از پردازش، می توانید ماسک پیش زمینه تصویر خود را با فراخوانی 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 به یک نتیجه تقسیم بندی دقیق برسد، تصویر باید حداقل 512x512 پیکسل باشد.
  • فوکوس ضعیف تصویر نیز می تواند بر دقت تأثیر بگذارد. اگر نتایج قابل قبولی دریافت نکردید، از کاربر بخواهید که تصویر را دوباره بگیرد.