تصنيف المواضيع باستخدام حزمة تعلّم الآلة لنظام التشغيل Android

استخدِم حزمة تعلّم الآلة لإضافة ميزات تقسيم العناصر إلى شرائح بسهولة إلى تطبيقك.

الميزة التفاصيل
اسم حزمة تطوير البرامج (SDK) play-services-mlkit-subject-segmentation
التنفيذ غير مجمَّع: يتم تنزيل النموذج بشكل ديناميكي باستخدام "خدمات 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". يمكنك ضبط تطبيقك لتنزيل النموذج تلقائيًا على الجهاز بعد تثبيت تطبيقك من "متجر Google 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>

يمكنك أيضًا التحقّق بشكل صريح من توفّر النموذج وطلب تنزيله من خلال "خدمات Google Play" باستخدام ModuleInstallClient API.

إذا لم تفعّل تنزيل النماذج عند التثبيت أو لم تطلب تنزيلها بشكل صريح، سيتم تنزيل النموذج عند تشغيل أداة تقسيم الصور لأول مرة. لن يؤدي تقديم طلبات قبل اكتمال عملية التنزيل إلى ظهور أي نتائج.

1. إعداد الصورة المصدر

لإجراء تقسيم على صورة، أنشئ عنصر 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 مع درجات التدوير.

2. إنشاء مثيل 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();
صورة نقطية في المقدّمة

وبالمثل، يمكنك أيضًا الحصول على صورة نقطية للعنصر في المقدّمة.

يتيح لك الخيار Call 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);

3- معالجة صورة

مرِّر عنصر 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
                // ...
            }
        });

4. الحصول على نتيجة تقسيم الموضوع

استرداد أقنعة المقدمة والصور النقطية

بعد المعالجة، يمكنك استرداد قناع المقدّمة لصورتك التي يتم استدعاؤها 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());
}

نصائح لتحسين الأداء

في كل جلسة تطبيق، يكون الاستنتاج الأول غالبًا أبطأ من الاستنتاجات اللاحقة بسبب عملية تهيئة النموذج. إذا كان وقت الاستجابة المنخفض مهمًا، ننصحك بإجراء استنتاج "وهمي" مسبقًا.

تعتمد جودة النتائج على جودة الصورة المُدخَلة:

  • لكي تحصل حزمة تعلّم الآلة على نتيجة تقسيم دقيقة، يجب أن يكون حجم الصورة 512×512 بكسل على الأقل.
  • يمكن أن يؤثر عدم وضوح الصورة أيضًا في الدقة. إذا لم تحصل على نتائج مقبولة، اطلب من المستخدم إعادة التقاط الصورة.