Распознавание символов по изображению C++

* Примечание: хотя в этом посте много говорится о билинейной интерполяции, я оставил название более общим и включил дополнительную информацию на случай, если у кого-то возникнут какие-либо идеи о том, как я могу сделать это лучше

У меня были проблемы с реализацией способа идентификации букв на изображении, чтобы создать программу для поиска слов. В основном в образовательных, но и в портативных целях, я пытался сделать это без использования библиотеки. Можно предположить, что изображение, из которого будут выбраны персонажи, не содержит ничего, кроме головоломки. Хотя эта страница распознает только небольшой набор символов, я использовал ее, чтобы направлять свои усилия вместе с этим. Как предложено в статье, у меня есть изображение каждой буквы, уменьшенное до 5х5, чтобы сравнить каждую неизвестную букву с. Я добился наилучшего успеха, уменьшив неизвестное до 5x5, используя билинейную передискретизацию и суммируя квадраты разности интенсивности каждого соответствующего пикселя в известных и неизвестных изображениях. Чтобы попытаться получить более точные результаты, я также добавил квадрат разности соотношений ширины: высоты и отношения белого и черного пикселей в верхней и нижней половине каждого изображения. Известное изображение с наиболее близким "показателем различия" к неизвестному изображению считается неизвестной буквой. Проблема заключается в том, что это имеет точность только около 50%. Чтобы улучшить это, я попытался использовать более крупные выборки (вместо 5x5 я попробовал 15x15), но это оказалось еще менее эффективным. Я также попытался просмотреть известные и неизвестные изображения, найти элементы и формы и определить соответствие на основе двух изображений, имеющих примерно одинаковое количество одинаковых функций. Например, фигуры, подобные приведенным ниже, были определены и подсчитаны (где представляет черный пиксель). Это оказалось менее эффективным, чем оригинальный метод.

  ■ ■                 ■   ■
  ■                     ■

Итак, вот пример: загружается следующее изображение:

Физический поиск слова

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

Поиск слов обработан

Затем я беру каждую букву, перепечатываю ее и сравниваю с известными изображениями.

* Примечание: известные образцы используют шрифт ариального размера 12, масштабированный в фотошопе до 5x5 с использованием билинейной интерполяции.

Вот пример успешного совпадения: выбрана следующая буква:

N

уменьшено до:

N масштабируется

который выглядит как

N маленький

издалека. Это успешно соответствует известному образцу N:

N известно

Вот неудачное совпадение:

выбран и уменьшен до:

R масштабируется

что, к большому удивлению, не соответствует известному образцу R

R известный

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

void Image::scaleTo(int width, int height)
{
    int originalWidth = this->width;
    int originalHeight = this->height;
    Image * originalData = new Image(this->width, this->height, 0, 0);
    for (int i = 0; i < this->width * this->height; i++) {
        int x = i % this->width;
        int y = i / this->width;
        originalData->setPixel(x, y, this->getPixel(x, y));
    }
    this->resize(width, height); //simply resizes the image, after the resize it is just a black bmp.
    double factorX = (double)originalWidth / width;
    double factorY = (double)originalHeight / height;
    float * xCenters = new float[originalWidth]; //the following stores the "centers" of each pixel.
    float * yCenters = new float[originalHeight];
    float * newXCenters = new float[width];
    float * newYCenters = new float[height];
    //1 represents one of the originally sized pixel's side length
    for (int i = 0; i < originalWidth; i++)
        xCenters[i] = i + 0.5;
    for (int i = 0; i < width; i++)
        newXCenters[i] = (factorX * i) + (factorX / 2.0);
    for (int i = 0; i < height; i++)
        newYCenters[i] = (factorY * i) + (factorY / 2.0);
    for (int i = 0; i < originalHeight; i++)
        yCenters[i] = i + 0.5;

    /*  p[0]            p[1]
                  p
        p[2]            p[3] */
    //the following will find the closest points to the sampled pixel that still remain in this order
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            POINT p[4]; //POINT used is the Win32 struct POINT
            float pDists[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
            float xDists[4];
            float yDists[4];
            for (int i = 0; i < originalWidth; i++) {
                for (int j = 0; j < originalHeight; j++) {
                    float xDist = abs(xCenters[i] - newXCenters[x]);
                    float yDist = abs(yCenters[j] - newYCenters[y]);
                    float dist = sqrt(xDist * xDist + yDist * yDist);
                    if (xCenters[i] < newXCenters[x] && yCenters[j] < newYCenters[y] && dist < pDists[0]) {
                        p[0] = { i, j };
                        pDists[0] = dist;
                        xDists[0] = xDist;
                        yDists[0] = yDist;
                    }
                    else if (xCenters[i] > newXCenters[x] && yCenters[j] < newYCenters[y] && dist < pDists[1]) {
                        p[1] = { i, j };
                        pDists[1] = dist;
                        xDists[1] = xDist;
                        yDists[1] = yDist;
                    }
                    else if (xCenters[i] < newXCenters[x] && yCenters[j] > newYCenters[y] && dist < pDists[2]) {
                        p[2] = { i, j };
                        pDists[2] = dist;
                        xDists[2] = xDist;
                        yDists[2] = yDist;
                    }
                    else if (xCenters[i] > newXCenters[x] && yCenters[j] > newYCenters[y] && dist < pDists[3]) {
                        p[3] = { i, j };
                        pDists[3] = dist;
                        xDists[3] = xDist;
                        yDists[3] = yDist;
                    }
                }
            }
            //channel is a typedef for unsigned char
            //getOPixel(point) is a macro for originalData->getPixel(point.x, point.y)
            float r1 = (xDists[3] / (xDists[2] + xDists[3])) * getOPixel(p[2]).r + (xDists[2] / (xDists[2] + xDists[3])) * getOPixel(p[3]).r; 
            float r2 = (xDists[1] / (xDists[0] + xDists[1])) * getOPixel(p[0]).r + (xDists[0] / (xDists[0] + xDists[1])) * getOPixel(p[1]).r; 
            float interpolated = (yDists[0] / (yDists[0] + yDists[3])) * r1 + (yDists[3] / (yDists[0] + yDists[3])) * r2;
            channel r = (channel)round(interpolated);

            r1 = (xDists[3] / (xDists[2] + xDists[3])) * getOPixel(p[2]).g + (xDists[2] / (xDists[2] + xDists[3])) * getOPixel(p[3]).g; //yDist[3]
            r2 = (xDists[1] / (xDists[0] + xDists[1])) * getOPixel(p[0]).g + (xDists[0] / (xDists[0] + xDists[1])) * getOPixel(p[1]).g; //yDist[0]
            interpolated = (yDists[0] / (yDists[0] + yDists[3])) * r1 + (yDists[3] / (yDists[0] + yDists[3])) * r2;
            channel g = (channel)round(interpolated);

            r1 = (xDists[3] / (xDists[2] + xDists[3])) * getOPixel(p[2]).b + (xDists[2] / (xDists[2] + xDists[3])) * getOPixel(p[3]).b; //yDist[3]
            r2 = (xDists[1] / (xDists[0] + xDists[1])) * getOPixel(p[0]).b + (xDists[0] / (xDists[0] + xDists[1])) * getOPixel(p[1]).b; //yDist[0]
            interpolated = (yDists[0] / (yDists[0] + yDists[3])) * r1 + (yDists[3] / (yDists[0] + yDists[3])) * r2;
            channel b = (channel)round(interpolated);

            this->setPixel(x, y, { r, g, b });
        }

    }
    delete[] xCenters;
    delete[] yCenters;
    delete[] newXCenters;
    delete[] newYCenters;
    delete originalData;
}

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

ОБНОВЛЕНИЕ: Итак, как и предполагалось, я начал увеличивать известный набор данных уменьшенными буквами из поиска слов. Это значительно улучшило точность примерно с 50% до 70% (проценты рассчитаны из очень небольшого размера выборки, поэтому относитесь к цифрам слегка). В основном, я использую исходный набор символов в качестве основы (этот оригинальный набор был на самом деле наиболее точным из других наборов, которые я пробовал ex: набор, рассчитанный с использованием того же алгоритма повторной выборки, набор с использованием другого шрифта и т. Д.) И я просто вручную добавляю известных к этому набору. Я в основном вручную назначу первые 20 или около того изображений, выбранных при поиске, их соответствующие буквы и сохраню их в известной папке набора. Я все еще выбираю самый близкий из всех известных наборов, чтобы соответствовать букве. Будет ли это все еще хорошим методом или должны быть внесены какие-то изменения? Я также реализовал функцию, при которой, если буква примерно на 90% совпадает с известной буквой, я предполагаю, что совпадение правильное и текущее "неизвестно" списку известных. Я мог видеть, что это возможно в обоих направлениях, я чувствую, что это может либо. сделать программу более точной с течением времени или б. укрепить первоначальное предположение и, возможно, со временем сделать программу менее точной. Я на самом деле не заметил, что это вызывает изменения (в лучшую или в худшую сторону). Я на правильном пути с этим? Я пока не собираюсь называть это решенным, пока не получу чуть более высокую точность и не протестирую программу на новых примерах.

1 ответ

Я предлагаю не изобретать велосипед и использовать любой существующий C++ OCR библиотека, как тессеракт

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