Извлечение текста из светодиодной панели

Мне нужно извлечь значения килограммов (кг), показанные на изображении ниже:

Я вручную обрезал изображение, чтобы изолировать текстовую часть, и применил несколько методов обработки изображения, таких как преобразование в оттенки серого, определение порога, размытие по Гауссу и расширение. Однако результаты оказались не такими четкими, как я ожидал, и Tesseract OCR не смог их прочитать. Вот некоторые из обработанных изображений:

В настоящее время я использую EmguCV и Tesseract и пробовал различные модели тессеракта, включая tessdata_best (английский), lets и letsgodigital. К сожалению, ни одна из этих попыток не увенчалась успехом.

Конкретный используемый язык или библиотека не имеет решающего значения, поскольку я планирую преобразовать решение на C#. Окончательная реализация будет для мобильного приложения с использованием Xamarin.Forms.

Ниже приведен пример метода, который я использовал безуспешно:

public static void Apply()
        {
            var folderName = "letsgodigital";
            var dataname = "letsgodigital";

            string tesseractPath = @$"./{folderName}";
            string imagePath = @"img.jpg";

            Mat image = CvInvoke.Imread(imagePath, ImreadModes.Color);


            Mat blurredImg = new Mat();
            CvInvoke.Blur(image, blurredImg, new Size(9, 9), new Point(-1, -1));

            Mat grayImg = new Mat();
            CvInvoke.CvtColor(blurredImg, grayImg, ColorConversion.Bgr2Gray);

            Mat binaryImg = new Mat();
            CvInvoke.Threshold(grayImg, binaryImg, 122, 255, ThresholdType.Binary);

            binaryImg.Save("full_pannel_bw.png");

            using (var engine = new TesseractEngine(tesseractPath, dataname, EngineMode.Default))
            {
                engine.DefaultPageSegMode = PageSegMode.SingleLine;

                using (var img = Pix.LoadFromFile("full_pannel_bw.png"))
                {
                    using (var page = engine.Process(img))
                    {
                        string text = page.GetText();
                        Console.WriteLine("tesseract got: \"{0}\"", text.Trim());
                    }
                }
            }
        }

Редактировать мой окончательный процесс но тессеракт не может это прочитать. Я получаю пустой текст. Теперь я пытаюсь сделать этот текст на изображении темнее.

static void loggg()
        {
            Mat img = CvInvoke.Imread("5.jpg", ImreadModes.Color);

            VectorOfMat channels = new VectorOfMat();
            CvInvoke.Split(img, channels);

            Mat redChannel = new Mat();
            CvInvoke.Subtract(channels[2], channels[1], redChannel);
            CvInvoke.Subtract(redChannel, channels[0], redChannel);

            CvInvoke.Threshold(redChannel, redChannel, 40, 255, ThresholdType.Binary);


            Mat invertedRedChannel = new Mat();
            CvInvoke.BitwiseNot(redChannel, invertedRedChannel);
            Mat morphKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(2, 2), new Point(-1, -1));
            CvInvoke.MorphologyEx(invertedRedChannel, invertedRedChannel, MorphOp.Close, morphKernel, new Point(-1, -1), 1, BorderType.Constant, new MCvScalar(255));

             Mat dilateKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(1, 1), new Point(-1, -1));
            CvInvoke.Dilate(invertedRedChannel, invertedRedChannel, dilateKernel, new Point(-1, -1), 1, BorderType.Constant, new MCvScalar(0));

            invertedRedChannel.Save("darker_red_text.jpg");

            img.Dispose();
            redChannel.Dispose();
            invertedRedChannel.Dispose();
            channels.Dispose();
        }

вам ДЕЙСТВИТЕЛЬНО нужно настроить экспозицию вашей камеры. светодиоды насыщают датчик. плохой. очень очень плохо. уменьшите экспозицию. вся остальная картинка никого не волнует, только дисплей.

Christoph Rackwitz 07.05.2024 18:38

вот рецепт, который я прототипировал с помощью каких-то древних графических «коробок и труб»: отрегулируйте гамму, фильтр нижних частот (для HSL, выберите «Насыщенность» | выберите только красный канал), бинаризацию. -- вы можете заменять и переставлять различные части этого, например, тип фильтра нижних частот, делать это до или после выбора канала, ... i.sstatic.net/65Sdyo1B.jpg Я не буду писать C#. Могу предложить Python. нет никакой гарантии, что все, что я предлагаю, будет работать без корректировок на других изображениях.

Christoph Rackwitz 07.05.2024 18:46

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

Christoph Rackwitz 07.05.2024 18:50

@ChristophRackwitz спасибо за ответ. Это выглядит очень ясно. Я пробовал использовать API Google, и он работает, но я предпочитаю обрабатывать его локально. Я был бы признателен, если бы вы поделились со мной кодом Python; Я попробую преобразовать его в C#. Кроме того, мой предыдущий подход также включал выбор красного канала, но результат был не так ясен, как ваш.

boss 08.05.2024 02:21

Я опубликую правильный ответ через несколько часов, если запомню;)

Christoph Rackwitz 08.05.2024 13:33
Стоит ли изучать 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
5
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Сначала несколько комментариев к картинке:

  • Это масштабировано. Цифровое разрешение составляет 2496 на 3328 пикселей, но оптическое разрешение примерно в 3,3 раза меньше. Пиксели с повышенной дискретизацией четко различимы.
  • Он насыщен. Некоторые из самых ярких «красных» пикселей на светодиодной панели на самом деле не красные, а белые. Механизм сложен, и, вероятно, здесь задействовано множество эффектов. Свет проникает в соседние пиксели (другие цвета), что становится заметно, когда света много.

Рекомендации:

  • Не используйте «цифровой зум». Во всяком случае, не этот тип. Это вам ничего не даст.
  • Уменьшите физическую экспозицию, то есть уменьшите время экспозиции и/или уменьшите диафрагму. Цифровой фильтр (усиление, регулировка яркости/контрастности постфактум) ничего не даст. Следует следить за тем, чтобы светодиодная панель находилась в динамическом диапазоне, т. е. не насыщала датчик. Если вы все сделаете правильно, все окружение будет выглядеть темным, включая темные части светодиодной панели.

Отображаемый текст яркий и красный. Вы можете использовать оба свойства. Итак, вы должны выбрать красный канал изображения, а затем порог.

Это всего лишь красный канал:

Я применю «гамма»-отображение, которое является нелинейным. Это то, что можно попробовать и сохранить, если результаты окажутся лучше. Если бы он был линейным, он бы ничего не делал, в любом случае до порога (который придет позже).

Темные светодиоды панели по-прежнему выглядят достаточно светлыми (уровень ~0,25), но уже не такими яркими, как раньше (~0,5). Можно применить альтернативные или дополнительные сопоставления, чтобы сделать темные части панели еще темнее.

Это уже представляет собой своего рода порог... со значениями, выбранными вручную.

Теперь вы также можете видеть светодиоды и пробелы между ними в буквах. Я просто применю фильтр нижних частот, чтобы сгладить это. Это поможет с определением порога, поскольку внутри и снаружи букв от этих «выбросов» не будет «шума».

Для определения порога обычно рекомендуется попробовать автоматические алгоритмы, такие как Otsu. Разбираясь в этом, Оцу часто давал мне пороговые значения, при которых буквы соединялись, поэтому большую часть времени я работал с пороговыми значениями, выбранными вручную. Благодаря дополнительному растяжению контраста, которое буквально оставляет только черный цвет между всеми буквами (см. последнее изображение), у Оцу нет другого выбора, кроме как «работать». Это снова с порогом, выбранным вручную.

Я думаю, что это выглядит достаточно хорошо даже для простого старого Tesseract OCR. Если его нужно инвертировать, просто инвертируйте его.


Вот пример Python, использующий функции OpenCV, которые должны быть эквивалентны даже в сторонних привязках C#.

Я сразу конвертирую в плавающую точку. Это предотвращает обрезку или перенос чисел, если я выхожу за «обычный» диапазон значений (т. е. значения могут опускаться ниже 0 и превышать 255/1,0). Это также просто удобно для некоторых математических вычислений. imshow() интерпретирует числа с плавающей точкой в ​​диапазоне от 0,0 до 1,0, но imwrite() просто преобразует их в целые числа, поэтому вам придется уменьшить масштаб.

im = cv.imread("QsvdNqcn.jpg")

# convert to float32 and scale to 0.0 .. 1.0
im = im * np.float32(1/255) # Mat::convertTo() with rtype=CV_32F and alpha=1.0/255.0

# getting a region, just for demonstration purposes
(x,y,w,h) = 763, 1281, 1167, 388
im = im[y:y+h, x:x+w] # Mat::operator()(cv::Rect)

(blue, green, red) = cv.split(im)

red_linear = red ** (1/0.45) # cv::pow()

# more contrast stretching to make "dark" parts darker
vmin, vmax = 0.7, 1.0
red_linear = (red_linear - vmin) / (vmax - vmin) # cv::Mat in C++ supports such expressions too

lowpassed = cv.GaussianBlur(red_linear, None, sigmaX=4.0)

(th, mask) = cv.threshold(lowpassed, 0.25, 1.0, cv.THRESH_BINARY)
# with Otsu, that'd take converting back to uint8 ranged 0..255
# (th, mask) = cv.threshold(np.clip(lowpassed * 255, 0, 255).astype(np.uint8), 128, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)

Большое спасибо, я преобразовал этот код в C#, и он работает как положено. Единственная проблема в том, что у меня есть разные изображения одной и той же светодиодной панели. И ваш код у них не работает, потому что вы дали точные координаты. Но я попробую извлечь только эту часть

boss 09.05.2024 13:44

не стесняйтесь публиковать такие фотографии, чтобы я мог уточнить подход к ним. -- Урожай был просто для демонстрации. вы можете применить это ко всему изображению. Тогда вы увидите ответы на другие яркие объекты, а не только на красный текст. еще одна операция или две определят расположение красного текста.

Christoph Rackwitz 09.05.2024 17:07

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

Christoph Rackwitz 09.05.2024 21:43

Спасибо за ваш ответ. Я загрузил все изображения, которые у меня есть ibb.co/C6v6cY0 ibb.co/q5T7nLV ibb.co/28QVjDf ibb.co/tDxvSz5 Это выглядит довольно сложно. Завтра я поговорю со своим начальником, чтобы использовать Google Vision API. Но тот факт, что она будет использоваться с телефоном, будет преимуществом для пользователя, который сможет попробовать читать с помощью камеры под разными углами, пока не прочтет текст.

boss 10.05.2024 02:36

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

Christoph Rackwitz 11.05.2024 10:00

Спасибо за ваш ответ. Сегодня я попробовал использовать Paddle OCR, и он очень хорошо справился с обработкой изображений и распознаванием текста. Однако я думаю, что мне нужно использовать веб-сервис, потому что мобильные телефоны могут не справиться с этим.

boss 13.05.2024 14:15

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