التعرّف على النص في الصور باستخدام حزمة تعلُّم الآلة على Android

يمكنك استخدام ML Kit للتعرّف على النص في الصور. تتضمّن حزمة تعلُّم الآلة واجهة برمجة تطبيقات عامة الأغراض مناسبة للتعرّف على النص في الصور، مثل نص لوحة إرشادية في الشارع، وواجهة برمجة تطبيقات محسَّنة للتعرّف على نص المستندات. تتضمّن واجهة برمجة التطبيقات للأغراض العامة نماذج على الجهاز ونماذج مستندة إلى السحابة الإلكترونية. لا يتوفّر التعرّف على نص المستند إلا كنموذج مستند إلى السحابة الإلكترونية. يمكنك الاطّلاع على النظرة العامة لمقارنة النماذج المستندة إلى السحابة الإلكترونية والنماذج التي تعمل على الجهاز.

قبل البدء

  1. أضِف Firebase إلى مشروع Android الخاص بك، في حال لم يسبق لك إجراء ذلك.
  2. أضِف العناصر التابعة لمكتبات ML Kit على Android إلى ملف Gradle الخاص بالوحدة (على مستوى التطبيق) (عادةً app/build.gradle):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
    }
  3. اختياري ولكن يُنصح به: إذا كنت تستخدم واجهة برمجة التطبيقات التي تعمل على الجهاز، عليك إعداد تطبيقك لتنزيل نموذج تعلُّم الآلة تلقائيًا على الجهاز بعد تثبيت تطبيقك من "متجر Play".

    لإجراء ذلك، أضِف البيان التالي إلى ملف AndroidManifest.xml في تطبيقك:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="ocr" />
      <!-- To use multiple models: android:value="ocr,model2,model3" -->
    </application>
    في حال عدم تفعيل تنزيل النماذج أثناء التثبيت، سيتم تنزيل النموذج في المرة الأولى التي تشغّل فيها أداة الرصد على الجهاز. لن تؤدي الطلبات التي تقدّمها قبل اكتمال التنزيل إلى ظهور أي نتائج.
  4. إذا كنت تريد استخدام النموذج المستند إلى السحابة الإلكترونية ولم يسبق لك تفعيل واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية لمشروعك، عليك إجراء ذلك الآن باتّباع الخطوات التالية:

    1. افتح صفحة واجهات برمجة التطبيقات في ML Kit ضمن Firebase.
    2. إذا لم يسبق لك ترقية مشروعك إلى خطة أسعار Blaze، انقر على ترقية لإجراء ذلك. (لن يُطلب منك الترقية إلا إذا لم يكن مشروعك مشتركًا في خطة Blaze).

      يمكن للمشاريع على مستوى Blaze فقط استخدام واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية.

    3. إذا لم تكن واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية مفعّلة، انقر على تفعيل واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية.

    يمكنك تخطّي هذه الخطوة إذا كنت تريد استخدام النموذج على الجهاز فقط.

أنت الآن جاهز لبدء التعرّف على النص في الصور.

إرشادات حول الصور المدخَلة

  • لكي يتعرّف ML Kit على النص بدقة، يجب أن تحتوي الصور المدخلة على نص يمثّله عدد كافٍ من وحدات البكسل. من المفترض أن يكون حجم كل حرف 16×16 بكسل على الأقل بالنسبة إلى النصوص اللاتينية. بالنسبة إلى النصوص الصينية واليابانية والكورية (التي تتوافق فقط مع واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية)، يجب أن يكون حجم كل حرف 24 × 24 بكسل. وبالنسبة إلى جميع اللغات، لا تتحسّن الدقة بشكل عام إذا كان حجم الأحرف أكبر من 24 × 24 بكسل.

    على سبيل المثال، قد تكون صورة بحجم 640x480 مناسبة لمسح بطاقة عمل ضوئيًا تشغل العرض الكامل للصورة. لمسح مستند ضوئيًا مطبوع على ورق بحجم Letter، قد تحتاج إلى صورة بحجم 720x1280 بكسل.

  • يمكن أن يؤدي عدم وضوح الصورة إلى انخفاض دقة التعرّف على النص. إذا لم تحصل على نتائج مقبولة، اطلب من المستخدم إعادة التقاط الصورة.

  • إذا كنت تستخدم تطبيقًا يتعرّف على النص في الوقت الفعلي، قد تحتاج أيضًا إلى مراعاة الأبعاد الإجمالية للصور المدخلة. يمكن معالجة الصور الأصغر حجمًا بشكل أسرع، لذا لتقليل وقت الاستجابة، التقط الصور بدقة أقل (مع مراعاة متطلبات الدقة المذكورة أعلاه) وتأكَّد من أنّ النص يشغل أكبر قدر ممكن من الصورة. يمكنك أيضًا الاطّلاع على نصائح لتحسين الأداء في الوقت الفعلي.


التعرّف على النص في الصور

للتعرّف على نص في صورة باستخدام نموذج على الجهاز أو نموذج مستند إلى السحابة الإلكترونية، شغِّل أداة التعرّف على النص كما هو موضّح أدناه.

1. تشغيل أداة التعرّف على النص

للتعرّف على نص في صورة، أنشئ عنصر FirebaseVisionImage من Bitmap أو media.Image أو ByteBuffer أو مصفوفة بايت أو ملف على الجهاز. بعد ذلك، مرِّر الكائن FirebaseVisionImage إلى الطريقة processImage الخاصة بالكائن FirebaseVisionTextRecognizer.

  1. أنشئ عنصر FirebaseVisionImage من صورتك.

    • لإنشاء عنصر FirebaseVisionImage من عنصر media.Image، مثلاً عند التقاط صورة من كاميرا جهاز، مرِّر عنصر media.Image وزاوية دوران الصورة إلى FirebaseVisionImage.fromMediaImage().

      إذا كنت تستخدم مكتبة CameraX، سيحسب لك الفئتان OnImageCapturedListener وImageAnalysis.Analyzer قيمة الدوران، لذلك ما عليك سوى تحويل الدوران إلى أحد الثوابت ROTATION_ في ML Kit قبل استدعاء FirebaseVisionImage.fromMediaImage():

      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)
    • لإنشاء عنصر FirebaseVisionImage من معرّف URI لملف، مرِّر سياق التطبيق ومعرّف URI للملف إلى FirebaseVisionImage.fromFilePath(). ويكون ذلك مفيدًا عند استخدام ACTION_GET_CONTENT intent لطلب أن يختار المستخدم صورة من تطبيق المعرض.

      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()
      }
    • لإنشاء عنصر FirebaseVisionImage من ByteBuffer أو مصفوفة بايت، عليك أولاً حساب درجة دوران الصورة كما هو موضّح أعلاه بالنسبة إلى الإدخال 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)
    • لإنشاء كائن FirebaseVisionImage من كائن Bitmap، اتّبِع الخطوات التالية:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      يجب أن تكون الصورة الممثّلة بالكائن Bitmap في الوضع العمودي، بدون الحاجة إلى تدوير إضافي.

  2. احصل على مثيل من FirebaseVisionTextRecognizer.

    لاستخدام النموذج على الجهاز، اتّبِع الخطوات التالية:

    Java

    FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance()
            .getOnDeviceTextRecognizer();

    Kotlin

    val detector = FirebaseVision.getInstance()
            .onDeviceTextRecognizer

    لاستخدام النموذج المستند إلى السحابة الإلكترونية، اتّبِع الخطوات التالية:

    Java

    FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance()
            .getCloudTextRecognizer();
    // Or, to change the default settings:
    //   FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance()
    //          .getCloudTextRecognizer(options);
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FirebaseVisionCloudTextRecognizerOptions options = new FirebaseVisionCloudTextRecognizerOptions.Builder()
            .setLanguageHints(Arrays.asList("en", "hi"))
            .build();

    Kotlin

    val detector = FirebaseVision.getInstance().cloudTextRecognizer
    // Or, to change the default settings:
    // val detector = FirebaseVision.getInstance().getCloudTextRecognizer(options)
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    val options = FirebaseVisionCloudTextRecognizerOptions.Builder()
            .setLanguageHints(listOf("en", "hi"))
            .build()
  3. أخيرًا، مرِّر الصورة إلى الطريقة processImage:

    Java

    Task<FirebaseVisionText> result =
            detector.processImage(image)
                    .addOnSuccessListener(new OnSuccessListener<FirebaseVisionText>() {
                        @Override
                        public void onSuccess(FirebaseVisionText firebaseVisionText) {
                            // Task completed successfully
                            // ...
                        }
                    })
                    .addOnFailureListener(
                            new OnFailureListener() {
                                @Override
                                public void onFailure(@NonNull Exception e) {
                                    // Task failed with an exception
                                    // ...
                                }
                            });

    Kotlin

    val result = detector.processImage(image)
            .addOnSuccessListener { firebaseVisionText ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }

2. استخراج النص من كتل النص التي تم التعرّف عليها

في حال نجاح عملية التعرّف على النص، سيتم تمرير عنصر FirebaseVisionText إلى أداة معالجة النجاح. يحتوي كائن FirebaseVisionText على النص الكامل الذي تم التعرّف عليه في الصورة، بالإضافة إلى صفر أو أكثر من كائنات TextBlock.

يمثّل كل TextBlock كتلة نصية مستطيلة الشكل تحتوي على صفر أو أكثر من عناصر Line. يحتوي كل كائن Line على صفر أو أكثر من كائنات Element التي تمثّل الكلمات والكيانات الشبيهة بالكلمات (التواريخ والأرقام وما إلى ذلك).

بالنسبة إلى كل كائن TextBlock وLine وElement، يمكنك الحصول على النص الذي تم التعرّف عليه في المنطقة وإحداثيات حدود المنطقة.

على سبيل المثال:

Java

String resultText = result.getText();
for (FirebaseVisionText.TextBlock block: result.getTextBlocks()) {
    String blockText = block.getText();
    Float blockConfidence = block.getConfidence();
    List<RecognizedLanguage> blockLanguages = block.getRecognizedLanguages();
    Point[] blockCornerPoints = block.getCornerPoints();
    Rect blockFrame = block.getBoundingBox();
    for (FirebaseVisionText.Line line: block.getLines()) {
        String lineText = line.getText();
        Float lineConfidence = line.getConfidence();
        List<RecognizedLanguage> lineLanguages = line.getRecognizedLanguages();
        Point[] lineCornerPoints = line.getCornerPoints();
        Rect lineFrame = line.getBoundingBox();
        for (FirebaseVisionText.Element element: line.getElements()) {
            String elementText = element.getText();
            Float elementConfidence = element.getConfidence();
            List<RecognizedLanguage> elementLanguages = element.getRecognizedLanguages();
            Point[] elementCornerPoints = element.getCornerPoints();
            Rect elementFrame = element.getBoundingBox();
        }
    }
}

Kotlin

val resultText = result.text
for (block in result.textBlocks) {
    val blockText = block.text
    val blockConfidence = block.confidence
    val blockLanguages = block.recognizedLanguages
    val blockCornerPoints = block.cornerPoints
    val blockFrame = block.boundingBox
    for (line in block.lines) {
        val lineText = line.text
        val lineConfidence = line.confidence
        val lineLanguages = line.recognizedLanguages
        val lineCornerPoints = line.cornerPoints
        val lineFrame = line.boundingBox
        for (element in line.elements) {
            val elementText = element.text
            val elementConfidence = element.confidence
            val elementLanguages = element.recognizedLanguages
            val elementCornerPoints = element.cornerPoints
            val elementFrame = element.boundingBox
        }
    }
}

نصائح لتحسين الأداء في الوقت الفعلي

إذا كنت تريد استخدام النموذج على الجهاز للتعرّف على النص في تطبيق يعمل في الوقت الفعلي، اتّبِع الإرشادات التالية لتحقيق أفضل معدّلات عرض اللقطات:

  • تقليل عدد طلبات التعرّف على النص إذا توفّر إطار فيديو جديد أثناء تشغيل أداة التعرّف على النصوص، يجب تجاهل الإطار.
  • إذا كنت تستخدم ناتج أداة التعرّف على النصوص لتراكب الرسومات على صورة الإدخال، احصل أولاً على النتيجة من &quot;حزمة تعلُّم الآلة&quot;، ثم اعرض الصورة والتراكب في خطوة واحدة. وبذلك، يتم العرض على مساحة العرض مرة واحدة فقط لكل إطار إدخال.
  • إذا كنت تستخدم Camera2 API، التقط الصور بتنسيق ImageFormat.YUV_420_888.

    إذا كنت تستخدم الإصدار القديم من Camera API، التقط الصور بتنسيق ImageFormat.NV21.

  • ننصحك بالتقاط الصور بدقة أقل. ومع ذلك، يجب أيضًا مراعاة متطلبات أبعاد الصورة في واجهة برمجة التطبيقات هذه.

الخطوات التالية


التعرّف على النص في صور المستندات

للتعرّف على نص مستند، اضبط مشغّل التعرّف على نص المستند المستند إلى السحابة الإلكترونية وشغّله كما هو موضّح أدناه.

توفّر واجهة برمجة التطبيقات للتعرّف على نص المستندات، الموضّحة أدناه، واجهة تهدف إلى تسهيل التعامل مع صور المستندات. ومع ذلك، إذا كنت تفضّل الواجهة التي توفّرها واجهة برمجة التطبيقات FirebaseVisionTextRecognizer، يمكنك استخدامها بدلاً من ذلك لفحص المستندات من خلال ضبط أداة التعرّف على النص في السحابة الإلكترونية على استخدام نموذج النص الكثيف.

لاستخدام واجهة برمجة التطبيقات للتعرّف على نص المستند، اتّبِع الخطوات التالية:

1. تشغيل أداة التعرّف على النص

للتعرّف على نص في صورة، أنشئ عنصر FirebaseVisionImage من Bitmap أو media.Image أو ByteBuffer أو مصفوفة بايت أو ملف على الجهاز. بعد ذلك، مرِّر الكائن FirebaseVisionImage إلى الطريقة processImage الخاصة بالكائن FirebaseVisionDocumentTextRecognizer.

  1. أنشئ عنصر FirebaseVisionImage من صورتك.

    • لإنشاء عنصر FirebaseVisionImage من عنصر media.Image، مثلاً عند التقاط صورة من كاميرا جهاز، مرِّر عنصر media.Image وزاوية دوران الصورة إلى FirebaseVisionImage.fromMediaImage().

      إذا كنت تستخدم مكتبة CameraX، سيحسب لك الفئتان OnImageCapturedListener وImageAnalysis.Analyzer قيمة الدوران، لذلك ما عليك سوى تحويل الدوران إلى أحد الثوابت ROTATION_ في ML Kit قبل استدعاء FirebaseVisionImage.fromMediaImage():

      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)
    • لإنشاء عنصر FirebaseVisionImage من معرّف URI لملف، مرِّر سياق التطبيق ومعرّف URI للملف إلى FirebaseVisionImage.fromFilePath(). ويكون ذلك مفيدًا عند استخدام ACTION_GET_CONTENT intent لطلب أن يختار المستخدم صورة من تطبيق المعرض.

      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()
      }
    • لإنشاء عنصر FirebaseVisionImage من ByteBuffer أو مصفوفة بايت، عليك أولاً حساب درجة دوران الصورة كما هو موضّح أعلاه بالنسبة إلى الإدخال 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)
    • لإنشاء كائن FirebaseVisionImage من كائن Bitmap، اتّبِع الخطوات التالية:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      يجب أن تكون الصورة الممثّلة بالكائن Bitmap في الوضع العمودي، بدون الحاجة إلى تدوير إضافي.

  2. الحصول على مثيل من FirebaseVisionDocumentTextRecognizer:

    Java

    FirebaseVisionDocumentTextRecognizer detector = FirebaseVision.getInstance()
            .getCloudDocumentTextRecognizer();
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FirebaseVisionCloudDocumentRecognizerOptions options =
            new FirebaseVisionCloudDocumentRecognizerOptions.Builder()
                    .setLanguageHints(Arrays.asList("en", "hi"))
                    .build();
    FirebaseVisionDocumentTextRecognizer detector = FirebaseVision.getInstance()
            .getCloudDocumentTextRecognizer(options);

    Kotlin

    val detector = FirebaseVision.getInstance()
            .cloudDocumentTextRecognizer
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    val options = FirebaseVisionCloudDocumentRecognizerOptions.Builder()
            .setLanguageHints(listOf("en", "hi"))
            .build()
    val detector = FirebaseVision.getInstance()
            .getCloudDocumentTextRecognizer(options)

  3. أخيرًا، مرِّر الصورة إلى الطريقة processImage:

    Java

    detector.processImage(myImage)
            .addOnSuccessListener(new OnSuccessListener<FirebaseVisionDocumentText>() {
                @Override
                public void onSuccess(FirebaseVisionDocumentText result) {
                    // Task completed successfully
                    // ...
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Task failed with an exception
                    // ...
                }
            });

    Kotlin

    detector.processImage(myImage)
            .addOnSuccessListener { firebaseVisionDocumentText ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }

2. استخراج النص من كتل النص التي تم التعرّف عليها

في حال نجاح عملية التعرّف على النص، سيتم عرض عنصر FirebaseVisionDocumentText. يحتوي عنصر FirebaseVisionDocumentText على النص الكامل الذي تم التعرّف عليه في الصورة وعلى تسلسل هرمي للعناصر يعكس بنية المستند الذي تم التعرّف عليه:

بالنسبة إلى كل عنصر من عناصر Block وParagraph وWord وSymbol، يمكنك الحصول على النص الذي تم التعرّف عليه في المنطقة وإحداثيات المربّع المحيط بالمنطقة.

على سبيل المثال:

Java

String resultText = result.getText();
for (FirebaseVisionDocumentText.Block block: result.getBlocks()) {
    String blockText = block.getText();
    Float blockConfidence = block.getConfidence();
    List<RecognizedLanguage> blockRecognizedLanguages = block.getRecognizedLanguages();
    Rect blockFrame = block.getBoundingBox();
    for (FirebaseVisionDocumentText.Paragraph paragraph: block.getParagraphs()) {
        String paragraphText = paragraph.getText();
        Float paragraphConfidence = paragraph.getConfidence();
        List<RecognizedLanguage> paragraphRecognizedLanguages = paragraph.getRecognizedLanguages();
        Rect paragraphFrame = paragraph.getBoundingBox();
        for (FirebaseVisionDocumentText.Word word: paragraph.getWords()) {
            String wordText = word.getText();
            Float wordConfidence = word.getConfidence();
            List<RecognizedLanguage> wordRecognizedLanguages = word.getRecognizedLanguages();
            Rect wordFrame = word.getBoundingBox();
            for (FirebaseVisionDocumentText.Symbol symbol: word.getSymbols()) {
                String symbolText = symbol.getText();
                Float symbolConfidence = symbol.getConfidence();
                List<RecognizedLanguage> symbolRecognizedLanguages = symbol.getRecognizedLanguages();
                Rect symbolFrame = symbol.getBoundingBox();
            }
        }
    }
}

Kotlin

val resultText = result.text
for (block in result.blocks) {
    val blockText = block.text
    val blockConfidence = block.confidence
    val blockRecognizedLanguages = block.recognizedLanguages
    val blockFrame = block.boundingBox
    for (paragraph in block.paragraphs) {
        val paragraphText = paragraph.text
        val paragraphConfidence = paragraph.confidence
        val paragraphRecognizedLanguages = paragraph.recognizedLanguages
        val paragraphFrame = paragraph.boundingBox
        for (word in paragraph.words) {
            val wordText = word.text
            val wordConfidence = word.confidence
            val wordRecognizedLanguages = word.recognizedLanguages
            val wordFrame = word.boundingBox
            for (symbol in word.symbols) {
                val symbolText = symbol.text
                val symbolConfidence = symbol.confidence
                val symbolRecognizedLanguages = symbol.recognizedLanguages
                val symbolFrame = symbol.boundingBox
            }
        }
    }
}

الخطوات التالية