数字识别和其他的所有计算机视觉相关的应用都会分为两个步骤:ROI抽取和识别。

1. ROI抽取即将感兴趣的区域从原始图像中分离初来,这个步骤包括二值化,噪点的消除等
2. 识别即通过一些分类器将第一步中的结果进行分类,事实上属于机器学习的一个典型应用

数字识别步骤:

1.先处理图像:

转换为灰度值(灰度图较之原始图片,将三个维度的矩阵变成了一个维度)

转换为二值图(二值图即将灰度图转换成黑白图,每个点只有两种可能:非黑即白)

Mat srcImage = imread("number.png");
Mat dstImage, grayImage, Image;
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); threshold(grayImage, Image, , , 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, -, (,,) );

检测完轮廓后,使用contours迭代器遍历每一个轮廓,找到并画出包围这个轮廓的最小矩阵。

    vector<vector<Point>>::iterator It;
for(It = contours.begin();It < contours.end();It++){ //画出可包围数字的最小矩形
Point2f vertex[];
RotatedRect rect = minAreaRect(*It);
rect.points(vertex); for( int j = ; j < ; j++)
line(dstImage,vertex[j], vertex[ (j+)% ],Scalar(,,),);
}

但是,上述方法画出的矩形为旋转矩形(不一定水平) ,所以不采用这种方法。应使用boundingRect()画出矩形。

vector<vector<Point>>::iterator It;
for(It = contours.begin();It < contours.end();It++){ //画出可包围数字的最小矩形
Point2f vertex[];
Rect rect = boundingRect(*It);
vertex[] = rect.tl(); //矩阵左上角的点
vertex[].x = (float)rect.tl().x, vertex[].y = (float)rect.br().y; //矩阵左下方的点
vertex[] = rect.br(); //矩阵右下角的点
vertex[].x = (float)rect.br().x, vertex[].y = (float)rect.tl().y; //矩阵右上方的点 for( int j = ; j < ; j++)
line(dstImage,vertex[j], vertex[ (j+)% ],Scalar(,,),);

画出图像如下图

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[];

我按轮廓检测顺序的将矩阵的中心点存入结构体中,然后调用sort()函数。

con[i].x = (vertex[].x+vertex[].x+vertex[].x+vertex[].x) / 4.0;                 //根据中心点判断图像的位置
con[i].y = (vertex[].y+vertex[].y+vertex[].y+vertex[].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+); //错误

4.切割各个数字:

使用ROI进行切割,关于ROI详见 http://www.cnblogs.com/farewell-farewell/p/5905107.html

我在此处写的ROI法分隔图片的方法如下,但是存在内存访问上的问题。

IplImage* num[];
for(int j = ; 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,);
cvCopy(src,num[j]);
cvResetImageROI(src);
}

最后换另一种方法,更简单,将其分割

    Mat num[];
for(int j = ; 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 = ; i < ; i++){
uchar * data1 = src.ptr<uchar>(i);
uchar * data2 = now.ptr<uchar>(i);
for(int j = ; 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,); sample = imread("1.jpg");
Threshold(src,sample,); sample = imread("2.jpg");
Threshold(src,sample,); sample = imread("3.jpg");
Threshold(src,sample,); sample = imread("4.jpg");
Threshold(src,sample,); sample = imread("5.jpg");
Threshold(src,sample,); sample = imread("6.jpg");
Threshold(src,sample,); sample = imread("7.jpg");
Threshold(src,sample,); sample = imread("8.jpg");
Threshold(src,sample,); sample = imread("9.jpg");
Threshold(src,sample,); sort(result,result+); if(result[].bi > 0.6) {
cout << "第" << order << "个数字为 "<< result[]. num<<endl;
cout << "识别精度为 " << result[].bi <<endl;
}
else cout << "第" << order << "个数字无法识别"<<endl;
}
void Threshold(Mat &src,Mat &sample ,int m)
{
cvtColor(sample, sample, COLOR_BGR2GRAY);
threshold(sample, sample, , , CV_THRESH_BINARY_INV);
result[m].bi = compare(src,sample);
result[m].num = m;
} }con[]; struct result{
double bi;
int num; bool operator<(result &m){
if(bi < m.bi)return true;
else return false;
}
}result[];

大功告成~

完整的代码:

#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[]; struct result{
double bi;
int num; bool operator<(result &m){
if(bi < m.bi)return true;
else return false;
}
}result[]; Mat num[];
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, , , CV_THRESH_BINARY_INV); //定义轮廓和层次结构
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(Image,contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); int i = ;
Point2f pp[][];
vector<vector<Point>>::iterator It;
Rect rect[];
for(It = contours.begin();It < contours.end();It++){ //画出可包围数字的最小矩形
Point2f vertex[];
rect[i] = boundingRect(*It);
vertex[] = rect[i].tl(); //矩阵左上角的点
vertex[].x = (float)rect[i].tl().x, vertex[].y = (float)rect[i].br().y; //矩阵左下方的点
vertex[] = rect[i].br(); //矩阵右下角的点
vertex[].x = (float)rect[i].br().x, vertex[].y = (float)rect[i].tl().y; //矩阵右上方的点 for( int j = ; j < ; j++)
line(dstImage,vertex[j], vertex[ (j+)% ],Scalar(,,),); con[i].x = (vertex[].x+vertex[].x+vertex[].x+vertex[].x) / 4.0; //根据中心点判断图像的位置
con[i].y = (vertex[].y+vertex[].y+vertex[].y+vertex[].y) / 4.0;
con[i].order = i;
i++;
}
sort(con,con+i); for(int j = ; 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], , , CV_THRESH_BINARY_INV);
deal(num[j],j+);
} system("pause");
return ;
} void Threshold(Mat &src,Mat &sample ,int m)
{
cvtColor(sample, sample, COLOR_BGR2GRAY);
threshold(sample, sample, , , 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,); sample = imread("1.jpg");
Threshold(src,sample,); sample = imread("2.jpg");
Threshold(src,sample,); sample = imread("3.jpg");
Threshold(src,sample,); sample = imread("4.jpg");
Threshold(src,sample,); sample = imread("5.jpg");
Threshold(src,sample,); sample = imread("6.jpg");
Threshold(src,sample,); sample = imread("7.jpg");
Threshold(src,sample,); sample = imread("8.jpg");
Threshold(src,sample,); sample = imread("9.jpg");
Threshold(src,sample,); sort(result,result+); if(result[].bi > 0.6) {
cout << "第" << order << "个数字为 "<< result[]. num<<endl;
cout << "识别精度为 " << result[].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 = ; i < ; i++){
uchar * data1 = src.ptr<uchar>(i);
uchar * data2 = now.ptr<uchar>(i);
for(int j = ; j < row * col; j++){
int a = data1[j];
int b = data2[j];
if( a == b)same++;
else difPoint++;
}
}
return same/(same+difPoint) ;
}

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

  1. OpenCV——识别手写体数字

    这个是树莓派上运行的, opencv3 opencv提供了一张手写数字图片给我们,如下图所示,可以作为识别手写数字的样本库. 0到9共十个数字,每个数字有五行,一行100个数字.首先要把这5000个数 ...

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

    本篇只给出实现的代码,下一篇将讲一讲实现的原理,及其Adline网络中的LMS算法原理. 包含两个类: package com.cgjr.com; import java.security.Diges ...

  3. Java基于opencv实现图像数字识别(五)—投影法分割字符

    Java基于opencv实现图像数字识别(五)-投影法分割字符 水平投影法 1.水平投影法就是先用一个数组统计出图像每行黑色像素点的个数(二值化的图像): 2.选出一个最优的阀值,根据比这个阀值大或小 ...

  4. Java基于opencv实现图像数字识别(四)—图像降噪

    Java基于opencv实现图像数字识别(四)-图像降噪 我们每一步的工作都是基于前一步的,我们先把我们前面的几个函数封装成一个工具类,以后我们所有的函数都基于这个工具类 这个工具类呢,就一个成员变量 ...

  5. Java基于opencv实现图像数字识别(三)—灰度化和二值化

    Java基于opencv实现图像数字识别(三)-灰度化和二值化 一.灰度化 灰度化:在RGB模型中,如果R=G=B时,则彩色表示灰度颜色,其中R=G=B的值叫灰度值:因此,灰度图像每个像素点只需一个字 ...

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

    Java基于opencv实现图像数字识别(二)-基本流程 做一个项目之前呢,我们应该有一个总体把握,或者是进度条:来一步步的督促着我们来完成这个项目,在我们正式开始前呢,我们先讨论下流程. 我做的主要 ...

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

    Java基于opencv实现图像数字识别(一) 最近分到了一个任务,要做数字识别,我分配到的任务是把数字一个个的分开:当时一脸懵逼,直接百度java如何分割图片中的数字,然后就百度到了用Buffere ...

  8. 转载:使用 OpenCV 识别 QRCode

    原文链接:http://coolshell.cn/articles/10590.html#jtss-tsina 识别二维码的项目数不胜数,每次都是开箱即用,方便得很. 这次想用 OpenCV 从零识别 ...

  9. OpenCV识别技术

    OpenCV识别技术# 老师:james 20181019 # 识别技术# Pycharm + Python3 + OpenCV """ 一.识别技术: 什么是OpenC ...

随机推荐

  1. OpenGL ES 2.0 混合

    混合技术 混合技术就是将俩个片元调和,主要用于将通过各项测试准备进入帧缓冲的片元(源片元)与原有片元按照设定的比例加权计算出最终片元的颜色值. OpenGL ES 2.0中是通过设置混合因子来指定两个 ...

  2. c# 中的线程和同步

    一.新建线程的3种方法 a)异步委托:b)Thread类:c)线程池: 二.异步委托 1.简单使用,检查委托是否完成其任务 a) 通过 BeginInvoke()  的返回值IAsyncResult ...

  3. NAS4Free 安装配置(一)开箱图

    拆箱记录 东西不错,做工很好 包装箱 背面 正面(未装前面板) 底部 前面板打开后 打开上盖 开机正面图

  4. MvcPager概述

    MvcPager 概述   MvcPager分页控件是在ASP.NET MVC Web应用程序中实现分页功能的一系列扩展方法,该分页控件的最初的实现方法借鉴了网上流行的部分源代码, 尤其是ScottG ...

  5. Core Data (一)备

    序 恩,用Core Data也有一段时间了.大大小小的坑也都坑过了.重来没有认真的记录一次.这次需要好好的理一理Core Data.就当一次绝好的机会记录下来.也为了自己加深认识. 为什么要用Core ...

  6. Linux怎样修改系统时间

    修改linux的时间可以使用date指令 修改日期: 时间设定成2009年5月10日的命令如下: #date -s 05/10/2009 修改时间: 将系统时间设定成上午10点18分0秒的命令如下. ...

  7. content的定义

    http://www.myexception.cn/HTML-CSS/1472528.html http://stackoverflow.com/questions/2770681/css-conte ...

  8. KEILC51可重入函数及模拟栈浅析

    MARK:文章中的红色部分是个人的理解. KEILC51可重入函数及模拟栈浅析 关键字:keilc51,模拟堆栈,可重入函数调用,参数传递,C?XBP,C?ADDXBP 摘要:本文较详细的介绍了kei ...

  9. Keil C51内存分配与优化

    C51的内存分配不同于一般的PC,内存空间有限,采用覆盖和共享技术.在Keil编译器中,经过编译后,会形成一个M51文件,在其内部可以详细的看到内存的分配情况. C51内存常见的两个误区: A.变量超 ...

  10. 转:helloworld:一个完整的WCF案例

    原文地址:http://blog.csdn.net/mane_yao/article/details/5852845 WCF的ABC: A代表Address-where(对象在哪里)B代表Bindin ...