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)?
4 ответа
Ну, я решил потренироваться сам над своим вопросом, чтобы решить вышеуказанную проблему. Я хотел реализовать простое распознавание текста с использованием функций 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. Это делает следующие вещи:
А) Он загружает изображение.
Б) Выбирает цифры (очевидно, путем поиска контура и применения ограничений на область и высоту букв, чтобы избежать ложных обнаружений).
C) Рисует ограничивающий прямоугольник вокруг одной буквы и ждет key press manually
, На этот раз мы сами нажимаем цифровую клавишу, соответствующую букве в поле.
D) Как только соответствующая цифровая клавиша нажата, она изменяет размер этого поля до 10x10 и сохраняет 100 пиксельных значений в массиве (здесь, выборки) и соответствующую введенную вручную цифру в другом массиве (здесь, ответы).
E) Затем сохраните оба массива в отдельных текстовых файлах.
В конце ручной классификации цифр все цифры в данных поезда ( 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)
Теперь мы приступаем к обучению и тестированию.
Для тестирования части я использовал изображение ниже, которое имеет тот же тип букв, которые я использовал для обучения.
Для обучения мы делаем следующее:
A) Загрузите текстовые файлы, которые мы уже сохранили ранее
B) создать экземпляр используемого классификатора (здесь это KNearest)
C) Затем мы используем функцию KNearest.train для обучения данных
Для целей тестирования мы делаем следующее:
А) Загружаем изображение, используемое для тестирования
Б) обработать изображение как раньше и извлечь каждую цифру, используя методы контура
C) Нарисуйте ограничивающий прямоугольник для него, затем измените размер до 10x10 и сохраните значения его пикселей в массиве, как было сделано ранее.
D) Затем мы используем функцию 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% точностью. Я предполагаю, что это потому, что все цифры имеют одинаковый вид и размер.
Но в любом случае, это хорошее начало для начинающих (я надеюсь, что так).
Для тех, кто интересуется кодом 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, и мы не тренировались для точки. Также я рассматриваю каждый контур на первом уровне иерархии в качестве примера ввода, пользователь может избежать его, вычисляя площадь.
Если вас интересует современное состояние машинного обучения, вам стоит изучить Deep Learning. Вы должны иметь CUDA, поддерживающий GPU, или использовать GPU на Amazon Web Services.
У Google Udacity есть хороший урок по этому вопросу с использованием Tensor Flow. Из этого туториала вы узнаете, как обучить свой собственный классификатор написанным от руки цифрам. Я получил точность более 97% на тестовом наборе с использованием Convolutional Networks.
У меня возникли проблемы с генерацией обучающих данных, потому что иногда было сложно определить последнюю выбранную букву, поэтому я повернул изображение на 1,5 градуса. Теперь каждый персонаж выбран по порядку, и тест по-прежнему показывает 100% точность после обучения. Вот код:
import numpy as np
import cv2
def rotate_image(image, angle):
image_center = tuple(np.array(image.shape[1::-1]) / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
return result
img = cv2.imread('training_image.png')
cv2.imshow('orig image', img)
whiteBorder = [255,255,255]
# extend the image border
image1 = cv2.copyMakeBorder(img, 80, 80, 80, 80, cv2.BORDER_CONSTANT, None, whiteBorder)
# rotate the image 1.5 degrees clockwise for ease of data entry
image_rot = rotate_image(image1, -1.5)
#crop_img = image_rot[y:y+h, x:x+w]
cropped = image_rot[70:350, 70:710]
cv2.imwrite('rotated.png', cropped)
cv2.imshow('rotated image', cropped)
cv2.waitKey(0)
В качестве примера данных я внес в сценарий некоторые изменения, например:
import sys
import numpy as np
import cv2
def sort_contours(contours, x_axis_sort='LEFT_TO_RIGHT', y_axis_sort='TOP_TO_BOTTOM'):
# initialize the reverse flag
x_reverse = False
y_reverse = False
if x_axis_sort == 'RIGHT_TO_LEFT':
x_reverse = True
if y_axis_sort == 'BOTTOM_TO_TOP':
y_reverse = True
boundingBoxes = [cv2.boundingRect(c) for c in contours]
# sorting on x-axis
sortedByX = zip(*sorted(zip(contours, boundingBoxes),
key=lambda b:b[1][0], reverse=x_reverse))
# sorting on y-axis
(contours, boundingBoxes) = zip(*sorted(zip(*sortedByX),
key=lambda b:b[1][1], reverse=y_reverse))
# return the list of sorted contours and bounding boxes
return (contours, boundingBoxes)
im = cv2.imread('rotated.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)
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
contours, boundingBoxes = sort_contours(contours, x_axis_sort='LEFT_TO_RIGHT', y_axis_sort='TOP_TO_BOTTOM')
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 and h < 40:
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.ubyte)
responses = responses.reshape((responses.size,1))
print("training complete")
np.savetxt('generalsamples.data',samples,fmt='%i')
np.savetxt('generalresponses.data',responses,fmt='%i')