您可以使用 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.3.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.1' }
如果您選擇使用 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 個單位寬的「字元」,因此寬度至少應為 1156 像素。
-
圖片對焦不佳可能會影響掃描準確度。如果應用程式無法取得可接受的結果,請要求使用者重新拍攝圖片。
-
對於一般應用程式,建議提供解析度較高的圖片,例如 1280x720 或 1920x1080,這樣就能在較遠的距離掃描條碼。
不過,在延遲時間至關重要的應用程式中,您可以透過以較低解析度擷取圖片的方式來提升效能,但必須要求條碼佔輸入圖片的大部分。另請參閱改善即時效能的訣竅。
1. 設定條碼掃描器
如果您知道要讀取哪些條碼格式,可以將條碼偵測器設為只偵測這些格式,藉此提升速度。舉例來說,如要只偵測 Aztec 代碼和 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();
支援的格式如下:
- Code 128 (
FORMAT_CODE_128
) - Code 39 (
FORMAT_CODE_39
) - 代碼 93 (
FORMAT_CODE_93
) - Codabar (
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
) - QR code (
FORMAT_QR_CODE
) - PDF417 (
FORMAT_PDF417
) - Aztec (
FORMAT_AZTEC
) - Data Matrix (
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; } }
改善即時成效的訣竅
如果您想在即時應用程式中掃描條碼,請遵循下列規範,以獲得最佳的幀率:
-
請勿以攝影機的原生解析度擷取輸入內容。在某些裝置上,以原生解析度擷取輸入內容會產生極大的圖片 (超過 1000 萬像素),導致延遲時間極長,且無法提升準確度。請改為只要求相機提供用於條碼偵測所需的大小,通常不超過 2000 萬像素。
如果掃描速度很重要,您可以進一步降低圖像擷取解析度。不過,請注意上述所述的條碼大小最低要求。
如果您嘗試從一系列串流影片影格中辨識條碼,辨識器可能會產生不同影格結果。請等到連續幾次都取得相同值,才能確保您傳回的結果正確無誤。
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-12-18 (世界標準時間)。