Как улучшить распознавание цифр модели, обученной на MNIST? - PullRequest
11 голосов
/ 15 октября 2019

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

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

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

enter image description here становится enter image description here и классифицируется как 4.

enter image description here становится enter image description here и классифицируется как 7.

enter image description here становится enter image description here и классифицируется как 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);
}

Ответы [ 3 ]

5 голосов
/ 20 октября 2019

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

Overlap of the source and processed images

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

4 голосов
/ 24 октября 2019

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

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

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

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

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

расширение: original symbol and dilated one

закрытие: original symbol and closed one

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

4 голосов
/ 22 октября 2019

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

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

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

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

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

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

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

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

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

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

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

...