zoukankan      html  css  js  c++  java
  • 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) ;
    }
  • 相关阅读:
    SQLServer性能诊断与调优
    (转).NET面试题整理之基础篇
    (转)[茗洋芳竹]程序员常用不常见很难得的地址大全,博主很辛苦
    (转)页面过度动画效果大集合
    (转)软件开发和团队”最小模式”初探2-6人模型(下)
    silverlight 乐动魔方 实战九 .
    (转)js+flash实现手写输入功能特效
    (转)软件开发和团队”最小模式”初探2-6人模型(上)
    (转)我眼中的PM
    silverlight 乐动魔方 实战十 .
  • 原文地址:https://www.cnblogs.com/farewell-farewell/p/5887068.html
Copyright © 2011-2022 走看看