คุณสามารถใช้ ML Kit เพื่อตรวจจับและติดตามวัตถุในเฟรมวิดีโอที่ต่อเนื่องกันได้
เมื่อส่งรูปภาพไปยัง ML Kit ระบบจะตรวจจับวัตถุได้สูงสุด 5 รายการในรูปภาพ พร้อมกับตำแหน่งของวัตถุแต่ละรายการในรูปภาพ เมื่อตรวจจับวัตถุใน สตรีมวิดีโอ วัตถุแต่ละรายการจะมีรหัสที่ไม่ซ้ำกันซึ่งคุณใช้ติดตามวัตถุ จากเฟรมหนึ่งไปยังอีกเฟรมหนึ่งได้ นอกจากนี้ คุณยังเลือกเปิดใช้การจัดประเภทวัตถุแบบหยาบ ได้ ซึ่งจะติดป้ายกำกับวัตถุด้วยคำอธิบายหมวดหมู่แบบกว้างๆ
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อ ดูตัวอย่างการใช้งาน API นี้
- ดูแอปตัวอย่าง Material Design เพื่อดูการใช้งาน API นี้แบบครบวงจร
ก่อนเริ่มต้น
- ในไฟล์
build.gradleระดับโปรเจ็กต์ ให้ตรวจสอบว่าได้รวม ที่เก็บ Maven ของ Google ไว้ในทั้งส่วนbuildscriptและallprojects - เพิ่มทรัพยากร Dependency สำหรับไลบรารี ML Kit สำหรับ Android ลงในไฟล์ Gradle ระดับแอปของโมดูล ซึ่งโดยปกติจะเป็นไฟล์
app/build.gradleดังนี้dependencies { // ... implementation 'com.google.mlkit:object-detection:17.0.2' }
1. กำหนดค่าตัวตรวจจับออบเจ็กต์
หากต้องการตรวจจับและติดตามวัตถุ ให้สร้างอินสแตนซ์ของ ObjectDetector ก่อน แล้วระบุการตั้งค่าตัวตรวจจับที่ต้องการเปลี่ยนจากค่าเริ่มต้น (ไม่บังคับ)
กำหนดค่าตัวตรวจจับวัตถุสำหรับ Use Case ของคุณด้วยออบเจ็กต์
ObjectDetectorOptionsคุณสามารถเปลี่ยนการตั้งค่าต่อไปนี้ได้การตั้งค่าตัวตรวจจับวัตถุ โหมดการตรวจจับ STREAM_MODE(ค่าเริ่มต้น) |SINGLE_IMAGE_MODEใน
STREAM_MODE(ค่าเริ่มต้น) ตัวตรวจจับวัตถุจะทำงานโดยมีเวลาในการตอบสนองต่ำ แต่อาจให้ผลลัพธ์ที่ไม่สมบูรณ์ (เช่น กรอบล้อมรอบหรือป้ายกำกับหมวดหมู่ที่ไม่ได้ระบุ) ในการเรียกใช้ตัวตรวจจับ 2-3 ครั้งแรก นอกจากนี้ ในSTREAM_MODEตัวตรวจจับจะกำหนดรหัสติดตามให้กับวัตถุ ซึ่งคุณสามารถใช้เพื่อ ติดตามวัตถุในเฟรมต่างๆ ได้ ใช้โหมดนี้เมื่อต้องการติดตาม วัตถุ หรือเมื่อเวลาในการตอบสนองต่ำมีความสำคัญ เช่น เมื่อประมวลผล สตรีมวิดีโอแบบเรียลไทม์ใน
SINGLE_IMAGE_MODEตัวตรวจจับวัตถุจะแสดงผลลัพธ์หลังจากกำหนดกรอบล้อมรอบของวัตถุแล้ว หากเปิดใช้การจัดประเภทด้วย ระบบจะแสดงผลลัพธ์หลังจากที่กรอบล้อมรอบ และป้ายกำกับหมวดหมู่พร้อมใช้งานแล้ว ด้วยเหตุนี้ เวลาในการตอบสนองของการตรวจจับจึงอาจสูงขึ้น นอกจากนี้ ในSINGLE_IMAGE_MODEระบบจะไม่กำหนดรหัสติดตาม ใช้ โหมดนี้หากเวลาในการตอบสนองไม่สำคัญและคุณไม่ต้องการจัดการกับ ผลลัพธ์บางส่วนตรวจจับและติดตามวัตถุหลายรายการ false(ค่าเริ่มต้น) |trueเลือกว่าจะตรวจจับและติดตามวัตถุได้สูงสุด 5 รายการหรือเฉพาะวัตถุที่โดดเด่นที่สุด (ค่าเริ่มต้น)
จัดประเภทวัตถุ false(ค่าเริ่มต้น) |trueเลือกว่าจะจัดประเภทวัตถุที่ตรวจพบเป็นหมวดหมู่แบบหยาบหรือไม่ เมื่อเปิดใช้ ตัวแยกประเภทวัตถุจะจัดประเภทวัตถุเป็นหมวดหมู่ต่อไปนี้ สินค้าแฟชั่น อาหาร ของใช้ในบ้าน สถานที่ และพืช
API การตรวจจับและติดตามวัตถุได้รับการปรับให้เหมาะกับ Use Case หลัก 2 กรณีต่อไปนี้
- การตรวจจับและติดตามวัตถุที่โดดเด่นที่สุดในช่องมองภาพของกล้องแบบเรียลไทม์
- การตรวจจับวัตถุหลายรายการจากรูปภาพนิ่ง
วิธีกำหนดค่า API สำหรับ Use Case เหล่านี้
Kotlin
// Live detection and tracking val options = ObjectDetectorOptions.Builder() .setDetectorMode(ObjectDetectorOptions.STREAM_MODE) .enableClassification() // Optional .build() // Multiple object detection in static images val options = ObjectDetectorOptions.Builder() .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() // Optional .build()
Java
// Live detection and tracking ObjectDetectorOptions options = new ObjectDetectorOptions.Builder() .setDetectorMode(ObjectDetectorOptions.STREAM_MODE) .enableClassification() // Optional .build(); // Multiple object detection in static images ObjectDetectorOptions options = new ObjectDetectorOptions.Builder() .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() // Optional .build();
รับอินสแตนซ์ของ
ObjectDetectorดังนี้Kotlin
val objectDetector = ObjectDetection.getClient(options)
Java
ObjectDetector objectDetector = ObjectDetection.getClient(options);
2. เตรียมรูปภาพอินพุต
หากต้องการตรวจจับและติดตามวัตถุ ให้ส่งรูปภาพไปยังเมธอดprocess() ของอินสแตนซ์ ObjectDetector
ตัวตรวจจับวัตถุจะทำงานโดยตรงจาก Bitmap, NV21 ByteBuffer หรือ media.Image ที่มีรูปแบบ YUV_420_888 เราขอแนะนำให้สร้าง InputImage จากแหล่งที่มาเหล่านั้นหากคุณเข้าถึงแหล่งที่มาใดแหล่งที่มาหนึ่งได้โดยตรง หากสร้าง
จากแหล่งที่มาอื่นๆ เราจะจัดการการแปลง
ภายในให้คุณ ซึ่งอาจมีประสิทธิภาพน้อยกว่าInputImage
สำหรับแต่ละเฟรมของวิดีโอหรือรูปภาพในลำดับ ให้ทำดังนี้
คุณสามารถสร้าง 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. ประมวลผลรูปภาพ
ส่งรูปภาพไปยังเมธอดprocess() ดังนี้
Kotlin
objectDetector.process(image) .addOnSuccessListener { detectedObjects -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
objectDetector.process(image) .addOnSuccessListener( new OnSuccessListener<List<DetectedObject>>() { @Override public void onSuccess(List<DetectedObject> detectedObjects) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. ดูข้อมูลเกี่ยวกับวัตถุที่ตรวจพบ
หากการเรียก process() สำเร็จ ระบบจะส่งรายการ DetectedObject ไปยัง Listener ที่สำเร็จ
DetectedObject แต่ละรายการจะมีพร็อพเพอร์ตี้ต่อไปนี้
| กรอบล้อมรอบ | Rect ที่ระบุตำแหน่งของออบเจ็กต์ใน
รูปภาพ |
||||||
| รหัสติดตาม | จำนวนเต็มที่ระบุออบเจ็กต์ในรูปภาพ เป็น Null ใน SINGLE_IMAGE_MODE | ||||||
| ป้ายกำกับ |
|
Kotlin
for (detectedObject in detectedObjects) { val boundingBox = detectedObject.boundingBox val trackingId = detectedObject.trackingId for (label in detectedObject.labels) { val text = label.text if (PredefinedCategory.FOOD == text) { ... } val index = label.index if (PredefinedCategory.FOOD_INDEX == index) { ... } val confidence = label.confidence } }
Java
// The list of detected objects contains one item if multiple // object detection wasn't enabled. for (DetectedObject detectedObject : detectedObjects) { Rect boundingBox = detectedObject.getBoundingBox(); Integer trackingId = detectedObject.getTrackingId(); for (Label label : detectedObject.getLabels()) { String text = label.getText(); if (PredefinedCategory.FOOD.equals(text)) { ... } int index = label.getIndex(); if (PredefinedCategory.FOOD_INDEX == index) { ... } float confidence = label.getConfidence(); } }
การรับประกันประสบการณ์ของผู้ใช้ที่ยอดเยี่ยม
เพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ดีที่สุด ให้ปฏิบัติตามหลักเกณฑ์ต่อไปนี้ในแอป
- การตรวจจับวัตถุจะสำเร็จหรือไม่ขึ้นอยู่กับความซับซ้อนของภาพของวัตถุ วัตถุที่มีฟีเจอร์ภาพจำนวนน้อยอาจต้องใช้พื้นที่ส่วนใหญ่ของรูปภาพจึงจะตรวจพบ คุณควรให้คำแนะนำแก่ผู้ใช้เกี่ยวกับการจับภาพอินพุตที่ทำงานได้ดีกับวัตถุประเภทที่คุณต้องการตรวจจับ
- เมื่อใช้การจัดประเภท หากต้องการตรวจจับวัตถุที่ไม่ได้อยู่ในหมวดหมู่ที่รองรับอย่างชัดเจน ให้ใช้การจัดการพิเศษสำหรับวัตถุที่ไม่รู้จัก
นอกจากนี้ โปรดดู แอปตัวอย่าง ML Kit Material Design และ คอลเล็กชัน รูปแบบ Material Design สำหรับฟีเจอร์ที่ขับเคลื่อนด้วยแมชชีนเลิร์นนิง
การปรับปรุงประสิทธิภาพการทำงาน
หากต้องการใช้การตรวจจับวัตถุในแอปพลิเคชันแบบเรียลไทม์ ให้ปฏิบัติตามหลักเกณฑ์ต่อไปนี้เพื่อให้ได้อัตราเฟรมที่ดีที่สุด
เมื่อใช้โหมดสตรีมมิงในแอปพลิเคชันแบบเรียลไทม์ อย่าใช้การตรวจจับวัตถุหลายรายการ เนื่องจากอุปกรณ์ส่วนใหญ่จะไม่สามารถสร้างอัตราเฟรมที่เพียงพอได้
ปิดใช้การจัดประเภทหากไม่จำเป็น
- หากใช้
Cameraหรือcamera2API ให้ควบคุมการเรียกตัวตรวจจับ หากเฟรมวิดีโอใหม่ พร้อมใช้งานขณะที่ตัวตรวจจับกำลังทำงาน ให้ทิ้งเฟรมนั้น ดูคลาสVisionProcessorBaseในแอปตัวอย่างเริ่มต้นใช้งานฉบับย่อเพื่อดูตัวอย่าง - หากใช้
CameraXAPI ให้ตรวจสอบว่าได้ตั้งค่ากลยุทธ์การควบคุมปริมาณข้อมูลเป็นค่าเริ่มต้นImageAnalysis.STRATEGY_KEEP_ONLY_LATESTซึ่งจะรับประกันว่าระบบจะส่งรูปภาพเพียงรูปเดียวสำหรับการวิเคราะห์ในแต่ละครั้ง หากมีการสร้างรูปภาพเพิ่มเติมเมื่อตัววิเคราะห์ไม่ว่าง ระบบจะทิ้งรูปภาพเหล่านั้นโดยอัตโนมัติและไม่จัดคิวเพื่อส่ง เมื่อปิดรูปภาพที่กำลังวิเคราะห์โดยการเรียก ImageProxy.close() ระบบจะส่งรูปภาพล่าสุดถัดไป - หากใช้เอาต์พุตของตัวตรวจจับเพื่อวางซ้อนกราฟิกบน
รูปภาพอินพุต ให้รับผลลัพธ์จาก ML Kit ก่อน จากนั้นแสดงรูปภาพ
และวางซ้อนในขั้นตอนเดียว ซึ่งจะแสดงผลไปยังพื้นผิวการแสดงผล
เพียงครั้งเดียวสำหรับแต่ละเฟรมอินพุต ดูคลาส
CameraSourcePreviewและGraphicOverlayในแอปตัวอย่างเริ่มต้นใช้งานฉบับย่อเพื่อดูตัวอย่าง - หากใช้ Camera2 API ให้จับภาพใน
ImageFormat.YUV_420_888รูปแบบ หากใช้ Camera API เวอร์ชันเก่า ให้จับภาพในImageFormat.NV21รูปแบบ