หลังจากฝึกโมเดลของคุณเอง โดยใช้ AutoML Vision Edge แล้ว คุณจะใช้โมเดลดังกล่าวในแอปเพื่อติดป้ายกำกับ รูปภาพได้
ก่อนเริ่มต้น
- เพิ่ม Firebase ลงในโปรเจ็กต์ Android หากยังไม่ได้เพิ่ม
- เพิ่มทรัพยากร Dependency สำหรับคลัง Android ของ ML Kit ลงในไฟล์ 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-automl:18.0.5' }
1. โหลดโมเดล
ML Kit จะเรียกใช้โมเดลที่สร้างด้วย AutoML บนอุปกรณ์ อย่างไรก็ตาม คุณสามารถ กำหนดค่า ML Kit ให้โหลดโมเดลจากระยะไกลจาก Firebase, จาก ที่เก็บข้อมูลในเครื่อง หรือทั้ง 2 อย่าง
การโฮสต์โมเดลใน Firebase ช่วยให้คุณอัปเดตโมเดลได้โดยไม่ต้องเผยแพร่ แอปเวอร์ชันใหม่ และใช้ Remote Config และ A/B Testing เพื่อ แสดงโมเดลที่แตกต่างกันแบบไดนามิกต่อผู้ใช้กลุ่มต่างๆ ได้
หากเลือกที่จะระบุเฉพาะโมเดลโดยโฮสต์ด้วย Firebase และไม่รวมไว้กับแอป คุณจะลดขนาดการดาวน์โหลดเริ่มต้นของแอปได้ โปรดทราบว่าหากไม่ได้รวมโมเดลไว้กับแอป ฟังก์ชันที่เกี่ยวข้องกับโมเดลจะใช้ไม่ได้จนกว่าแอปจะดาวน์โหลดโมเดลเป็นครั้งแรก
การรวมโมเดลไว้กับแอปจะช่วยให้มั่นใจได้ว่าฟีเจอร์ ML ของแอปจะยังคงทำงานได้เมื่อโมเดลที่โฮสต์ใน Firebase ไม่พร้อมใช้งาน
กำหนดค่าแหล่งที่มาของโมเดลที่โฮสต์ใน Firebase
หากต้องการใช้โมเดลที่โฮสต์จากระยะไกล ให้สร้างออบเจ็กต์ FirebaseAutoMLRemoteModel
โดยระบุชื่อที่คุณกำหนดให้กับโมเดลเมื่อเผยแพร่
Java
// Specify the name you assigned in the Firebase console.
FirebaseAutoMLRemoteModel remoteModel =
new FirebaseAutoMLRemoteModel.Builder("your_remote_model").build();
Kotlin
// Specify the name you assigned in the Firebase console.
val remoteModel = FirebaseAutoMLRemoteModel.Builder("your_remote_model").build()
จากนั้นเริ่มงานดาวน์โหลดโมเดล โดยระบุเงื่อนไขที่คุณต้องการอนุญาตให้ดาวน์โหลด หากโมเดลไม่ได้อยู่ในอุปกรณ์ หรือหากมีโมเดลเวอร์ชันใหม่กว่า งานจะดาวน์โหลดโมเดลจาก Firebase แบบไม่พร้อมกัน
Java
FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
.requireWifi()
.build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
// Success.
}
});
Kotlin
val conditions = FirebaseModelDownloadConditions.Builder()
.requireWifi()
.build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener {
// Success.
}
แอปจำนวนมากจะเริ่มงานดาวน์โหลดในโค้ดการเริ่มต้น แต่คุณสามารถทำได้ทุกเมื่อก่อนที่จะต้องใช้โมเดล
กำหนดค่าแหล่งที่มาของโมเดลในเครื่อง
วิธีรวมโมเดลกับแอป
- แตกไฟล์โมเดลและข้อมูลเมตาจากที่เก็บถาวรแบบ ZIP ที่คุณดาวน์โหลดจากคอนโซล Firebase เราขอแนะนำให้คุณใช้ไฟล์ตามที่ดาวน์โหลด โดยไม่ต้องแก้ไข (รวมถึงชื่อไฟล์)
-
รวมโมเดลและไฟล์ข้อมูลเมตาของโมเดลไว้ในแพ็กเกจแอป
- หากไม่มีโฟลเดอร์ชิ้นงานในโปรเจ็กต์ ให้สร้างโฟลเดอร์โดย
คลิกขวาที่โฟลเดอร์
app/
แล้วคลิก ใหม่ > โฟลเดอร์ > โฟลเดอร์ชิ้นงาน - สร้างโฟลเดอร์ย่อยในโฟลเดอร์ชิ้นงานเพื่อเก็บไฟล์โมเดล
- คัดลอกไฟล์
model.tflite
,dict.txt
และmanifest.json
ไปยังโฟลเดอร์ย่อย (ไฟล์ทั้ง 3 ไฟล์ต้องอยู่ในโฟลเดอร์เดียวกัน)
- หากไม่มีโฟลเดอร์ชิ้นงานในโปรเจ็กต์ ให้สร้างโฟลเดอร์โดย
คลิกขวาที่โฟลเดอร์
- เพิ่มโค้ดต่อไปนี้ลงในไฟล์
build.gradle
ของแอปเพื่อให้แน่ใจว่า Gradle จะไม่บีบอัดไฟล์โมเดลเมื่อสร้างแอป ไฟล์โมเดลจะรวมอยู่ในแพ็กเกจแอปและพร้อมใช้งานกับ ML Kit เป็นชิ้นงานดิบandroid { // ... aaptOptions { noCompress "tflite" } }
- สร้างออบเจ็กต์
FirebaseAutoMLLocalModel
โดยระบุเส้นทางไปยังไฟล์ Manifest ของโมเดลJava
FirebaseAutoMLLocalModel localModel = new FirebaseAutoMLLocalModel.Builder() .setAssetFilePath("manifest.json") .build();
Kotlin
val localModel = FirebaseAutoMLLocalModel.Builder() .setAssetFilePath("manifest.json") .build()
สร้างโปรแกรมติดป้ายกำกับรูปภาพจากโมเดล
หลังจากกำหนดค่าแหล่งที่มาของโมเดลแล้ว ให้สร้างFirebaseVisionImageLabeler
ออบเจ็กต์จากแหล่งที่มาใดแหล่งที่มาหนึ่ง
หากมีเฉพาะโมเดลที่รวมไว้ในเครื่อง ให้สร้างโปรแกรมติดป้ายกำกับจากออบเจ็กต์ FirebaseAutoMLLocalModel
แล้วกำหนดค่าเกณฑ์คะแนนความเชื่อมั่นที่คุณต้องการ (ดูประเมินโมเดล)
Java
FirebaseVisionImageLabeler labeler;
try {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions options =
new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console
// to determine an appropriate value.
.build();
labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
// ...
}
Kotlin
val options = FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0) // Evaluate your model in the Firebase console
// to determine an appropriate value.
.build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
หากมีโมเดลที่โฮสต์จากระยะไกล คุณจะต้องตรวจสอบว่าได้
ดาวน์โหลดโมเดลแล้วก่อนที่จะเรียกใช้ คุณตรวจสอบสถานะของงานดาวน์โหลดโมเดลได้โดยใช้เมธอด isModelDownloaded()
ของตัวจัดการโมเดล
แม้ว่าคุณจะต้องยืนยันเรื่องนี้ก่อนเรียกใช้เครื่องมือติดป้ายกำกับเท่านั้น แต่หากคุณมีทั้งโมเดลที่โฮสต์จากระยะไกลและโมเดลที่รวมไว้ในเครื่อง การตรวจสอบนี้อาจมีประโยชน์เมื่อสร้างอินสแตนซ์ของเครื่องมือติดป้ายกำกับรูปภาพ กล่าวคือ สร้างเครื่องมือติดป้ายกำกับจากโมเดลระยะไกลหากดาวน์โหลดแล้ว และจากโมเดลในเครื่องหากยังไม่ได้ดาวน์โหลด
Java
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean isDownloaded) {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder optionsBuilder;
if (isDownloaded) {
optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel);
} else {
optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel);
}
FirebaseVisionOnDeviceAutoMLImageLabelerOptions options = optionsBuilder
.setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console
// to determine an appropriate threshold.
.build();
FirebaseVisionImageLabeler labeler;
try {
labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
// Error.
}
}
});
Kotlin
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel)
} else {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Firebase console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
}
หากมีเฉพาะโมเดลที่โฮสต์จากระยะไกล คุณควรปิดใช้ฟังก์ชันการทำงานที่เกี่ยวข้องกับโมเดล เช่น ทำให้ส่วนหนึ่งของ UI เป็นสีเทาหรือซ่อนไว้ จนกว่าคุณจะยืนยันว่าดาวน์โหลดโมเดลแล้ว คุณทำได้โดยแนบ Listener
ไปยังเมธอด download()
ของ Model Manager ดังนี้
Java
FirebaseModelManager.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
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener {
// Download complete. Depending on your app, you could enable the ML
// feature, or switch from the local model to the remote model, etc.
}
2. เตรียมรูปภาพอินพุต
จากนั้นสร้างFirebaseVisionImage
ออบเจ็กต์
โดยใช้ตัวเลือกใดตัวเลือกหนึ่งที่อธิบายไว้ในส่วนนี้ แล้วส่งไปยังอินสแตนซ์ของ
FirebaseVisionImageLabeler
(อธิบายไว้ในส่วนถัดไป) สำหรับแต่ละรูปภาพที่คุณต้องการติดป้ายกำกับ
คุณสร้างออบเจ็กต์ FirebaseVisionImage
จากออบเจ็กต์ media.Image
, ไฟล์ในอุปกรณ์, อาร์เรย์ไบต์ หรือออบเจ็กต์ Bitmap
ได้โดยทำดังนี้
-
หากต้องการสร้างออบเจ็กต์
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()
ซึ่งจะมีประโยชน์เมื่อคุณ ใช้ACTION_GET_CONTENT
Intent เพื่อแจ้งให้ผู้ใช้เลือก รูปภาพจากแอปแกลเลอรี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
ต้อง ตั้งตรงโดยไม่ต้องหมุนเพิ่มเติม
3. เรียกใช้เครื่องมือติดป้ายกำกับรูปภาพ
หากต้องการติดป้ายกำกับวัตถุในรูปภาพ ให้ส่งFirebaseVisionImage
วัตถุไปยังเมธอด processImage()
ของ FirebaseVisionImageLabeler
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
// ...
}
หากติดป้ายกำกับรูปภาพสำเร็จ ระบบจะส่งอาร์เรย์ของออบเจ็กต์ FirebaseVisionImageLabel
ไปยังเครื่องมือฟังที่สำเร็จ จากออบเจ็กต์แต่ละรายการ คุณจะได้รับ
ข้อมูลเกี่ยวกับฟีเจอร์ที่ระบบจดจำในรูปภาพ
เช่น
Java
for (FirebaseVisionImageLabel label: labels) {
String text = label.getText();
float confidence = label.getConfidence();
}
Kotlin
for (label in labels) {
val text = label.text
val confidence = label.confidence
}
เคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์
- จำกัดจำนวนการเรียกไปยังเครื่องตรวจจับ หากมีเฟรมวิดีโอใหม่พร้อมใช้งานขณะที่เครื่องตรวจจับทำงาน ให้ทิ้งเฟรม
- หากคุณใช้เอาต์พุตของเครื่องตรวจจับเพื่อซ้อนทับกราฟิกบน รูปภาพอินพุต ให้รับผลลัพธ์จาก ML Kit ก่อน จากนั้นจึงแสดงรูปภาพ และซ้อนทับในขั้นตอนเดียว การทำเช่นนี้จะทำให้คุณแสดงผลไปยังพื้นผิวการแสดงผล เพียงครั้งเดียวสำหรับแต่ละเฟรมอินพุต
-
หากใช้ API ของ Camera2 ให้ถ่ายภาพในรูปแบบ
ImageFormat.YUV_420_888
หากใช้ Camera API เวอร์ชันเก่า ให้ถ่ายภาพในรูปแบบ
ImageFormat.NV21