มีสองวิธีในการผสานรวมโมเดลที่ฝึกจาก AutoML Vision Edge: คุณสามารถรวมโมเดลโดยใส่ไว้ในโฟลเดอร์แอสเซทของแอป หรือดาวน์โหลดแบบไดนามิกจาก Firebase
ตัวเลือกการรวมโมเดล | |
---|---|
รวมอยู่ในแอพของคุณ |
|
โฮสต์กับ Firebase |
|
ก่อนจะเริ่ม
เพิ่มการพึ่งพาสำหรับไลบรารี 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' }
หากคุณต้องการดาวน์โหลด model อย่าลืม เพิ่ม Firebase ในโครงการ Android ของคุณ หากคุณยังไม่ได้ดำเนินการดังกล่าว ไม่จำเป็นเมื่อคุณรวมโมเดล
1. โหลดโมเดล
กำหนดค่าแหล่งที่มาของโมเดลในพื้นที่
วิธีรวมโมเดลกับแอปของคุณ:
แยกโมเดลและข้อมูลเมตาออกจากไฟล์ zip ที่คุณดาวน์โหลดจากคอนโซล Firebase เราขอแนะนำให้คุณใช้ไฟล์ตามที่ดาวน์โหลดมา โดยไม่ต้องแก้ไข (รวมถึงชื่อไฟล์)
รวมโมเดลของคุณและไฟล์ข้อมูลเมตาในแพ็คเกจแอพของคุณ:
- หากคุณไม่มีโฟลเดอร์เนื้อหาในโปรเจ็กต์ ให้สร้างขึ้นโดยคลิกขวาที่
app/
โฟลเดอร์ จากนั้นคลิก ใหม่ > โฟลเดอร์ > โฟลเดอร์เนื้อหา - สร้างโฟลเดอร์ย่อยภายใต้โฟลเดอร์ทรัพย์สินเพื่อเก็บไฟล์โมเดล
- คัดลอกไฟล์
model.tflite
,dict.txt
และmanifest.json
ไปยังโฟลเดอร์ย่อย (ทั้งสามไฟล์ต้องอยู่ในโฟลเดอร์เดียวกัน)
- หากคุณไม่มีโฟลเดอร์เนื้อหาในโปรเจ็กต์ ให้สร้างขึ้นโดยคลิกขวาที่
เพิ่มสิ่งต่อไปนี้ในไฟล์
build.gradle
ของแอปเพื่อให้แน่ใจว่า Gradle จะไม่บีบอัดไฟล์โมเดลเมื่อสร้างแอป:android { // ... aaptOptions { noCompress "tflite" } }
ไฟล์โมเดลจะรวมอยู่ในแพ็คเกจแอปและพร้อมให้ ML Kit เป็นสินทรัพย์ดิบ
สร้างวัตถุ
LocalModel
โดยระบุพาธไปยังไฟล์รายการโมเดล:Java
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();
คอตลิน
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();
คอตลิน
// 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);
คอตลิน
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()
ของตัวจัดการโมเดล
แม้ว่าคุณจะต้องยืนยันสิ่งนี้ก่อนเรียกใช้ labeler เท่านั้น หากคุณมีทั้งโมเดลที่โฮสต์จากระยะไกลและโมเดลที่รวมอยู่ในเครื่อง คุณควรดำเนินการตรวจสอบนี้เมื่อสร้างอินสแตนซ์ labeler: สร้าง labeler จากโมเดลระยะไกลหาก มันถูกดาวน์โหลดและจากรุ่นในพื้นที่เป็นอย่างอื่น
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);
}
});
คอตลิน
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.
}
});
คอตลิน
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 // ... } }
Kotlin+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; }
Kotlin+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);
Kotlin+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(); }
Kotlin+KTX
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
การใช้ ByteBuffer
หรือ ByteArray
ในการสร้างวัตถุ InputImage
จาก ByteBuffer
หรือ ByteArray
ก่อนอื่นให้คำนวณระดับการหมุนของรูปภาพตามที่อธิบายไว้ก่อนหน้านี้สำหรับ media.Image
input จากนั้น สร้างวัตถุ InputImage
ด้วยบัฟเฟอร์หรืออาร์เรย์ พร้อมกับความสูง ความกว้าง รูปแบบการเข้ารหัสสี และระดับการหมุนของรูปภาพ:
Java
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
Kotlin+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);
Kotlin+KTX
val image = InputImage.fromBitmap(bitmap, 0)
รูปภาพแสดงด้วยวัตถุ Bitmap
พร้อมกับองศาการหมุน
3. เรียกใช้เครื่องติดฉลากรูปภาพ
หากต้องการติดป้ายกำกับวัตถุในรูปภาพ ให้ส่งวัตถุ image
ไปยังเมธอด 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();
}
คอตลิน
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