คุณสามารถใช้ ML Kit เพื่อตรวจจับและติดตามออบเจ็กต์ในเฟรมวิดีโอที่ต่อเนื่องกันได้
เมื่อส่งรูปภาพไปยัง ML Kit ระบบจะตรวจจับวัตถุได้สูงสุด 5 รายการในรูปภาพ พร้อมกับตำแหน่งของวัตถุแต่ละรายการในรูปภาพ เมื่อตรวจหาออบเจ็กต์ในสตรีมวิดีโอ ออบเจ็กต์แต่ละรายการจะมีรหัสที่ไม่ซ้ำกันซึ่งคุณใช้เพื่อติดตามออบเจ็กต์จากเฟรมหนึ่งไปยังอีกเฟรมหนึ่งได้ นอกจากนี้ คุณยังเลือกเปิดใช้การแยกประเภทออบเจ็กต์แบบหยาบได้ด้วย ซึ่งจะติดป้ายกำกับออบเจ็กต์ด้วยคำอธิบายหมวดหมู่แบบกว้าง
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อดูตัวอย่างการใช้งาน API นี้
- ดูการติดตั้งใช้งาน API นี้ตั้งแต่ต้นจนจบได้ในแอปการสาธิต Material Design
ก่อนเริ่มต้น
- ในไฟล์
build.gradleระดับโปรเจ็กต์ ให้ตรวจสอบว่าได้รวมที่เก็บ Maven ของ Google ไว้ในทั้งส่วนbuildscriptและallprojects - เพิ่มทรัพยากร Dependency สำหรับไลบรารี ML Kit สำหรับ Android ลงในไฟล์ Gradle ระดับแอปของโมดูล ซึ่งโดยปกติคือ
app/build.gradledependencies { // ... implementation 'com.google.mlkit:object-detection:17.0.2' }
1. กำหนดค่าเครื่องตรวจจับออบเจ็กต์
หากต้องการตรวจหาและติดตามออบเจ็กต์ ให้สร้างอินสแตนซ์ของ ObjectDetector ก่อน แล้วระบุการตั้งค่าเครื่องตรวจหาที่ต้องการเปลี่ยนแปลงจากการตั้งค่าเริ่มต้น (ไม่บังคับ)
กำหนดค่าเครื่องตรวจจับออบเจ็กต์สำหรับกรณีการใช้งานของคุณด้วยออบเจ็กต์
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 สำหรับกรณีการใช้งานเหล่านี้
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();
รับอินสแตนซ์ของ
ObjectDetectorKotlin
val objectDetector = ObjectDetection.getClient(options)
Java
ObjectDetector objectDetector = ObjectDetection.getClient(options);
2. เตรียมรูปภาพอินพุต
หากต้องการตรวจจับและติดตามวัตถุ ให้ส่งรูปภาพไปยังเมธอดprocess() ของObjectDetectorอินสแตนซ์
เครื่องตรวจจับออบเจ็กต์จะทำงานโดยตรงจาก Bitmap, NV21 ByteBuffer หรือ media.Image YUV_420_888 เราขอแนะนำให้สร้าง InputImage จากแหล่งข้อมูลเหล่านั้น
หากคุณมีสิทธิ์เข้าถึงแหล่งข้อมูลใดแหล่งข้อมูลหนึ่งโดยตรง หากคุณสร้าง InputImage จากแหล่งที่มาอื่นๆ เราจะจัดการ Conversion
ภายในให้คุณ ซึ่งอาจมีประสิทธิภาพน้อยกว่า
สำหรับแต่ละเฟรมของวิดีโอหรือรูปภาพในลำดับ ให้ทำดังนี้
คุณสร้าง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() ซึ่งจะมีประโยชน์เมื่อคุณ
ใช้ ACTION_GET_CONTENTIntent เพื่อแจ้งให้ผู้ใช้เลือก
รูปภาพจากแอปแกลเลอรี
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 ไปยัง
เครื่องมือฟังที่สำเร็จ
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(); } }
รับประกันประสบการณ์การใช้งานที่ยอดเยี่ยม
โปรดปฏิบัติตามหลักเกณฑ์ต่อไปนี้ในแอปเพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีที่สุด
- การตรวจจับออบเจ็กต์ที่สำเร็จขึ้นอยู่กับความซับซ้อนของภาพของออบเจ็กต์ หากต้องการให้ตรวจพบ วัตถุที่มีฟีเจอร์ภาพจำนวนน้อยอาจต้องใช้พื้นที่ส่วนใหญ่ของรูปภาพ คุณควรให้คำแนะนำแก่ผู้ใช้เกี่ยวกับ การบันทึกอินพุตที่ทำงานได้ดีกับออบเจ็กต์ประเภทที่คุณต้องการตรวจหา
- เมื่อใช้การแยกประเภท หากต้องการตรวจหาออบเจ็กต์ที่ไม่ได้อยู่ในหมวดหมู่ที่รองรับอย่างชัดเจน ให้ใช้การจัดการพิเศษสำหรับออบเจ็กต์ที่ไม่รู้จัก
นอกจากนี้ โปรดดู แอปการสาธิต Material Design ของ ML Kit และคอลเล็กชัน Material Design รูปแบบสำหรับฟีเจอร์ที่ทำงานด้วยแมชชีนเลิร์นนิง
Improving performance
หากต้องการใช้การตรวจจับออบเจ็กต์ในแอปพลิเคชันแบบเรียลไทม์ ให้ทำตามหลักเกณฑ์ต่อไปนี้เพื่อให้ได้อัตราเฟรมที่ดีที่สุด
เมื่อใช้โหมดการสตรีมในแอปพลิเคชันแบบเรียลไทม์ อย่าใช้การตรวจจับออบเจ็กต์หลายรายการ เนื่องจากอุปกรณ์ส่วนใหญ่จะไม่สามารถสร้างอัตราเฟรมที่เพียงพอได้
ปิดใช้การแยกประเภทหากไม่ต้องการ
- หากคุณใช้ API ของ
Cameraหรือcamera2ให้ควบคุมการเรียกไปยังเครื่องตรวจจับ หากมีเฟรมวิดีโอใหม่ ขณะที่เครื่องตรวจจับทำงาน ให้ทิ้งเฟรม ดูตัวอย่างได้ในคลาสVisionProcessorBaseในแอปตัวอย่างฉบับย่อ - หากใช้
CameraXAPI โปรดตรวจสอบว่าได้ตั้งค่ากลยุทธ์การควบคุมปริมาณคำขอเป็นค่าเริ่มต้นImageAnalysis.STRATEGY_KEEP_ONLY_LATESTซึ่งจะรับประกันว่าระบบจะส่งรูปภาพเพียงรูปเดียวเพื่อวิเคราะห์ในแต่ละครั้ง หากมีการสร้างรูปภาพเพิ่มเติม เมื่อเครื่องมือวิเคราะห์ทำงานหนัก ระบบจะทิ้งรูปภาพเหล่านั้นโดยอัตโนมัติและจะไม่จัดคิวเพื่อ นำส่ง เมื่อปิดรูปภาพที่กำลังวิเคราะห์โดยการเรียกใช้ ImageProxy.close() ระบบจะส่งรูปภาพล่าสุดถัดไป - หากใช้เอาต์พุตของเครื่องตรวจจับเพื่อซ้อนทับกราฟิกบนรูปภาพอินพุต ให้รับผลลัพธ์จาก ML Kit ก่อน จากนั้นจึงแสดงรูปภาพและซ้อนทับในขั้นตอนเดียว ซึ่งจะแสดงผลในพื้นที่แสดง
เพียงครั้งเดียวสำหรับแต่ละเฟรมอินพุต ดูคลาส
CameraSourcePreviewและGraphicOverlayในแอปตัวอย่างฉบับย่อเพื่อดูตัวอย่าง - หากใช้ Camera2 API ให้ถ่ายภาพในรูปแบบ
ImageFormat.YUV_420_888หากใช้ Camera API เวอร์ชันเก่า ให้จับภาพในรูปแบบImageFormat.NV21