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

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

الميزة التفاصيل
اسم Sdk play-services-mlkit-subject-segmentation
التنفيذ غير مجمّع: يتم تنزيل النموذج ديناميكيًا باستخدام خدمات Google Play.
تأثير حجم التطبيق زيادة في الحجم حوالي 200 كيلوبايت.
وقت الإعداد وقد يحتاج المستخدمون إلى الانتظار حتى يتم تنزيل النموذج قبل استخدامه لأول مرة.

جرّبه الآن

  • يمكنك تجربة نموذج التطبيق من أجل يمكنك الاطّلاع على مثال حول استخدام واجهة برمجة التطبيقات هذه.

قبل البدء

  1. في ملف build.gradle على مستوى المشروع، تأكَّد من تضمين مستودع Maven التابع لشركة Google في كلٍّ من القسمَين "buildscript" و"allprojects".
  2. أضِف الاعتمادية لمكتبة تقسيم مواضيع أدوات تعلُّم الآلة إلى ملف 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>

يمكنك أيضًا التحقّق بشكل واضح من مدى توفّر الطراز وطلب تنزيله من خلال "خدمات 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لغة Java
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لغة Java
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():

KotlinJava
val image = InputImage.fromMediaImage(mediaImage, rotation)
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

استخدام معرف موارد منتظم (URI) لملف

لإنشاء InputImage من معرف موارد منتظم (URI) لملف، فمرر سياق التطبيق ومعرف الموارد المنتظم (URI) للملف إلى InputImage.fromFilePath() يكون ذلك مفيدًا عندما يجب استخدام هدف ACTION_GET_CONTENT لتطلب من المستخدم الاختيار. صورة من تطبيق المعرض الخاص به.

KotlinJava
val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}
InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

يتم استخدام ByteBuffer أو ByteArray

لإنشاء InputImage كائن من ByteBuffer أو ByteArray، احسب الصورة أولاً درجة التدوير كما هو موضح سابقًا لإدخال media.Image. بعد ذلك، يمكنك إنشاء الكائن InputImage باستخدام المخزن المؤقت أو المصفوفة بالإضافة إلى الارتفاع والعرض وتنسيق ترميز الألوان ودرجة التدوير:

Kotlinلغة Java
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، قدِّم التعريف التالي:

KotlinJava
val image = InputImage.fromBitmap(bitmap, 0)
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

يتم تمثيل الصورة بواسطة كائن Bitmap مع درجات التدوير.

2. إنشاء مثيل لـ SubjectSegmenter

تحديد خيارات شرائح الجمهور

لتقسيم صورتك، عليك أولاً إنشاء مثيل لـ SubjectSegmenterOptions كـ متابعة:

Kotlinلغة Java
val options = SubjectSegmenterOptions.Builder()
       // enable options
       .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        // enable options
        .build();

في ما يلي تفاصيل كل خيار:

قناع الثقة في المقدّمة

يتيح لك قناع الثقة في المقدّمة التفريق بين الموضوع الذي تعمل في المقدّمة الخلفية.

يمكنك طلب الرقم enableForegroundConfidenceMask() من خلال الخيارات التي تتيح لك استرداد المبلغ في وقت لاحق. قناع المقدّمة من خلال استدعاء getForegroundMask() على تم إرجاع عنصر SubjectSegmentationResult بعد معالجة الصورة.

Kotlinلغة Java
val options = SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build();
صورة نقطية في المقدمة

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

يمكنك طلب الرقم enableForegroundBitmap() من خلال الخيارات التي تتيح لك استرداد المبلغ في وقت لاحق. الصورة النقطية في المقدّمة من خلال استدعاء getForegroundBitmap() على تم إرجاع عنصر SubjectSegmentationResult بعد معالجة الصورة.

Kotlinلغة Java
val options = SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build();
قناع ثقة متعدد المواضيع

كما هو الحال مع خيارات المقدّمة، يمكنك استخدام SubjectResultOptions لتفعيل هذه الميزة. قناع الثقة لكل موضوع في المقدّمة على النحو التالي:

Kotlinلغة Java
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لغة Java
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لغة Java
val segmenter = SubjectSegmentation.getClient(options)
SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

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

اجتَز خطوات InputImage المُعدّة الكائن في طريقة process في SubjectSegmenter:

Kotlinلغة Java
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لغة Java
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لغة Java
val foregroundBitmap = result.foregroundBitmap
Bitmap foregroundBitmap = result.getForegroundBitmap();

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

وبالمثل، يمكنك استرداد القناع للعناصر المقسّمة من خلال استدعاء getConfidenceMask() عن كل موضوع على النحو التالي:

Kotlinلغة Java
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لغة Java
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 بكسل على الأقل.
  • ويمكن أن يؤثر التركيز الضعيف للصورة أيضًا في الدقة. إذا لم تحصل على نتائج مقبولة، اطلب من المستخدم تلخيص الصورة.