ติดป้ายกำกับรูปภาพด้วยโมเดลที่ฝึกด้วย AutoML ใน Android
หลังจากฝึกโมเดลของคุณเองโดยใช้ AutoML Vision Edge แล้ว คุณจะใช้โมเดลดังกล่าวในแอปเพื่อติดป้ายกำกับรูปภาพได้ การผสานรวมโมเดลที่ฝึกจาก AutoML Vision Edge มี 2 วิธี ได้แก่ คุณสามารถรวมโมเดลโดยวางไว้ในโฟลเดอร์เนื้อหาของแอป หรือจะดาวน์โหลดแบบไดนามิกจาก Firebase ก็ได้ตัวเลือกการรวมโมเดล | |
---|---|
รวมอยู่ในแอป |
|
โฮสต์ด้วย Firebase |
|
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อ ดูตัวอย่างการใช้ API นี้
ก่อนเริ่มต้น
1. ตรวจสอบว่าได้รวมในไฟล์build.gradle
ระดับโปรเจ็กต์แล้ว
ที่เก็บ Maven ของ Google ทั้งใน buildscript
และ
allprojects
ส่วน2. เพิ่มทรัพยากร Dependency สำหรับไลบรารี ML Kit Android ลงในโมดูล ไฟล์ Gradle ระดับแอป ซึ่งปกติจะเป็น
app/build.gradle
:
สำหรับการรวมโมเดลกับแอป ให้ทำดังนี้
dependencies { // ... // Image labeling feature with bundled automl model implementation 'com.google.mlkit:image-labeling-automl:16.2.1' }สำหรับการดาวน์โหลดโมเดลแบบไดนามิกจาก Firebase ให้เพิ่ม
linkFirebase
การพึ่งพา:
dependencies { // ... // Image labeling feature with automl model downloaded // from firebase implementation 'com.google.mlkit:image-labeling-automl:16.2.1' implementation 'com.google.mlkit:linkfirebase:16.0.1' }3. หากต้องการดาวน์โหลดโมเดล โปรดตรวจสอบว่า เพิ่ม Firebase ลงในโปรเจ็กต์ Android หากคุณยังไม่ได้ดำเนินการ ทั้งนี้ไม่จำเป็นต้องทำขั้นตอนนี้เมื่อคุณรวมกลุ่มโมเดล
1. โหลดโมเดล
กำหนดค่าแหล่งข้อมูลโมเดลในเครื่อง
วิธีรวมโมเดลกับแอป1. แยกโมเดลและข้อมูลเมตาของโมเดลจากชุดไฟล์ ZIP ที่คุณดาวน์โหลด จากคอนโซล Firebase เราขอแนะนำให้คุณใช้ไฟล์ที่ดาวน์โหลดมา โดยไม่ต้องแก้ไข (รวมถึงชื่อไฟล์)
2. รวมโมเดลและไฟล์ข้อมูลเมตาของโมเดลไว้ในแพ็กเกจแอป
ก. หากไม่มีโฟลเดอร์ชิ้นงานในโปรเจ็กต์ ให้สร้างโฟลเดอร์โดย คลิกขวาที่โฟลเดอร์
app/
แล้วคลิก
ใหม่ > โฟลเดอร์ > โฟลเดอร์ชิ้นงานข. สร้างโฟลเดอร์ย่อยภายในโฟลเดอร์ชิ้นงานเพื่อเก็บไฟล์โมเดล
ค. คัดลอกไฟล์
model.tflite
, dict.txt
และ
manifest.json
ลงในโฟลเดอร์ย่อย (ทั้ง 3 ไฟล์ต้องอยู่ใน
โฟลเดอร์เดียวกัน)3. เพิ่มข้อมูลต่อไปนี้ลงในไฟล์
build.gradle
ของแอปเพื่อให้ Gradle ไม่บีบอัดไฟล์โมเดลเมื่อสร้างแอป
android { // ... aaptOptions { noCompress "tflite" } }ไฟล์โมเดลจะรวมอยู่ในแพ็กเกจแอปและพร้อมให้ ML Kit ใช้เป็นชิ้นงานดิบ
หมายเหตุ: ตั้งแต่ปลั๊กอิน Android Gradle เวอร์ชัน 4.1 เป็นต้นไป ระบบจะเพิ่ม .tflite ลงในรายการ noCompress โดยค่าเริ่มต้น และคุณไม่จําเป็นต้องใช้คำสั่งข้างต้นอีกต่อไป
4. สร้างออบเจ็กต์
LocalModel
โดยระบุเส้นทางไปยังไฟล์ Manifest ของรูปแบบ ดังนี้
Kotlin
val localModel = AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build()
Java
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();
กำหนดค่าแหล่งข้อมูลรูปแบบที่โฮสต์ใน Firebase
หากต้องการใช้โมเดลที่โฮสต์จากระยะไกล ให้สร้างออบเจ็กต์ RemoteModel
ระบุชื่อที่คุณกำหนดให้กับโมเดลเมื่อเผยแพร่โมเดล:
Kotlin
// Specify the name you assigned in the Firebase console. val remoteModel = AutoMLImageLabelerRemoteModel.Builder("your_model_name").build()
Java
// Specify the name you assigned in the Firebase console. AutoMLImageLabelerRemoteModel remoteModel = new AutoMLImageLabelerRemoteModel.Builder("your_model_name").build();
จากนั้นเริ่มงานดาวน์โหลดโมเดล โดยระบุเงื่อนไขที่ ที่คุณต้องการอนุญาตให้ดาวน์โหลด หากไม่มีรุ่นนี้อยู่ในอุปกรณ์ หรือรุ่นที่ใหม่กว่า ของโมเดลพร้อมใช้งาน งานจะดาวน์โหลด จาก Firebase ได้ดังนี้
Kotlin
val downloadConditions = DownloadConditions.Builder() .requireWifi() .build() RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener { // Success. }
Java
DownloadConditions downloadConditions = new DownloadConditions.Builder() .requireWifi() .build(); RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(@NonNull Task task) { // Success. } });
แอปจำนวนมากเริ่มงานดาวน์โหลดในโค้ดการเริ่มต้น แต่คุณ จากนั้นคุณจะสามารถทำได้ทุกเมื่อก่อนที่จะต้องใช้โมเดลนี้
สร้างเครื่องมือติดป้ายกำกับรูปภาพจากโมเดล
หลังจากกำหนดค่าแหล่งที่มาของโมเดลแล้ว ให้สร้างออบเจ็กต์ ImageLabeler
จาก 1 รายการ
ทั้งหมด
หากคุณมีเพียงโมเดลที่รวมอยู่ในเครื่อง ให้สร้างเครื่องติดป้ายกำกับจากออบเจ็กต์ AutoMLImageLabelerLocalModel
แล้วกําหนดค่าเกณฑ์คะแนนความเชื่อมั่นที่ต้องการ (ดูประเมินโมเดล)
Kotlin
val autoMLImageLabelerOptions = AutoMLImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0) // Evaluate your model in the Firebase console // to determine an appropriate value. .build() val labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)
Java
AutoMLImageLabelerOptions autoMLImageLabelerOptions = new AutoMLImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console // to determine an appropriate value. .build(); ImageLabeler labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)
หากมีโมเดลที่โฮสต์จากระยะไกล คุณจะต้องตรวจสอบว่าได้ดาวน์โหลดโมเดลแล้วก่อนที่จะเรียกใช้ คุณตรวจสอบสถานะของงานดาวน์โหลดรูปแบบได้โดยใช้isModelDownloaded()
วิธีการของตัวจัดการรูปแบบ
แม้ว่าคุณจะต้องยืนยันข้อมูลนี้ก่อนเรียกใช้โปรแกรมติดป้ายกำกับ แต่หากมีทั้งโมเดลที่โฮสต์จากระยะไกลและโมเดลที่รวมอยู่ในเครื่อง ก็อาจต้องทำการตรวจสอบนี้เมื่อสร้างอินสแตนซ์โปรแกรมติดป้ายกำกับรูปภาพ โดยสร้างโปรแกรมติดป้ายกำกับจากโมเดลระยะไกลหากมีการดาวน์โหลดไว้ และจากโมเดลในเครื่องหากไม่ได้ดาวน์โหลดไว้
Kotlin
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener { isDownloaded -> val optionsBuilder = if (isDownloaded) { AutoMLImageLabelerOptions.Builder(remoteModel) } else { AutoMLImageLabelerOptions.Builder(localModel) } // Evaluate your model in the Firebase console to determine an appropriate threshold. val options = optionsBuilder.setConfidenceThreshold(0.0f).build() val labeler = ImageLabeling.getClient(options) }
Java
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Boolean isDownloaded) { AutoMLImageLabelerOptions.Builder optionsBuilder; if (isDownloaded) { optionsBuilder = new AutoMLImageLabelerOptions.Builder(remoteModel); } else { optionsBuilder = new AutoMLImageLabelerOptions.Builder(localModel); } AutoMLImageLabelerOptions options = optionsBuilder .setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console // to determine an appropriate threshold. .build(); ImageLabeler labeler = ImageLabeling.getClient(options); } });
หากคุณมีเฉพาะโมเดลที่โฮสต์จากระยะไกล คุณควรปิดใช้โมเดลที่เกี่ยวข้องกับ
เช่น เป็นสีเทาหรือซ่อนบางส่วนของ UI จนถึง
คุณยืนยันว่าดาวน์โหลดโมเดลแล้ว คุณสามารถทำได้โดยการแนบ Listener
ไปยังเมธอด download()
ของผู้จัดการโมเดล:
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. }
Java
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener(new OnSuccessListener() { @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. } });
2. เตรียมรูปภาพอินพุต
จากนั้นสร้างออบเจ็กต์ InputImage
จากรูปภาพสำหรับรูปภาพแต่ละรูปที่ต้องการติดป้ายกำกับ เครื่องมือติดป้ายกำกับรูปภาพจะทำงานได้เร็วที่สุดเมื่อคุณใช้ Bitmap
หรือหากใช้ camera2 API ให้ใช้ YUV_420_888 media.Image
ซึ่งเราขอแนะนำให้ใช้เมื่อเป็นไปได้
คุณสามารถสร้างInputImage
จากแหล่งที่มาต่างๆ ซึ่งอธิบายไว้ด้านล่าง
กำลังใช้media.Image
วิธีสร้าง InputImage
จากออบเจ็กต์ media.Image
เช่น เมื่อคุณจับภาพจาก
กล้องของอุปกรณ์ ส่งวัตถุ media.Image
และ
การหมุนเวียนเป็น InputImage.fromMediaImage()
หากคุณใช้ไลบรารี CameraX คลาส OnImageCapturedListener
และ ImageAnalysis.Analyzer
จะคํานวณค่าการหมุนให้คุณ
Kotlin
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 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 // ... } } }
หากไม่ได้ใช้คลังกล้องที่ระบุองศาการหมุนของรูปภาพ คุณสามารถคำนวณองศาการหมุนจากองศาการหมุนของอุปกรณ์และการวางแนวของเซ็นเซอร์กล้องในอุปกรณ์ได้โดยทำดังนี้
Kotlin
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 }
Java
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()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
การใช้ URI ของไฟล์
วิธีสร้าง InputImage
จาก URI ของไฟล์ แล้วส่งบริบทของแอปและ URI ของไฟล์ไปยัง
InputImage.fromFilePath()
วิธีนี้มีประโยชน์เมื่อคุณ
ใช้ Intent ACTION_GET_CONTENT
เพื่อแจ้งให้ผู้ใช้เลือก
รูปภาพจากแอปแกลเลอรี
Kotlin
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
พร้อมบัฟเฟอร์หรืออาร์เรย์ ร่วมกับรูปภาพ
ความสูง ความกว้าง รูปแบบการเข้ารหัสสี และระดับการหมุน:
Kotlin
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 )
Java
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
ให้ทำการประกาศต่อไปนี้
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
รูปภาพแสดงด้วยวัตถุ Bitmap
พร้อมองศาการหมุน
3. เรียกใช้เครื่องมือติดป้ายกำกับรูปภาพ
หากต้องการติดป้ายกำกับวัตถุในรูปภาพ ให้ส่งออบเจ็กต์image
ไปยังออบเจ็กต์ของ ImageLabeler
process()
วิธี
Kotlin
labeler.process(image) .addOnSuccessListener { labels -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
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 // ... } });
4. ดูข้อมูลเกี่ยวกับวัตถุที่ติดป้ายกำกับ
หากดำเนินการติดป้ายกำกับรูปภาพสำเร็จ รายการ ImageLabel
ออบเจ็กต์ที่ส่งผ่านไปยัง Listener ที่สำเร็จ ออบเจ็กต์ ImageLabel
แต่ละรายการแสดงถึงสิ่งที่ติดป้ายกำกับในรูปภาพ คุณจะได้รับข้อความของแต่ละป้ายกำกับ
คะแนนความเชื่อมั่นของการแข่งขัน และดัชนีของการแข่งขัน
เช่น
Kotlin
for (label in labels) { val text = label.text val confidence = label.confidence val index = label.index }
Java
for (ImageLabel label : labels) { String text = label.getText(); float confidence = label.getConfidence(); int index = label.getIndex(); }
เคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์
หากต้องการติดป้ายกำกับรูปภาพในแอปพลิเคชันแบบเรียลไทม์ ให้ทำตามหลักเกณฑ์ต่อไปนี้เพื่อให้ได้อัตราเฟรมที่ดีที่สุด
- หากคุณใช้แท็ก
Camera
หรือcamera2
API, ควบคุมการเรียกไปยังผู้ติดป้ายกำกับรูปภาพ หากเฟรมวิดีโอใหม่พร้อมใช้งานขณะที่โปรแกรมติดป้ายกำกับรูปภาพทำงานอยู่ ให้วางเฟรมนั้น โปรดดูVisionProcessorBase
ในแอปตัวอย่างการเริ่มต้นอย่างรวดเร็วสำหรับตัวอย่าง - หากคุณใช้
CameraX
API ให้ตรวจสอบว่าได้ตั้งค่ากลยุทธ์การลดแรงดันเป็นค่าเริ่มต้นแล้วImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
วิธีนี้ช่วยให้มั่นใจว่าจะมีการส่งรูปภาพเพียงรูปเดียวเพื่อการวิเคราะห์ในแต่ละครั้ง หากมีการสร้างรูปภาพเพิ่มเติมเมื่อเครื่องมือวิเคราะห์ไม่ว่าง ระบบจะทิ้งรูปภาพเหล่านั้นโดยอัตโนมัติและจะไม่จัดคิวเพื่อนำส่ง เมื่อปิดการวิเคราะห์รูปภาพด้วยการเรียกใช้ ImageProxy.close() ระบบจะส่งรูปภาพล่าสุดถัดไป - หากคุณใช้เอาต์พุตของเครื่องติดป้ายกำกับรูปภาพเพื่อวางกราฟิกซ้อนทับบนรูปภาพอินพุต ให้รับผลลัพธ์จาก ML Kit ก่อน จากนั้นจึงแสดงผลรูปภาพและวางซ้อนในขั้นตอนเดียว การดำเนินการนี้จะแสดงผลบนพื้นผิวการแสดงผลเพียงครั้งเดียวสำหรับเฟรมอินพุตแต่ละเฟรม ดูตัวอย่างได้จากคลาส
CameraSourcePreview
และGraphicOverlay
ในแอปตัวอย่างการเริ่มต้นใช้งาน - หากคุณใช้ Camera2 API ให้จับภาพในรูปแบบ
ImageFormat.YUV_420_888
หากคุณใช้ Camera API รุ่นเก่า ให้จับภาพในImageFormat.NV21