شناسایی و ردیابی اشیاء با ML Kit در اندروید

می توانید از کیت ML برای شناسایی و ردیابی اشیاء در فریم های ویدیویی متوالی استفاده کنید.

هنگامی که یک تصویر را به ML Kit ارسال می کنید، حداکثر پنج شی را در تصویر به همراه موقعیت هر شی در تصویر تشخیص می دهد. هنگام شناسایی اشیاء در جریان های ویدئویی، هر شی دارای یک شناسه منحصر به فرد است که می توانید از آن برای ردیابی شی از فریم به فریم استفاده کنید. همچنین می‌توانید به‌صورت اختیاری طبقه‌بندی اشیاء درشت را فعال کنید، که اشیا را با توضیحات دسته‌بندی گسترده برچسب‌گذاری می‌کند.

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

قبل از شروع

  1. در فایل build.gradle در سطح پروژه خود، مطمئن شوید که مخزن Maven Google را در هر دو بخش buildscript و allprojects خود قرار دهید.
  2. وابستگی های کتابخانه های اندروید ML Kit را به فایل gradle سطح برنامه ماژول خود اضافه کنید، که معمولا app/build.gradle است:
    dependencies {
      // ...
    
      implementation 'com.google.mlkit:object-detection:17.0.2'
    
    }

1. آشکارساز شی را پیکربندی کنید

برای شناسایی و ردیابی اشیا، ابتدا یک نمونه از ObjectDetector ایجاد کنید و به صورت اختیاری تنظیمات آشکارساز را که می خواهید از حالت پیش فرض تغییر دهید، مشخص کنید.

  1. با یک شی ObjectDetectorOptions آشکارساز شی را برای مورد استفاده خود پیکربندی کنید. می توانید تنظیمات زیر را تغییر دهید:

    تنظیمات آشکارساز شی
    حالت تشخیص STREAM_MODE (پیش‌فرض) | SINGLE_IMAGE_MODE

    در STREAM_MODE (پیش‌فرض)، آشکارساز شی با تأخیر کم کار می‌کند، اما ممکن است نتایج ناقصی (مانند جعبه‌های مرزی نامشخص یا برچسب‌های دسته) در چند فراخوانی اول آشکارساز ایجاد کند. همچنین، در STREAM_MODE ، آشکارساز شناسه‌های ردیابی را به اشیا اختصاص می‌دهد که می‌توانید از آنها برای ردیابی اشیاء در فریم‌ها استفاده کنید. زمانی که می‌خواهید اشیاء را ردیابی کنید، یا زمانی که تأخیر کم اهمیت دارد، از این حالت استفاده کنید، مانند هنگام پردازش جریان‌های ویدیویی در زمان واقعی.

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

    چندین اشیاء را شناسایی و ردیابی کنید false (پیش فرض) | true

    آیا برای شناسایی و ردیابی حداکثر پنج شی یا فقط برجسته ترین شی (پیش فرض).

    طبقه بندی اشیاء false (پیش فرض) | true

    اینکه آیا اشیاء شناسایی شده در دسته های درشت طبقه بندی شوند یا نه. هنگامی که آشکارساز شیء فعال باشد، اشیاء را به دسته‌های زیر طبقه‌بندی می‌کند: کالاهای مد، غذا، کالاهای خانگی، مکان‌ها و گیاهان.

    API تشخیص و ردیابی شی برای این دو مورد اصلی بهینه شده است:

    • تشخیص زنده و ردیابی برجسته ترین شی در منظره یاب دوربین.
    • تشخیص چندین شی از یک تصویر ثابت

    برای پیکربندی API برای این موارد استفاده:

    کاتلین

    // Live detection and tracking
    val options = ObjectDetectorOptions.Builder()
            .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
            .enableClassification()  // Optional
            .build()
    
    // Multiple object detection in static images
    val options = ObjectDetectorOptions.Builder()
            .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
            .enableMultipleObjects()
            .enableClassification()  // Optional
            .build()

    جاوا

    // Live detection and tracking
    ObjectDetectorOptions options =
            new ObjectDetectorOptions.Builder()
                    .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
                    .enableClassification()  // Optional
                    .build();
    
    // Multiple object detection in static images
    ObjectDetectorOptions options =
            new ObjectDetectorOptions.Builder()
                    .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
                    .enableMultipleObjects()
                    .enableClassification()  // Optional
                    .build();
  2. نمونه ای از ObjectDetector را دریافت کنید:

    کاتلین

    val objectDetector = ObjectDetection.getClient(options)

    جاوا

    ObjectDetector objectDetector = ObjectDetection.getClient(options);

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

برای شناسایی و ردیابی اشیا، تصاویر را به process() نمونه ObjectDetector ارسال کنید.

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

برای هر فریم ویدیو یا تصویر در یک دنباله، موارد زیر را انجام دهید:

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

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

تصویر را به متد process() منتقل کنید:

کاتلین

objectDetector.process(image)
    .addOnSuccessListener { detectedObjects ->
        // Task completed successfully
        // ...
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }

جاوا

objectDetector.process(image)
    .addOnSuccessListener(
        new OnSuccessListener<List<DetectedObject>>() {
            @Override
            public void onSuccess(List<DetectedObject> detectedObjects) {
                // Task completed successfully
                // ...
            }
        })
    .addOnFailureListener(
        new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

4. اطلاعاتی در مورد اشیاء شناسایی شده دریافت کنید

اگر فراخوانی process() موفق شود، لیستی از DetectedObject به شنونده موفقیت ارسال می شود.

هر DetectedObject دارای ویژگی های زیر است:

جعبه مرزی Rect که موقعیت شی را در تصویر نشان می دهد.
شناسه پیگیری یک عدد صحیح که شی را در بین تصاویر شناسایی می کند. در SINGLE_IMAGE_MODE خالی است.
برچسب ها
توضیحات برچسب توضیحات متن برچسب این یکی از ثابت های رشته تعریف شده در PredefinedCategory خواهد بود.
فهرست برچسب شاخص برچسب در بین تمام برچسب های پشتیبانی شده توسط طبقه بندی کننده. این یکی از ثابت های عدد صحیح تعریف شده در PredefinedCategory خواهد بود.
برچسب اعتماد به نفس ارزش اطمینان طبقه بندی شی.

کاتلین

for (detectedObject in detectedObjects) {
    val boundingBox = detectedObject.boundingBox
    val trackingId = detectedObject.trackingId
    for (label in detectedObject.labels) {
        val text = label.text
        if (PredefinedCategory.FOOD == text) {
            ...
        }
        val index = label.index
        if (PredefinedCategory.FOOD_INDEX == index) {
            ...
        }
        val confidence = label.confidence
    }
}

جاوا

// The list of detected objects contains one item if multiple
// object detection wasn't enabled.
for (DetectedObject detectedObject : detectedObjects) {
    Rect boundingBox = detectedObject.getBoundingBox();
    Integer trackingId = detectedObject.getTrackingId();
    for (Label label : detectedObject.getLabels()) {
        String text = label.getText();
        if (PredefinedCategory.FOOD.equals(text)) {
            ...
        }
        int index = label.getIndex();
        if (PredefinedCategory.FOOD_INDEX == index) {
            ...
        }
        float confidence = label.getConfidence();
    }
}

تضمین یک تجربه کاربری عالی

برای بهترین تجربه کاربری، این دستورالعمل ها را در برنامه خود دنبال کنید:

  • تشخیص موفق شی به پیچیدگی بصری شی بستگی دارد. برای شناسایی، اشیاء با تعداد کمی از ویژگی های بصری ممکن است نیاز داشته باشند که بخش بیشتری از تصویر را اشغال کنند. شما باید راهنمایی هایی را در مورد گرفتن ورودی به کاربران ارائه دهید که به خوبی با نوع اشیایی که می خواهید شناسایی کنید کار می کند.
  • هنگامی که از طبقه بندی استفاده می کنید، اگر می خواهید اشیایی را شناسایی کنید که به طور واضح در دسته های پشتیبانی شده قرار نمی گیرند، مدیریت ویژه ای را برای اشیاء ناشناخته اجرا کنید.

همچنین، برنامه ویترین ML Kit Material Design و Material Design Patterns برای مجموعه ویژگی‌های مبتنی بر یادگیری ماشین را بررسی کنید.

بهبود عملکرد

اگر می خواهید از تشخیص شی در یک برنامه بلادرنگ استفاده کنید، این دستورالعمل ها را برای دستیابی به بهترین نرخ فریم دنبال کنید:

  • وقتی از حالت پخش در یک برنامه بلادرنگ استفاده می‌کنید، از تشخیص چند شیء استفاده نکنید، زیرا اکثر دستگاه‌ها قادر به تولید نرخ فریم مناسب نیستند.

  • اگر به آن نیاز ندارید، طبقه بندی را غیرفعال کنید.

  • اگر از Camera یا camera2 API استفاده می کنید، دریچه گاز با آشکارساز تماس می گیرد. اگر یک قاب ویدیویی جدید در حین کار کردن آشکارساز در دسترس قرار گرفت، قاب را رها کنید. برای مثال، کلاس VisionProcessorBase را در برنامه نمونه سریع شروع کنید.
  • اگر از CameraX API استفاده می‌کنید، مطمئن شوید که استراتژی فشار برگشتی روی مقدار پیش‌فرض ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST تنظیم شده است.STRATEGY_KEEP_ONLY_LATEST. این تضمین می کند که هر بار فقط یک تصویر برای تجزیه و تحلیل تحویل داده می شود. اگر در زمانی که آنالایزر مشغول است، تصاویر بیشتری تولید شود، به طور خودکار حذف می شوند و برای تحویل در صف قرار نمی گیرند. هنگامی که تصویر مورد تجزیه و تحلیل با فراخوانی ImageProxy.close بسته شد، آخرین تصویر بعدی تحویل داده می شود.
  • اگر از خروجی آشکارساز برای همپوشانی گرافیک روی تصویر ورودی استفاده می‌کنید، ابتدا نتیجه را از کیت ML بگیرید، سپس تصویر را در یک مرحله رندر کنید و همپوشانی کنید. این تنها یک بار برای هر فریم ورودی به سطح نمایشگر نمایش داده می شود. برای مثال، کلاس‌های CameraSourcePreview و GraphicOverlay را در برنامه نمونه شروع سریع ببینید.
  • اگر از Camera2 API استفاده می کنید، تصاویر را با فرمت ImageFormat.YUV_420_888 بگیرید. اگر از دوربین قدیمی‌تر API استفاده می‌کنید، تصاویر را با فرمت ImageFormat.NV21 بگیرید.