ocr — Распознавание символов по изображению Переполнение стека

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

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

  ■ ■                 ■   ■
■                     ■

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

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

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

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

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

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

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

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% совпадает с известной буквой, я предполагаю, что совпадение правильное и текущее «неизвестно» списку известных. Я мог видеть, что это возможно в обоих направлениях, я чувствую, что это может либо. сделать программу более точной с течением времени или б. укрепить первоначальное предположение и, возможно, со временем сделать программу менее точной. Я на самом деле не заметил, что это вызывает изменения (в лучшую или в худшую сторону). Я на правильном пути с этим? Я пока не собираюсь называть это решенным, пока не получу чуть более высокую точность и не протестирую программу на новых примерах.

4

Решение

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

0

Другие решения

Других решений пока нет …