คุณใช้ ML Kit เพื่อจดจําและถอดรหัสบาร์โค้ดได้
ฟีเจอร์ | เลิกรวมกลุ่มแล้ว | รวมกลุ่ม |
---|---|---|
การใช้งาน | โมเดลจะดาวน์โหลดแบบไดนามิกผ่านบริการ Google Play | โมเดลจะลิงก์อยู่กับแอปในเวลาที่สร้างบิลด์ |
ขนาดแอป | มีขนาดเพิ่มขึ้นประมาณ 200 KB | มีขนาดเพิ่มขึ้นประมาณ 2.4 MB |
เวลาเริ่มต้น | อาจต้องรอให้โมเดลดาวน์โหลดก่อนที่จะใช้งานครั้งแรก | โมเดลพร้อมใช้งานทันที |
ลองใช้งาน
- ลองใช้แอปตัวอย่างเพื่อดูตัวอย่างการใช้งาน API นี้
- ดูแอป Showcase Material Design สําหรับการใช้งาน API นี้แบบครบวงจร
ข้อควรทราบก่อนที่จะเริ่มต้น
ในไฟล์
build.gradle
ระดับโปรเจ็กต์ อย่าลืมใส่ที่เก็บ Maven ของ Google ทั้งในbuildscript
และallprojects
เพิ่มทรัพยากร Dependency สําหรับไลบรารี Android ของ ML Kit ไปยังไฟล์ Gradle ระดับแอปของโมดูล ซึ่งปกติจะเป็น
app/build.gradle
เลือกทรัพยากร Dependency ต่อไปนี้ 1 รายการตามที่ต้องการสําหรับการรวมแพ็กเกจกับแอปของคุณ ให้ทําดังนี้
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.0.3' }
สําหรับการใช้โมเดลในบริการ Google Play
dependencies { // ... // Use this dependency to use the dynamically downloaded model in Google Play Services implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0' }
หากเลือกใช้โมเดลในบริการ Google Play คุณสามารถกําหนดค่าแอปให้ดาวน์โหลดโมเดลลงในอุปกรณ์โดยอัตโนมัติหลังจากที่ติดตั้งแอปจาก Play Store แล้ว โดยเพิ่มการประกาศต่อไปนี้ลงในไฟล์
AndroidManifest.xml
ของแอป<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode" > <!-- To use multiple models: android:value="barcode,model2,model3" --> </application>
นอกจากนี้ คุณยังตรวจสอบความพร้อมใช้งานของโมเดลได้อย่างชัดเจนและส่งคําขอดาวน์โหลดผ่าน ModuleInstallClient API ของบริการ Google Play ได้ด้วย
หากคุณไม่เปิดใช้การดาวน์โหลดโมเดลเวลาติดตั้งหรือขอการดาวน์โหลดอย่างชัดแจ้ง ระบบจะดาวน์โหลดโมเดลในครั้งแรกที่คุณเรียกใช้เครื่องมือสแกน คําขอที่คุณสร้าง ก่อนที่การดาวน์โหลดจะเสร็จสมบูรณ์ จะไม่พบผลลัพธ์
หลักเกณฑ์เกี่ยวกับรูปภาพที่ป้อน
-
เพื่อให้ ML Kit อ่านบาร์โค้ดได้อย่างแม่นยํา รูปภาพอินพุตต้องมีบาร์โค้ดที่แสดงข้อมูลพิกเซลเพียงพอ
ข้อกําหนดของข้อมูลพิกเซลที่เจาะจงจะขึ้นอยู่กับทั้งประเภทบาร์โค้ดและจํานวนข้อมูลที่เข้ารหัสในนั้น เนื่องจากบาร์โค้ดหลายรายการจะรองรับเพย์โหลดขนาดตัวแปร โดยทั่วไป หน่วยที่มีความหมายน้อยที่สุดของบาร์โค้ดควรกว้างอย่างน้อย 2 พิกเซล และสําหรับโค้ด 2 มิติ ความสูง 2 พิกเซล
เช่น บาร์โค้ด EAN-13 ประกอบด้วยแท่งและช่องว่างที่มีความกว้าง 1, 2, 3 หรือ 4 หน่วย ดังนั้นรูปภาพบาร์โค้ด EAN-13 จึงมีแถบและการเว้นวรรคที่กว้างอย่างน้อย 2, 4, 6 และ 8 พิกเซล เนื่องจากบาร์โค้ด EAN-13 มีความกว้างรวม 95 หน่วย บาร์โค้ดจึงควรกว้างอย่างน้อย 190 พิกเซล
รูปแบบความหนาแน่นมากขึ้น เช่น PDF417 ต้องการขนาดพิกเซลที่มากขึ้นเพื่อให้ ML Kit อ่านได้อย่างน่าเชื่อถือ เช่น โค้ด PDF417 อาจมี "คํา" กว้าง 17 หน่วยสูงสุด 34 หน่วยในแถวเดียว ซึ่งควรกว้างอย่างน้อย 1,156 พิกเซล
-
การโฟกัสรูปภาพต่ําอาจส่งผลต่อความแม่นยําในการสแกน หากแอปไม่ได้รับผลลัพธ์ที่ยอมรับ ให้ขอให้ผู้ใช้สรุปรูปภาพ
-
สําหรับแอปพลิเคชันทั่วไป ขอแนะนําให้ใช้รูปภาพที่มีความละเอียดสูงขึ้น เช่น 1280x720 หรือ 1920x1080 ซึ่งทําให้สแกนบาร์โค้ดได้จากระยะที่ใหญ่ขึ้นจากกล้องไป
อย่างไรก็ตาม ในแอปพลิเคชันที่มีเวลาในการตอบสนองสูง ก็สามารถปรับปรุงประสิทธิภาพได้โดยการจับภาพที่ความละเอียดต่ําลง แต่กําหนดให้บาร์โค้ดเป็นรูปภาพส่วนใหญ่ที่ป้อน ดูเคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์
1. กําหนดค่าเครื่องสแกนบาร์โค้ด
หากคุณทราบรูปแบบบาร์โค้ดที่ต้องการอ่าน คุณสามารถปรับปรุงความเร็วของตัวตรวจจับบาร์โค้ดด้วยการกําหนดค่าให้ตรวจจับเฉพาะรูปแบบดังกล่าวเช่น หากต้องการตรวจจับเฉพาะโค้ด Aztec และคิวอาร์โค้ด ให้สร้างออบเจ็กต์ BarcodeScannerOptions
ตามตัวอย่างต่อไปนี้
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC) .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC) .build();
ระบบรองรับรูปแบบต่อไปนี้
- รหัส 128 (
FORMAT_CODE_128
) - รหัส 39 (
FORMAT_CODE_39
) - รหัส 93 (
FORMAT_CODE_93
) - โคดาบาร์ (
FORMAT_CODABAR
) - EAN-13 (
FORMAT_EAN_13
) - EAN-8 (
FORMAT_EAN_8
) - ITF (
FORMAT_ITF
) - UPC-A (
FORMAT_UPC_A
) - UPC-E (
FORMAT_UPC_E
) - คิวอาร์โค้ด (
FORMAT_QR_CODE
) - PDF417 (
FORMAT_PDF417
) - แอซเท็ก (
FORMAT_AZTEC
) - เมทริกซ์ข้อมูล (
FORMAT_DATA_MATRIX
)
2. เตรียมรูปภาพอินพุต
หากต้องการจดจําบาร์โค้ดในรูปภาพ ให้สร้างออบเจ็กต์InputImage
จาก Bitmap
, media.Image
, ByteBuffer
, อาร์เรย์ไบต์ หรือไฟล์ในอุปกรณ์ จากนั้นส่งออบเจ็กต์ InputImage
ไปยังเมธอด process
ของ BarcodeScanner
คุณสามารถสร้างออบเจ็กต์ 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. รับอินสแตนซ์ของ BarcodeScanner
Kotlin
val scanner = BarcodeScanning.getClient() // Or, to specify the formats to recognize: // val scanner = BarcodeScanning.getClient(options)
Java
BarcodeScanner scanner = BarcodeScanning.getClient(); // Or, to specify the formats to recognize: // BarcodeScanner scanner = BarcodeScanning.getClient(options);
4. ประมวลผลรูปภาพ
ส่งรูปภาพไปยังเมธอดprocess
ดังนี้
Kotlin
val result = scanner.process(image) .addOnSuccessListener { barcodes -> // Task completed successfully // ... } .addOnFailureListener { // Task failed with an exception // ... }
Java
Task<List<Barcode>> result = scanner.process(image) .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() { @Override public void onSuccess(List<Barcode> barcodes) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
5. รับข้อมูลจากบาร์โค้ด
หากการดําเนินการจดจําบาร์โค้ดสําเร็จ ระบบจะส่งรายการออบเจ็กต์Barcode
ไปยัง Listener ที่ประสบความสําเร็จ ออบเจ็กต์ Barcode
แต่ละรายการแสดงถึงบาร์โค้ดที่ตรวจพบในรูปภาพ สําหรับบาร์โค้ดแต่ละรายการ คุณจะได้รับพิกัดที่ล้อมรอบในรูปภาพอินพุต รวมถึงข้อมูลดิบที่เข้ารหัสด้วยบาร์โค้ด นอกจากนี้ หากเครื่องสแกนบาร์โค้ดสามารถระบุประเภทข้อมูลที่เข้ารหัสด้วยบาร์โค้ด คุณก็สามารถรับออบเจ็กต์ที่มีข้อมูลที่แยกวิเคราะห์ได้
เช่น
Kotlin
for (barcode in barcodes) { val bounds = barcode.boundingBox val corners = barcode.cornerPoints val rawValue = barcode.rawValue val valueType = barcode.valueType // See API reference for complete list of supported types when (valueType) { Barcode.TYPE_WIFI -> { val ssid = barcode.wifi!!.ssid val password = barcode.wifi!!.password val type = barcode.wifi!!.encryptionType } Barcode.TYPE_URL -> { val title = barcode.url!!.title val url = barcode.url!!.url } } }
Java
for (Barcode barcode: barcodes) { Rect bounds = barcode.getBoundingBox(); Point[] corners = barcode.getCornerPoints(); String rawValue = barcode.getRawValue(); int valueType = barcode.getValueType(); // See API reference for complete list of supported types switch (valueType) { case Barcode.TYPE_WIFI: String ssid = barcode.getWifi().getSsid(); String password = barcode.getWifi().getPassword(); int type = barcode.getWifi().getEncryptionType(); break; case Barcode.TYPE_URL: String title = barcode.getUrl().getTitle(); String url = barcode.getUrl().getUrl(); break; } }
เคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์
หากต้องการสแกนบาร์โค้ดในแอปพลิเคชันแบบเรียลไทม์ ให้ทําตามหลักเกณฑ์ต่อไปนี้เพื่อให้อัตราเฟรมดีที่สุด
-
อย่าบันทึกอินพุตที่ความละเอียดตามค่าเริ่มต้นของกล้อง ในอุปกรณ์บางอย่าง การบันทึกอินพุตที่ความละเอียดตามค่าเริ่มต้นจะสร้างรูปภาพขนาดใหญ่มาก (10+ เมกะพิกเซล) ซึ่งส่งผลให้เวลาในการตอบสนองช้ามากและไม่ส่งผลเสียต่อความแม่นยํา ให้ขอขนาดจากกล้องที่จําเป็นสําหรับการตรวจหาบาร์โค้ดเท่านั้น ซึ่งโดยปกติแล้วต้องมีขนาดไม่เกิน 2 เมกะพิกเซล
หากความเร็วในการสแกนเป็นสิ่งสําคัญ คุณลดความละเอียดในการจับภาพลงได้ อย่างไรก็ตาม โปรดทราบว่าข้อกําหนดขนาดบาร์โค้ดขั้นต่ําที่ระบุไว้ด้านบน
หากคุณพยายามจดจําบาร์โค้ดจากเฟรมสตรีมวิดีโอที่ต่อเนื่องกัน ระบบจดจําอาจสร้างผลลัพธ์ที่ต่างกันจากแต่ละเฟรม คุณควรรอจนกว่าชุดค่าเดียวกันซึ่งต่อเนื่องกันจะมั่นใจได้ว่าคุณจะได้รับผลลัพธ์ที่ดี
ระบบยังไม่รองรับหมายเลข Checksum สําหรับ ITF และ CODE-39
- หากคุณใช้ API ของ
Camera
หรือcamera2
คุณจะควบคุมการใช้ตัวตรวจจับได้ หากเฟรมวิดีโอใหม่พร้อมใช้งานขณะที่ตัวตรวจจับทํางาน ให้วางเฟรมนั้น ดูตัวอย่างคลาสVisionProcessorBase
ในแอปตัวอย่างคู่มือเริ่มต้นฉบับย่อ - หากคุณใช้ API
CameraX
โปรดตรวจสอบว่าได้ตั้งค่ากลยุทธ์ความกดดันเป็นค่าเริ่มต้นImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
ตัวเลือกนี้จะรับประกันว่าจะมีการส่งรูปภาพสําหรับการวิเคราะห์ข้อมูลเพียงครั้งละ 1 รูป หากมีการสร้างรูปภาพเพิ่มเติมเมื่อเครื่องมือวิเคราะห์ไม่ว่าง รูปภาพเหล่านั้นจะถูกทิ้งโดยอัตโนมัติและไม่อยู่ในคิวเพื่อนําส่ง เมื่อปิดรูปภาพที่จะวิเคราะห์แล้วโดยเรียก ImageProxy.close() ระบบจะส่งรูปภาพล่าสุดถัดไป - หากใช้เอาต์พุตของตัวตรวจจับเพื่อวางซ้อนกราฟิกบนรูปภาพอินพุต ก่อนอื่นให้ดูผลลัพธ์จาก ML Kit จากนั้นแสดงผลรูปภาพและวางซ้อนในขั้นตอนเดียว วิธีนี้จะแสดงผลบนแพลตฟอร์มจอแสดงผลเพียงครั้งเดียวสําหรับเฟรมอินพุตแต่ละเฟรม ดูตัวอย่างคลาส
CameraSourcePreview
และGraphicOverlay
ในแอปตัวอย่างเริ่มต้นอย่างรวดเร็ว - หากใช้ Camera2 API ให้จับภาพในรูปแบบ
ImageFormat.YUV_420_888
หากคุณใช้ Camera API เวอร์ชันเก่า ให้จับภาพในรูปแบบImageFormat.NV21