ติดป้ายกำกับรูปภาพด้วย ML Kit บน Android

คุณสามารถใช้ ML Kit เพื่อติดป้ายกำกับวัตถุที่ระบบจดจำในรูปภาพได้โดยใช้โมเดลในอุปกรณ์หรือโมเดลในระบบคลาวด์ ดูภาพรวมเพื่อดูประโยชน์ของแต่ละแนวทาง

ก่อนเริ่มต้น

  1. เพิ่ม Firebase ลงในโปรเจ็กต์ Android หากยังไม่ได้ดำเนินการ
  2. เพิ่มทรัพยากร Dependency สำหรับคลัง ML Kit สำหรับ Android ลงในไฟล์ Gradle ของโมดูล (ระดับแอป) (โดยปกติจะเป็น app/build.gradle)
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-image-label-model:20.0.1'
    }
  3. ไม่บังคับแต่แนะนํา: หากคุณใช้ API ในอุปกรณ์ ให้กําหนดค่าแอปให้ดาวน์โหลดโมเดล ML ลงในอุปกรณ์โดยอัตโนมัติหลังจากติดตั้งแอปจาก Play Store

    โดยเพิ่มการประกาศต่อไปนี้ลงในไฟล์ AndroidManifest.xml ของแอป

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="label" />
      <!-- To use multiple models: android:value="label,model2,model3" -->
    </application>
    หากคุณไม่ได้เปิดใช้การดาวน์โหลดโมเดลขณะติดตั้ง ระบบจะดาวน์โหลดโมเดลเมื่อคุณเรียกใช้เครื่องตรวจจับในอุปกรณ์เป็นครั้งแรก คำขอที่คุณส่งก่อนการดาวน์โหลดเสร็จสมบูรณ์จะไม่มีผล
  4. หากต้องการใช้โมเดลที่อยู่ในระบบคลาวด์และยังไม่ได้เปิดใช้ API ในระบบคลาวด์สําหรับโปรเจ็กต์ ให้ทําดังนี้

    1. เปิดหน้า ML Kit API ของคอนโซล Firebase
    2. หากยังไม่ได้อัปเกรดโปรเจ็กต์เป็นแพ็กเกจราคา Blaze ให้คลิกอัปเกรด (ระบบจะแจ้งให้คุณอัปเกรดเฉพาะในกรณีที่โปรเจ็กต์ไม่ได้อยู่ในแพ็กเกจ Blaze)

      เฉพาะโปรเจ็กต์ระดับ Blaze เท่านั้นที่ใช้ API บนระบบคลาวด์ได้

    3. หากยังไม่ได้เปิดใช้ API ที่อยู่ในระบบคลาวด์ ให้คลิกเปิดใช้ API ที่อยู่ในระบบคลาวด์

    หากต้องการใช้เฉพาะโมเดลในอุปกรณ์ ให้ข้ามขั้นตอนนี้

ตอนนี้คุณก็พร้อมติดป้ายกำกับรูปภาพโดยใช้โมเดลในอุปกรณ์หรือโมเดลในระบบคลาวด์แล้ว

1. เตรียมรูปภาพอินพุต

สร้างออบเจ็กต์ FirebaseVisionImage จากรูปภาพ เครื่องมือติดป้ายกำกับรูปภาพจะทำงานได้เร็วที่สุดเมื่อคุณใช้ Bitmap หรือหากใช้ camera2 API ให้ใช้ media.Image ในรูปแบบ JPEG ซึ่งเราขอแนะนำให้ใช้เมื่อเป็นไปได้

  • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จากออบเจ็กต์ media.Image เช่น เมื่อจับภาพจากกล้องของอุปกรณ์ ให้ส่งออบเจ็กต์ media.Image และการหมุนของรูปภาพไปยัง FirebaseVisionImage.fromMediaImage()

    หากคุณใช้ไลบรารี CameraX คลาส OnImageCapturedListener และ ImageAnalysis.Analyzer จะคํานวณค่าการหมุนให้คุณ คุณจึงต้องแปลงการหมุนเป็นค่าคงที่ ROTATION_ ของ ML Kit ก่อนเรียกใช้ FirebaseVisionImage.fromMediaImage()

    Java

    private class YourAnalyzer implements ImageAnalysis.Analyzer {
    
        private int degreesToFirebaseRotation(int degrees) {
            switch (degrees) {
                case 0:
                    return FirebaseVisionImageMetadata.ROTATION_0;
                case 90:
                    return FirebaseVisionImageMetadata.ROTATION_90;
                case 180:
                    return FirebaseVisionImageMetadata.ROTATION_180;
                case 270:
                    return FirebaseVisionImageMetadata.ROTATION_270;
                default:
                    throw new IllegalArgumentException(
                            "Rotation must be 0, 90, 180, or 270.");
            }
        }
    
        @Override
        public void analyze(ImageProxy imageProxy, int degrees) {
            if (imageProxy == null || imageProxy.getImage() == null) {
                return;
            }
            Image mediaImage = imageProxy.getImage();
            int rotation = degreesToFirebaseRotation(degrees);
            FirebaseVisionImage image =
                    FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
            // Pass image to an ML Kit Vision API
            // ...
        }
    }

    Kotlin

    private class YourImageAnalyzer : ImageAnalysis.Analyzer {
        private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
            0 -> FirebaseVisionImageMetadata.ROTATION_0
            90 -> FirebaseVisionImageMetadata.ROTATION_90
            180 -> FirebaseVisionImageMetadata.ROTATION_180
            270 -> FirebaseVisionImageMetadata.ROTATION_270
            else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
        }
    
        override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
            val mediaImage = imageProxy?.image
            val imageRotation = degreesToFirebaseRotation(degrees)
            if (mediaImage != null) {
                val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                // Pass image to an ML Kit Vision API
                // ...
            }
        }
    }

    หากไม่ได้ใช้คลังกล้องที่ระบุการหมุนของรูปภาพ คุณจะคำนวณการหมุนได้จากการหมุนของอุปกรณ์และการวางแนวของเซ็นเซอร์กล้องในอุปกรณ์ โดยทำดังนี้

    Java

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }
    
    /**
     * 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, Context context)
            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);
    
        // On most devices, the sensor orientation is 90 degrees, but for some
        // devices it is 270 degrees. For devices with a sensor orientation of
        // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
        CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
        int sensorOrientation = cameraManager
                .getCameraCharacteristics(cameraId)
                .get(CameraCharacteristics.SENSOR_ORIENTATION);
        rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
    
        // Return the corresponding FirebaseVisionImageMetadata rotation value.
        int result;
        switch (rotationCompensation) {
            case 0:
                result = FirebaseVisionImageMetadata.ROTATION_0;
                break;
            case 90:
                result = FirebaseVisionImageMetadata.ROTATION_90;
                break;
            case 180:
                result = FirebaseVisionImageMetadata.ROTATION_180;
                break;
            case 270:
                result = FirebaseVisionImageMetadata.ROTATION_270;
                break;
            default:
                result = FirebaseVisionImageMetadata.ROTATION_0;
                Log.e(TAG, "Bad rotation value: " + rotationCompensation);
        }
        return result;
    }

    Kotlin

    private val ORIENTATIONS = SparseIntArray()
    
    init {
        ORIENTATIONS.append(Surface.ROTATION_0, 90)
        ORIENTATIONS.append(Surface.ROTATION_90, 0)
        ORIENTATIONS.append(Surface.ROTATION_180, 270)
        ORIENTATIONS.append(Surface.ROTATION_270, 180)
    }
    /**
     * 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, context: Context): 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)
    
        // On most devices, the sensor orientation is 90 degrees, but for some
        // devices it is 270 degrees. For devices with a sensor orientation of
        // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
        val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
        val sensorOrientation = cameraManager
                .getCameraCharacteristics(cameraId)
                .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
        rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
    
        // Return the corresponding FirebaseVisionImageMetadata rotation value.
        val result: Int
        when (rotationCompensation) {
            0 -> result = FirebaseVisionImageMetadata.ROTATION_0
            90 -> result = FirebaseVisionImageMetadata.ROTATION_90
            180 -> result = FirebaseVisionImageMetadata.ROTATION_180
            270 -> result = FirebaseVisionImageMetadata.ROTATION_270
            else -> {
                result = FirebaseVisionImageMetadata.ROTATION_0
                Log.e(TAG, "Bad rotation value: $rotationCompensation")
            }
        }
        return result
    }

    จากนั้นส่งออบเจ็กต์ media.Image และค่าการหมุนไปยัง FirebaseVisionImage.fromMediaImage() ดังนี้

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

    Kotlin

    val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
  • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จาก URI ของไฟล์ ให้ส่งบริบทแอปและ URI ของไฟล์ไปยัง FirebaseVisionImage.fromFilePath() ซึ่งจะมีประโยชน์เมื่อคุณใช้ Intent ACTION_GET_CONTENT เพื่อแจ้งให้ผู้ใช้เลือกรูปภาพจากแอปแกลเลอรี

    Java

    FirebaseVisionImage image;
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri);
    } catch (IOException e) {
        e.printStackTrace();
    }

    Kotlin

    val image: FirebaseVisionImage
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri)
    } catch (e: IOException) {
        e.printStackTrace()
    }
  • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จาก ByteBuffer หรืออาร์เรย์ไบต์ ให้คำนวณการหมุนภาพก่อนตามที่อธิบายไว้ข้างต้นสำหรับอินพุต media.Image

    จากนั้นสร้างออบเจ็กต์ FirebaseVisionImageMetadata ซึ่งมีข้อมูลความสูง กว้าง รูปแบบการเข้ารหัสสี และการหมุนของรูปภาพ

    Java

    FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
            .setWidth(480)   // 480x360 is typically sufficient for
            .setHeight(360)  // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build();

    Kotlin

    val metadata = FirebaseVisionImageMetadata.Builder()
            .setWidth(480) // 480x360 is typically sufficient for
            .setHeight(360) // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build()

    ใช้บัฟเฟอร์หรืออาร์เรย์ รวมถึงออบเจ็กต์ข้อมูลเมตาเพื่อสร้างออบเจ็กต์ FirebaseVisionImage ดังนี้

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
    // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

    Kotlin

    val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
    // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
  • วิธีสร้างออบเจ็กต์ FirebaseVisionImage จากออบเจ็กต์ Bitmap

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

    Kotlin

    val image = FirebaseVisionImage.fromBitmap(bitmap)
    รูปภาพที่แสดงโดยออบเจ็กต์ Bitmap ต้องตั้งตรงโดยไม่จำเป็นต้องหมุนเพิ่มเติม

2. กําหนดค่าและเรียกใช้โปรแกรมติดป้ายกำกับรูปภาพ

หากต้องการติดป้ายกำกับวัตถุในรูปภาพ ให้ส่งผ่านออบเจ็กต์ FirebaseVisionImage ไปยังเมธอด processImage ของ FirebaseVisionImageLabeler

  1. ก่อนอื่น ให้รับอินสแตนซ์ของ FirebaseVisionImageLabeler

    หากต้องการใช้เครื่องมือติดป้ายกำกับรูปภาพในอุปกรณ์ ให้ทำดังนี้

    Java

    FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
        .getOnDeviceImageLabeler();
    
    // Or, to set the minimum confidence required:
    // FirebaseVisionOnDeviceImageLabelerOptions options =
    //     new FirebaseVisionOnDeviceImageLabelerOptions.Builder()
    //         .setConfidenceThreshold(0.7f)
    //         .build();
    // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
    //     .getOnDeviceImageLabeler(options);
    

    Kotlin

    val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler()
    
    // Or, to set the minimum confidence required:
    // val options = FirebaseVisionOnDeviceImageLabelerOptions.Builder()
    //     .setConfidenceThreshold(0.7f)
    //     .build()
    // val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler(options)
    

    หากต้องการใช้โปรแกรมติดป้ายกำกับรูปภาพในระบบคลาวด์ ให้ทำดังนี้

    Java

    FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
        .getCloudImageLabeler();
    
    // Or, to set the minimum confidence required:
    // FirebaseVisionCloudImageLabelerOptions options =
    //     new FirebaseVisionCloudImageLabelerOptions.Builder()
    //         .setConfidenceThreshold(0.7f)
    //         .build();
    // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
    //     .getCloudImageLabeler(options);
    

    Kotlin

    val labeler = FirebaseVision.getInstance().getCloudImageLabeler()
    
    // Or, to set the minimum confidence required:
    // val options = FirebaseVisionCloudImageLabelerOptions.Builder()
    //     .setConfidenceThreshold(0.7f)
    //     .build()
    // val labeler = FirebaseVision.getInstance().getCloudImageLabeler(options)
    

  2. จากนั้นส่งรูปภาพไปยังเมธอด processImage() ดังนี้

    Java

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

    Kotlin

    labeler.processImage(image)
        .addOnSuccessListener { labels ->
          // Task completed successfully
          // ...
        }
        .addOnFailureListener { e ->
          // Task failed with an exception
          // ...
        }
    

3. ดูข้อมูลเกี่ยวกับวัตถุที่ติดป้ายกำกับ

หากการดำเนินการติดป้ายกำกับรูปภาพสำเร็จ ระบบจะส่งรายการออบเจ็กต์ FirebaseVisionImageLabel ไปยัง Listener ของความสำเร็จ ออบเจ็กต์ FirebaseVisionImageLabel แต่ละรายการแสดงถึงสิ่งที่ติดป้ายกำกับในรูปภาพ สำหรับป้ายกำกับแต่ละรายการ คุณสามารถดูคำอธิบายข้อความของป้ายกำกับ รหัสเอนทิตี Knowledge Graph (หากมี) และคะแนนความเชื่อมั่นของการจับคู่ เช่น

Java

for (FirebaseVisionImageLabel label: labels) {
  String text = label.getText();
  String entityId = label.getEntityId();
  float confidence = label.getConfidence();
}

Kotlin

for (label in labels) {
  val text = label.text
  val entityId = label.entityId
  val confidence = label.confidence
}

เคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์

หากต้องการติดป้ายกำกับรูปภาพในแอปพลิเคชันแบบเรียลไทม์ ให้ทำตามหลักเกณฑ์ต่อไปนี้เพื่อให้ได้อัตราเฟรมที่ดีที่สุด

  • จำกัดการเรียกใช้โปรแกรมติดป้ายกำกับรูปภาพ หากเฟรมวิดีโอใหม่พร้อมใช้งานขณะที่โปรแกรมติดป้ายกำกับรูปภาพทำงานอยู่ ให้วางเฟรมนั้น
  • หากคุณใช้เอาต์พุตของเครื่องติดป้ายกำกับรูปภาพเพื่อวางกราฟิกซ้อนทับบนรูปภาพอินพุต ให้รับผลลัพธ์จาก ML Kit ก่อน จากนั้นจึงแสดงผลรูปภาพและวางซ้อนในขั้นตอนเดียว ซึ่งจะทำให้คุณแสดงผลไปยังพื้นผิวการแสดงผลเพียงครั้งเดียวสำหรับเฟรมอินพุตแต่ละเฟรม
  • หากคุณใช้ Camera2 API ให้จับภาพในรูปแบบ ImageFormat.YUV_420_888

    หากคุณใช้ Camera API เวอร์ชันเก่า ให้ถ่ายภาพในรูปแบบ ImageFormat.NV21

ขั้นตอนถัดไป