Bạn có thể sử dụng Bộ công cụ học máy để nhận dạng và giải mã mã vạch.
Tính năng | Không nhóm | Gộp chung |
---|---|---|
Triển khai | Mô hình được tải xuống linh động thông qua Dịch vụ Google Play. | Mô hình được liên kết tĩnh với ứng dụng của bạn tại thời điểm xây dựng. |
Kích thước ứng dụng | Tăng kích thước khoảng 200 KB. | Tăng kích thước khoảng 2,4 MB. |
Thời gian khởi động | Có thể phải đợi tải mô hình xuống trước khi sử dụng lần đầu tiên. | Mô hình có sẵn ngay lập tức. |
Dùng thử
- Hãy khám phá ứng dụng mẫu để xem ví dụ về cách sử dụng API này.
- Hãy xem ứng dụng quảng cáo Material Design để biết cách triển khai toàn diện API này.
Trước khi bắt đầu
Trong tệp
build.gradle
cấp dự án, hãy nhớ đưa kho lưu trữ Maven của Google vào cả hai phầnbuildscript
vàallprojects
.Thêm các phần phụ thuộc cho thư viện Android của Bộ công cụ học máy vào tệp gradle cấp ứng dụng của mô-đun, thường là
app/build.gradle
. Hãy chọn một trong các phần phụ thuộc sau tuỳ theo nhu cầu của bạn:Cách kết hợp mô hình với ứng dụng:
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.2.0' }
Cách sử dụng mô hình này trong Dịch vụ 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.3.0' }
Nếu chọn sử dụng mô hình trong Dịch vụ Google Play, bạn có thể định cấu hình để ứng dụng tự động tải mô hình đó xuống thiết bị sau khi ứng dụng được cài đặt từ Cửa hàng Play. Để thực hiện việc này, hãy thêm nội dung khai báo sau vào tệp
AndroidManifest.xml
của ứng dụng:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode" > <!-- To use multiple models: android:value="barcode,model2,model3" --> </application>
Bạn cũng có thể kiểm tra rõ ràng tình trạng sẵn có của mô hình và yêu cầu tải xuống thông qua ModuleInstallClient API của Dịch vụ Google Play.
Nếu bạn không cho phép tải mô hình xuống khi cài đặt hoặc yêu cầu tải xuống rõ ràng, thì mô hình sẽ được tải xuống trong lần đầu bạn chạy trình quét. Yêu cầu bạn thực hiện trước khi tải xuống hoàn tất không có kết quả nào.
Nguyên tắc đối với hình ảnh nhập vào
-
Để Bộ công cụ học máy đọc chính xác mã vạch, hình ảnh đầu vào phải chứa mã vạch được biểu thị bằng đủ dữ liệu pixel.
Các yêu cầu cụ thể về dữ liệu pixel phụ thuộc vào cả loại mã vạch và lượng dữ liệu được mã hoá trong mã vạch, vì nhiều mã vạch hỗ trợ tải trọng có kích thước khác nhau. Nhìn chung, đơn vị có ý nghĩa nhỏ nhất của mã vạch phải rộng ít nhất 2 pixel và cao 2 pixel đối với mã 2 chiều.
Ví dụ: mã vạch EAN-13 được tạo thành từ các thanh và khoảng trắng có chiều rộng là 1, 2, 3 hoặc 4 đơn vị. Do đó, hình ảnh mã vạch EAN-13 lý tưởng nhất là có các thanh và khoảng rộng ít nhất 2, 4, 6 và 8 pixel. Vì mã vạch EAN-13 có tổng chiều rộng là 95 đơn vị nên mã vạch phải có chiều rộng tối thiểu là 190 pixel.
Các định dạng mật mã (chẳng hạn như PDF417) cần có kích thước pixel lớn hơn để Bộ công cụ học máy đọc được các định dạng đó một cách đáng tin cậy. Ví dụ: một mã PDF417 có thể có tối đa 34 "từ" có chiều rộng 17 đơn vị trong một hàng, lý tưởng nhất là có chiều rộng tối thiểu là 1156 pixel.
-
Hình ảnh lấy nét kém có thể ảnh hưởng đến độ chính xác của quá trình quét. Nếu ứng dụng của bạn không nhận được kết quả chấp nhận được, hãy yêu cầu người dùng chụp lại hình ảnh.
-
Đối với các ứng dụng thông thường, bạn nên cung cấp hình ảnh có độ phân giải cao hơn, chẳng hạn như 1280x720 hoặc 1920x1080, để có thể quét mã vạch từ khoảng cách lớn hơn từ máy ảnh.
Tuy nhiên, trong các ứng dụng đòi hỏi độ trễ quan trọng, bạn có thể cải thiện hiệu suất bằng cách chụp ảnh ở độ phân giải thấp hơn nhưng mã vạch chiếm phần lớn hình ảnh đầu vào. Ngoài ra, hãy xem Mẹo cải thiện hiệu suất theo thời gian thực.
1. Định cấu hình máy quét mã vạch
Nếu biết các định dạng mã vạch mà bạn muốn đọc, bạn có thể cải thiện tốc độ của trình phát hiện mã vạch bằng cách định cấu hình để chỉ phát hiện các định dạng đó.Ví dụ: để chỉ phát hiện mã Aztec và mã QR, hãy tạo một đối tượng BarcodeScannerOptions
như trong ví dụ sau:
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();
Các định dạng sau được hỗ trợ:
- Mã 128 (
FORMAT_CODE_128
) - Mã 39 (
FORMAT_CODE_39
) - Mã 93 (
FORMAT_CODE_93
) - Codabar (
FORMAT_CODABAR
) - EAN-13 (
FORMAT_EAN_13
) - EAN-8 (
FORMAT_EAN_8
) - Ý (
FORMAT_ITF
) - UPC-A (
FORMAT_UPC_A
) - UPC-E (
FORMAT_UPC_E
) - Mã QR (
FORMAT_QR_CODE
) - PDF417 (
FORMAT_PDF417
) - Tiếng Aztec (
FORMAT_AZTEC
) - Ma trận dữ liệu (
FORMAT_DATA_MATRIX
)
Kể từ mẫu 17.1.0 đi kèm và mẫu chưa đóng gói 18.2.0, bạn cũng có thể gọi enableAllPotentialBarcodes()
để trả về tất cả các mã vạch tiềm năng ngay cả khi không thể giải mã. Tính năng này có thể được dùng để hỗ trợ khả năng phát hiện sâu hơn, chẳng hạn như bằng cách phóng to máy ảnh để có hình ảnh rõ ràng hơn về bất kỳ mã vạch nào trong hộp giới hạn được trả về.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build();
Further on, starting from bundled library 17.2.0 and unbundled library 18.3.0, a new feature called auto-zoom has been introduced to further enhance the barcode scanning experience. With this feature enabled, the app is notified when all barcodes within the view are too distant for decoding. As a result, the app can effortlessly adjust the camera's zoom ratio to the recommended setting provided by the library, ensuring optimal focus and readability. This feature will significantly enhance the accuracy and success rate of barcode scanning, making it easier for apps to capture information precisely.
To enable auto-zooming and customize the experience, you can utilize the
setZoomSuggestionOptions()
method along with your
own ZoomCallback
handler and desired maximum zoom
ratio, as demonstrated in the code below.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .setZoomSuggestionOptions( new ZoomSuggestionOptions.Builder(zoomCallback) .setMaxSupportedZoomRatio(maxSupportedZoomRatio) .build()) // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .setZoomSuggestionOptions( new ZoomSuggestionOptions.Builder(zoomCallback) .setMaxSupportedZoomRatio(maxSupportedZoomRatio) .build()) // Optional .build();
zoomCallback
is required to be provided to handle whenever the library
suggests a zoom should be performed and this callback will always be called on
the main thread.
The following code snippet shows an example of defining a simple callback.
Kotlin
fun setZoom(ZoomRatio: Float): Boolean { if (camera.isClosed()) return false camera.getCameraControl().setZoomRatio(zoomRatio) return true }
Java
boolean setZoom(float zoomRatio) { if (camera.isClosed()) { return false; } camera.getCameraControl().setZoomRatio(zoomRatio); return true; }
maxSupportedZoomRatio
is related to the camera hardware, and different camera
libraries have different ways to fetch it (see the javadoc of the setter
method). In case this is not provided, an
unbounded zoom ratio might be produced by the library which might not be
supported. Refer to the
setMaxSupportedZoomRatio()
method
introduction to see how to get the max supported zoom ratio with different
Camera libraries.
When auto-zooming is enabled and no barcodes are successfully decoded within
the view, BarcodeScanner
triggers your zoomCallback
with the requested
zoomRatio
. If the callback correctly adjusts the camera to this zoomRatio
,
it is highly probable that the most centered potential barcode will be decoded
and returned.
A barcode may remain undecodable even after a successful zoom-in. In such cases,
BarcodeScanner
may either invoke the callback for another round of zoom-in
until the maxSupportedZoomRatio
is reached, or provide an empty list (or a
list containing potential barcodes that were not decoded, if
enableAllPotentialBarcodes()
was called) to the OnSuccessListener
(which
will be defined in step 4. Process the image).
2. Prepare the input image
To recognize barcodes in an image, create anInputImage
object
from either a Bitmap
, media.Image
, ByteBuffer
, byte array, or a file on
the device. Then, pass the InputImage
object to the
BarcodeScanner
's process
method.
You can create an InputImage
object from different sources, each is explained below.
Using a media.Image
To create an InputImage
object from a media.Image
object, such as when you capture an image from a
device's camera, pass the media.Image
object and the image's
rotation to InputImage.fromMediaImage()
.
If you use the
CameraX library, the OnImageCapturedListener
and
ImageAnalysis.Analyzer
classes calculate the rotation value
for you.
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 // ... } } }
Nếu không sử dụng thư viện máy ảnh để cung cấp độ xoay của hình ảnh, bạn có thể tính độ xoay này dựa trên độ xoay của thiết bị và hướng của cảm biến máy ảnh trong thiết bị:
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; }
Sau đó, hãy truyền đối tượng media.Image
và giá trị độ xoay đến InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Sử dụng URI tệp
Để tạo đối tượng InputImage
từ URI tệp, hãy chuyển ngữ cảnh ứng dụng và URI tệp đến InputImage.fromFilePath()
. Điều này rất hữu ích khi bạn sử dụng ý định ACTION_GET_CONTENT
để nhắc người dùng chọn một hình ảnh từ ứng dụng thư viện của họ.
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(); }
Sử dụng ByteBuffer
hoặc ByteArray
Để tạo một đối tượng InputImage
từ ByteBuffer
hoặc ByteArray
, trước tiên, hãy tính toán độ xoay hình ảnh như mô tả trước đó cho dữ liệu đầu vào media.Image
.
Sau đó, hãy tạo đối tượng InputImage
bằng vùng đệm hoặc mảng, cùng với chiều cao, chiều rộng, định dạng mã hoá màu và độ xoay của hình ảnh:
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 );
Sử dụng Bitmap
Để tạo đối tượng InputImage
từ đối tượng Bitmap
, hãy khai báo sau:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Hình ảnh được biểu thị bằng đối tượng Bitmap
cùng với độ xoay.
3. Nhận một bản sao của 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. Xử lý hình ảnh
Truyền hình ảnh vào phương thứcprocess
:
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. Lấy thông tin từ mã vạch
Nếu thao tác nhận dạng mã vạch thành công, thì một danh sách các đối tượngBarcode
sẽ được chuyển đến trình nghe thành công. Mỗi đối tượng Barcode
đại diện cho một mã vạch được phát hiện trong hình ảnh. Đối với mỗi mã vạch, bạn có thể lấy toạ độ giới hạn trong hình ảnh đầu vào, cũng như dữ liệu thô được mã hoá bằng mã vạch. Ngoài ra, nếu máy quét mã vạch có thể xác định loại dữ liệu được mã hoá bằng mã vạch, thì bạn có thể lấy đối tượng chứa dữ liệu đã phân tích cú pháp.
Ví dụ:
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; } }
Mẹo cải thiện hiệu suất theo thời gian thực
Nếu bạn muốn quét mã vạch trong ứng dụng theo thời gian thực, hãy làm theo các nguyên tắc sau để đạt được tốc độ khung hình tốt nhất:
-
Đừng chụp dữ liệu đầu vào ở độ phân giải gốc của máy ảnh. Trên một số thiết bị, việc thu thập dữ liệu đầu vào ở độ phân giải gốc sẽ tạo ra những hình ảnh cực lớn (10 megapixel trở lên), dẫn đến độ trễ rất kém mà không giúp tăng độ chính xác. Thay vào đó, chỉ yêu cầu kích thước từ máy ảnh cần thiết để phát hiện mã vạch, thường không quá 2 megapixel.
Nếu tốc độ quét quan trọng, bạn có thể giảm thêm độ phân giải ảnh chụp. Tuy nhiên, hãy lưu ý các yêu cầu tối thiểu về kích thước mã vạch nêu trên.
Nếu bạn đang cố gắng nhận dạng mã vạch trong một chuỗi khung hình video phát trực tuyến, thì trình nhận dạng có thể cho ra các kết quả khác nhau giữa các khung hình. Bạn nên đợi cho đến khi nhận được một chuỗi liên tiếp có cùng giá trị để chắc chắn rằng bạn đang trả về một kết quả tốt.
Chữ số tổng kiểm không được hỗ trợ cho ITF và CODE-39.
- Nếu bạn sử dụng API
Camera
hoặccamera2
, hãy điều tiết lệnh gọi đến trình phát hiện. Nếu có khung video mới trong khi trình phát hiện đang chạy, hãy bỏ khung hình đó. Hãy xem lớpVisionProcessorBase
trong ứng dụng mẫu bắt đầu nhanh để biết ví dụ. - Nếu bạn sử dụng API
CameraX
, hãy nhớ đặt chiến lược backpressure về giá trị mặc địnhImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Điều này đảm bảo mỗi lần chỉ gửi một hình ảnh để phân tích. Nếu thêm hình ảnh được tạo khi trình phân tích bận, các hình ảnh đó sẽ tự động bị loại bỏ và không được đưa vào hàng đợi phân phối. Sau khi bạn đóng hình ảnh đang được phân tích bằng cách gọi ImageProxy.close(), hình ảnh mới nhất tiếp theo sẽ được phân phối. - Nếu bạn dùng đầu ra của trình phát hiện để phủ đồ hoạ lên hình ảnh đầu vào, trước tiên, hãy lấy kết quả từ Bộ công cụ học máy rồi kết xuất hình ảnh và lớp phủ chỉ trong một bước. API này chỉ kết xuất trên nền tảng màn hình một lần cho mỗi khung đầu vào. Hãy xem các lớp
CameraSourcePreview
vàGraphicOverlay
trong ứng dụng mẫu bắt đầu nhanh để biết ví dụ. - Nếu bạn sử dụng API Camera2, hãy chụp ảnh ở định dạng
ImageFormat.YUV_420_888
. Nếu bạn sử dụng API Camera cũ, hãy chụp ảnh ở định dạngImageFormat.NV21
.
Trừ khi có lưu ý khác, nội dung của trang này được cấp phép theo Giấy phép ghi nhận tác giả 4.0 của Creative Commons và các mẫu mã lập trình được cấp phép theo Giấy phép Apache 2.0. Để biết thông tin chi tiết, vui lòng tham khảo Chính sách trang web của Google Developers. Java là nhãn hiệu đã đăng ký của Oracle và/hoặc các đơn vị liên kết với Oracle.
Cập nhật lần gần đây nhất: 2024-02-28 UTC.