OpenCV-Python'da Basit Rakam Tanıma OCR


380

OpenCV-Python (cv2) bir "Rakam Tanıma OCR" uygulamaya çalışıyorum. Sadece öğrenme amaçlıdır. OpenCV'deki KNearest ve SVM özelliklerini öğrenmek istiyorum.

Her basamağın 100 örneğine (yani görüntüler) sahibim. Onlarla antrenman yapmak istiyorum.

letter_recog.pyOpenCV örneği ile birlikte gelen bir örnek vardır. Ama yine de nasıl kullanılacağını anlayamadım. Ben örnekleri, yanıtlar vb ne anlamıyorum. Ayrıca, ilk başta anlamadım bir txt dosyası yükler.

Daha sonra biraz arama yaparken, cpp örneklerinde bir letter_recognition.data bulabilirim. Ben kullandım ve letter_recog.py modelinde cv2.KNearest için bir kod yaptım (sadece test için):

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()

Bana 20000 büyüklüğünde bir dizi verdi, ne olduğunu anlamıyorum.

Sorular:

1) letter_recognition.data dosyası nedir? Bu dosyayı kendi veri kümemden nasıl oluşturabilirim?

2) Neyi ifade results.reval()eder?

3) letter_recognition.data dosyasını (KNearest veya SVM) kullanarak basit bir basamak tanıma aracını nasıl yazabiliriz?

Yanıtlar:


527

Yukarıdaki sorunu çözmek için soruma kendim egzersiz yapmaya karar verdim. İstediğim, OpenCV'deki KNearest veya SVM özelliklerini kullanarak basit bir OCR uygulamak. Ve aşağıda ne yaptığımı ve nasıl olduğunu. (sadece KNearest'in basit OCR amaçları için nasıl kullanılacağını öğrenmek içindir).

1) İlk sorum OpenCV örnekleri ile gelen letter_recognition.data dosyası hakkındaydı. O dosyanın içinde ne olduğunu bilmek istedim.

Bir mektubu ve o mektubun 16 özelliğini içerir.

Ve this SOFonu bulmama yardım etti. Bu 16 özellik makalede açıklanmıştır Letter Recognition Using Holland-Style Adaptive Classifiers. (Sonunda bazı özellikleri anlamadığım halde)

2) Tüm bu özellikleri anlamadan bildiğim için, bu yöntemi yapmak zordur. Başka makaleler denedim, ama yeni başlayanlar için hepsi biraz zordu.

So I just decided to take all the pixel values as my features. (Doğruluk veya performans konusunda endişelenmedim, sadece en azından en az doğrulukla çalışmasını istedim)

Eğitim verilerim için aşağıdaki görüntüyü aldım:

resim açıklamasını buraya girin

(Antrenman verilerinin daha az olduğunu biliyorum. Ancak, tüm harfler aynı yazı tipi ve boyutta olduğu için bunu denemeye karar verdim).

Verileri eğitime hazırlamak için OpenCV'de küçük bir kod hazırladım. Aşağıdaki şeyleri yapar:

  1. Görüntüyü yükler.
  2. Rakamları seçer (açıkçası yanlış tespitleri önlemek için kontur bulma ve harflerin alanı ve yüksekliği üzerinde kısıtlamalar uygulayarak).
  3. Sınırlayıcı dikdörtgeni bir harfin çevresine çizer ve bekleyin key press manually. Bu sefer kutudaki harfe karşılık gelen rakam tuşuna basıyoruz .
  4. Karşılık gelen sayı tuşuna basıldığında, bu kutuyu 10x10 değerine yeniden boyutlandırır ve bir diziye (burada, örnekler) 100 piksel değeri ve başka bir diziye karşılık gelen manuel olarak girilen basamağı (burada yanıtlar) kaydeder.
  5. Ardından her iki diziyi ayrı txt dosyalarına kaydedin.

Rakamların manuel olarak sınıflandırılmasının sonunda, tren verilerindeki (train.png) tüm rakamlar kendimiz manuel olarak etiketlenir, görüntü aşağıdaki gibi görünecektir:

resim açıklamasını buraya girin

Aşağıda yukarıdaki amaç için kullandığım kod (tabii ki, çok temiz değil):

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)

Şimdi eğitim ve test bölümüne giriyoruz.

Test bölümü için ben eğitmek için kullandığım aynı tür harfleri olan görüntü altında kullandım.

resim açıklamasını buraya girin

Eğitim için aşağıdakileri yapıyoruz :

  1. Daha önce kaydettiğimiz txt dosyalarını yükleyin
  2. kullandığımız bir sınıflandırıcı örneği oluşturun (burada, KNearest)
  3. Sonra verileri eğitmek için KNearest.train işlevini kullanıyoruz

Test amaçlı olarak aşağıdakileri yapıyoruz:

  1. Test için kullanılan resmi yüklüyoruz
  2. görüntüyü daha önceki gibi işleyin ve her basamağı kontur yöntemlerini kullanarak çıkarın
  3. Bunun için sınırlayıcı kutu çizin, ardından 10x10 değerine yeniden boyutlandırın ve piksel değerlerini daha önce olduğu gibi bir dizide saklayın.
  4. Sonra verdiğimiz öğeye en yakın öğeyi bulmak için KNearest.find_nearest () işlevini kullanırız. (Şanslıysa, doğru rakamı tanır.)

Son iki adımı (eğitim ve test) aşağıdaki tek koda ekledim:

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)

Ve işe yaradı, aşağıda elde ettiğim sonuç:

resim açıklamasını buraya girin


Burada% 100 doğrulukla çalıştı. Bunun tüm basamakların aynı tür ve aynı boyutta olması nedeniyle olduğunu düşünüyorum.

Ama her şekilde, bu yeni başlayanlar için gitmek için iyi bir başlangıç ​​(umarım).


67
+1 Uzun yazı, ama çok eğitici. Bu opencv etiket bilgisi
karlphillip

12
birinin ilgilenmesi durumunda, bu koddan, bazı çan ve ıslıklarla birlikte uygun bir OO motoru yaptım: github.com/goncalopp/simple-ocr-opencv
goncalopp

10
İyi tanımlanmış mükemmel bir yazı tipiniz olduğunda SVM ve KNN kullanmaya gerek olmadığını unutmayın. Örneğin, 0, 4, 6, 9 rakamları bir grup, 1, 2, 3, 5, 7 rakamları başka bir grup ve 8 başka bir grup oluşturur. Bu grup euler numarasına göre verilir. Daha sonra "0" ın uç noktası yoktur, "4" iki tane içerir ve "6" ve "9", sentroid pozisyonu ile ayırt edilir. "3" diğer grupta 3 uç noktaya sahip olan tek gruptur. "1" ve "7", iskelet uzunluğu ile ayırt edilir. Dışbükey gövdeyi rakam ile birlikte değerlendirirken, "5" ve "2" nin iki deliği vardır ve en büyük deliğin sentroidi ile ayırt edilebilirler.
mmgp

4
Sorun var .. Teşekkürler. Harika bir öğreticiydi. Küçük bir hata yapıyordum. Eğer başka biri benim ve @rash gibi aynı sorunla karşı karşıya kalırsa bunun nedeni yanlış tuşa basmanızdır. Kutudaki her numara için, üzerine eğitim alması için hayır girmeniz gerekir. Umarım yardımcı olur.
shalki

19
Mükemmel bir öğretici. Teşekkür ederim! Bunu OpenCV'nin en son (3.1) versjonkuyla çalışmak için gereken birkaç değişiklik vardır: konturlar, hiyerarşi = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, kontürler, hiyerarşi = cv2.findContours (harmanlama, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => model = cv2.ml.KNearest_create (), model.train (örnekler, yanıtlar) => model.train (örnekler, cv2.ml .ROW_SAMPLE, yanıtlar), retval, sonuçlar, neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval, sonuçlar, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Johannes Brodwall

53

C ++ kodu ile ilgilenenler için aşağıdaki kodu başvurabilirsiniz. Güzel açıklama için Abid Rahman'a teşekkürler .


Prosedür yukarıdakiyle aynıdır, ancak kontur bulma sadece ilk hiyerarşi seviyesi konturunu kullanır, böylece algoritma her basamak için sadece dış kontur kullanır.

Örnek ve Etiket verileri oluşturma kodu

//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();

Eğitim ve test kodu

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();

Sonuç

Sonuç olarak, ilk satırdaki nokta 8 olarak algılanır ve nokta için eğitim almadık. Ayrıca birinci hiyerarşi düzeyinde her konturu örnek girdi olarak görüyorum, kullanıcı alanı hesaplayarak bundan kaçınabilir.

Sonuçlar


1
Bu kodu çalıştırmak için yoruldum. Örnek ve etiket verileri oluşturabildim. Ama ben test-eğitim dosyasını çalıştırdığınızda, bir hata ile çalışır *** stack smashing detected ***:ve bu nedenle yukarıda (yeşil renkli rakamlar) gibi son bir doğru görüntü elde
edemiyorum

1
i değiştirmek char name[4];için kodunuzda char name[7];ve ben yığını ilgili hata alamadım ama yine de ben doğru sonuçları almıyorum. Burada gibi bir görüntü alıyorum < i.imgur.com/qRkV2B4.jpg >
skm

@skm Görüntüdeki basamak sayısıyla aynı kontur sayısını aldığınızdan emin olun, ayrıca sonucu konsolda yazdırmayı deneyin.
Haris

1
Merhaba, kullanmak için eğitimli bir ağ yükleyebilir miyiz?
yode

14

Makine Öğrenimi alanında son teknoloji ile ilgileniyorsanız, Derin Öğrenme konusuna bakmalısınız. GPU'yu destekleyen bir CUDA'nız olmalı veya alternatif olarak Amazon Web Services'ta GPU'yu kullanmalısınız.

Google Udacity'nin bu konuda Tensor Flow'u kullanarak güzel bir öğreticisi var . Bu eğitici, kendi sınıflandırıcıyı elle yazılmış basamaklar üzerinde nasıl eğiteceğinizi öğretecektir. Convolutional Networks kullanarak test setinde% 97'nin üzerinde bir doğruluk elde ettim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.