Как улучшить распознавание цифр модели, обученной на MNIST?

12

Я работаю над многозначным распознаванием от руки Java, используя OpenCVбиблиотеку для предварительной обработки и сегментации, а также Kerasмодель, обученную MNIST (с точностью 0,98) для распознавания.

Признание, кажется, работает довольно хорошо, кроме одной вещи. Сеть нередко не распознает их (номер «один»). Я не могу понять, происходит ли это из-за предварительной обработки / неправильной реализации сегментации, или если сеть, обученная по стандартному MNIST, просто не видела номер один, который похож на мои тестовые случаи.

Вот как выглядят проблемные цифры после предварительной обработки и сегментации:

введите описание изображения здесьстановится введите описание изображения здесьи классифицируется как 4.

введите описание изображения здесьстановится введите описание изображения здесьи классифицируется как 7.

введите описание изображения здесьстановится введите описание изображения здесьи классифицируется как 4. И так далее...

Это можно исправить, улучшив процесс сегментации? Или, скорее, улучшая тренировочный набор?

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

Моя предварительная обработка состоит из изменения размера, преобразования в оттенки серого, бинаризации, инверсии и расширения. Вот код:

Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);

Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);

Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);

Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);

Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);

Предварительно обработанное изображение затем сегментируется на отдельные цифры следующим образом:

List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

// code to sort contours
// code to check that contour is a valid char

List rects = new ArrayList<>();

for (MatOfPoint contour : contours) {
     Rect boundingBox = Imgproc.boundingRect(contour);
     Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);

     rects.add(rectCrop);
}

for (int i = 0; i < rects.size(); i++) {
    Rect x = (Rect) rects.get(i);
    Mat digit = new Mat(preprocessed, x);

    int border = 50;
    Mat result = digit.clone();
    Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));

    Imgproc.resize(result, result, new Size(28, 28));
    digits.add(result);
}
youngpanda
источник
1
Вы используете маску или (маскированные?) оригинальные оттенки серого в качестве входных данных для вашей классификации?
Micka
@Micka Я использую предварительно обработанную (бинаризованную, инвертированную, расширенную) версию. Те, которые соответствуют обучающему набору MNIST. В моем посте приведены примеры числа «1» после предварительной обработки.
youngpanda

Ответы:

5

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

Перекрытие исходного и обработанного изображений

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

Сэр
источник
Я считаю, что @SiR имеет некоторый вес, попробуйте не изменять соотношение сторон числовых литералов.
ZdaR
Извините, я не совсем понимаю. Как вы думаете, мой процесс расширения или изменения размера является проблемой? Я только изменяю размер изображения в начале с этой строки Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);. Здесь соотношение сторон остается тем же, где я могу нарушить пропорции?
youngpanda
@SiR в ответ на ваши правки выше: да, я не просто изменяю размер изображения, я применяю различные операции, одна из которых - расширение, которое является морфологическим, которое вызывает небольшое «искажение», поскольку оно вызывает яркие области внутри изображение, чтобы «расти». Или вы имеете в виду изменение размера в самом конце, где я делаю изображения 28x28?
youngpanda
@youngpanda, вы можете найти обсуждение здесь stackoverflow.com/questions/28525436/… интересно. Это может дать вам подсказку, почему ваш подход не приносит хороших результатов
SiR
@SiR спасибо за ссылку, я знаком с LeNet, но приятно читать снова
youngpanda
5

Уже опубликовано несколько ответов, но ни один из них не отвечает на ваш актуальный вопрос о предварительной обработке изображений .

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

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

Обычно лучше использовать раскрытие вместо эрозии и закрытие вместо расширения, поскольку в этом случае исходное двоичное изображение изменяется гораздо меньше (но достигается желаемый эффект очистки острых краев или заполнения зазоров). Так что в вашем случае вы должны проверить закрытие (расширение изображения с последующей эрозией с тем же ядром). В случае, если очень маленькое изображение 8 * 8 сильно изменяется, когда вы расширяетесь даже с ядром 1 * 1 (1 пиксель составляет более 16% изображения), что меньше на больших изображениях).

Чтобы визуализировать идею, смотрите следующие рисунки (из учебных пособий OpenCV: 1 , 2 ):

дилатация: оригинальный и расширенный символ

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

Надеюсь, поможет.

f4f
источник
Спасибо за вклад! На самом деле это не учебный проект, так в чем же тогда проблема? .. Мое изображение довольно большое, когда я применяю расширение, 8x8 - это не размер изображения, а фактор изменения размера для высоты и ширины. Но это все еще может быть вариантом улучшения, чтобы попробовать различные математические операции. Я не знал об открытии и закрытии, я попробую! Спасибо.
youngpanda
Моя ошибка, неправильно прочитанный вызов изменения размера, как это было с 8 * 8 как новый размер. Если вы хотите использовать OCR в реальном мире, вы должны рассмотреть возможность передачи обучения вашей оригинальной сети на данных, типичных для вашей области использования. По крайней мере, проверьте, улучшает ли это точность, как правило, это следует делать.
f4f
Еще одна вещь, которую нужно проверить - это порядок предварительной обработки: grayscale-> binary-> inverse-> resize. Изменение размера является дорогостоящей операцией, и я не вижу необходимости применять его для цветного изображения. И сегментирование символов может быть выполнено без обнаружения контура (с чем-то менее дорогостоящим), если у вас есть какой-то определенный формат ввода, но это может быть трудно реализовать.
f4f
Если бы у меня был другой набор данных, кроме MNIST, я мог бы попробовать перенести обучение :) Я постараюсь изменить порядок предварительной обработки и вернуться к вам. Спасибо! Я еще не нашел более простого варианта, чем определение контура для моей проблемы ...
youngpanda
1
Хорошо. Вы можете сами собирать данные из этих изображений, которые вы будете использовать для распознавания текста, это обычная практика.
f4f
4

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

  1. Предварительная обработка изображения

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

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

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

  1. Алгоритм обучения

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

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

  1. Архитектура ANN

Это большая проблема. Как определить лучшую архитектуру ANN для решения задачи? Там нет общих способов сделать это. Но есть несколько способов приблизиться к идеалу. Например, вы можете прочитать эту книгу . Это поможет вам лучше понять вашу проблему. Также вы можете найти здесь некоторые эвристические формулы, чтобы соответствовать количеству скрытых слоев / элементов для вашего ANN. Также здесь вы найдете небольшой обзор для этого.

Я надеюсь, что это поможет.

Егор Замотаев
источник
1. Если я вас правильно понимаю, я не могу обрезать до фиксированного размера, это изображение многозначного числа, и все случаи различаются по размеру / месту и т. Д. Или вы имели в виду что-то другое? Да, я пробовал разные методы бинаризации и настройки параметров, если вы это имеете в виду. 2. На самом деле признание в MNIST велико, никаких переоснащений не происходит, упомянутая мной точность - это точность теста. Ни сеть, ни ее обучение не являются проблемой. 3. Спасибо за все ссылки, я очень доволен своей архитектурой, хотя, конечно, всегда есть возможности для улучшения.
youngpanda
Да, вы поняли. Но у вас всегда есть возможность сделать ваш набор данных более унифицированным. В вашем случае просто лучше обрезать изображения цифр по контурам, как вы уже это делаете. Но после этого будет лучше просто увеличить ваши цифровые изображения до единого размера в соответствии с максимальным размером цифрового изображения по шкале x и y. Вы можете предпочесть центр области контура цифр, чтобы сделать это. Это даст вам более чистые входные данные для вашего алгоритма обучения.
Егор Замотаев
Вы имеете в виду, что я должен пропустить расширение? В конце я уже центрирую изображение, когда применяю границу (50 пикселей с каждой стороны). После этого я изменяю размер каждой цифры до 28x28, так как это размер, который нам нужен для MNIST. Вы имеете в виду, что я могу изменить размер до 28x28 по-другому?
youngpanda
1
Да, расширение нежелательно. Ваши контуры могут иметь разные соотношения по высоте и ширине, поэтому здесь вам нужно усовершенствовать свой алгоритм. По крайней мере, вы должны делать изображения с одинаковыми пропорциями. Поскольку у вас есть входные размеры изображения 28x28, вы должны подготовить изображения с одинаковым соотношением 1: 1 по шкалам x и y. Вы должны получить не 50 пикселей для каждой стороны изображения, а X, Y пикселей, которые удовлетворяют условию: contourSizeX + borderSizeX == contourSizeY + borderSizeY. Это все.
Егор Замотаев
Я уже пытался без дилатации (забыл упомянуть в посте). Это не изменило никаких результатов ... Мой номер границы был экспериментальным. В идеале мне нужно, чтобы мои цифры соответствовали размеру 20x20 (размер был нормализован как таковой в наборе данных), а после этого
сдвигали
1

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

  1. Как заметил @ f4f, мне нужно было собрать свой собственный набор данных с реальными данными. Это уже очень помогло.

  2. Я внес важные изменения в свою предварительную обработку сегментации. После получения отдельных контуров я сначала нормализую размеры изображений, чтобы они поместились в 20x20пиксельную рамку (как они есть MNIST). После этого я центрирую прямоугольник в центре 28x28изображения, используя центр масс (который для двоичных изображений является средним значением по обоим измерениям).

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

youngpanda
источник