OpenCV-Python의 간단한 숫자 인식 OCR


380

OpenCV-Python (cv2)에서 "Digit Recognition OCR"을 구현하려고합니다. 그것은 단지 학습 목적입니다. OpenCV의 KNearest 및 SVM 기능을 모두 배우고 싶습니다.

각 숫자의 샘플 100 개 (예 : 이미지)가 있습니다. 그들과 함께 훈련하고 싶습니다.

샘플이 letter_recog.pyOpenCV의 샘플이 제공됩니다. 그러나 나는 여전히 그것을 사용하는 방법을 알 수 없었습니다. 나는 샘플, 응답 등이 무엇인지 이해하지 못합니다. 또한 처음에는 txt 파일을로드하지만 처음에는 이해하지 못했습니다.

나중에 조금 검색하면 cpp 샘플에서 letter_recognition.data를 찾을 수 있습니다. 나는 그것을 사용하고 letter_recog.py 모델에서 cv2.KNearest 코드를 만들었습니다 (테스트 용).

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

글쎄, 나는 위의 문제를 해결하기 위해 내 질문에 스스로 운동하기로 결정했다. 내가 원하는 것은 OpenCV에서 KNearest 또는 SVM 기능을 사용하여 간단한 OCR을 구현하는 것입니다. 아래는 내가 한 일과 방법입니다. (단순한 OCR 목적으로 KNearest를 사용하는 방법을 배우기위한 것입니다).

1) 첫 번째 질문은 OpenCV 샘플과 함께 제공되는 letter_recognition.data 파일에 관한 것입니다. 그 파일 안에 무엇이 있는지 알고 싶었습니다.

그것은 그 편지의 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_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"에는 두 개의 구멍이 있으며 가장 큰 구멍의 중심으로 구별 할 수 있습니다.
mmgp

4
문제가 생겼습니다. 감사합니다. 훌륭한 튜토리얼이었습니다. 나는 작은 실수를하고 있었다. 다른 사람이 나와 @rash와 같은 문제에 직면하면 잘못된 키를 누르기 때문입니다. 상자에있는 각 번호에 대해 훈련을 받으려면 해당 번호를 입력해야합니다. 희망이 도움이됩니다.
shalki

19
훌륭한 튜토리얼. 감사합니다! 이것을 OpenCV의 최신 (3.1) 버전에서 사용하려면 몇 가지 변경 사항이 있습니다. contours, hierarchy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, contours, hierarchy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), 모델 = cv2.KNearest () => 모델 = cv2.ml.KNearest_create (), model.train (samples, responses) => model.train (samples, cv2.ml .ROW_SAMPLE, 응답), 답답, 결과, neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval, 결과, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Johannes Brodwall

53

C ++ 코드에 관심이있는 사람들은 아래 코드를 참조하십시오. 좋은 설명을 해준 Abid Rahman 에게 감사드립니다 .


절차는 위와 동일하지만 형상 찾기는 첫 번째 계층 레벨 윤곽 만 사용하므로 알고리즘은 각 숫자에 대해 외부 윤곽 만 사용합니다.

샘플 및 라벨 데이터 생성을위한 코드

//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 이미지의 자릿수와 동일한 윤곽 수를 얻고 콘솔에 결과를 인쇄 해보십시오.
Haris

1
안녕하세요, 훈련 된 그물을 사용하여로드 할 수 있습니까?
yode

14

기계 학습의 최신 기술에 관심이 있다면 딥 러닝을 살펴 봐야합니다. GPU를 지원하는 CUDA가 있거나 Amazon Web Services에서 GPU를 사용해야합니다.

Google Udacity는 Tensor Flow를 사용하여 이에 대한 훌륭한 자습서를 제공합니다 . 이 튜토리얼에서는 직접 작성한 숫자로 자신의 분류기를 훈련시키는 방법을 알려줍니다. Convolutional Networks를 사용한 테스트 세트에서 97 % 이상의 정확도를 얻었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.