OCR nhận dạng chữ số đơn giản trong OpenCV-Python


380

Tôi đang cố gắng thực hiện "OCR nhận dạng chữ số" trong OpenCV-Python (cv2). Nó chỉ dành cho mục đích học tập. Tôi muốn tìm hiểu cả hai tính năng KNearest và SVM trong OpenCV.

Tôi có 100 mẫu (tức là hình ảnh) của mỗi chữ số. Tôi muốn đào tạo với họ.

Có một mẫu letter_recog.pyđi kèm với mẫu OpenCV. Nhưng tôi vẫn không thể tìm ra cách sử dụng nó. Tôi không hiểu các mẫu, phản hồi là gì, v.v. Ngoài ra, lúc đầu, nó tải một tệp txt, mà trước tiên tôi không hiểu.

Sau này khi tìm kiếm một chút, tôi có thể tìm thấy một letter_recognition.data trong các mẫu cpp. Tôi đã sử dụng nó và tạo mã cho cv2.KNearest trong mô hình của letter_recog.py (chỉ để thử nghiệm):

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

Nó cho tôi một mảng kích thước 20000, tôi không hiểu nó là gì.

Câu hỏi:

1) Tệp letter_recognition.data là gì? Làm thế nào để xây dựng tập tin đó từ tập dữ liệu của riêng tôi?

2) Cái gì results.reval()biểu thị?

3) Làm thế nào chúng ta có thể viết một công cụ nhận dạng chữ số đơn giản bằng cách sử dụng tệp letter_recognition.data (KNearest hoặc SVM)?

Câu trả lời:


527

Vâng, tôi quyết định tự tập luyện theo câu hỏi của mình để giải quyết vấn đề trên. Điều tôi muốn là triển khai OCR đơn giản bằng các tính năng KNearest hoặc SVM trong OpenCV. Và dưới đây là những gì tôi đã làm và làm thế nào. (nó chỉ dành cho việc học cách sử dụng KNearest cho các mục đích OCR đơn giản).

1) Câu hỏi đầu tiên của tôi là về tệp letter_recognition.data đi kèm với các mẫu OpenCV. Tôi muốn biết những gì bên trong tập tin đó.

Nó chứa một chữ cái, cùng với 16 tính năng của bức thư đó.

this SOFgiúp tôi tìm thấy nó. 16 tính năng này được giải thích trong bài báo Letter Recognition Using Holland-Style Adaptive Classifiers. (Mặc dù tôi không hiểu một số tính năng ở cuối)

2) Vì tôi biết, nếu không hiểu tất cả các tính năng đó, thật khó để thực hiện phương pháp đó. Tôi đã thử một số giấy tờ khác, nhưng tất cả đều hơi khó khăn cho người mới bắt đầu.

So I just decided to take all the pixel values as my features. (Tôi không lo lắng về độ chính xác hoặc hiệu suất, tôi chỉ muốn nó hoạt động, ít nhất là với độ chính xác thấp nhất)

Tôi lấy hình ảnh dưới đây cho dữ liệu đào tạo của tôi:

nhập mô tả hình ảnh ở đây

(Tôi biết số lượng dữ liệu đào tạo ít hơn. Nhưng, vì tất cả các chữ cái có cùng phông chữ và kích thước, tôi quyết định thử về điều này).

Để chuẩn bị dữ liệu cho đào tạo, tôi đã tạo một mã nhỏ trong OpenCV. Nó làm những điều sau đây:

  1. Nó tải hình ảnh.
  2. Chọn các chữ số (rõ ràng bằng cách tìm đường viền và áp dụng các ràng buộc về diện tích và chiều cao của chữ cái để tránh phát hiện sai).
  3. Vẽ hình chữ nhật giới hạn xung quanh một chữ cái và chờ đợi key press manually. Lần này chúng ta tự bấm phím chữ số tương ứng với chữ cái trong hộp.
  4. Khi nhấn phím chữ số tương ứng, nó sẽ thay đổi kích thước hộp này thành 10 x 10 và lưu 100 giá trị pixel trong một mảng (ở đây, các mẫu) và chữ số được nhập thủ công tương ứng trong một mảng khác (ở đây, phản hồi).
  5. Sau đó lưu cả hai mảng trong các tệp txt riêng biệt.

Khi kết thúc phân loại thủ công các chữ số, tất cả các chữ số trong dữ liệu xe lửa (train.png) đều được chúng tôi dán nhãn thủ công, hình ảnh sẽ như sau:

nhập mô tả hình ảnh ở đây

Dưới đây là mã tôi đã sử dụng cho mục đích trên (tất nhiên, không quá rõ ràng):

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)

Bây giờ chúng tôi nhập vào để đào tạo và kiểm tra một phần.

Để kiểm tra phần tôi đã sử dụng bên dưới hình ảnh, có cùng loại chữ tôi đã sử dụng để đào tạo.

nhập mô tả hình ảnh ở đây

Đối với đào tạo, chúng tôi làm như sau :

  1. Tải các tệp txt mà chúng tôi đã lưu trước đó
  2. tạo một thể hiện của trình phân loại mà chúng ta đang sử dụng (ở đây, nó là KNearest)
  3. Sau đó, chúng tôi sử dụng hàm KNearest.train để đào tạo dữ liệu

Đối với mục đích thử nghiệm, chúng tôi làm như sau:

  1. Chúng tôi tải hình ảnh được sử dụng để thử nghiệm
  2. xử lý hình ảnh như trước đó và trích xuất từng chữ số bằng các phương pháp đường viền
  3. Vẽ hộp giới hạn cho nó, sau đó thay đổi kích thước thành 10 x 10 và lưu trữ các giá trị pixel của nó trong một mảng như được thực hiện trước đó.
  4. Sau đó, chúng tôi sử dụng hàm KNearest.find_nearest () để tìm mục gần nhất với mục chúng tôi đã cung cấp. (Nếu may mắn, nó nhận ra chữ số chính xác.)

Tôi đã bao gồm hai bước cuối cùng (đào tạo và kiểm tra) trong một mã dưới đây:

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)

Và nó đã hoạt động, dưới đây là kết quả tôi nhận được:

nhập mô tả hình ảnh ở đây


Ở đây nó hoạt động với độ chính xác 100%. Tôi cho rằng điều này là do tất cả các chữ số cùng loại và cùng kích cỡ.

Nhưng dù sao đi nữa, đây là một khởi đầu tốt cho người mới bắt đầu (tôi hy vọng vậy).


67
+1 Bài viết dài, nhưng rất giáo dục. Điều này sẽ đi đến thông tin thẻ opencv
karlphillip

12
trong trường hợp có ai đó quan tâm, tôi đã tạo ra một công cụ OO thích hợp từ mã này, cùng với một số tiếng chuông và còi: github.com/goncalopp/simple-oc-opencv
goncalopp

10
Lưu ý rằng không cần sử dụng SVM và KNN khi bạn có một phông chữ hoàn hảo được xác định rõ. Chẳng hạn, các chữ số 0, 4, 6, 9 tạo thành một nhóm, các chữ số 1, 2, 3, 5, 7 tạo thành một chữ số khác và 8 chữ số khác. Nhóm này được đưa ra bởi số euler. Sau đó "0" không có điểm cuối, "4" có hai và "6" và "9" được phân biệt bởi vị trí trung tâm. "3" là người duy nhất, trong nhóm khác, có 3 điểm cuối. "1" và "7" được phân biệt bởi chiều dài bộ xương. Khi xem xét thân tàu lồi cùng với chữ số, "5" và "2" có hai lỗ và chúng có thể được phân biệt bằng tâm của lỗ lớn nhất.
mmgp

4
Có vấn đề .. Cảm ơn bạn. Đó là một hướng dẫn tuyệt vời. Tôi đã phạm một lỗi nhỏ. Nếu bất cứ ai khác phải đối mặt với vấn đề tương tự như tôi và @rash thì đó là do bạn đang nhấn nhầm phím. Đối với mỗi số trong hộp, bạn phải nhập không để nó được đào tạo về nó. Mong rằng sẽ giúp.
shalki

19
Một hướng dẫn tuyệt vời. Cảm ơn bạn! Có một vài thay đổi cần thiết để làm việc này với phiên bản mới nhất (3.1) của OpenCV: contours, hVELy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, contours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => model = cv2.ml.KNearest_create (), model.train (mẫu, phản hồi) => model. .ROW_SAMPLE, phản ứng), retval, kết quả, neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval, kết quả, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Johannes Brodwall

53

Đối với những người quan tâm đến mã C ++ có thể tham khảo mã bên dưới. Cảm ơn Abid Rahman vì lời giải thích tốt đẹp.


Quy trình tương tự như trên nhưng, việc tìm đường viền chỉ sử dụng đường viền mức phân cấp đầu tiên, do đó thuật toán chỉ sử dụng đường viền ngoài cho mỗi chữ số.

Mã để tạo mẫu và dữ liệu Nhãn

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

Mã đào tạo và kiểm tra

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

Kết quả

Kết quả là dấu chấm trong dòng đầu tiên được phát hiện là 8 và chúng tôi chưa được đào tạo về dấu chấm. Ngoài ra tôi đang xem xét mọi đường viền ở cấp độ phân cấp đầu tiên là đầu vào mẫu, người dùng có thể tránh nó bằng cách tính toán khu vực.

Các kết quả


1
Tôi mệt mỏi để chạy mã này. Tôi đã có thể tạo dữ liệu mẫu và nhãn. Nhưng khi tôi chạy tệp đào tạo thử nghiệm, nó chạy có lỗi *** stack smashing detected ***:và do đó tôi không nhận được hình ảnh phù hợp cuối cùng như bạn đang ở trên (chữ số màu xanh lục)
skm

1
Tôi thay đổi char name[4];mã của bạn thành char name[7];và tôi đã không nhận được lỗi liên quan đến ngăn xếp nhưng tôi vẫn không nhận được kết quả chính xác. Tôi đang nhận được một hình ảnh như ở đây < i.imgur.com/qRkV2B4.jpg >
skm

@skm Đảm bảo rằng bạn đang nhận được số đường viền giống như số chữ số trong hình ảnh, cũng thử bằng cách in kết quả trên bảng điều khiển.
Haris

1
Xin chào, chúng tôi có thể tải một mạng được đào tạo để sử dụng?
yode

14

Nếu bạn quan tâm đến trạng thái của nghệ thuật trong Machine Learning, bạn nên tìm hiểu về Deep Learning. Bạn nên có GPU hỗ trợ CUDA hoặc sử dụng thay thế GPU trên Dịch vụ web của Amazon.

Google Udacity có một hướng dẫn tuyệt vời về điều này bằng cách sử dụng Tensor Flow . Hướng dẫn này sẽ dạy cho bạn cách huấn luyện bộ phân loại của riêng bạn trên các chữ số viết tay. Tôi đã nhận được độ chính xác hơn 97% trên bộ thử nghiệm bằng cách sử dụng Mạng kết hợp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.