Подготовить сложное изображение для OCR - PullRequest
11 голосов
/ 22 февраля 2012

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

Текущая проблема - предварительная обработка изображения. Поскольку кредитные карты могут иметь фон и другую сложную графику, текст не такой четкий, как при сканировании документа. Я проводил эксперименты с обнаружением краев (Canny Edge, Sobel), но это не было так успешно. Кроме того, вычисление разницы между изображением в оттенках серого и размытым (как указано в Удалите цвет фона при обработке изображений для OCR ) не привело к результату OCRable.

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

Есть ли у вас какие-либо предложения, как преобразовать источник в читаемое двоичное изображение? Является ли определение края подходящим способом или я должен придерживаться базового определения цвета?

Вот пример подхода с использованием градаций серого (где я явно не доволен результатами):

Исходное изображение:

Original image

Изображение в оттенках серого:

Greyscale image

Пороговое изображение:

Thresholded image

Спасибо за любой совет, Валентин

Ответы [ 3 ]

5 голосов
/ 23 февраля 2012

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

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

Реализация преобразования ширины штриха (SWT) (Java, C # ...)

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

Есливы можете идентифицировать частичные сегменты символов, затем вы можете использовать некоторые направленные операции морфологии, чтобы помочь объединить сегменты.Например, если у вас есть два почти горизонтальных сегмента, как показано ниже, где 0 - это фон, а 1 - это передний план ...

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0
0 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 0

, тогда вы можете выполнить морфологическую операцию «закрыть» вдоль горизонтального направления.только чтобы присоединиться к этим сегментам.Ядро может быть чем-то вроде

x x x x x
1 1 1 1 1
x x x x x

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

5 голосов
/ 22 февраля 2012

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

Пример:

Работать только с областью 20 пикселей снизу, 30 пикселей от слева до 10 пикселей справа до 30 пикселей снизу (создание прямоугольник) - это будет охватывать все карты MasterCard

Когда я работал с программами обработки изображений (веселый проект), я увеличил контраст изображения, преобразовал его в шкалу серого, взял среднее значение каждого отдельного значения RGB в 1 пиксель и сравнил его со всеми вокруг пикселями:

Пример:

PixAvg[i,j] = (Pix.R + Pix.G + Pix.B)/3
if ((PixAvg[i,j] - PixAvg[i,j+1])>30)
    boolEdge == true;

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

В моем проекте для просмотра обнаружения краев я создал отдельный массив логических значений, который содержал значения из boolEdge, и массив пикселей. Пиксельный массив был заполнен только черными и белыми точками. Он получил значения из логического массива, где boolEdge = true - белая точка, а boolEdge = false - черная точка. Таким образом, в итоге вы получите массив пикселей (полное изображение), который содержит только белые и черные точки.

Оттуда намного легче определить, где начинается число, а где заканчивается число.

1 голос
/ 25 февраля 2014

в моей реализации я попытался использовать код здесь: http://rnd.azoft.com/algorithm-identifying-barely-legible-embossed-text-image/ результаты лучше, но не достаточно ... мне трудно найти правильные параметры для текстурных карт.

(void)processingByStrokesMethod:(cv::Mat)src dst:(cv::Mat*)dst { 
cv::Mat tmp;  
cv::GaussianBlur(src, tmp, cv::Size(3,3), 2.0);                    // gaussian blur  
tmp = cv::abs(src - tmp);                                          // matrix of differences between source image and blur iamge  

//Binarization:  
cv::threshold(tmp, tmp, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);  

//Using method of strokes:  
int Wout = 12;  
int Win = Wout/2;  
int startXY = Win;  
int endY = src.rows - Win;  
int endX = src.cols - Win;  

for (int j = startXY; j < endY; j++) {  
    for (int i = startXY; i < endX; i++) {  
        //Only edge pixels:  
        if (tmp.at<unsigned char="">(j,i) == 255)  
        {  
            //Calculating maxP and minP within Win-region:  
            unsigned char minP = src.at<unsigned char="">(j,i);  
            unsigned char maxP = src.at<unsigned char="">(j,i);  
            int offsetInWin = Win/2;  

            for (int m = - offsetInWin; m < offsetInWin; m++) {  
                for (int n = - offsetInWin; n < offsetInWin; n++) {  
                    if (src.at<unsigned char="">(j+m,i+n) < minP) {  
                        minP = src.at<unsigned char="">(j+m,i+n);  
                    }else if (src.at<unsigned char="">(j+m,i+n) > maxP) {  
                        maxP = src.at<unsigned char="">(j+m,i+n);  
                    }  
                }  
            }  

            //Voiting:  
            unsigned char meanP = lroundf((minP+maxP)/2.0);  

            for (int l = -Win; l < Win; l++) {  
                for (int k = -Win; k < Win; k++) {  
                    if (src.at<unsigned char="">(j+l,i+k) >= meanP) {  
                        dst->at<unsigned char="">(j+l,i+k)++;  
                    }  
                }  
            }  
        }  
    }  
}  

///// Normalization of imageOut:  
unsigned char maxValue = dst->at<unsigned char="">(0,0);  

for (int j = 0; j < dst->rows; j++) {              //finding max value of imageOut  
    for (int i = 0; i < dst->cols; i++) {  
        if (dst->at<unsigned char="">(j,i) > maxValue)  
            maxValue = dst->at<unsigned char="">(j,i);  
    }  
}  
float knorm = 255.0 / maxValue;  

for (int j = 0; j < dst->rows; j++) {             //normalization of imageOut  
    for (int i = 0; i < dst->cols; i++) {  
        dst->at<unsigned char="">(j,i) = lroundf(dst->at<unsigned char="">(j,i)*knorm);  
    }  
}  
...