Простое распознавание знаков OCR в OpenCV-Python

Я пытаюсь реализовать "распознавание цифр 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)?

Ответ 1

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

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

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

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

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

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

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

enter image description here

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

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

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

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

enter image description here

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

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)

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

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

enter image description here

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

  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)

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

enter image description here


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

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

Ответ 2

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


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

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

//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, и мы не прошли обучение для точки. Также я рассматриваю каждый контур на первом уровне иерархии как образец ввода, пользователь может избежать этого, вычислив область.

Results

Ответ 3

Если вы заинтересованы в современном уровне техники в Machine Learning, вы должны изучить Deep Learning. У вас должен быть CUDA, поддерживающий GPU, или альтернативно использовать GPU на веб-сервисах Amazon.

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