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' }
-
프로젝트에 클라우드 기반 API를 아직 사용 설정하지 않았으면 지금 설정하세요.
- Firebase Console의 ML Kit API 페이지를 엽니다.
-
프로젝트를 Blaze 요금제로 아직 업그레이드하지 않은 경우 업그레이드를 클릭하여 업그레이드하세요. 프로젝트가 Blaze 요금제가 아닌 경우에만 업그레이드하라는 메시지가 표시됩니다.
Blaze 수준 프로젝트만 클라우드 기반 API를 사용할 수 있습니다.
- 클라우드 기반 API가 아직 사용 설정되지 않은 경우 클라우드 기반 API 사용 설정을 클릭합니다.
랜드마크 감지기 구성
기본적으로 Cloud 감지기에서는 모델의 STABLE
버전을 사용하여 최대 10개의 결과를 반환합니다. 이러한 설정을 변경하려면 FirebaseVisionCloudDetectorOptions
객체를 사용하여 지정합니다.
예를 들어 두 기본 설정을 모두 변경하려면 다음의 예시와 같이 FirebaseVisionCloudDetectorOptions
객체를 빌드합니다.
Java
FirebaseVisionCloudDetectorOptions options = new FirebaseVisionCloudDetectorOptions.Builder() .setModelType(FirebaseVisionCloudDetectorOptions.LATEST_MODEL) .setMaxResults(15) .build();
Kotlin
val options = FirebaseVisionCloudDetectorOptions.Builder() .setModelType(FirebaseVisionCloudDetectorOptions.LATEST_MODEL) .setMaxResults(15) .build()
기본 설정을 사용하려면 다음 단계에 나오는 FirebaseVisionCloudDetectorOptions.DEFAULT
를 사용하면 됩니다.
랜드마크 감지기 실행
이미지 속 랜드마크를 인식하려면Bitmap
, media.Image
, ByteBuffer
, 바이트 배열, 기기의 파일에서 FirebaseVisionImage
객체를 만듭니다. 그런 다음 FirebaseVisionImage
객체를 FirebaseVisionCloudLandmarkDetector
의 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
객체로 표현된 이미지가 추가 회전이 필요 없는 수직 상태여야 합니다.
-
FirebaseVisionCloudLandmarkDetector
의 인스턴스를 가져옵니다.Java
FirebaseVisionCloudLandmarkDetector detector = FirebaseVision.getInstance() .getVisionCloudLandmarkDetector(); // Or, to change the default settings: // FirebaseVisionCloudLandmarkDetector detector = FirebaseVision.getInstance() // .getVisionCloudLandmarkDetector(options);
Kotlin
val detector = FirebaseVision.getInstance() .visionCloudLandmarkDetector // Or, to change the default settings: // val detector = FirebaseVision.getInstance() // .getVisionCloudLandmarkDetector(options)
마지막으로 이미지를
detectInImage
메서드에 전달합니다.Java
Task<List<FirebaseVisionCloudLandmark>> result = detector.detectInImage(image) .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionCloudLandmark>>() { @Override public void onSuccess(List<FirebaseVisionCloudLandmark> firebaseVisionCloudLandmarks) { // 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 { firebaseVisionCloudLandmarks -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
인식된 랜드마크 정보 가져오기
랜드마크 인식 작업이 성공하면FirebaseVisionCloudLandmark
객체의 목록이 성공 리스너에 전달됩니다. 각 FirebaseVisionCloudLandmark
객체는 이미지에서 인식된 랜드마크를 나타냅니다. 랜드마크별로 입력 이미지의 경계 좌표, 랜드마크의 이름, 위도 및 경도, 지식 그래프 항목 ID(해당하는 경우), 일치 신뢰도 점수를 가져올 수 있습니다. 예를 들면 다음과 같습니다.
Java
for (FirebaseVisionCloudLandmark landmark: firebaseVisionCloudLandmarks) { Rect bounds = landmark.getBoundingBox(); String landmarkName = landmark.getLandmark(); String entityId = landmark.getEntityId(); float confidence = landmark.getConfidence(); // Multiple locations are possible, e.g., the location of the depicted // landmark and the location the picture was taken. for (FirebaseVisionLatLng loc: landmark.getLocations()) { double latitude = loc.getLatitude(); double longitude = loc.getLongitude(); } }
Kotlin
for (landmark in firebaseVisionCloudLandmarks) { val bounds = landmark.boundingBox val landmarkName = landmark.landmark val entityId = landmark.entityId val confidence = landmark.confidence // Multiple locations are possible, e.g., the location of the depicted // landmark and the location the picture was taken. for (loc in landmark.locations) { val latitude = loc.latitude val longitude = loc.longitude } }
다음 단계
- Cloud API를 사용하는 앱을 프로덕션 환경에 배포하기 전에 몇 가지 추가 단계를 수행하여 승인되지 않은 API 액세스를 방지하고 그로 인한 영향을 완화합니다.