Распознавание вертикального текста на японском языке с VNRecounceeTextRequest не работает

Я использую возможности Apple OCR, предоставляемые Vision Framework, для распознавания текста на изображениях. Хотя я добился больших успехов с горизонтальным текстом на японском, корейском и китайском языках, я столкнулся с проблемами с вертикальным текстом.

Проблема: При попытке распознать вертикальный текст на этих языках OCR возвращает ноль.

Что я пробовал:

  • Горизонтальный текст: идеально подходит для японского, корейского и китайского языков.
  • Вертикальный текст: возвращает ноль для тех же языков.
  • Переворот изображения: я попытался повернуть изображение в разные стороны, но он все равно возвращает ноль.

примеры изображений

Фрагмент кода:

  func ocr() {
    
    guard let image = UIImage(named: imageName) else {
        print("Failed to load image")
        return
    }
    
    guard let cgImage = image.cgImage else {
        print("Failed to get CGImage from UIImage")
        return
    }
    
    // Request handler
    let handler = VNImageRequestHandler(cgImage: cgImage, orientation: .right, options: [:])
    
    let recognizeRequest = VNRecognizeTextRequest { (request, error) in
                    
        if let error = error {
            print("Failed to recognize text: \(error.localizedDescription)")
            return
        }
        
        // Parse the results as text
        guard let result = request.results as? [VNRecognizedTextObservation] else {
            print("No text found")
            return
        }
        
        let stringArray = result.compactMap { result in
            result.topCandidates(1).first?.string
        }
        
        
        let recognizedString = stringArray.joined(separator: "\n")
        
        
        let singleLineText = recognizedString
            .components(separatedBy: .newlines)
            .joined(separator: " ")

        
        DispatchQueue.main.async {
            self.recognizeText = singleLineText
        }
    }
    recognizeRequest.recognitionLanguages = ["ja"]

    recognizeRequest.revision = VNRecognizeTextRequestRevision3

    recognizeRequest.automaticallyDetectsLanguage = true
    
    recognizeRequest.recognitionLevel = .accurate
    recognizeRequest.usesLanguageCorrection = false
    
    
    do {
        try handler.perform([recognizeRequest])
    } catch {
        print("Failed to perform text recognition: \(error.localizedDescription)")
    }

}

Спасибо за ваше терпение. Я воспроизвел проблему и работаю над ее решением. Я также подтвердил, что горизонтальные ряды символов нормально читаются в вашем коде; Я взял изображения вертикальных символов и создал изображения с горизонтальными символами слева направо и справа налево. Я продолжу работу и сообщу вам о прогрессе (если таковой будет). Я рад, что вы указали на эту проблему, потому что в ближайшем будущем мне понадобится такое же решение для моего приложения.

Rethunk 25.06.2024 14:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
116
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Чтобы читать японские символы, выровненные по вертикали, есть хакерское решение. Как только у меня будет реализация Swift, я опубликую ее, а пока пошаговое описание, которое я привожу ниже, может оказаться достаточным для вашего прогресса.

Но сначала для быстрого сравнения проверьте производительность Google Vision API, который вы можете попробовать бесплатно: https://cloud.google.com/vision/docs/drag-and-drop

Попробуйте загрузить изображения отдельных столбцов в Google Vision API, чтобы проверить, улучшится ли производительность. Альтернативно попробуйте изменить изображение, чтобы обеспечить больше пробелов между соседними столбцами символов.

Однако с использованием платформы Vision по состоянию на июнь 2024 года стало ясно, что VNRecounceeTextRequest не будет читать вертикальный столбец символов. Вот некоторые наблюдения, основанные на ваших изображениях

VNRecounceeTextRequest отлично работает для двух и более символов, выровненных по горизонтали.

Например, оба следующих текста будут выглядеть следующим образом:

Символы также читаются, если порядок обратный:

Когда представлен один символ, он не будет читаться. (Это похоже на трудности Vision с чтением отдельных арабских цифр, которые, похоже, частично «исправлены» в последней версии VNRecounceeTextRequest).

Однако символ можно сделать читабельным, создав изображение, в котором он дублируется. (Это также работает для латинского алфавита.)

Вы сможете воспроизвести эти результаты с отдельными символами и дубликатами символов, используя свой любимый редактор изображений. Делайте заметки при редактировании изображений, потому что предлагаемый мной хак будет следовать аналогичным шагам.

Короче говоря, с небольшой помощью некоторых алгоритмов обработки изображений, которые (насколько мне известно) недоступны ни в одной из библиотек Apple, мы собираемся разбивать изображения на вертикальные столбцы символов, чтобы создавать новые изображения с горизонтальными рядами символов. те же персонажи. Тогда Vision прекрасно прочитает новые изображения.

Вот основные шаги:

  1. Из изображения размера (ширина, высота) создайте новое изображение размера (высота, ширина).
  2. Определите ограничивающую рамку для каждого персонажа. (Подробнее описано ниже).
  3. Определите количество столбцов и символы, принадлежащие каждому столбцу.
  4. Скопируйте и вставьте прямоугольное фрагмент изображения для каждого символа из исходного изображения в новое изображение, но вместо перемещения по столбцу (в направлении изображения Y) вы будете вставлять символы подряд (в направлении изображения X). ).
  5. Запустите код OCR на искусственно созданном изображении.

Подробнее.

1. Создайте новое изображение CGImage, вероятно, здесь подойдет. Работать с CGImages достаточно легко, но для более эффективной обработки изображений может помочь работа с данными изображения на более низком уровне.

2. Определите ограничивающую рамку для каждого символа. Подойдите к этому шагу с помощью простой техники, а затем повторите ее, чтобы повысить надежность. Для первоначальных реализаций я настоятельно рекомендую методы, которые вы можете реализовать самостоятельно, или использовать функции обработки изображений, которые вы сможете понять после небольшого изучения.

В качестве первой реализации вы можете попробовать либо маркировку связанных компонентов, либо заливку заливкой.

https://en.wikipedia.org/wiki/Connected-comComponent_labeling

https://en.wikipedia.org/wiki/Flood_fill

В общих чертах, вы можете рассматривать маркировку связанных компонентов как заливку заливкой, но с заполненными областями, имеющими номера в качестве меток: область 1, область 2, область 3 и так далее.

Как только все темные пиксели персонажа будут идентифицированы как принадлежащие одному «компоненту» (или капле), вы сможете легко найти границы этого капли. Обычно можно найти прямоугольные границы — min x, min y, max x, max y — но можно также найти «жесткие» границы в виде выпуклой оболочки или связанной с ней формы.

https://en.wikipedia.org/wiki/Convex_hull#:~:text=The%20convex%20hull%20may%20be,of%20points%20in%20the%20subset.

В библиотеке с открытым исходным кодом OpenCV есть функция findContours(), которая работает хорошо. Это традиционный алгоритм с удовлетворительной реализацией. Под прикрытием скрываются два разных метода: традиционный «многопроходной» алгоритм и относительно новый «однопроходный» алгоритм. Однопроходный алгоритм основан на статье некоторых японских исследователей. Если хотите, я могу раскопать эту статью.

Google «opencv Swift Tutorial», чтобы найти инструкции по интеграции библиотеки OpenCV в ваш проект. Прежде чем пытаться это сделать, убедитесь, что вы правильно разветвили свой код!

Моя собственная реализация однопроходного алгоритма в Swift вполне пригодна, но она предназначена для тестирования прототипов, а не для использования в производстве.

Чтобы внести ясность: я не говорю, что это «правильное» решение для отделения символов от фона, но это довольно простой метод, который вы можете реализовать прямо сейчас. Есть более эффективные методы, которые требуют гораздо большего объяснения.

Предостережение: я больше знаком с китайскими иероглифами, но с японскими иероглифами я также ожидаю, что некоторые радикалы могут представлять проблему. Если два радикала принадлежат друг другу, но не связаны общей чертой или если штрих сильно потускнел, то у вас будут две ограничивающие рамки.

https://laits.utexas.edu/japanese/joshu/kanji/kanji_radicals/radicals2.html

На данный момент, я надеюсь, достаточно ясно: если вы можете определить прямоугольник пикселей, в котором находится символ, то вы можете скопировать этот прямоугольник пикселей из исходного изображения в новое изображение с горизонтально выровненными символами.

3. Определите столбцы символов Было бы удобно просто предложить пользователю выбрать вертикальное или горизонтальное распознавание текста. Простой!

Если между столбцами достаточно разделения, то не составит труда идентифицировать символы, принадлежащие одному и тому же столбцу, даже если этот столбец слегка наклонен относительно вертикальной оси (Y) изображения.

Если вертикальное разделение между ограничительными рамками для символов в одном столбце не намного меньше горизонтального разделения между столбцами, то у вас могут возникнуть трудности с надежным определением того, находятся ли столбцы в строках или столбцах.

Если ваш код определил, что вертикальное и горизонтальное расстояние между символами одинаково и что трудно отличить вертикальное и горизонтальное выравнивание символов, то вы можете попробовать запустить распознавание символов в обеих ориентациях, а затем выбрать «победителя». Схемы голосования были довольно распространенным методом повышения точности чтения OCR и до сих пор могут использоваться в некоторых библиотеках.

4. Скопируйте и вставьте пиксели символов в новое изображение. Для CGImage это достаточно просто.

Если вы работаете с OpenCV, то есть используете тип cv::Mat для представления данных изображения, метод определения фрагмента изображения прост, но немного отличается.

5. Запустите распознавание символов на изображении, выровненном по горизонтали. Используя ваш код, распознавание символов работало нормально, когда я создавал ряды символов с помощью редактора изображений для управления исходными изображениями символов, расположенными в столбцах.

Другой код Как я упоминал выше, VNRecounceeTextRequestRevision3 не позволяет читать отдельные японские символы, по крайней мере, с теми настройками параметров, которые я тестировал. Таким образом, если ваш код обнаружил ограничивающую рамку для одного символа, вы можете создать изображение с двумя копиями этого символа, подтвердить, что двойной символ читается, а затем использовать результаты OCR только для одного символа.

В настоящее время библиотеки OCR, которые могут читать целые страницы текста, будут включать проверки надежности и автокоррекцию для повышения точности отдельных слов (или, я предполагаю, японских символов) в зависимости от контекста, в котором находится символ.


Когда у меня будет время, я отредактирую этот пост, чтобы предоставить более подробную информацию о реализации. Я не хотел вас задерживать, если приведенного выше описания вам будет достаточно. Извините, я очень занят на этой неделе.

Я ожидаю, что какая-то предварительно обученная модель или какая-то библиотека OCR смогут хорошо обрабатывать вертикально выровненные японские символы. Если вы прочитаете учебники по распознаванию текста, вы обнаружите, что там цитируется множество японских и китайских исследователей. Возможно, эти документы приведут вас к сторонней библиотеке, которая отлично справляется с распознаванием текста на устройстве для японского языка; вам может понадобиться кто-то, кто знает японский язык, чтобы помочь вам с инструкциями по установке, если научная работа написана на английском языке, но инструкции по установке изначально японские.

Но мы должны пойти на определенные уступки краткости человеческой жизни, и я думаю, что мы с вами предпочли бы иметь хак сейчас, который мы могли бы заменить позже. Ура, технический долг!

Спасибо за ваше время и усилия, написавшие этот ответ, он не дал мне ответ, который я искал, но дал мне информацию, о которой я раньше не знал.

Basel 25.06.2024 23:36

Проверьте мой ответ. Я нашел способ поддержать вертикальный текст!

Basel 26.06.2024 00:24

@Базель, молодец, нашел ответ! Мне тоже SwiftyTesseract будет полезен. И я скажу, что одна из причин, по которой я пишу длинные ответы, заключается в том, что время от времени лакомый кусочек может быть полезен по другим причинам. Еще раз: молодец!

Rethunk 26.06.2024 02:08
Ответ принят как подходящий

Попробовав Apple Vision в течение двух недель, я обнаружил, что он не поддерживает напрямую вертикальный текст. Поэтому я искал альтернативные решения и обнаружил, что библиотека Tesseract OCR, хорошо зарекомендовавший себя инструмент, разработанный Google более 20 лет назад, потенциально может решить эту проблему. В частности, я нашел обученную модель для вертикального японского текста (jpn_vert.traineddata) в репозитории Tesseract.

Для iOS я использовал библиотеку SwiftyTesseract, которая более современна и хорошо подходит для моих нужд. Ниже приведены шаги, которые я выполнил, чтобы запустить его:

Шаги:

  1. Установите SwiftyTesseract: Добавьте SwiftyTesseract в свой проект с помощью диспетчера пакетов Swift.
  2. Import SwiftyTesseract
  3. Загрузите jpn_vert.traineddata из здесь
  4. Добавьте обученные данные в свой проект:
  • Создайте папку с именем tessdata.

  • Добавьте jpn_vert.traineddata в эту папку.

  • Перетащите папку tessdata в свой проект Xcode и выберите «Создать ссылки на папки».

  • на Edit Scheme, затем Run внутри Environment Variables добавить имя: TESSDATA_PREFIX значение: $(PROJECT_DIR)/tessdata

Добавить это расширение

public typealias PageSegmentationMode = TessPageSegMode

public extension PageSegmentationMode {
  static let osdOnly = PSM_OSD_ONLY
  static let autoOsd = PSM_AUTO_OSD
  static let autoOnly = PSM_AUTO_ONLY
  static let auto = PSM_AUTO
  static let singleColumn = PSM_SINGLE_COLUMN
  static let singleBlockVerticalText = PSM_SINGLE_BLOCK_VERT_TEXT
  static let singleBlock = PSM_SINGLE_BLOCK
  static let singleLine = PSM_SINGLE_LINE
  static let singleWord = PSM_SINGLE_WORD
  static let circleWord = PSM_CIRCLE_WORD
  static let singleCharacter = PSM_SINGLE_CHAR
  static let sparseText = PSM_SPARSE_TEXT
  static let sparseTextOsd = PSM_SPARSE_TEXT_OSD
  static let count = PSM_COUNT
}

public extension Tesseract {
  var pageSegmentationMode: PageSegmentationMode {
    get {
      perform { tessPointer in
        TessBaseAPIGetPageSegMode(tessPointer)
      }
    }
    set {
      perform { tessPointer in
        TessBaseAPISetPageSegMode(tessPointer, newValue)
      }
    }
  }
}

Использование:

 func japaneseOCR() {
    let tesseract = Tesseract(languages: [ .custom("jpn_vert")])
    
    tesseract.pageSegmentationMode = .singleBlockVerticalText
    
    guard let image = UIImage(named: imageName) else {
        print("Failed to load image")
        return
    }
    
    guard let imageData = image.jpegData(compressionQuality: 1.0) else {
        print("Failed to load imageData")
        return
    }

    let result: Result<String, Tesseract.Error> = tesseract.performOCR(on: imageData)

    let result1 = try? result.get()
            
    self.recognizeText = result1 ?? ""
}

Результат

Отлично сделано! Обязательно примите свой ответ как правильный. :) Я с нетерпением жду возможности попробовать это сам.

Rethunk 26.06.2024 02:07

Другие вопросы по теме