Просте розпізнавання цифр OCR у OpenCV-Python


380

Я намагаюся реалізувати "розпізнавання цифр OCR" у OpenCV-Python (cv2). Це просто з метою навчання. Я хотів би дізнатись і функції KNearest, і SVM в OpenCV.

У мене є 100 зразків (тобто зображень) кожної цифри. Я хотів би тренуватися з ними.

Є зразок, letter_recog.pyякий поставляється із зразком OpenCV. Але я все ще не міг розібратися, як ним користуватися. Я не розумію, що таке зразки, відповіді тощо. Також спочатку він завантажує файл txt, який я спочатку не зрозумів.

Пізніше, коли я трохи пошукав, я міг знайти 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)?

Відповіді:


527

Що ж, я вирішив потренуватися на своєму питанні, щоб вирішити вищезгадану проблему. Що я хотів - це реалізувати спрощений 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. Потім збережіть обидва масиви в окремих файлах txt.

Наприкінці ручної класифікації цифр усі цифри в даних поїзда (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. Завантажте файли txt, які ми вже зберегли раніше
  2. створити екземпляр класифікатора, який ми використовуємо (ось це KNearest)
  3. Тоді ми використовуємо функцію KNearest.train для підготовки даних

Для тестування ми робимо наступне:

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

Останні два етапи (навчання та тестування) я включив у єдиний код нижче:

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

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" мають два отвори, і їх можна розрізнити за центроїдом найбільшого отвору.
mmgp

4
Зрозумів проблему .. Дякую. Це був чудовий підручник. Я робив невелику помилку. Якщо хтось ще стикається з таким самим питанням, як я і @rash, це тому, що ви натискаєте неправильну клавішу. Для кожного номера у полі ви повинні ввести це число, щоб воно не навчалось на ньому. Сподіваюся, що це допомагає.
шалкі

19
Зоряний підручник. Дякую! Для роботи з останньою (3.1) версією OpenCV потрібно кілька змін: контури, ієрархія = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, контури, ієрархія = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => модель = cv2.ml.KNearest_create (), model.train (зразки, відповіді) => model.train (зразки, cv2.ml .ROW_SAMPLE, відповіді), вилучення, результати, susjed_resp, dists = model.find_nevable (roismall, k = 1) => відкликання, результати, susjed_resp, dists = model.find_nevable (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
Здрастуйте, чи могли б ми завантажити завантажену мережу?
йоде

14

Якщо ви зацікавлені в стані машинного навчання, вам слід заглянути в Deep Learning. У вас має бути CUDA, що підтримує GPU, або альтернативно використовувати GPU на Amazon Web Services.

Google Udacity має хороший підручник з цього приводу за допомогою тензору потоку . Цей підручник навчить вас тренувати власний класифікатор за рукописними цифрами. Я отримав точність понад 97% на тестовому наборі за допомогою Convolutional Networks.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.