You can use ML Kit to recognize and decode barcodes.
Feature | Unbundled | Bundled |
---|---|---|
Implementation
|
Model is dynamically downloaded via Google Play Services. | Model is statically linked to your app at build time. |
App size
|
About 200 KB size increase. | About 2.4 MB size increase. |
Initialization time
|
Might have to wait for model to download before first use. | Model is available immediately. |
Try it out
- Play around with the sample app to see an example usage of this API.
- See the Material Design showcase app for an end-to-end implementation of this API.
Before you begin
-
In your project-level
build.gradle
file, make sure to include Google's Maven repository in both yourbuildscript
andallprojects
sections. -
Add the dependencies for the ML Kit Android libraries to your module's app-level gradle file, which is usually
app/build.gradle
. Choose one of the following dependencies based on your needs:For bundling the model with your app:
dependencies { // ... // Use this dependency to bundle the model with your app implementation ' com . google . mlkit : barcode - scanning : 17.3.0 ' }
For using the model in Google Play Services:
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' }
-
If you choose to use the model in Google Play Services, you can configure your app to automatically download the model to the device after your app is installed from the Play Store. To do so, add the following declaration to your app's
AndroidManifest.xml
file:< application ... > ... < meta - data android : name = "com.google.mlkit.vision.DEPENDENCIES" android : value = "barcode" > < ! -- To use multiple models : android : value = "barcode,model2,model3" -- > < / application >
You can also explicitly check the model availability and request download through Google Play services ModuleInstallClient API .
If you don't enable install-time model downloads or request explicit download, the model is downloaded the first time you run the scanner. Requests you make before the download has completed produce no results.
Input image guidelines
-
For ML Kit to accurately read barcodes, input images must contain barcodes that are represented by sufficient pixel data.
The specific pixel data requirements are dependent on both the type of barcode and the amount of data that's encoded in it, since many barcodes support a variable size payload. In general, the smallest meaningful unit of the barcode should be at least 2 pixels wide, and for 2-dimensional codes, 2 pixels tall.
For example, EAN-13 barcodes are made up of bars and spaces that are 1, 2, 3, or 4 units wide, so an EAN-13 barcode image ideally has bars and spaces that are at least 2, 4, 6, and 8 pixels wide. Because an EAN-13 barcode is 95 units wide in total, the barcode should be at least 190 pixels wide.
Denser formats, such as PDF417, need greater pixel dimensions for ML Kit to reliably read them. For example, a PDF417 code can have up to 34 17-unit wide "words" in a single row, which would ideally be at least 1156 pixels wide.
-
Poor image focus can impact scanning accuracy. If your app isn't getting acceptable results, ask the user to recapture the image.
-
For typical applications, it's recommended to provide a higher resolution image, such as 1280x720 or 1920x1080, which makes barcodes scannable from a larger distance away from the camera.
However, in applications where latency is critical, you can improve performance by capturing images at a lower resolution, but requiring that the barcode make up the majority of the input image. Also see Tips to improve real-time performance .
1. Configure the barcode scanner
If you know which barcode formats you expect to read, you can improve the speed of the barcode detector by configuring it to only detect those formats.For example, to detect only Aztec code and QR codes, build a BarcodeScannerOptions
object as in the following example:
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 ();
The following formats are supported:
- Code 128 (
FORMAT_CODE_128
) - Code 39 (
FORMAT_CODE_39
) - Code 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
)
Starting from bundled model 17.1.0 and unbundled model 18.2.0, you can also call enableAllPotentialBarcodes()
to return all potential barcodes even if they
cannot be decoded. This can be used to facilitate further detection, for example
by zooming in the camera to get a clearer image of any barcode in the returned
bounding box.
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 an InputImage
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 // ... } } }
If you don't use a camera library that gives you the image's rotation degree, you can calculate it from the device's rotation degree and the orientation of camera sensor in the device:
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 ; }
Then, pass the media.Image
object and the
rotation degree value to InputImage.fromMediaImage()
:
Kotlin
val image = InputImage . fromMediaImage ( mediaImage , rotation )
Java
InputImage image = InputImage . fromMediaImage ( mediaImage , rotation );
Using a file URI
To create an InputImage
object from a file URI, pass the app context and file URI to InputImage.fromFilePath()
. This is useful when you
use an ACTION_GET_CONTENT
intent to prompt the user to select
an image from their gallery app.
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 (); }
Using a ByteBuffer
or ByteArray
To create an InputImage
object from a ByteBuffer
or a ByteArray
, first calculate the image
rotation degree as previously described for media.Image
input.
Then, create the InputImage
object with the buffer or array, together with image's
height, width, color encoding format, and rotation degree:
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 );
Using a Bitmap
To create an InputImage
object from a Bitmap
object, make the following declaration:
Kotlin
val image = InputImage . fromBitmap ( bitmap , 0 )
Java
InputImage image = InputImage . fromBitmap ( bitmap , rotationDegree );
The image is represented by a Bitmap
object together with rotation degrees.
3. Get an instance of BarcodeScanner
Kotlin
val scanner = BarcodeScanning . getClient () // Or, to specify the formats to recognize: // val scanner = BarcodeScanning.getClient(options)