您可以使用 ML Kit 辨識條碼並加以解碼。
特徵 | 未分類 | 組合 |
---|---|---|
導入作業 | 模型會透過 Google Play 服務動態下載。 | 模型會在建構期間以靜態方式連結至應用程式。 |
應用程式大小 | 大小增加約 200 KB。 | 大小增加約 2.4 MB。 |
初始化時間 | 可能要等到模型下載完畢再開始使用。 | 模型可立即使用。 |
馬上試試
- 您可以試用範例應用程式,查看這個 API 的使用範例。
- 如需此 API 的端對端實作,請參閱 Material Design 展示應用程式。
事前準備
在專案層級的
build.gradle
檔案中,請務必在buildscript
和allprojects
區段中納入 Google 的 Maven 存放區。將 ML Kit Android 程式庫的依附元件,新增至模組的應用程式層級 Gradle 檔案,通常為
app/build.gradle
。請根據您的需求選擇下列其中一種依附元件:將模型與應用程式搭配使用:
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.2.0' }
在 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' }
如果您選擇在 Google Play 服務中使用模型,可以設定讓應用程式在從 Play 商店安裝應用程式後,自動將模型下載至裝置。如要這麼做,請在應用程式的
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>
您也可以明確檢查模型可用性,並透過 Google Play 服務 ModuleInstallClient API 要求下載。
如果您未啟用安裝期間模型下載功能,或要求明確下載,系統會在您首次執行掃描器時下載模型。下載完成前發出的要求不會產生任何結果。
輸入圖片規範
-
為了讓 ML Kit 準確讀取條碼,輸入圖片必須包含以充足的像素資料表示的條碼。
具體的像素資料需求取決於條碼的類型和編碼的資料量,因為許多條碼都支援變數大小酬載。一般來說,條碼最小的有意義的單位寬度至少為 2 像素,2D 代碼的高度則至少為 2 像素。
舉例來說,EAN-13 條碼是由寬度為 1、2、3 或 4 個單位的長條和空間組成,因此在 EAN-13 條碼圖片上,理想的長條和空間大小至少要有 2、4、6 和 8 像素。由於 EAN-13 條碼的總寬為 95 個單位,因此條碼的寬度至少要有 190 像素。
PDF417 等假載格式需要較大的像素尺寸,才能讓 ML Kit 穩定讀取。舉例來說,PDF417 程式碼的單一資料列最多可包含 34 個 17 個單位的「words」,理想寬度至少為 1156 像素。
-
圖像對焦品質不佳可能會影響掃描的準確度。如果應用程式無法取得可接受的結果,請要求使用者重新擷取圖片。
-
針對一般應用程式,建議您提供解析度較高的圖片 (例如 1280x720 或 1920x1080),讓從遠離相機遠處的條碼進行掃描。
然而,在對延遲至關重要的應用程式中,您可以採用較低的解析度擷取圖片,藉此改善效能,但由於條碼必須構成輸入圖片大部分的內容。另請參閱「即時效能改善提示」。
1. 設定條碼掃描器
如果您知道自己預期讀取的條碼格式,可以將條碼偵測工具設為僅偵測這些格式,藉此加快條碼偵測工具的速度。舉例來說,如果只要偵測 Aztec code 和 QR code,請建構 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
) - 通用產品代碼 (
FORMAT_UPC_A
) - UPC-E (
FORMAT_UPC_E
) - QR code (
FORMAT_QR_CODE
) - PDF417 (
FORMAT_PDF417
) - 阿茲特克 (
FORMAT_AZTEC
) - 資料矩陣 (
FORMAT_DATA_MATRIX
)
從隨附模型 17.1.0 和未組合模型 18.2.0 開始,您也可以呼叫 enableAllPotentialBarcodes()
來傳回所有可能的條碼,即使無法解碼。這可以用於進一步偵測,例如放大相機,在傳回的定界框中更清楚呈現任何條碼。
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 // ... } } }
如果您未使用相機程式庫提供圖像的旋轉角度,可以將裝置旋轉角度和裝置相機感應器方向做為計算依據:
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
如要從檔案 URI 建立 InputImage
物件,請將應用程式結構定義和檔案 URI 傳遞至 InputImage.fromFilePath()
。使用 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
如要從 ByteBuffer
或 ByteArray
建立 InputImage
物件,請先按照之前的 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
如要從 Bitmap
物件建立 InputImage
物件,請宣告下列宣告:
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
物件清單傳遞至成功事件監聽器。每個 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; } }
即時效能改善訣竅
如要在即時應用程式中掃描條碼,請遵循下列準則,以達到最佳影格速率:
-
請勿以相機原始解析度擷取輸入內容。在某些裝置上,以原生解析度擷取輸入內容會產生極大 (超過 1,000 萬像素) 的圖片,因此可大幅降低延遲,且無法提升準確率。而是只向偵測條碼 (通常不超過 200 萬像素) 的相機要求大小。
如果掃描速度很重要,可以進一步降低圖片拍攝解析度。但請注意,上述的條碼大小下限規定。
如果您嘗試從一系列串流影片影格中辨識條碼,辨識器可能會在影格之間產生不同的結果。您應等到連續取得相同值的一系列結果,才能確信傳回的結果良好。
ITF 和 CODE-39 不支援總和檢查碼。
- 如果使用
Camera
或camera2
API,請調節偵測工具的呼叫頻率。如果在偵測工具執行時有新的影片影格,請捨棄影格。如需範例,請參閱快速入門導覽課程範例應用程式中的VisionProcessorBase
類別。 - 如果使用
CameraX
API,請確定背壓策略已設為其預設值ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
。這麼做可保證系統一次只會傳送一張圖片進行分析。如果分析器忙碌時產生更多圖片,系統會自動捨棄這些圖片,不會排入傳送佇列。呼叫 ImageProxy.close() 關閉要分析的圖片後,就會傳送下一個最新的映像檔。 - 如果使用偵測工具的輸出內容在輸入圖片上重疊圖像,請先從 ML Kit 取得結果,然後透過一個步驟算繪圖片和疊加層。在每個輸入影格中,這個操作只會轉譯一次到顯示介面一次。如需範例,請參閱快速入門導覽課程範例應用程式中的
CameraSourcePreview
和GraphicOverlay
類別。 - 如果你使用 Camera2 API,請擷取
ImageFormat.YUV_420_888
格式的圖片。如果您使用舊版 Camera API,請拍攝ImageFormat.NV21
格式的圖片。
除非另有註明,否則本頁面中的內容是採用創用 CC 姓名標示 4.0 授權,程式碼範例則為阿帕契 2.0 授權。詳情請參閱《Google Developers 網站政策》。Java 是 Oracle 和/或其關聯企業的註冊商標。
上次更新時間:2024-07-12 (世界標準時間)。