Google is committed to advancing racial equity for Black communities. See how.
หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

ติดป้ายกำกับรูปภาพด้วยโมเดลที่ฝึก AutoML บน Android

หลังจากที่คุณ ฝึกโมเดลของคุณเองโดยใช้ AutoML Vision Edge คุณสามารถใช้ในแอพของคุณเพื่อติดป้ายกำกับรูปภาพ

มีสองวิธีในการรวมโมเดลที่ฝึกจาก AutoML Vision Edge: คุณสามารถรวมโมเดลโดยวางไว้ในโฟลเดอร์เนื้อหาของแอปหรือดาวน์โหลดแบบไดนามิกจาก Firebase

ตัวเลือกการรวมโมเดล
รวมอยู่ในแอปของคุณ
  • โมเดลนี้เป็นส่วนหนึ่งของ APK ของแอปของคุณ
  • รุ่นนี้พร้อมใช้งานทันทีแม้ว่าอุปกรณ์ Android จะออฟไลน์อยู่ก็ตาม
  • ไม่จำเป็นต้องมีโครงการ Firebase
โฮสต์กับ Firebase
  • โฮสต์โมเดลโดยอัปโหลดไปยัง Firebase Machine Learning
  • ลดขนาด APK
  • มีการดาวน์โหลดโมเดลตามความต้องการ
  • ผลักดันการอัปเดตโมเดลโดยไม่ต้องเผยแพร่แอปของคุณอีกครั้ง
  • การทดสอบ A / B อย่างง่ายด้วย Firebase Remote Config
  • ต้องมีโปรเจ็กต์ Firebase

ก่อนที่คุณจะเริ่ม

  1. เพิ่มการอ้างอิงสำหรับไลบรารี ML Kit Android ไปยังไฟล์ gradle ระดับแอปของโมดูลซึ่งโดยปกติจะเป็น app/build.gradle :

    สำหรับการรวมโมเดลเข้ากับแอปของคุณ:

    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
    }
    

    สำหรับการดาวน์โหลดโมเดลจาก Firebase แบบไดนามิกให้เพิ่มการอ้างอิง linkFirebase :

    dependencies {
      // ...
      // Image labeling feature with automl model downloaded
      // from firebase
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
      implementation 'com.google.mlkit:linkfirebase:16.1.0'
    }
    
  2. หากคุณต้องการดาวน์โหลดโมเดล ตรวจสอบให้แน่ใจว่าคุณได้ เพิ่ม Firebase ในโปรเจ็กต์ Android ของคุณ แล้วหากคุณยังไม่ได้ดำเนินการ สิ่งนี้ไม่จำเป็นเมื่อคุณรวมโมเดล

1. โหลดแบบจำลอง

กำหนดค่าแหล่งที่มาของโมเดลภายใน

ในการรวมโมเดลเข้ากับแอปของคุณ:

  1. แยกโมเดลและข้อมูลเมตาออกจากไฟล์ zip ที่คุณดาวน์โหลดจากคอนโซล Firebase เราขอแนะนำให้คุณใช้ไฟล์ในขณะที่ดาวน์โหลดโดยไม่ต้องแก้ไข (รวมถึงชื่อไฟล์ด้วย)

  2. รวมโมเดลของคุณและไฟล์ข้อมูลเมตาไว้ในแพ็กเกจแอปของคุณ:

    1. หากคุณไม่มีโฟลเดอร์สินทรัพย์ในโปรเจ็กต์ให้สร้างโฟลเดอร์โดยคลิกขวาที่ app/ โฟลเดอร์จากนั้นคลิก ใหม่> โฟลเดอร์> โฟลเดอร์เนื้อหา
    2. สร้างโฟลเดอร์ย่อยภายใต้โฟลเดอร์ assets เพื่อบรรจุไฟล์โมเดล
    3. คัดลอกไฟล์ model.tflite , dict.txt และ manifest.json ไปยังโฟลเดอร์ย่อย (ทั้งสามไฟล์ต้องอยู่ในโฟลเดอร์เดียวกัน)
  3. เพิ่มสิ่งต่อไปนี้ในไฟล์ build.gradle ของแอปเพื่อให้แน่ใจว่า Gradle ไม่บีบอัดไฟล์โมเดลเมื่อสร้างแอป:

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    

    ไฟล์โมเดลจะรวมอยู่ในแพ็คเกจแอพและพร้อมใช้งานสำหรับ ML Kit ในรูปแบบดิบ

  4. สร้างอ็อบเจ็กต์ LocalModel โดยระบุพา ธ ไปยังไฟล์ manifest ของโมเดล:

    Java

    AutoMLImageLabelerLocalModel localModel =
        new AutoMLImageLabelerLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            // or .setAbsoluteFilePath(absolute file path to manifest file)
            .build();
    

    Kotlin

    val localModel = LocalModel.Builder()
        .setAssetManifestFilePath("manifest.json")
        // or .setAbsoluteManifestFilePath(absolute file path to manifest file)
        .build()
    

กำหนดค่าแหล่งที่มาของโมเดลที่โฮสต์โดย Firebase

ในการใช้โมเดลที่โฮสต์จากระยะไกลให้สร้างอ็อบเจ็กต์ CustomRemoteModel โดยระบุชื่อที่คุณกำหนดโมเดลเมื่อคุณเผยแพร่:

Java

// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
    new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
    new CustomRemoteModel.Builder(firebaseModelSource).build();

Kotlin

// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
    .build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).build()

จากนั้นเริ่มงานดาวน์โหลดโมเดลโดยระบุเงื่อนไขที่คุณต้องการอนุญาตให้ดาวน์โหลด หากโมเดลไม่ได้อยู่ในอุปกรณ์หรือหากมีรุ่นที่ใหม่กว่านี้งานจะดาวน์โหลดโมเดลจาก Firebase แบบอะซิงโครนัส:

Java

DownloadConditions downloadConditions = new DownloadConditions.Builder()
        .requireWifi()
        .build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(@NonNull Task<Void> task) {
                // Success.
            }
        });

Kotlin

val downloadConditions = DownloadConditions.Builder()
    .requireWifi()
    .build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
    .addOnSuccessListener {
        // Success.
    }

แอพจำนวนมากเริ่มงานดาวน์โหลดในรหัสเริ่มต้น แต่คุณสามารถทำได้เมื่อใดก็ได้ก่อนที่คุณจะต้องใช้โมเดล

สร้างเครื่องติดฉลากรูปภาพจากแบบจำลองของคุณ

หลังจากคุณกำหนดค่าแหล่งที่มาของโมเดลของคุณแล้วให้สร้างออบเจ็กต์ ImageLabeler จากหนึ่งในนั้น

หากคุณมีเฉพาะโมเดลที่รวมในเครื่องเพียงแค่สร้าง Labeler จากออบเจ็กต์ CustomImageLabelerOptions ของคุณและกำหนดค่าเกณฑ์คะแนนความเชื่อมั่นที่คุณต้องการ (ดู ประเมินโมเดลของคุณ ):

Java

CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Cloud console
                                   // to determine an appropriate value.
    .build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);

Kotlin

val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Cloud console
                                   // to determine an appropriate value.
    .build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)

หากคุณมีโมเดลที่โฮสต์จากระยะไกลคุณจะต้องตรวจสอบว่าได้ดาวน์โหลดแล้วก่อนที่จะเรียกใช้ คุณสามารถตรวจสอบสถานะของงานดาวน์โหลดโมเดลโดยใช้ isModelDownloaded() ของตัวจัดการโมเดล

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

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener<Boolean>() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                CustomImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new CustomImageLabelerOptions.Builder(localModel);
                }
                CustomImageLabelerOptions options = optionsBuilder
                        .setConfidenceThreshold(0.0f)  // Evaluate your model in the Cloud console
                                                       // to determine an appropriate threshold.
                        .build();

                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded ->
        val optionsBuilder =
            if (isDownloaded) {
                CustomImageLabelerOptions.Builder(remoteModel)
            } else {
                CustomImageLabelerOptions.Builder(localModel)
            }
        // Evaluate your model in the Cloud console to determine an appropriate threshold.
        val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
        val labeler = ImageLabeling.getClient(options)
}

หากคุณมีเพียงโมเดลที่โฮสต์จากระยะไกลคุณควรปิดใช้งานฟังก์ชันที่เกี่ยวข้องกับโมเดลเช่นเป็นสีเทาหรือซ่อนส่วนของ UI ของคุณจนกว่าคุณจะยืนยันว่ามีการดาวน์โหลดโมเดลแล้ว คุณสามารถทำได้โดยแนบ Listener เข้ากับวิธี download() ของ model manager:

Java

RemoteModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void v) {
              // Download complete. Depending on your app, you could enable
              // the ML feature, or switch from the local model to the remote
              // model, etc.
            }
        });

Kotlin

RemoteModelManager.getInstance().download(remoteModel, conditions)
    .addOnSuccessListener {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.
    }

2. เตรียมภาพที่ป้อน

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

คุณสามารถสร้าง InputImage จากแหล่งที่มาที่แตกต่างกันซึ่งแต่ละแหล่งจะอธิบายไว้ด้านล่าง

การใช้ media.Image

เพื่อสร้าง InputImage วัตถุจาก media.Image วัตถุเช่นเมื่อคุณจับภาพจากกล้องถ่ายรูปของอุปกรณ์ที่ผ่านการ media.Image วัตถุและการหมุนภาพที่จะ InputImage.fromMediaImage()

หากคุณใช้ไลบรารี CameraX OnImageCapturedListener และ ImageAnalysis.Analyzer คำนวณค่าการหมุนให้คุณ

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        if (imageProxy == null || imageProxy.getImage() == null) {
            return;
        }
        Image mediaImage = imageProxy.getImage();
        InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees);
        // Pass image to an ML Kit Vision API
        // ...
    }
}

โคตรลิน + KTX

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
            // ...
        }
    }
}

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

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;
}

โคตรลิน + KTX

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 และค่าองศาการหมุนไปที่ InputImage.fromMediaImage() :

Java

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

โคตรลิน + KTX

val image = InputImage.fromMediaImage(mediaImage, rotation)

การใช้ไฟล์ URI

ในการสร้างออบเจ็กต์ InputImage จากไฟล์ URI ให้ส่งบริบทของแอพและ URI ไปยัง InputImage.fromFilePath() สิ่งนี้มีประโยชน์เมื่อคุณใช้ ACTION_GET_CONTENT เจตนาเพื่อแจ้งให้ผู้ใช้เลือกภาพจากแอปแกลเลอรีของตน

Java

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

โคตรลิน + KTX

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

ใช้ ByteBuffer หรือ ByteArray

ในการสร้างออบเจ็กต์ InputImage จาก ByteBuffer หรือ ByteArray แรกให้คำนวณองศาการหมุนภาพตามที่อธิบายไว้ก่อนหน้านี้สำหรับ media.Image จากนั้นสร้างวัตถุ InputImage ด้วยบัฟเฟอร์หรืออาร์เรย์พร้อมกับความสูงความกว้างรูปแบบการเข้ารหัสสีและระดับการหมุนของภาพ:

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

โคตรลิน + KTX

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

ใช้ Bitmap

ในการสร้างออบเจ็กต์ InputImage จากอ็อบเจ็กต์ Bitmap ให้ทำการประกาศดังต่อไปนี้:

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

โคตรลิน + KTX

val image = InputImage.fromBitmap(bitmap, 0)

รูปภาพจะแสดงโดยอ็อบเจ็กต์ Bitmap พร้อมกับองศาการหมุน

3. เรียกใช้โปรแกรมติดฉลากรูปภาพ

ในการติดป้ายกำกับวัตถุในรูปภาพให้ส่งออบเจ็กต์ image ไปยัง ImageLabeler process() ของ ImageLabeler

Java

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

Kotlin

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

4. รับข้อมูลเกี่ยวกับวัตถุที่ติดฉลาก

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

Java

for (ImageLabel label : labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
    int index = label.getIndex();
}

Kotlin

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

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

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

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