تشخیص متن در تصاویر با کیت ML در iOS

برای تشخیص متن در تصاویر می توانید از کیت ML استفاده کنید. ML Kit هم یک API همه منظوره مناسب برای تشخیص متن در تصاویر مانند متن تابلوهای خیابان دارد و هم یک API بهینه شده برای تشخیص متن اسناد. API همه منظوره دارای مدل های روی دستگاه و مبتنی بر ابر است. تشخیص متن سند فقط به عنوان یک مدل مبتنی بر ابر در دسترس است. برای مقایسه مدل‌های ابر و روی دستگاه ، نمای کلی را ببینید.

قبل از اینکه شروع کنی

  1. اگر قبلاً Firebase را به برنامه خود اضافه نکرده اید، این کار را با دنبال کردن مراحل راهنمای شروع کار انجام دهید.
  2. کتابخانه‌های ML Kit را در فایل پادفایل خود قرار دهید:
    pod 'Firebase/MLVision', '6.25.0'
    # If using an on-device API:
    pod 'Firebase/MLVisionTextModel', '6.25.0'
    
    پس از نصب یا به‌روزرسانی Pods پروژه، حتماً پروژه Xcode خود را با استفاده از .xcworkspace آن باز کنید.
  3. در برنامه خود، Firebase را وارد کنید:

    سریع

    import Firebase

    هدف-C

    @import Firebase;
  4. اگر می‌خواهید از مدل مبتنی بر Cloud استفاده کنید و قبلاً APIهای مبتنی بر ابر را برای پروژه خود فعال نکرده‌اید، اکنون این کار را انجام دهید:

    1. صفحه ML Kit APIs کنسول Firebase را باز کنید.
    2. اگر قبلاً پروژه خود را به طرح قیمت گذاری Blaze ارتقا نداده اید، برای انجام این کار، روی ارتقا کلیک کنید. (فقط اگر پروژه شما در طرح Blaze نباشد، از شما خواسته می شود که ارتقا دهید.)

      فقط پروژه های سطح Blaze می توانند از API های مبتنی بر ابر استفاده کنند.

    3. اگر APIهای مبتنی بر Cloud قبلاً فعال نشده‌اند، روی Enable Cloud-based APIs کلیک کنید.

    اگر می خواهید فقط از مدل روی دستگاه استفاده کنید، می توانید از این مرحله صرف نظر کنید.

اکنون شما آماده شروع به تشخیص متن در تصاویر هستید.

دستورالعمل های تصویر ورودی

  • برای اینکه کیت ML بتواند متن را به طور دقیق تشخیص دهد، تصاویر ورودی باید حاوی متنی باشند که با داده پیکسلی کافی نشان داده شود. در حالت ایده آل، برای متن لاتین، هر کاراکتر باید حداقل 16x16 پیکسل باشد. برای متن چینی، ژاپنی و کره ای (فقط توسط API های مبتنی بر ابر پشتیبانی می شود)، هر نویسه باید ۲۴×۲۴ پیکسل باشد. برای همه زبان ها، معمولاً هیچ مزیتی برای دقت بزرگتر از 24x24 پیکسل وجود ندارد.

    بنابراین، برای مثال، یک تصویر 640x480 ممکن است برای اسکن کارت ویزیتی که تمام عرض تصویر را اشغال می کند، به خوبی کار کند. برای اسکن یک سند چاپ شده روی کاغذ با اندازه حرف، ممکن است به یک تصویر 720x1280 پیکسل نیاز باشد.

  • فوکوس ضعیف تصویر می تواند به دقت تشخیص متن آسیب برساند. اگر نتایج قابل قبولی دریافت نکردید، از کاربر بخواهید که تصویر را دوباره بگیرد.

  • اگر متن را در یک برنامه بلادرنگ تشخیص می دهید، ممکن است بخواهید ابعاد کلی تصاویر ورودی را نیز در نظر بگیرید. تصاویر کوچک‌تر را می‌توان سریع‌تر پردازش کرد، بنابراین برای کاهش تأخیر، تصاویر را با وضوح پایین‌تر ثبت کنید (با در نظر گرفتن الزامات دقت بالا) و اطمینان حاصل کنید که متن تا حد امکان تصویر را اشغال می‌کند. همچنین به نکاتی برای بهبود عملکرد در زمان واقعی مراجعه کنید.


تشخیص متن در تصاویر

برای تشخیص متن در یک تصویر با استفاده از یک مدل روی دستگاه یا مبتنی بر ابر، شناسایی متن را همانطور که در زیر توضیح داده شده است اجرا کنید.

1. شناسه متن را اجرا کنید

تصویر را به عنوان «UIImage» یا «CMSampleBufferRef» به روش «process(_:completion:)» «VisionTextRecognizer» ارسال کنید:
  1. با تماس با onDeviceTextRecognizer یا cloudTextRecognizer یک نمونه از VisionTextRecognizer دریافت کنید:

    سریع

    برای استفاده از مدل روی دستگاه:

    let vision = Vision.vision()
    let textRecognizer = vision.onDeviceTextRecognizer()
    

    برای استفاده از مدل ابری:

    let vision = Vision.vision()
    let textRecognizer = vision.cloudTextRecognizer()
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    let options = VisionCloudTextRecognizerOptions()
    options.languageHints = ["en", "hi"]
    let textRecognizer = vision.cloudTextRecognizer(options: options)
    

    هدف-C

    برای استفاده از مدل روی دستگاه:

    FIRVision *vision = [FIRVision vision];
    FIRVisionTextRecognizer *textRecognizer = [vision onDeviceTextRecognizer];
    

    برای استفاده از مدل ابری:

    FIRVision *vision = [FIRVision vision];
    FIRVisionTextRecognizer *textRecognizer = [vision cloudTextRecognizer];
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FIRVisionCloudTextRecognizerOptions *options =
            [[FIRVisionCloudTextRecognizerOptions alloc] init];
    options.languageHints = @[@"en", @"hi"];
    FIRVisionTextRecognizer *textRecognizer = [vision cloudTextRecognizerWithOptions:options];
    
  2. یک شی VisionImage با استفاده از UIImage یا CMSampleBufferRef ایجاد کنید.

    برای استفاده از UIImage :

    1. در صورت لزوم، تصویر را بچرخانید تا ویژگی imageOrientation آن .up باشد.
    2. یک شی VisionImage با استفاده از UIImage با چرخش صحیح ایجاد کنید. هیچ ابرداده چرخشی را مشخص نکنید—مقدار پیش‌فرض، .topLeft . باید استفاده شود.

      سریع

      let image = VisionImage(image: uiImage)

      هدف-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

    برای استفاده از CMSampleBufferRef :

    1. یک شی VisionImageMetadata ایجاد کنید که جهت داده های تصویر موجود در بافر CMSampleBufferRef را مشخص می کند.

      برای دریافت جهت تصویر:

      سریع

      func imageOrientation(
          deviceOrientation: UIDeviceOrientation,
          cameraPosition: AVCaptureDevice.Position
          ) -> VisionDetectorImageOrientation {
          switch deviceOrientation {
          case .portrait:
              return cameraPosition == .front ? .leftTop : .rightTop
          case .landscapeLeft:
              return cameraPosition == .front ? .bottomLeft : .topLeft
          case .portraitUpsideDown:
              return cameraPosition == .front ? .rightBottom : .leftBottom
          case .landscapeRight:
              return cameraPosition == .front ? .topRight : .bottomRight
          case .faceDown, .faceUp, .unknown:
              return .leftTop
          }
      }

      هدف-C

      - (FIRVisionDetectorImageOrientation)
          imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                                 cameraPosition:(AVCaptureDevicePosition)cameraPosition {
        switch (deviceOrientation) {
          case UIDeviceOrientationPortrait:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationLeftTop;
            } else {
              return FIRVisionDetectorImageOrientationRightTop;
            }
          case UIDeviceOrientationLandscapeLeft:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationBottomLeft;
            } else {
              return FIRVisionDetectorImageOrientationTopLeft;
            }
          case UIDeviceOrientationPortraitUpsideDown:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationRightBottom;
            } else {
              return FIRVisionDetectorImageOrientationLeftBottom;
            }
          case UIDeviceOrientationLandscapeRight:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationTopRight;
            } else {
              return FIRVisionDetectorImageOrientationBottomRight;
            }
          default:
            return FIRVisionDetectorImageOrientationTopLeft;
        }
      }

      سپس، شی فوق داده را ایجاد کنید:

      سریع

      let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
      let metadata = VisionImageMetadata()
      metadata.orientation = imageOrientation(
          deviceOrientation: UIDevice.current.orientation,
          cameraPosition: cameraPosition
      )

      هدف-C

      FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
      AVCaptureDevicePosition cameraPosition =
          AVCaptureDevicePositionBack;  // Set to the capture device you used.
      metadata.orientation =
          [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                       cameraPosition:cameraPosition];
    2. یک شی VisionImage با استفاده از شی CMSampleBufferRef و ابرداده چرخش ایجاد کنید:

      سریع

      let image = VisionImage(buffer: sampleBuffer)
      image.metadata = metadata

      هدف-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  3. سپس تصویر را به متد process(_:completion:) منتقل کنید:

    سریع

    textRecognizer.process(visionImage) { result, error in
      guard error == nil, let result = result else {
        // ...
        return
      }
    
      // Recognized text
    }
    

    هدف-C

    [textRecognizer processImage:image
                      completion:^(FIRVisionText *_Nullable result,
                                   NSError *_Nullable error) {
      if (error != nil || result == nil) {
        // ...
        return;
      }
    
      // Recognized text
    }];
    

2. متن را از بلوک های متن شناخته شده استخراج کنید

اگر عملیات تشخیص متن با موفقیت انجام شود، یک شیء [`VisionText`][VisionText] را برمی گرداند. یک شی «VisionText» حاوی متن کامل شناسایی شده در تصویر و صفر یا بیشتر شیء [`VisionTextBlock`][VisionTextBlock] است. هر «VisionTextBlock» یک بلوک مستطیلی از متن را نشان می‌دهد که حاوی صفر یا بیشتر شیء [`VisionTextLine»][VisionTextLine] است. هر شی «VisionTextLine» حاوی صفر یا بیشتر شیء [`VisionTextElement`][VisionTextElement] است که بیانگر کلمات و موجودات کلمه مانند (تاریخ، اعداد و غیره) است. برای هر شی «VisionTextBlock»، «VisionTextLine» و «VisionTextElement»، می‌توانید متن را در منطقه و مختصات مرزی منطقه شناسایی کنید. مثلا:

سریع

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

هدف-C

NSString *resultText = result.text;
for (FIRVisionTextBlock *block in result.blocks) {
  NSString *blockText = block.text;
  NSNumber *blockConfidence = block.confidence;
  NSArray<FIRVisionTextRecognizedLanguage *> *blockLanguages = block.recognizedLanguages;
  NSArray<NSValue *> *blockCornerPoints = block.cornerPoints;
  CGRect blockFrame = block.frame;
  for (FIRVisionTextLine *line in block.lines) {
    NSString *lineText = line.text;
    NSNumber *lineConfidence = line.confidence;
    NSArray<FIRVisionTextRecognizedLanguage *> *lineLanguages = line.recognizedLanguages;
    NSArray<NSValue *> *lineCornerPoints = line.cornerPoints;
    CGRect lineFrame = line.frame;
    for (FIRVisionTextElement *element in line.elements) {
      NSString *elementText = element.text;
      NSNumber *elementConfidence = element.confidence;
      NSArray<FIRVisionTextRecognizedLanguage *> *elementLanguages = element.recognizedLanguages;
      NSArray<NSValue *> *elementCornerPoints = element.cornerPoints;
      CGRect elementFrame = element.frame;
    }
  }
}

نکاتی برای بهبود عملکرد در زمان واقعی

اگر می‌خواهید از مدل روی دستگاه برای تشخیص متن در یک برنامه بلادرنگ استفاده کنید، این دستورالعمل‌ها را برای دستیابی به بهترین نرخ فریم دنبال کنید:

  • دریچه گاز با شناسه متن تماس می گیرد. اگر یک قاب ویدیویی جدید در حالی که تشخیص دهنده متن در حال اجرا است در دسترس قرار گرفت، قاب را رها کنید.
  • اگر از خروجی تشخیص‌دهنده متن برای همپوشانی گرافیک روی تصویر ورودی استفاده می‌کنید، ابتدا نتیجه را از ML Kit دریافت کنید، سپس تصویر را رندر کنید و در یک مرحله همپوشانی کنید. با انجام این کار، برای هر فریم ورودی فقط یک بار به سطح نمایشگر رندر می دهید. به عنوان مثال، کلاس‌های previewOverlayView و FIRDetectionOverlayView را در برنامه نمونه ویترینی ببینید.
  • گرفتن تصاویر با وضوح کمتر را در نظر بگیرید. با این حال، الزامات ابعاد تصویر این API را نیز در نظر داشته باشید.

مراحل بعدی


تشخیص متن در تصاویر اسناد

برای تشخیص متن یک سند، شناسه متن سند مبتنی بر ابر را پیکربندی و اجرا کنید که در زیر توضیح داده شده است.

API تشخیص متن سند، که در زیر توضیح داده شده است، رابطی را ارائه می دهد که برای کار با تصاویر اسناد راحت تر است. با این حال، اگر رابط ارائه شده توسط API متن پراکنده را ترجیح می دهید، می توانید به جای آن برای اسکن اسناد با پیکربندی شناساگر متن ابری برای استفاده از مدل متن متراکم از آن استفاده کنید.

برای استفاده از API تشخیص متن سند:

1. شناسه متن را اجرا کنید

تصویر را به عنوان UIImage یا CMSampleBufferRef به روش VisionDocumentTextRecognizer process(_:completion:) منتقل کنید:

  1. با فراخوانی cloudDocumentTextRecognizer یک نمونه از VisionDocumentTextRecognizer دریافت کنید:

    سریع

    let vision = Vision.vision()
    let textRecognizer = vision.cloudDocumentTextRecognizer()
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    let options = VisionCloudDocumentTextRecognizerOptions()
    options.languageHints = ["en", "hi"]
    let textRecognizer = vision.cloudDocumentTextRecognizer(options: options)
    

    هدف-C

    FIRVision *vision = [FIRVision vision];
    FIRVisionDocumentTextRecognizer *textRecognizer = [vision cloudDocumentTextRecognizer];
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FIRVisionCloudDocumentTextRecognizerOptions *options =
            [[FIRVisionCloudDocumentTextRecognizerOptions alloc] init];
    options.languageHints = @[@"en", @"hi"];
    FIRVisionDocumentTextRecognizer *textRecognizer = [vision cloudDocumentTextRecognizerWithOptions:options];
    
  2. یک شی VisionImage با استفاده از UIImage یا CMSampleBufferRef ایجاد کنید.

    برای استفاده از UIImage :

    1. در صورت لزوم، تصویر را بچرخانید تا ویژگی imageOrientation آن .up باشد.
    2. یک شی VisionImage با استفاده از UIImage با چرخش صحیح ایجاد کنید. هیچ ابرداده چرخشی را مشخص نکنید—مقدار پیش‌فرض، .topLeft . باید استفاده شود.

      سریع

      let image = VisionImage(image: uiImage)

      هدف-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

    برای استفاده از CMSampleBufferRef :

    1. یک شی VisionImageMetadata ایجاد کنید که جهت داده های تصویر موجود در بافر CMSampleBufferRef را مشخص می کند.

      برای دریافت جهت تصویر:

      سریع

      func imageOrientation(
          deviceOrientation: UIDeviceOrientation,
          cameraPosition: AVCaptureDevice.Position
          ) -> VisionDetectorImageOrientation {
          switch deviceOrientation {
          case .portrait:
              return cameraPosition == .front ? .leftTop : .rightTop
          case .landscapeLeft:
              return cameraPosition == .front ? .bottomLeft : .topLeft
          case .portraitUpsideDown:
              return cameraPosition == .front ? .rightBottom : .leftBottom
          case .landscapeRight:
              return cameraPosition == .front ? .topRight : .bottomRight
          case .faceDown, .faceUp, .unknown:
              return .leftTop
          }
      }

      هدف-C

      - (FIRVisionDetectorImageOrientation)
          imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                                 cameraPosition:(AVCaptureDevicePosition)cameraPosition {
        switch (deviceOrientation) {
          case UIDeviceOrientationPortrait:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationLeftTop;
            } else {
              return FIRVisionDetectorImageOrientationRightTop;
            }
          case UIDeviceOrientationLandscapeLeft:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationBottomLeft;
            } else {
              return FIRVisionDetectorImageOrientationTopLeft;
            }
          case UIDeviceOrientationPortraitUpsideDown:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationRightBottom;
            } else {
              return FIRVisionDetectorImageOrientationLeftBottom;
            }
          case UIDeviceOrientationLandscapeRight:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationTopRight;
            } else {
              return FIRVisionDetectorImageOrientationBottomRight;
            }
          default:
            return FIRVisionDetectorImageOrientationTopLeft;
        }
      }

      سپس، شی فوق داده را ایجاد کنید:

      سریع

      let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
      let metadata = VisionImageMetadata()
      metadata.orientation = imageOrientation(
          deviceOrientation: UIDevice.current.orientation,
          cameraPosition: cameraPosition
      )

      هدف-C

      FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
      AVCaptureDevicePosition cameraPosition =
          AVCaptureDevicePositionBack;  // Set to the capture device you used.
      metadata.orientation =
          [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                       cameraPosition:cameraPosition];
    2. یک شی VisionImage با استفاده از شی CMSampleBufferRef و ابرداده چرخش ایجاد کنید:

      سریع

      let image = VisionImage(buffer: sampleBuffer)
      image.metadata = metadata

      هدف-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  3. سپس تصویر را به متد process(_:completion:) منتقل کنید:

    سریع

    textRecognizer.process(visionImage) { result, error in
      guard error == nil, let result = result else {
        // ...
        return
      }
    
      // Recognized text
    }
    

    هدف-C

    [textRecognizer processImage:image
                      completion:^(FIRVisionDocumentText *_Nullable result,
                                   NSError *_Nullable error) {
      if (error != nil || result == nil) {
        // ...
        return;
      }
    
        // Recognized text
    }];
    

2. متن را از بلوک های متن شناخته شده استخراج کنید

اگر عملیات تشخیص متن موفقیت آمیز باشد، یک شی VisionDocumentText را برمی گرداند. یک شی VisionDocumentText حاوی متن کامل شناسایی شده در تصویر و سلسله مراتبی از اشیاء است که ساختار سند شناسایی شده را منعکس می کند:

برای هر شی VisionDocumentTextBlock ، VisionDocumentTextParagraph ، VisionDocumentTextWord ، و VisionDocumentTextSymbol ، می‌توانید متن را در منطقه و مختصات مرزی منطقه شناسایی کنید.

مثلا:

سریع

let resultText = result.text
for block in result.blocks {
    let blockText = block.text
    let blockConfidence = block.confidence
    let blockRecognizedLanguages = block.recognizedLanguages
    let blockBreak = block.recognizedBreak
    let blockCornerPoints = block.cornerPoints
    let blockFrame = block.frame
    for paragraph in block.paragraphs {
        let paragraphText = paragraph.text
        let paragraphConfidence = paragraph.confidence
        let paragraphRecognizedLanguages = paragraph.recognizedLanguages
        let paragraphBreak = paragraph.recognizedBreak
        let paragraphCornerPoints = paragraph.cornerPoints
        let paragraphFrame = paragraph.frame
        for word in paragraph.words {
            let wordText = word.text
            let wordConfidence = word.confidence
            let wordRecognizedLanguages = word.recognizedLanguages
            let wordBreak = word.recognizedBreak
            let wordCornerPoints = word.cornerPoints
            let wordFrame = word.frame
            for symbol in word.symbols {
                let symbolText = symbol.text
                let symbolConfidence = symbol.confidence
                let symbolRecognizedLanguages = symbol.recognizedLanguages
                let symbolBreak = symbol.recognizedBreak
                let symbolCornerPoints = symbol.cornerPoints
                let symbolFrame = symbol.frame
            }
        }
    }
}

هدف-C

NSString *resultText = result.text;
for (FIRVisionDocumentTextBlock *block in result.blocks) {
  NSString *blockText = block.text;
  NSNumber *blockConfidence = block.confidence;
  NSArray<FIRVisionTextRecognizedLanguage *> *blockRecognizedLanguages = block.recognizedLanguages;
  FIRVisionTextRecognizedBreak *blockBreak = block.recognizedBreak;
  CGRect blockFrame = block.frame;
  for (FIRVisionDocumentTextParagraph *paragraph in block.paragraphs) {
    NSString *paragraphText = paragraph.text;
    NSNumber *paragraphConfidence = paragraph.confidence;
    NSArray<FIRVisionTextRecognizedLanguage *> *paragraphRecognizedLanguages = paragraph.recognizedLanguages;
    FIRVisionTextRecognizedBreak *paragraphBreak = paragraph.recognizedBreak;
    CGRect paragraphFrame = paragraph.frame;
    for (FIRVisionDocumentTextWord *word in paragraph.words) {
      NSString *wordText = word.text;
      NSNumber *wordConfidence = word.confidence;
      NSArray<FIRVisionTextRecognizedLanguage *> *wordRecognizedLanguages = word.recognizedLanguages;
      FIRVisionTextRecognizedBreak *wordBreak = word.recognizedBreak;
      CGRect wordFrame = word.frame;
      for (FIRVisionDocumentTextSymbol *symbol in word.symbols) {
        NSString *symbolText = symbol.text;
        NSNumber *symbolConfidence = symbol.confidence;
        NSArray<FIRVisionTextRecognizedLanguage *> *symbolRecognizedLanguages = symbol.recognizedLanguages;
        FIRVisionTextRecognizedBreak *symbolBreak = symbol.recognizedBreak;
        CGRect symbolFrame = symbol.frame;
      }
    }
  }
}

مراحل بعدی