OCR การรับรู้ตัวเลขอย่างง่ายใน OpenCV-Python


380

ฉันกำลังพยายามใช้ "OCR Recognition Digit" ใน OpenCV-Python (cv2) มันเป็นเพียงเพื่อการเรียนรู้ ฉันต้องการเรียนรู้ทั้งคุณสมบัติ KNearest และ SVM ใน OpenCV

ฉันมี 100 ตัวอย่าง (เช่นภาพ) ของแต่ละหลัก ฉันต้องการฝึกกับพวกเขา

มีตัวอย่างอยู่ letter_recog.pyที่มาพร้อมกับตัวอย่าง OpenCV แต่ฉันก็ยังนึกไม่ออกว่าจะใช้ยังไง ฉันไม่เข้าใจว่าตัวอย่างการตอบกลับคืออะไรและมันโหลดไฟล์ txt ในตอนแรกซึ่งฉันไม่เข้าใจก่อน

ต่อมาในการค้นหาเล็กน้อยฉันสามารถหา letter_recognition.data ในตัวอย่าง cpp ฉันใช้มันและสร้างรหัสสำหรับ cv2.K ใกล้เคียงที่สุดในรูปแบบของ 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()

มันทำให้ฉันมีขนาดใหญ่กว่า 20,000 ฉันไม่เข้าใจว่ามันคืออะไร

คำถาม:

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. ดึง bounding 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
บทช่วยสอนตัวเอก ขอบคุณ! มีการเปลี่ยนแปลงเล็กน้อยเพื่อให้สิ่งนี้ทำงานได้กับเวอร์ชันล่าสุด (3.1) เวอร์ชันของ OpenCV: contours, hierarchy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, contours, hierarchy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => model = cv2.ml.KNearest_create (), model.train (ตัวอย่าง, การตอบกลับ) => model.train (ตัวอย่าง, cv2.ML .ROW_SAMPLE การตอบสนอง) retval ผล neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval ผล neigh_resp, dists = model.find_nearest (roismall, k = 1)
โยฮันเน 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

หากคุณมีความสนใจในศิลปะสมัยใหม่ในการเรียนรู้ของเครื่องคุณควรศึกษาลึกลงไป คุณควรมี CUDA ที่รองรับ GPU หรือใช้ GPU บน Amazon Web Services

Google Udacity มีการกวดวิชาที่ดีเกี่ยวกับเรื่องนี้โดยใช้Tensor ไหล บทช่วยสอนนี้จะสอนวิธีฝึกฝนตัวจําแนกของคุณเองด้วยตัวเลขที่เขียนด้วยมือ ฉันมีความแม่นยำมากกว่า 97% ในชุดทดสอบโดยใช้ Convolutional Networks

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.