OpenCV——識別印刷體數字


數字識別和其他的所有計算機視覺相關的應用都會分為兩個步驟:ROI抽取識別

1. ROI抽取即將感興趣的區域從原始圖像中分離初來,這個步驟包括二值化,噪點的消除等
2. 識別即通過一些分類器將第一步中的結果進行分類,事實上屬於機器學習的一個典型應用

 

數字識別步驟:

1.先處理圖像:

  轉換為灰度值(灰度圖較之原始圖片,將三個維度的矩陣變成了一個維度)

  轉換為二值圖(二值圖即將灰度圖轉換成黑白圖,每個點只有兩種可能:非黑即白)

Mat srcImage = imread("number.png");
Mat dstImage, grayImage, Image; 
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
    
threshold(grayImage, Image, 48, 255, CV_THRESH_BINARY_INV);

PS:48即為閾值,如果灰度高於48,那么該點會被認為是255,否則為0。

2.檢測並勾勒輪廓:
   輪廓檢測將二值圖中的可連通的區域用一坨點表示,默認的輪廓檢查會返回一個點的序列,使這個序列構成一個圖形將該連通區域的所有點包圍起來,比如四個點構成一個矩形。

特例:由於8這個數字中有兩個圓圈,默認的輪廓檢查會將這兩個圓圈都檢測到,8就會有三個輪廓,同樣還可能出現這種情況的還有數字4,6,9。

因此需要指定findContours()函數僅搜索最外層的輪廓,而不關注內部可能出現的任何輪廓。

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy; 
    findContours(Image,contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    drawContours(dstImage, contours, -1, (255,255,255) );

檢測完輪廓后,使用contours迭代器遍歷每一個輪廓,找到並畫出包圍這個輪廓的最小矩陣。

    vector<vector<Point>>::iterator It;
    for(It = contours.begin();It < contours.end();It++){                        //畫出可包圍數字的最小矩形
        Point2f vertex[4];  
        RotatedRect rect = minAreaRect(*It);
        rect.points(vertex);

        for( int j = 0; j < 4; j++)
            line(dstImage,vertex[j], vertex[ (j+1)%4 ],Scalar(0,0,255),1);
    }

但是,上述方法畫出的矩形為旋轉矩形(不一定水平) ,所以不采用這種方法。應使用boundingRect()畫出矩形。

vector<vector<Point>>::iterator It;
    for(It = contours.begin();It < contours.end();It++){                        //畫出可包圍數字的最小矩形
        Point2f vertex[4];  
        Rect rect = boundingRect(*It);
        vertex[0] = rect.tl();                                                              //矩陣左上角的點
        vertex[1].x = (float)rect.tl().x, vertex[1].y = (float)rect.br().y;                 //矩陣左下方的點
        vertex[2] = rect.br();                                                              //矩陣右下角的點
        vertex[3].x = (float)rect.br().x, vertex[3].y = (float)rect.tl().y;                 //矩陣右上方的點

 for( int j = 0; j < 4; j++)
    line(dstImage,vertex[j], vertex[ (j+1)%4 ],Scalar(0,0,255),1);

畫出圖像如下圖

3.數字順序整理:

由於輪廓檢測時,不一定按照圖中所給順序進行檢測,所以在檢測輪廓時,要記錄所給數字的坐標,根據x,y坐標進行排序。

由於用上述方法在同一行畫出的矩形位於同一水平面,因此直接比較其某一點坐標即可。對此,我寫出如下結構體:

struct con{
    double x,y;                    //輪廓位置
    int order;                      //輪廓向量contours中的第幾個

    bool operator<(con &m){
        if(y > m.y) return false;    
        else  if( y == m.y){
            if(x < m.x) return true;
            else return false;
        }                           
        else return true;
    }

}con[100];

 我按輪廓檢測順序的將矩陣的中心點存入結構體中,然后調用sort()函數。

con[i].x = (vertex[0].x+vertex[1].x+vertex[2].x+vertex[3].x) / 4.0;                 //根據中心點判斷圖像的位置
con[i].y = (vertex[0].y+vertex[1].y+vertex[2].y+vertex[3].y) / 4.0;

//cout << i <<":"<< endl;
//cout << vertex[3].x<<"  "<< vertex[3].y<<endl;
con[i].order = i; 

 但是用這種方法上圖中的數字”4“一直在最前面,改了好久也沒有結果,就先着手下一步。

PS:  最后發現了問題,如下:

sort(con,con+i);                                    //正確
sort(con,con+i+1);                                //錯誤

4.切割各個數字:

使用ROI進行切割,關於ROI詳見 http://www.cnblogs.com/farewell-farewell/p/5905107.html

 我在此處寫的ROI法分隔圖片的方法如下,但是存在內存訪問上的問題。

IplImage* num[10];
for(int j = 0; j < i; j++){
    int k = con[i].order;
    IplImage* src = cvLoadImage("number.jpg");
    cvSetImageROI(src,rect[k]);
        num[j] = cvCreateImage(cvSize(rect[k].width,rect[k].height),IPL_DEPTH_8U,2);
    cvCopy(src,num[j]);
    cvResetImageROI(src);
}

 最后換另一種方法,更簡單,將其分割

    Mat num[10];
    for(int j = 0; j < i; j++){
        cout << "s "<<j<<endl;
        int k = con[j].order;
        cout << "k "<<k<<endl;
        srcImage(rect[k]).copyTo(num[j]);
    }

 分割后的數字按順序存放在num[10]圖像數組中。

5.最后的識別

 將按輪廓線切割好的數字放於程序文件中,然后采用逐點像素遍歷的方法來進行對比

//兩圖象逐像素對比的函數
double compare(Mat &src, Mat &sample)
{
    double same = 0.0, difPoint = 0.0;
    Mat now;
    resize(sample,now,src.size());
    int row = now.rows;
    int col = now.cols *  now.channels();
    for(int i = 0; i < 1; i++){
        uchar * data1 = src.ptr<uchar>(i);
        uchar * data2 = now.ptr<uchar>(i);
        for(int j = 0; j < row * col; j++){
            int  a = data1[j];
            int b = data2[j];
            if( a == b)same++;
            else difPoint++;
        }
    }
    return same/(same+difPoint) ;
}

 

//選取符合程度最高的數字
void deal(Mat &src,int order)
{
    
    sample = imread("0.jpg");
    Threshold(src,sample,0);

    sample = imread("1.jpg");
    Threshold(src,sample,1);

    sample = imread("2.jpg");
    Threshold(src,sample,2);

    sample = imread("3.jpg");
    Threshold(src,sample,3);

    sample = imread("4.jpg");
    Threshold(src,sample,4);

    sample = imread("5.jpg");
    Threshold(src,sample,5);

    sample = imread("6.jpg");
    Threshold(src,sample,6);

    sample = imread("7.jpg");
    Threshold(src,sample,7);

    sample = imread("8.jpg");
    Threshold(src,sample,8);

    sample = imread("9.jpg");
    Threshold(src,sample,9);

    sort(result,result+10);

    if(result[9].bi > 0.6) {
        cout << "" << order << "個數字為 "<< result[9]. num<<endl;
        cout << "識別精度為 " << result[9].bi <<endl;
    }
    else cout << "" << order << "個數字無法識別"<<endl;
}
void Threshold(Mat &src,Mat &sample ,int m)
{
    cvtColor(sample, sample, COLOR_BGR2GRAY);
    threshold(sample, sample, 48, 255, CV_THRESH_BINARY_INV);
    result[m].bi = compare(src,sample);
    result[m].num = m;
}


}con[15];

struct result{
    double bi;
    int num;

    bool operator<(result &m){
        if(bi < m.bi)return true;
        else return false;
    }
}result[15];

大功告成~

 

完整的代碼:

#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;

struct con{
    double x,y;                    //輪廓位置
    int order;                      //輪廓向量contours中的第幾個

    bool operator<(con &m){
        if(y > m.y) return false;    
        else  if( y == m.y){
            if(x < m.x) return true;
            else return false;
        }                           
        else return true;
    }

}con[15];

struct result{
    double bi;
    int num;

    bool operator<(result &m){
        if(bi < m.bi)return true;
        else return false;
    }
}result[15];

Mat num[15];
Mat sample;
void deal(Mat &src,int order);
double compare(Mat &src, Mat &sample);
void Threshold(Mat &src,Mat &sample,int m);

int main( )
{
    Mat srcImage = imread("number.png");
    Mat dstImage, grayImage, Image; 
    srcImage.copyTo(dstImage);
    cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
    threshold(grayImage, Image, 48, 255, CV_THRESH_BINARY_INV);

    //定義輪廓和層次結構
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy; 
    findContours(Image,contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    
    int i = 0;
    Point2f pp[5][4];
    vector<vector<Point>>::iterator It;
    Rect rect[10];
    for(It = contours.begin();It < contours.end();It++){                        //畫出可包圍數字的最小矩形
        Point2f vertex[4];  
        rect[i] = boundingRect(*It);
        vertex[0] = rect[i].tl();                                                           //矩陣左上角的點
        vertex[1].x = (float)rect[i].tl().x, vertex[1].y = (float)rect[i].br().y;           //矩陣左下方的點
        vertex[2] = rect[i].br();                                                           //矩陣右下角的點
        vertex[3].x = (float)rect[i].br().x, vertex[3].y = (float)rect[i].tl().y;           //矩陣右上方的點

        for( int j = 0; j < 4; j++)
            line(dstImage,vertex[j], vertex[ (j+1)%4 ],Scalar(0,0,255),1);

        con[i].x = (vertex[0].x+vertex[1].x+vertex[2].x+vertex[3].x) / 4.0;                  //根據中心點判斷圖像的位置
        con[i].y = (vertex[0].y+vertex[1].y+vertex[2].y+vertex[3].y) / 4.0;
        con[i].order = i; 
        i++;
    }
    sort(con,con+i);

    for(int j = 0; j < i; j++){
        int k = con[j].order;
        srcImage(rect[k]).copyTo(num[j]);
        cvtColor(num[j], num[j], COLOR_BGR2GRAY);
        threshold(num[j], num[j], 48, 255, CV_THRESH_BINARY_INV);
        deal(num[j],j+1);
    }

    system("pause");
    return 0;
}

void Threshold(Mat &src,Mat &sample ,int m)
{
    cvtColor(sample, sample, COLOR_BGR2GRAY);
    threshold(sample, sample, 48, 255, CV_THRESH_BINARY_INV);
    result[m].bi = compare(src,sample);
    result[m].num = m;
}

void deal(Mat &src,int order)
{
    
    sample = imread("0.jpg");
    Threshold(src,sample,0);

    sample = imread("1.jpg");
    Threshold(src,sample,1);

    sample = imread("2.jpg");
    Threshold(src,sample,2);

    sample = imread("3.jpg");
    Threshold(src,sample,3);

    sample = imread("4.jpg");
    Threshold(src,sample,4);

    sample = imread("5.jpg");
    Threshold(src,sample,5);

    sample = imread("6.jpg");
    Threshold(src,sample,6);

    sample = imread("7.jpg");
    Threshold(src,sample,7);

    sample = imread("8.jpg");
    Threshold(src,sample,8);

    sample = imread("9.jpg");
    Threshold(src,sample,9);

    sort(result,result+10);

    if(result[9].bi > 0.6) {
        cout << "" << order << "個數字為 "<< result[9]. num<<endl;
        cout << "識別精度為 " << result[9].bi <<endl;
    }
    else cout << "" << order << "個數字無法識別"<<endl;
}

double compare(Mat &src, Mat &sample)
{
    double same = 0.0, difPoint = 0.0;
    Mat now;
    resize(sample,now,src.size());
    int row = now.rows;
    int col = now.cols *  now.channels();
    for(int i = 0; i < 1; i++){
        uchar * data1 = src.ptr<uchar>(i);
        uchar * data2 = now.ptr<uchar>(i);
        for(int j = 0; j < row * col; j++){
            int  a = data1[j];
            int b = data2[j];
            if( a == b)same++;
            else difPoint++;
        }
    }
    return same/(same+difPoint) ;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM