Мне нужно извлечь значения килограммов (кг), показанные на изображении ниже:
Я вручную обрезал изображение, чтобы изолировать текстовую часть, и применил несколько методов обработки изображения, таких как преобразование в оттенки серого, определение порога, размытие по Гауссу и расширение. Однако результаты оказались не такими четкими, как я ожидал, и 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();
}
вот рецепт, который я прототипировал с помощью каких-то древних графических «коробок и труб»: отрегулируйте гамму, фильтр нижних частот (для HSL, выберите «Насыщенность» | выберите только красный канал), бинаризацию. -- вы можете заменять и переставлять различные части этого, например, тип фильтра нижних частот, делать это до или после выбора канала, ... i.sstatic.net/65Sdyo1B.jpg Я не буду писать C#. Могу предложить Python. нет никакой гарантии, что все, что я предлагаю, будет работать без корректировок на других изображениях.
или... просто отправьте изображение в какой-нибудь веб-сервис искусственного интеллекта и попросите его извлечь из него цифры. ИИ в этом хорош.
@ChristophRackwitz спасибо за ответ. Это выглядит очень ясно. Я пробовал использовать API Google, и он работает, но я предпочитаю обрабатывать его локально. Я был бы признателен, если бы вы поделились со мной кодом Python; Я попробую преобразовать его в C#. Кроме того, мой предыдущий подход также включал выбор красного канала, но результат был не так ясен, как ваш.
Я опубликую правильный ответ через несколько часов, если запомню;)





Сначала несколько комментариев к картинке:
Рекомендации:
Отображаемый текст яркий и красный. Вы можете использовать оба свойства. Итак, вы должны выбрать красный канал изображения, а затем порог.
Это всего лишь красный канал:
Я применю «гамма»-отображение, которое является нелинейным. Это то, что можно попробовать и сохранить, если результаты окажутся лучше. Если бы он был линейным, он бы ничего не делал, в любом случае до порога (который придет позже).
Темные светодиоды панели по-прежнему выглядят достаточно светлыми (уровень ~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#, и он работает как положено. Единственная проблема в том, что у меня есть разные изображения одной и той же светодиодной панели. И ваш код у них не работает, потому что вы дали точные координаты. Но я попробую извлечь только эту часть
не стесняйтесь публиковать такие фотографии, чтобы я мог уточнить подход к ним. -- Урожай был просто для демонстрации. вы можете применить это ко всему изображению. Тогда вы увидите ответы на другие яркие объекты, а не только на красный текст. еще одна операция или две определят расположение красного текста.
Я еще немного поигрался с основными приемами поиска красного текста. к сожалению, он почти такого же цвета, как и некоторые части фона (ветки деревьев). Возможно, вы захотите просто обучить ИИ находить светодиодный матричный дисплей. если у вас есть терпение, вы можете даже научить его напрямую выводить текст на дисплее.
Спасибо за ваш ответ. Я загрузил все изображения, которые у меня есть ibb.co/C6v6cY0 ibb.co/q5T7nLV ibb.co/28QVjDf ibb.co/tDxvSz5 Это выглядит довольно сложно. Завтра я поговорю со своим начальником, чтобы использовать Google Vision API. Но тот факт, что она будет использоваться с телефоном, будет преимуществом для пользователя, который сможет попробовать читать с помощью камеры под разными углами, пока не прочтет текст.
некоторые из них выглядят так, будто улавливают лишь часть мигания дисплея. Я думаю, вам, возможно, придется контролировать время экспозиции камеры на уровне 100% (кадра), чтобы не потерять ни одну светодиодную вспышку. эти матричные дисплеи мигают каждым светодиодом по очереди, что выглядит нормально для людей и при изображении с «более длинной» выдержкой, но не в том случае, если камера пытается справиться с условиями дневного света.
Спасибо за ваш ответ. Сегодня я попробовал использовать Paddle OCR, и он очень хорошо справился с обработкой изображений и распознаванием текста. Однако я думаю, что мне нужно использовать веб-сервис, потому что мобильные телефоны могут не справиться с этим.
вам ДЕЙСТВИТЕЛЬНО нужно настроить экспозицию вашей камеры. светодиоды насыщают датчик. плохой. очень очень плохо. уменьшите экспозицию. вся остальная картинка никого не волнует, только дисплей.