OCR распознавания простых цифр в OpenCV-Python


380

Я пытаюсь реализовать «распознавание цифр OCR» в OpenCV-Python (cv2). Это только для учебных целей. Я хотел бы изучить возможности KNearest и SVM в OpenCV.

У меня есть 100 образцов (то есть изображений) каждой цифры. Я хотел бы тренироваться с ними.

Существует образец, letter_recog.pyкоторый поставляется с образцом OpenCV. Но я все еще не мог понять, как его использовать. Я не понимаю, что такое образцы, ответы и т. Д. Кроме того, сначала загружается текстовый файл, который я сначала не понял.

Позже, немного поискав, я смог найти letter_recognition.data в образцах cpp. Я использовал его и сделал код для cv2.KNearest в модели letter_recog.py (только для тестирования):

import numpy as np
import cv2

fn = 'letter-recognition.data'
a = np.loadtxt(fn, np.float32, delimiter=',', converters={ 0 : lambda ch : ord(ch)-ord('A') })
samples, responses = a[:,1:], a[:,0]

model = cv2.KNearest()
retval = model.train(samples,responses)
retval, results, neigh_resp, dists = model.find_nearest(samples, k = 10)
print results.ravel()

Это дало мне массив размером 20000, я не понимаю, что это такое.

Вопросов:

1) Что такое файл letter_recognition.data? Как создать этот файл из моего собственного набора данных?

2) Что results.reval()обозначает?

3) Как мы можем написать простой инструмент распознавания цифр, используя файл letter_recognition.data (либо KNearest, либо SVM)?

Ответы:


528

Ну, я решил потренироваться сам над своим вопросом, чтобы решить вышеуказанную проблему. Я хотел реализовать простое OCR с использованием функций KNearest или SVM в OpenCV. А ниже то, что я сделал и как. (это просто для изучения того, как использовать KNearest для простых целей OCR).

1) Мой первый вопрос был о файле letter_recognition.data, который поставляется с образцами OpenCV. Я хотел знать, что находится внутри этого файла.

Он содержит письмо, а также 16 функций этого письма.

И this SOFпомог мне найти его. Эти 16 функций объясняются в статье Letter Recognition Using Holland-Style Adaptive Classifiers. (Хотя я не понял некоторые функции в конце)

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

So I just decided to take all the pixel values as my features. (Я не беспокоился о точности или производительности, я просто хотел, чтобы это работало, по крайней мере, с наименьшей точностью)

Я взял изображение ниже для моих тренировочных данных:

введите описание изображения здесь

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

Чтобы подготовить данные для обучения, я сделал небольшой код в OpenCV. Это делает следующие вещи:

  1. Он загружает изображение.
  2. Выбирает цифры (очевидно, путем поиска контура и применения ограничений на область и высоту букв, чтобы избежать ложных обнаружений).
  3. Рисует ограничивающий прямоугольник вокруг одной буквы и жду key press manually. На этот раз мы нажимаем цифровую клавишу, соответствующую букве в поле.
  4. Как только соответствующая цифровая клавиша нажата, она изменяет размер этого поля до 10x10 и сохраняет 100-пиксельные значения в массиве (здесь образцы) и соответствующую вручную введенную цифру в другом массиве (здесь ответы)
  5. Затем сохраните оба массива в отдельных текстовых файлах.

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

введите описание изображения здесь

Ниже приведен код, который я использовал для вышеуказанной цели (конечно, не очень чистый):

import sys

import numpy as np
import cv2

im = cv2.imread('pitrain.png')
im3 = im.copy()

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
thresh = cv2.adaptiveThreshold(blur,255,1,1,11,2)

#################      Now finding Contours         ###################

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

samples =  np.empty((0,100))
responses = []
keys = [i for i in range(48,58)]

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)

        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            cv2.imshow('norm',im)
            key = cv2.waitKey(0)

            if key == 27:  # (escape to quit)
                sys.exit()
            elif key in keys:
                responses.append(int(chr(key)))
                sample = roismall.reshape((1,100))
                samples = np.append(samples,sample,0)

responses = np.array(responses,np.float32)
responses = responses.reshape((responses.size,1))
print "training complete"

np.savetxt('generalsamples.data',samples)
np.savetxt('generalresponses.data',responses)

Теперь мы приступаем к обучению и тестированию.

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

введите описание изображения здесь

Для обучения мы делаем следующее :

  1. Загрузите текстовые файлы, которые мы уже сохранили ранее
  2. создайте экземпляр используемого классификатора (здесь это KNearest)
  3. Затем мы используем функцию KNearest.train для обучения данных

Для целей тестирования мы делаем следующее:

  1. Загружаем изображение, используемое для тестирования
  2. обработайте изображение как ранее и извлеките каждую цифру, используя методы контура
  3. Нарисуйте для него ограничивающий прямоугольник, затем измените размер до 10x10 и сохраните значения его пикселей в массиве, как было сделано ранее.
  4. Затем мы используем функцию KNearest.find_nearest (), чтобы найти ближайший элемент к тому, который мы дали. (Если повезет, он распознает правильную цифру.)

Последние два шага (обучение и тестирование) я включил в один код ниже:

import cv2
import numpy as np

#######   training part    ############### 
samples = np.loadtxt('generalsamples.data',np.float32)
responses = np.loadtxt('generalresponses.data',np.float32)
responses = responses.reshape((responses.size,1))

model = cv2.KNearest()
model.train(samples,responses)

############################# testing part  #########################

im = cv2.imread('pi.png')
out = np.zeros(im.shape,np.uint8)
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)
        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            roismall = roismall.reshape((1,100))
            roismall = np.float32(roismall)
            retval, results, neigh_resp, dists = model.find_nearest(roismall, k = 1)
            string = str(int((results[0][0])))
            cv2.putText(out,string,(x,y+h),0,1,(0,255,0))

cv2.imshow('im',im)
cv2.imshow('out',out)
cv2.waitKey(0)

И это сработало, вот результат, который я получил:

введите описание изображения здесь


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

Но в любом случае, это хорошее начало для начинающих (я надеюсь, что так).


67
+1 Длинный пост, но очень познавательно. Это должно перейти к информации тега opencv
karlphillip

12
на случай, если кому-то будет интересно, я сделал из этого кода надлежащий OO-движок, а также некоторые навороты: github.com/goncalopp/simple-ocr-opencv
goncalopp

10
Обратите внимание, что нет необходимости использовать SVM и KNN, когда у вас есть четко определенный идеальный шрифт. Например, цифры 0, 4, 6, 9 образуют одну группу, цифры 1, 2, 3, 5, 7 образуют другую, а 8 - другую. Эта группа задается номером Эйлера. Тогда «0» не имеет конечных точек, «4» имеет две, а «6» и «9» различаются по положению центроида. «3» - единственный, в другой группе, с 3 конечными точками. «1» и «7» различаются по длине скелета. При рассмотрении выпуклой оболочки вместе с цифрой, «5» и «2» имеют два отверстия, и их можно отличить по центроиду самого большого отверстия.
ммгп

4
Есть проблема .. Спасибо. Это был отличный урок. Я делал небольшую ошибку. Если кто-то еще сталкивается с такой же проблемой, как я и @rash, то это потому, что вы нажимаете не ту клавишу. Для каждого числа в коробке вы должны ввести это нет, чтобы оно обучалось этому. Надеюсь, это поможет.
Шалки

19
Звездное учебное пособие. Спасибо! Есть несколько изменений, необходимых для работы с последней версией OpenCV (3.1): контуры, иерархия = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, контуры, иерархия = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => model = cv2.ml.KNearest_create (), model.train (образцы, ответы) => model.train (samples, cv2.ml) .ROW_SAMPLE, ответы), retval, результаты, adj_resp, dists = model.find_nearest (roismall, k = 1) => retval, результаты, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Йоханнес Бродуолл,

53

Для тех, кто интересуется кодом C ++, можете обратиться к приведенному ниже коду. Спасибо Абид Рахман за хорошее объяснение.


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

Код для создания образца и метки данных

//Process image to extract contour
Mat thr,gray,con;
Mat src=imread("digit.png",1);
cvtColor(src,gray,CV_BGR2GRAY);
threshold(gray,thr,200,255,THRESH_BINARY_INV); //Threshold to find contour
thr.copyTo(con);

// Create sample and label data
vector< vector <Point> > contours; // Vector for storing contour
vector< Vec4i > hierarchy;
Mat sample;
Mat response_array;  
findContours( con, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); //Find contour

for( int i = 0; i< contours.size(); i=hierarchy[i][0] ) // iterate through first hierarchy level contours
{
    Rect r= boundingRect(contours[i]); //Find bounding rect for each contour
    rectangle(src,Point(r.x,r.y), Point(r.x+r.width,r.y+r.height), Scalar(0,0,255),2,8,0);
    Mat ROI = thr(r); //Crop the image
    Mat tmp1, tmp2;
    resize(ROI,tmp1, Size(10,10), 0,0,INTER_LINEAR ); //resize to 10X10
    tmp1.convertTo(tmp2,CV_32FC1); //convert to float
    sample.push_back(tmp2.reshape(1,1)); // Store  sample data
    imshow("src",src);
    int c=waitKey(0); // Read corresponding label for contour from keyoard
    c-=0x30;     // Convert ascii to intiger value
    response_array.push_back(c); // Store label to a mat
    rectangle(src,Point(r.x,r.y), Point(r.x+r.width,r.y+r.height), Scalar(0,255,0),2,8,0);    
}

// Store the data to file
Mat response,tmp;
tmp=response_array.reshape(1,1); //make continuous
tmp.convertTo(response,CV_32FC1); // Convert  to float

FileStorage Data("TrainingData.yml",FileStorage::WRITE); // Store the sample data in a file
Data << "data" << sample;
Data.release();

FileStorage Label("LabelData.yml",FileStorage::WRITE); // Store the label data in a file
Label << "label" << response;
Label.release();
cout<<"Training and Label data created successfully....!! "<<endl;

imshow("src",src);
waitKey();

Код для обучения и тестирования

Mat thr,gray,con;
Mat src=imread("dig.png",1);
cvtColor(src,gray,CV_BGR2GRAY);
threshold(gray,thr,200,255,THRESH_BINARY_INV); // Threshold to create input
thr.copyTo(con);


// Read stored sample and label for training
Mat sample;
Mat response,tmp;
FileStorage Data("TrainingData.yml",FileStorage::READ); // Read traing data to a Mat
Data["data"] >> sample;
Data.release();

FileStorage Label("LabelData.yml",FileStorage::READ); // Read label data to a Mat
Label["label"] >> response;
Label.release();


KNearest knn;
knn.train(sample,response); // Train with sample and responses
cout<<"Training compleated.....!!"<<endl;

vector< vector <Point> > contours; // Vector for storing contour
vector< Vec4i > hierarchy;

//Create input sample by contour finding and cropping
findContours( con, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
Mat dst(src.rows,src.cols,CV_8UC3,Scalar::all(0));

for( int i = 0; i< contours.size(); i=hierarchy[i][0] ) // iterate through each contour for first hierarchy level .
{
    Rect r= boundingRect(contours[i]);
    Mat ROI = thr(r);
    Mat tmp1, tmp2;
    resize(ROI,tmp1, Size(10,10), 0,0,INTER_LINEAR );
    tmp1.convertTo(tmp2,CV_32FC1);
    float p=knn.find_nearest(tmp2.reshape(1,1), 1);
    char name[4];
    sprintf(name,"%d",(int)p);
    putText( dst,name,Point(r.x,r.y+r.height) ,0,1, Scalar(0, 255, 0), 2, 8 );
}

imshow("src",src);
imshow("dst",dst);
imwrite("dest.jpg",dst);
waitKey();

Результат

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

Результаты


1
Я устал, чтобы запустить этот код. Мне удалось создать образец и данные этикетки. Но когда я запускаю тестовый обучающий файл, он запускается с ошибкой, *** stack smashing detected ***:и, следовательно, я не получаю окончательное правильное изображение, как вы получаете выше (цифры зеленого цвета)
skm

1
Я изменил char name[4];ваш код на, char name[7];и я не получил ошибку, связанную со стеком, но все же я не получаю правильные результаты. Я получаю изображение как здесь < i.imgur.com/qRkV2B4.jpg >
skm

@skm Убедитесь, что вы получаете число контуров, равное количеству цифр в изображении, также попробуйте распечатать результат на консоли.
Харис

1
Здравствуйте, мы могли бы загрузить обученную сеть для использования?
yode

14

Если вас интересует современное состояние машинного обучения, вам стоит изучить Deep Learning. Вы должны иметь CUDA, поддерживающий GPU, или использовать GPU в Amazon Web Services.

У Google Udacity есть хороший урок по этому вопросу с использованием Tensor Flow . Из этого туториала вы узнаете, как обучить свой собственный классификатор написанным от руки цифрам. Я получил точность более 97% на тестовом наборе с использованием Convolutional Networks.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.