ML Kit를 사용하여 바코드를 인식하고 디코딩할 수 있습니다.
시작하기 전에
- 먼저 Android 프로젝트에 Firebase를 추가합니다.
- 모듈(앱 수준) Gradle 파일(일반적으로
app/build.gradle
)에 ML Kit Android 라이브러리의 종속 항목을 추가합니다.apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' dependencies { // ... implementation 'com.google.firebase:firebase-ml-vision:24.0.3' implementation 'com.google.firebase:firebase-ml-vision-barcode-model:16.0.1' }
입력 이미지 가이드라인
-
ML Kit가 바코드를 정확하게 읽으려면 입력 이미지에 충분한 픽셀 데이터로 표시된 바코드가 있어야 합니다.
대부분의 바코드가 가변 길이 페이로드를 지원하므로 특정 픽셀 데이터 요구사항은 바코드 유형 및 바코드에 인코딩된 데이터 양에 따라 다릅니다. 일반적으로 바코드의 의미 있는 최소 단위는 가로 2픽셀 이상이어야 합니다(2차원 코드의 경우 세로 2픽셀 이상).
예를 들어 EAN-13 바코드는 가로 1, 2, 3 또는 4단위의 바와 공간으로 구성됩니다. 따라서 이상적인 EAN-13 바코드 이미지는 가로 2, 4, 6, 8픽셀 이상의 바와 공간으로 이루어집니다. EAN-13 바코드는 가로가 총 95단위이므로 바코드는 가로 190픽셀 이상이어야 합니다.
PDF417 등의 밀집 형식을 사용하려면 ML Kit에서 확실히 읽을 수 있도록 더 큰 픽셀 크기가 필요합니다. 예를 들어 PDF417 코드는 한 행에 가로 17단위 '단어'를 34개까지 사용할 수 있으므로 가로 1,156 픽셀 이상이어야 합니다.
-
이미지 초점이 잘 맞지 않으면 스캔의 정확도가 저하될 수 있습니다. 허용 가능한 수준의 결과를 얻지 못하는 경우 사용자에게 이미지를 다시 캡처하도록 요청합니다.
-
일반적인 애플리케이션의 경우 카메라로부터 먼 거리에 놓인 바코드도 감지할 수 있도록 더 높은 해상도의 이미지(예: 1280x720 또는 1920x1080)를 제공하는 것이 좋습니다.
그러나 지연 시간이 중요한 요소인 애플리케이션에서는 낮은 해상도로 이미지를 캡처하되 바코드 영역이 입력 이미지의 대부분을 차지하도록 하여 성능을 개선할 수 있습니다 또한 실시간 성능 향상을 위한 팁도 참조하세요.
1. 바코드 인식기 구성
읽으려는 바코드 형식을 알고 있는 경우 해당 형식만 인식하도록 구성하여 바코드 인식기의 속도를 높일 수 있습니다.예를 들어 Aztec 코드와 QR 코드만 인식하려면 다음 예시와 같이 FirebaseVisionBarcodeDetectorOptions
객체를 빌드합니다.
Java
FirebaseVisionBarcodeDetectorOptions options = new FirebaseVisionBarcodeDetectorOptions.Builder() .setBarcodeFormats( FirebaseVisionBarcode.FORMAT_QR_CODE, FirebaseVisionBarcode.FORMAT_AZTEC) .build();
Kotlin
val options = FirebaseVisionBarcodeDetectorOptions.Builder() .setBarcodeFormats( FirebaseVisionBarcode.FORMAT_QR_CODE, FirebaseVisionBarcode.FORMAT_AZTEC) .build()
지원되는 형식은 다음과 같습니다.
- 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 코드(
FORMAT_QR_CODE
) - PDF417(
FORMAT_PDF417
) - Aztec(
FORMAT_AZTEC
) - Data Matrix(
FORMAT_DATA_MATRIX
)
2. 바코드 인식기 실행
이미지 속 바코드를 인식하려면Bitmap
, media.Image
, ByteBuffer
, 바이트 배열, 기기의 파일에서 FirebaseVisionImage
객체를 만듭니다. 그런 다음 FirebaseVisionImage
객체를 FirebaseVisionBarcodeDetector
의 detectInImage
메서드에 전달합니다.
이미지에서
FirebaseVisionImage
객체를 만듭니다.-
기기의 카메라에서 이미지를 캡처할 때와 같이
media.Image
객체에서FirebaseVisionImage
객체를 만들려면media.Image
객체 및 이미지 회전을FirebaseVisionImage.fromMediaImage()
에 전달합니다.CameraX 라이브러리를 사용하는 경우
OnImageCapturedListener
및ImageAnalysis.Analyzer
클래스가 회전 값을 계산하므로FirebaseVisionImage.fromMediaImage()
를 호출하기 전에 ML Kit의ROTATION_
상수 중 하나로 회전을 변환하기만 하면 됩니다.Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { private int degreesToFirebaseRotation(int degrees) { switch (degrees) { case 0: return FirebaseVisionImageMetadata.ROTATION_0; case 90: return FirebaseVisionImageMetadata.ROTATION_90; case 180: return FirebaseVisionImageMetadata.ROTATION_180; case 270: return FirebaseVisionImageMetadata.ROTATION_270; default: throw new IllegalArgumentException( "Rotation must be 0, 90, 180, or 270."); } } @Override public void analyze(ImageProxy imageProxy, int degrees) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); int rotation = degreesToFirebaseRotation(degrees); FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation); // Pass image to an ML Kit Vision API // ... } }
Kotlin
private class YourImageAnalyzer : ImageAnalysis.Analyzer { private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) { 0 -> FirebaseVisionImageMetadata.ROTATION_0 90 -> FirebaseVisionImageMetadata.ROTATION_90 180 -> FirebaseVisionImageMetadata.ROTATION_180 270 -> FirebaseVisionImageMetadata.ROTATION_270 else -> throw Exception("Rotation must be 0, 90, 180, or 270.") } override fun analyze(imageProxy: ImageProxy?, degrees: Int) { val mediaImage = imageProxy?.image val imageRotation = degreesToFirebaseRotation(degrees) if (mediaImage != null) { val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation) // Pass image to an ML Kit Vision API // ... } } }
이미지 회전을 제공하는 카메라 라이브러리를 사용하지 않는 경우 기기의 카메라 센서 방향 및 기기 회전에서 이미지 회전을 계산할 수 있습니다.
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 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, Context context) 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); // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360; // Return the corresponding FirebaseVisionImageMetadata rotation value. int result; switch (rotationCompensation) { case 0: result = FirebaseVisionImageMetadata.ROTATION_0; break; case 90: result = FirebaseVisionImageMetadata.ROTATION_90; break; case 180: result = FirebaseVisionImageMetadata.ROTATION_180; break; case 270: result = FirebaseVisionImageMetadata.ROTATION_270; break; default: result = FirebaseVisionImageMetadata.ROTATION_0; Log.e(TAG, "Bad rotation value: " + rotationCompensation); } return result; }
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * 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, context: Context): 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) // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360 // Return the corresponding FirebaseVisionImageMetadata rotation value. val result: Int when (rotationCompensation) { 0 -> result = FirebaseVisionImageMetadata.ROTATION_0 90 -> result = FirebaseVisionImageMetadata.ROTATION_90 180 -> result = FirebaseVisionImageMetadata.ROTATION_180 270 -> result = FirebaseVisionImageMetadata.ROTATION_270 else -> { result = FirebaseVisionImageMetadata.ROTATION_0 Log.e(TAG, "Bad rotation value: $rotationCompensation") } } return result }
그런 다음
media.Image
객체 및 회전 값을FirebaseVisionImage.fromMediaImage()
에 전달합니다.Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- 파일 URI에서
FirebaseVisionImage
객체를 만들려면 앱 컨텍스트 및 파일 URI를FirebaseVisionImage.fromFilePath()
에 전달합니다.ACTION_GET_CONTENT
인텐트를 사용하여 사용자에게 갤러리 앱에서 이미지를 선택하라는 메시지를 표시할 때 유용한 방법입니다.Java
FirebaseVisionImage image; try { image = FirebaseVisionImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Kotlin
val image: FirebaseVisionImage try { image = FirebaseVisionImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
ByteBuffer
또는 바이트 배열에서FirebaseVisionImage
객체를 만들려면 먼저 위에서 설명한 대로media.Image
입력의 이미지 회전을 계산합니다.그런 다음 이미지의 높이, 너비, 색상 인코딩 형식, 회전이 포함된
FirebaseVisionImageMetadata
객체를 만듭니다.Java
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build();
Kotlin
val metadata = FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build()
버퍼나 배열, 메타데이터 객체를 사용하여
FirebaseVisionImage
객체를 만듭니다.Java
FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata); // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
Kotlin
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
Bitmap
객체에서FirebaseVisionImage
객체를 만들려면 다음 안내를 따르세요.Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
객체로 표현된 이미지가 추가 회전이 필요 없는 수직 상태여야 합니다.
-
FirebaseVisionBarcodeDetector
의 인스턴스를 가져옵니다.Java
FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance() .getVisionBarcodeDetector(); // Or, to specify the formats to recognize: // FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance() // .getVisionBarcodeDetector(options);
Kotlin
val detector = FirebaseVision.getInstance() .visionBarcodeDetector // Or, to specify the formats to recognize: // val detector = FirebaseVision.getInstance() // .getVisionBarcodeDetector(options)
마지막으로 이미지를
detectInImage
메서드에 전달합니다.Java
Task<List<FirebaseVisionBarcode>> result = detector.detectInImage(image) .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() { @Override public void onSuccess(List<FirebaseVisionBarcode> barcodes) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Kotlin
val result = detector.detectInImage(image) .addOnSuccessListener { barcodes -> // Task completed successfully // ... } .addOnFailureListener { // Task failed with an exception // ... }
3. 바코드에서 정보 가져오기
바코드 인식 작업이 성공하면FirebaseVisionBarcode
객체의 목록이 성공 리스너에 전달됩니다. 각 FirebaseVisionBarcode
객체는 이미지에서 인식된 바코드를 나타냅니다. 바코드별로 입력 이미지의 경계 좌표 및 바코드로 인코딩된 원시 데이터를 가져올 수 있습니다. 또한 바코드 인식기가 바코드로 인코딩된 데이터 유형을 결정할 수 있는 경우,
파싱된 데이터가 포함된 객체를 가져올 수 있습니다.
예를 들면 다음과 같습니다.
Java
for (FirebaseVisionBarcode 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 FirebaseVisionBarcode.TYPE_WIFI: String ssid = barcode.getWifi().getSsid(); String password = barcode.getWifi().getPassword(); int type = barcode.getWifi().getEncryptionType(); break; case FirebaseVisionBarcode.TYPE_URL: String title = barcode.getUrl().getTitle(); String url = barcode.getUrl().getUrl(); break; } }
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) { FirebaseVisionBarcode.TYPE_WIFI -> { val ssid = barcode.wifi!!.ssid val password = barcode.wifi!!.password val type = barcode.wifi!!.encryptionType } FirebaseVisionBarcode.TYPE_URL -> { val title = barcode.url!!.title val url = barcode.url!!.url } } }
실시간 성능 향상을 위한 팁
실시간 애플리케이션에서 바코드를 스캔하려는 경우 최상의 프레임 속도를 얻으려면 다음 안내를 따르세요.
-
카메라의 기본 해상도로 입력을 캡처하지 마세요. 기기에 따라 기본 해상도로 입력을 캡처할 경우 매우 큰(10메가픽셀 이상) 이미지가 생성되므로 정확성 측면에서 아무런 효과 없이 지연 시간만 길어질 수 있습니다. 대신 카메라에서 바코드를 감지하는 데 필요한 크기만 요청하세요. 이 크기는 일반적으로 2메가픽셀 이하입니다.
스캔 속도가 중요한 경우에는 이미지 캡처 해상도를 더 낮추면 됩니다. 단, 위에서 설명한 바코드 크기 최소 요구사항에 유의해야 합니다.
- 인식기 호출을 제한합니다. 인식기가 실행 중일 때 새 동영상 프레임이 제공되는 경우 해당 프레임을 삭제합니다.
- 인식기 출력을 사용해서 입력 이미지에서 그래픽을 오버레이하는 경우 먼저 ML Kit에서 결과를 가져온 후 이미지를 렌더링하고 단일 단계로 오버레이합니다. 이렇게 하면 입력 프레임별로 한 번만 디스플레이 표면에 렌더링됩니다.
-
Camera2 API를 사용할 경우
ImageFormat.YUV_420_888
형식으로 이미지를 캡처합니다.이전 Camera API를 사용하는 경우
ImageFormat.NV21
형식으로 이미지를 캡처합니다.