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 ],Scala(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) ;
}
时间: 2024-11-13 15:34:27

OpenCV——识别印刷体数字的相关文章

OpenCV——识别手写体数字

这个是树莓派上运行的, opencv3 opencv提供了一张手写数字图片给我们,如下图所示,可以作为识别手写数字的样本库. 0到9共十个数字,每个数字有五行,一行100个数字.首先要把这5000个数字截取出来. 图片大小为1000*2000,则每个数字块大小为20*20. 1.截取样本并存储 以下代码为截取以上数字并将其存储在矩阵中的过程 训练的数据,一般都会是两个矩阵,一个矩阵存放着数据图像,另一个矩阵存放数据图像对应的数字 Mat src = imread("sample.png"

Adline网络识别印刷体数字0到9-java实现

本篇只给出实现的代码,下一篇将讲一讲实现的原理,及其Adline网络中的LMS算法原理. 包含两个类: package com.cgjr.com; import java.security.DigestInputStream; import java.util.Arrays; import org.neuroph.core.data.DataSet; import org.neuroph.core.data.DataSetRow; import org.neuroph.core.events.L

Java基于opencv实现图像数字识别(一)

Java基于opencv实现图像数字识别(一) 最近分到了一个任务,要做数字识别,我分配到的任务是把数字一个个的分开:当时一脸懵逼,直接百度java如何分割图片中的数字,然后就百度到了用BufferedImage这个类进行操作:尝试着做了一下,做到灰度化,和二值化就做不下去了:然后几乎就没有啥java的资料了,最多的好像都是c++,惹不起.惹不起...... 我也想尝试着用c++做一下,百度到了c++基于opencv来做图像识别的:但是要下vs啊,十几个g呢,我内存这么小,配置这么麻烦,而且vs

Java基于opencv实现图像数字识别(二)—基本流程

Java基于opencv实现图像数字识别(二)-基本流程 做一个项目之前呢,我们应该有一个总体把握,或者是进度条:来一步步的督促着我们来完成这个项目,在我们正式开始前呢,我们先讨论下流程. 我做的主要是表格中数字的识别,但这个不是重点.重点是通过这个我们可以举一反三,来实现我们自己的业务. 图像的识别主要分为两步:图片预处理和图像识别:这两步都很重要 图像预处理: 1. 图像灰度化:二值化 2. 图像降噪,去除干扰线 3. 图像腐蚀.膨胀处理 4. 字符分割 5. 字符归一化 图像识别: 1.

基于opencv 识别、定位二维码 (c++版)

前言 因工作需要,需要定位图片中的二维码:我遂查阅了相关资料,也学习了opencv开源库.通过一番努力,终于很好的实现了二维码定位.本文将讲解如何使用opencv定位二维码. 定位二维码不仅仅是为了识别二维码:还可以通过二维码对图像进行水平纠正以及相邻区域定位.定位二维码,不仅需要图像处理相关知识,还需要分析二维码的特性,本文先从二维码的特性讲起. 1 二维码特性 二维码在设计之初就考虑到了识别问题,所以二维码有一些特征是非常明显的. 二维码有三个“回“”字形图案,这一点非常明显.中间的一个点位

Aforge.net识别简易数字验证码问题

参考:https://www.bbsmax.com/A/rV57LjWGdP/ https://blog.csdn.net/louislong007/article/details/47683035 简易验证码样例: 验证码识别流程: 首先进行图像获取:火狐浏览器,找到获取验证码地址,获取验证码图像,传递给类,直接获取到验证码! 验证码获取: /// <summary> /// 通过GET方式获取验证码 /// </summary> /// <param name="

C++ opencv 数字识别

#include "cv.h" #include "highgui.h" #include "cxcore.h" #include <stdlib.h> #include <stdio.h> #define N 5//载入数字图片个数 char *testPic[] = {"test1.jpg"}; int thres = 115; //二值化阀值 int n_min = 80; //识别数字轮廓长度的

自强队视觉组训练项目-3 | 20180107-印刷体数字识别

DEADLINE: 21st Jan, 2018 (Duration: 2 weeks) 项目需求 数字识别是机器视觉的一个经典话题,其中较为基础的是印刷体数字识别,这期项目要求实现印刷体数字识别,即给一张包含印刷体数字的图片,你的任务是将其中的数字以字符形式输出. 项目描述 项目需求描述了基本要求,具体难度可以自己调节,比如可以给一张只含一个数字的图片,然后通过计算灰度总值区分不同印刷体数字的特征.这是一种极其简单的思路,实现起来也许还是要花点功夫.当然你可以围绕"印刷体数字识别"这

使用AI算法进行手写数字识别

人工智能 ??人工智能(Artificial Intelligence,简称AI)一词最初是在1956年Dartmouth学会上提出的,从那以后,研究者们发展了众多理论和原理,人工智能的概念也随之扩展.由于人工智能的研究是高度技术性和专业的,各分支领域都是深入且各不相通的,因而涉及范围极广 . 人工智能的核心问题包括建构能够跟人类似甚至超越人类的推理.知识.学习.交流.感知.使用工具和操控机械的能力等,当前人工智能已经有了初步成果,甚至在一些影像识别.语言分析.棋类游戏等等单方面的能力达到了超越