您可以使用机器学习套件识别和解码条形码。
特征 | 不分类显示 | 捆绑 |
---|---|---|
实现 | 模型通过 Google Play 服务动态下载。 | 模型在构建时静态关联到您的应用。 |
应用大小 | 大小增加约 200 KB。 | 大小增加约 2.4 MB。 |
初始化时间 | 可能需要等到模型下载完毕后才能首次使用。 | 模型立即可用。 |
试试看
- 请玩转示例应用,查看此 API 的示例用法。
- 如需了解此 API 的端到端实现,请参阅 Material Design 展示应用。
准备工作
请务必在您的项目级
build.gradle
文件中的buildscript
和allprojects
部分添加 Google 的 Maven 制品库。将 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 进行下载。
如果您未启用在安装时下载模型或请求明确下载,模型会在您首次运行扫描程序时下载。您在下载完成之前发出的请求不会产生任何结果。
输入图片准则
-
为了使机器学习套件准确读取条形码,输入图片必须包含由足够像素数据表示的条形码。
具体的像素数据要求取决于条形码的类型和编码数据量,因为许多条形码都支持大小可变的载荷。通常,条形码的最小有效单位应至少为 2 像素宽,对于二维代码,应至少为 2 像素高。
例如,EAN-13 条形码由宽度为 1、2、3 或 4 个单位的条形和空格组成,因此理想情况下,EAN-13 条形码图片应具有宽度至少为 2、4、6 和 8 像素的条形和空格。由于 EAN-13 条形码的总宽度为 95 个单位,因此条形码的宽度应至少为 190 像素。
PDF417 等更密集的格式需要更大的像素尺寸,以便机器学习套件可靠地读取它们。例如,一个 PDF417 代码在一行中最多可以包含 34 个 17 单元宽的“单词”,理想情况下宽度至少为 1156 像素。
-
图片聚焦不良会影响扫描准确性。如果您的应用未获得可接受的结果,请让用户重新拍摄图片。
-
对于典型应用,建议提供分辨率更高的图片(例如 1280x720 或 1920x1080),这样,在离相机较远的距离上就能扫描条形码。
但是,在延迟时间至关重要的应用中,您可以通过以较低分辨率捕获图片来提高性能,但要求条形码必须构成输入图片的主要部分。另请参阅提高实时性能的相关提示。
1. 配置条形码扫描器
如果您知道要读取哪些条形码格式,则可以将条形码检测器配置为仅检测这些格式,从而提高条形码检测器的速度。例如,如需仅检测 Aztec 码和 QR 码,请按照以下示例构建 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
) - 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
) - 二维码 (
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
intent 提示用户从图库应用中选择图片时,这非常有用。
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() 关闭正在分析的图片后,将传送下一张最新图片。 - 如果使用检测器的输出在输入图片上叠加图形,请先从机器学习套件获取结果,然后在一个步骤中渲染该图片并进行叠加。对于每个输入帧,该操作仅会渲染到显示 Surface 一次。如需查看示例,请参阅快速入门示例应用中的
CameraSourcePreview
和GraphicOverlay
类。 - 如果您使用 Camera2 API,请以
ImageFormat.YUV_420_888
格式捕获图片。如果您使用旧版 Camera API,请以ImageFormat.NV21
格式捕获图片。
如未另行说明,那么本页面中的内容已根据知识共享署名 4.0 许可获得了许可,并且代码示例已根据 Apache 2.0 许可获得了许可。有关详情,请参阅 Google 开发者网站政策。Java 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2024-07-12。