Android에서 ML Kit를 사용하여 랜드마크 인식

ML Kit를 사용하면 이미지 속 유명 랜드마크를 인식할 수 있습니다.

이 API의 사용 예시는 GitHub의 ML Kit 빠른 시작 샘플을 참조하세요.

시작하기 전에

  1. 아직 추가하지 않았다면 Android 프로젝트에 Firebase를 추가합니다.
  2. 프로젝트 수준 build.gradle 파일의 buildscriptallprojects 섹션에 Google의 Maven 저장소가 포함되어야 합니다.
  3. 모듈(앱 수준) Gradle 파일(일반적으로 app/build.gradle)에 ML Kit Android 라이브러리의 종속 항목을 추가합니다.
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.0'
    }
    apply plugin: 'com.google.gms.google-services'
    
  4. 프로젝트에 클라우드 기반 API를 아직 사용 설정하지 않았으면 지금 설정하세요.

    1. Firebase Console의 ML Kit API 페이지를 엽니다.
    2. 프로젝트를 Blaze 요금제로 아직 업그레이드하지 않은 경우 업그레이드를 클릭하여 업그레이드하세요. 프로젝트가 Blaze 요금제가 아닌 경우에만 업그레이드하라는 메시지가 표시됩니다.

      Blaze 수준 프로젝트만 Cloud 기반 API를 사용할 수 있습니다.

    3. Cloud 기반 API가 아직 사용 설정되지 않은 경우 Cloud 기반 API 사용 설정을 클릭합니다.

랜드마크 감지기 구성

기본적으로 클라우드 감지기에서는 모델의 STABLE 버전을 사용하여 최대 10개의 결과를 반환합니다. 이러한 설정을 변경하려면 FirebaseVisionCloudDetectorOptions 객체를 사용하여 설정을 지정합니다.

예를 들어 두 기본 설정을 모두 변경하려면 다음의 예와 같이 FirebaseVisionCloudDetectorOptions 객체를 빌드합니다.

자바

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를 사용하면 됩니다.

랜드마크 감지기 실행

이미지 속 랜드마크를 인식하려면 FirebaseVisionImage 객체를 Bitmap, media.Image, ByteBuffer, 바이트 배열 또는 기기의 파일에서 만듭니다. 그런 다음 FirebaseVisionImage 객체를 FirebaseVisionCloudLandmarkDetectordetectInImage 메소드에 전달합니다.

  1. 이미지에서 FirebaseVisionImage 객체를 만듭니다.

    • 기기의 카메라에서 이미지를 캡처할 때와 같이 FirebaseVisionImage 객체를 media.Image 객체에서 만들려면 media.Image 객체 및 이미지의 회전을 FirebaseVisionImage.fromMediaImage()에 전달합니다.

      CameraX 라이브러리를 사용하는 경우 OnImageCapturedListenerImageAnalysis.Analyzer 클래스가 회전 값을 계산하므로 FirebaseVisionImage.fromMediaImage()를 호출하기 전에 ML Kit의 ROTATION_ 상수 중 하나로 변환하기만 하면 됩니다.

      자바

      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
                  // ...
              }
          }
      }
      

      이미지 회전을 제공하는 카메라 라이브러리를 사용하지 않는 경우 기기의 카메라 센서 방향 및 기기 회전에서 이미지 회전을 계산할 수 있습니다.

      자바

      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()에 전달합니다.

      자바

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      Kotlin

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • 파일 URI에서 FirebaseVisionImage 객체를 만들려면 앱 컨텍스트 및 파일 URI를 FirebaseVisionImage.fromFilePath()에 전달합니다. ACTION_GET_CONTENT 인텐트를 사용하여 사용자에게 갤러리 앱에서 이미지를 선택하라는 메시지를 표시할 때 유용한 방법입니다.

      자바

      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 객체를 만듭니다.

      자바

      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 객체를 만듭니다.

      자바

      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 객체를 만들려면 다음 안내를 따르세요.

      자바

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Bitmap 객체로 표현된 이미지는 추가 회전이 필요 없는 수직 상태여야 합니다.

  2. FirebaseVisionCloudLandmarkDetector의 인스턴스를 가져옵니다.

    자바

    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)
  3. 마지막으로 이미지를 detectInImage 메소드에 전달합니다.

    자바

    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 (가능한 경우), 일치 신뢰도 점수를 가져올 수 있습니다. 예를 들면 다음과 같습니다.

자바

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
    }
}

다음 단계