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