[C++]现行的试卷封面并获取学生题目得分信息以及学号信息的原型系统

  大二的时候写的一个CV小玩意,最终决定还是把它放出来,也许会帮助到很多人,代码写的很丑,大家多多包涵。附加实验报告主要部分。大家会给这个课设打多少分呢?

课题背景及意义:

本项目主要目标是设计一套能自动分析我校现行的试卷封面并获取学生题目得分信息以及学号信息的原型系统。

本项目的实现有助于提升我校成绩管理的自动化程度以及试卷分析的量化程度,分担一部分期末教师阅卷的工作。

课题相关研究情况概述:

本项目进行至今已经完成了单个数字的识别,并且准确率高达98.74%。完成了试卷卷面的基本分析工作,可以准确定位评分栏并读取评分栏中的内容。不足之处在于没有实现一个成熟的分字功能,无法正确识别连续多个数字,这也就无法识别学号、大于9的分数信息。此外,还完成了界面的设计以及数据处理和用户界面的通信模块,并且设计并建立了相应的数据库。

主要研究内容:

准备工作:

1、读一些书本资料+论文,涉及图像处理、机器学习、opencv手册、大量相关论文等。

2Opencv环境配置,尝试使用caffe+cuda未果,最终,机器学习部分使用的是Opencvmachine learning库。

3、大致确定思路,选择可能会使用的训练模型,收集训练集,获取试卷封面样本。

正式工作:

模式识别部分:

1、学习图像处理相关的算法,实现一部分接下来的工作中需要用到的函数、模块:大津法求二值化阈值、图像二值化、获取灰度分布直方图、图片放缩、伽马校对等。

图像二值化:使用大津法对一张图片求出其阈值,之后遍历所有像素点,判断是否超出阈值,做相应的重新赋值操作。

大津法的基本思路和公式推导:

对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g

假设图像的背景较暗,并且图像的大小为M*N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:

 

得到等价公式:,这就是类间方差。

采用遍历的方法得到使类间方差g最大的阈值T,即为所求。

2、处理训练集。由于选用的是mnist在官网提供的美国中学生手写体,被打包在了一个文件里,编码释放该文件内容,共计六万幅28*28训练测试用手写数字图像。

3、查询opencv手册,查找opencvml库内有什么现成的模型,最终选定k-邻近算法(knn)作为本次工作的分类算法。

4、(特征提取)选择提取合适的特征向量来描述每一图数字图片,选择提取hog特征(方向梯度直方图)。

1)、将梯度方向平均划分为9个区间;细胞单元大小7*7;扫描窗口步长为7;块大小14*14,即四个细胞单元组成一个块,串联每个细胞中的特征获得块特征。

2)、计算梯度并构建梯度方向直方图:

定义Gx(x,y),Gy(x,y),H(x,y)分别表示输入图像中像素点(x,y)处的水平方向梯度、垂直方向梯度和该点像素值。则有如下公式:

Gx(x,y)=H(x+1,y)-H(x-1,y)

Gy(x,y)=H(x,y+1)-H(x,y-1)

定义G(x,y)为像素点(x,y)处的梯度幅值、梯度方向为α(x,y),则有:

 

4)、组合成块,获取整个图片的特征向量。

hog特征向量的维度计算:对于一张28*28的图片,每7*7的像素组成一个细胞单元,每2*2的细胞单元组成一个块,每个细胞单元有9个特征,所以每个块有2*2*9=36个特征。扫描窗口步长为7,那么水平方向就有28/7-1=3个窗口,竖直方向也有28/7-1=3个窗口。所以对于整张图片而言,有36*3*3=324个特征。

5、(模式识别)编码实现knn分类器对0~9十个数字进行分类,使用六万幅图片中的五万幅作为训练样本,剩余一万幅作为测试样本,准确超过98.5%关于kNN果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。

图像处理部分:

1、预处理,二值化、平滑处理、去噪。

2、定位表格的思路:hough变换提取线段,提取出的线段要通过判断长度来确定是不是符合要求,即是不是组成表格的线段。hough变换的思路很简单,就是把下的以横纵坐标为轴的坐标系下的直线方程投影到一个直角坐标系下,这时候直线变成了一个点。公式推导:

设直线

则直线:

化简:

竖:

横:

随后求横竖线的笛卡尔积集获取交点。

3、通过几何学运算获取横竖线段两两交点,将交点保存。公式推导:

 104     // (p1-p0)X(p2-p0)=0    
 105     // (p3-p0)X(p2-p0)=0
 106 
 107     // (y1-y2)x0+(x2-x1)y0+x1y2-x2y1=0
 108     // (y3-y4)x0+(x4-x3)y0+x3y4-x4y3=0
 109 
 110     // a1x0+b1y0+c1=0
 111     // a2x0+b2y0+c2=0
 112 
 113     // x0=(c1*b2-c2*b1)/(a2*b1-a1*b2)
 114     // y0=(a2*c1-a1*c2)/(a1*b2-a2*b1)

4、由于两个点可以确定一个矩形,所以提取出包含分数信息的矩形图像并保存。

5、 进一步处理刚刚提取的矩形图像,进行简单的预处理后裁切,使得数字占图片的比例尽可能地大,之后缩小成28*28的图片。

6、利用识别模块进行识别,将结果保存在Message结构体内,Message结构体定义:

 1 typedef struct Message {
 2                 string studentID;
 3                 size_t score[14];
 4                 size_t sum;
 5                 Message() {
 6                     studentID.clear();
 7                     memset(score, -1, sizeof(score));
 8                     sum = 0;
 9                 }
10             }Message;

附图:

 测试分类结果

测试单独的样例

线段识别

 卷面的分析和分数的识别

 识别过程中的中间文件

 

连通块分解

总结与展望:

1、关于界面:我比较注重实际的功能,在功能不完全的时候添加界面不但没有使工作变得快捷,反而会使程序不是那么容易调试。不过,未来还是会添加一个界面。

2、关于分类器的选择和性能的一点解释:knn不是真正需要训练,但是开工前简单分析了一下,觉得使用最好实现的knn分类效果会很好,所以没有使用卷积神经网络(cnn) IO上过于频繁,计算复杂度过高,hog特征提取出的特征向量维度过高,使得运算开销过大,所以需要优化。由于多线程对于IO频繁的程序性能上有较好的表现,所以在程序上做多进程优化;在对knn分类判断方法做改进,达到优化目的。具体是将单纯的欧几里德距离改变成中心向量投影的方式,即在分类时,先将待分类样本投影到样本中心所在的直线上,根据待分类样本的投影点和训练样本的投影点之间的距离关系确定样本类别(参见文献1001-7119(2013)12-0127-03)。可以将问题转换成多个二分类问题,从而达到优化的目的;另一种是换用其他的分类器,现在做手写字符识别技术基本都是采用了cnn,老师拟采用cnn也是考虑了这个原因,所以cnn是很不错的选择。另外,由于项目代码中涉及了不少的图像和矩阵运算,所以接下来可能考虑使用GPU做加速,我的笔记本的显卡是NVIDIA,所以厂商开发的CUDA运算平台是一个很好的选择。

3、关于表格中多个数字以及学号的提取:时间关系没有做数字的切分,但是我实现了一个根据连通块的简单的切分功能。具体一些就是任取一个非空白起点开始做dfs,拟定8个方向可以扩展,每次扩展一个像素。如果走到的一个像素非空白,那认为这个像素和上一个像素是属于同一个连通块的,那我就更新这个像素的连通块从属关系,维护像素属于哪个连通块的时候可以用并查集。由于受限太大,命中率低,我没有加入到demo中,而且这个切分是递归写的,如果不调整程序运行时栈的大小一定会爆栈,所以要么写成迭代形式的,要么手工开栈。这样就使得程序变得不可控了。所以未来的工作里一定会有这一项,可能会采取其他的切分方式,比如求最小包围矩阵、投影法或者滴水算法。

4、关于题目数少于表格数:会有这样的普遍情况,那就是一共给了十个题的格子,总题数却只有不到十个。这种问题的解决方法有很多。现在的程序中是事先默认一个题数,在识别的时候只去识别前几个题,最后保存信息只保存对应题数的分数等信息。还有一种方法是在分类器上做文章,就是规定一个阈值,相邻前k个中最多的几个样本距离的平均值如果大于某一个数值的话,那我就认为这些挑出来的被认为是“接近”的样本实际上不是真正的接近,他们只是相对于其他样本而言接近而已,所以可以认为这个待测样本是无法识别的。由于完全空白的表格实际上无法被分类器正确分类,所以我可以认为这种情况是上述情况的特例,这样遇到第一个无法识别的情况我就可以认为是没有这个题了。

5、关于表格的定位和提取:因为生产环境中的样本——试卷长得都一样,所以我认为没有必要采用其他类似腐蚀运算、膨胀运算等提取方法。因为试卷的样式实在是太唯一了,唯一可以影响算法的因素就是阅卷时老师照相的手抖得实在不行。只是在定位表格以后,如何能够优雅地把数字提取出来就是一个大问题了。目前我还没有一个好的方法,期待将来的学习可以让我有所突破。

6、关于图片的预处理:因为对计算机图形学了解得不是那么地深刻,所以对于要处理的图片需要做什么预处理还是会有很多疑问。我认为在将来的工作中应该加入更多的预处理,使图片的一些问题提前在预处理阶段解决,这样有助于后面工作的展开和准确率的提高。

7、关于整个项目代码的架构:我很少去写一个项目。加上有opencv这样庞大的第三方库,使得我很难掌控整个代码的架构,这一切都归咎于我没有想清楚一切问题就开始写代码了。我想一个完整的项目一定有清晰的逻辑结构,也一定有清晰的代码结构。所以我接下来会把代码分成多个文件,便于管理。

我的收获:

1、读了很多的资料文章,学到了很多有关计算机图形学和机器学习方面的知识,并且在这个项目上有部分体现。

2、了解了做一个项目的基本流程。这个项目虽然简单,但是代码写起来会很繁琐。因为小的细节问题实在是太多了,所以我采用了计算机网络课上学到的,像协议一样分层实现,每一层有每一层的工作,他们之间的工作不能越级,在这样一个规则下进行项目使得一切都变得清晰明了,于是就可以有更多的时间投入到具体问题的分析当中去了。

3、认识到自己有些浮躁,对一个问题的认识还没有达到深刻的前提下就开始盲目地做,导致自己走了很多很多的弯路。以后在分析和解决一个问题之前一定要先对问题本身多做一些讨论,确保自己理解问题的情况下再去着手寻找问题的解决方案。

4、思维不够活跃,解决问题的时候容易被固有知识限制,导致思考不出问题的解。应该多读一些论文和文章,多去了解一下别人的解决方法,开阔视野;再多做一些思维上的训练,锻炼思维能力。

5、上述的整个项目都是我一个人完成的,这会使项目受限于我自己的思维和能力。应当找一个人或者多个人讨论,一起做,这样就会使项目进展得更快,解决问题的方法也一定不止一个,相互学习会获得更大的进步。

 附上全部代码:

   1 #pragma warning(disable : 4018)
   2 
   3 #include <algorithm>
   4 #include <iostream>
   5 #include <iomanip>
   6 #include <cstring>
   7 #include <climits>
   8 #include <complex>
   9 #include <fstream>
  10 #include <cassert>
  11 #include <cstdio>
  12 #include <bitset>
  13 #include <vector>
  14 #include <deque>
  15 #include <queue>
  16 #include <stack>
  17 #include <ctime>
  18 #include <set>
  19 #include <map>
  20 #include <cmath>
  21 
  22 #include <opencv.hpp>
  23 #include <opencv2/core/core.hpp>
  24 #include <opencv2/imgproc/imgproc.hpp>
  25 #include <opencv2/highgui/highgui.hpp>
  26 
  27 using namespace std;
  28 using namespace cv;
  29 
  30 #define TEST Mat test_mat; 
  31     test_mat = imread("./test/0/0_05001.jpg"); 
  32     imshow("", test_mat); 
  33     waitKey()
  34 
  35 #define dbg(x) 
  36     cout << "Kirai Debug> " << #x << " = " << x << endl
  37 
  38 #define cvQueryHistValue_1D( hist, idx0 ) 
  39     ((float)cvGetReal1D( (hist)->bins, (idx0)))
  40 
  41 //#define MAIN_CPP_START    //识别模块
  42 
  43 //#define SVMTRAIN            //训练svm
  44 //#define ONLYONECLASS        //只获取一类样例
  45 //#define GETFILEONETIME    //某类样例集合中获取一个样例
  46 #define SAVETRAINED            //训练模式
  47 //#define SAVEASTXT            //保存knn训练结果到txt格式的文件中
  48 
  49 const int bin = 9;            //HOG特征提取时方向数
  50 const int ImgWidth = 28;    //图片宽度
  51 const int ImgHeight = 28;    //图片高度
  52 const int nImgNum = 5000;    //训练图片数量(每个数字)
  53 const int testNum = 2000;    //测试样例图片数量(每个数字)
  54 const char* sample = "./data/linetest.jpg";
  55 const char* bined = "./data/conv.jpg";
  56 const string rootDir("./sample/");
  57 const string jpg(".jpg");
  58 const string dirNames[11] = {
  59     rootDir + "0/", rootDir + "1/", rootDir + "2/",
  60     rootDir + "3/", rootDir + "4/", rootDir + "5/",
  61     rootDir + "6/", rootDir + "7/", rootDir + "8/",
  62     rootDir + "9/"
  63 };
  64 
  65 vector<Mat> samples;
  66 vector<int> labels;
  67 
  68 Mat dat_mat, res_mat;
  69 KNearest knn;
  70 //CvSVM svm;
  71 vector<float> descriptors;
  72 
  73 //卷面信息
  74 typedef struct Message {
  75     string studentID;
  76     size_t score[14];
  77     size_t sum;
  78     Message() {
  79         studentID.clear();
  80         memset(score, -1, sizeof(score));
  81         sum = 0;
  82     }
  83 }Message;
  84 
  85 //重载Point类中的运算符
  86 Point operator +(Point a, Point b) {return Point(a.x + b.x, a.y + b.y);}
  87 Point operator -(Point a, Point b) {return Point(a.x - b.x, a.y - b.y);}
  88 int operator ^(Point a, Point b) {return a.x * b.y - a.y * b.x;}
  89 int operator *(Point a, Point b) {return a.x * b.x + a.y * b.y;}
  90 
  91 //用两个无穷远的点来作为直线的标记
  92 typedef struct Line {
  93     Point a;
  94     Point b;
  95     Line() = default;
  96     Line(Point aa, Point bb) : a(aa), b(bb) {}
  97 }Line;
  98 vector<Line> para;//平行线
  99 vector<Line> vert;//垂直线
 100 
 101 //求p1p2和p3p4的交点p0(x0,y0)
 102 Point getCross(Line p, Line q) {
 103     //------------ 公式推导 ------------//
 104     // (p1-p0)X(p2-p0)=0    
 105     // (p3-p0)X(p2-p0)=0
 106 
 107     // (y1-y2)x0+(x2-x1)y0+x1y2-x2y1=0
 108     // (y3-y4)x0+(x4-x3)y0+x3y4-x4y3=0
 109 
 110     // a1x0+b1y0+c1=0
 111     // a2x0+b2y0+c2=0
 112 
 113     // x0=(c1*b2-c2*b1)/(a2*b1-a1*b2)
 114     // y0=(a2*c1-a1*c2)/(a1*b2-a2*b1)
 115     //--------------------------------//
 116     long double x1 = p.a.x, x2 = p.b.x;
 117     long double y1 = p.a.y, y2 = p.b.y;
 118     long double x3 = q.a.x, x4 = q.b.x;
 119     long double y3 = q.a.y, y4 = q.b.y;
 120     long double a1 = y1 - y2, b1 = x2 - x1, c1 = x1 * y2 - x2 * y1;
 121     long double a2 = y3 - y4, b2 = x4 - x3, c2 = x3 * y4 - x4 * y3;
 122     Point ans;
 123     ans.x = int((c1 * b2 - c2 * b1) / (a2 * b1 - a1 * b2));
 124     ans.y = int((a2 * c1 - a1 * c2) / (a1 * b2 - a2 * b1));
 125     return ans;
 126 }
 127 
 128 //大津法求二值化阈值
 129 int otsu(const IplImage* src_image) {
 130     double w0 = 0.0;
 131     double w1 = 0.0;
 132     double u0_temp = 0.0;
 133     double u1_temp = 0.0;
 134     double u0 = 0.0;
 135     double u1 = 0.0;
 136     double delta_temp = 0.0;
 137     double delta_max = 0.0;
 138 
 139     int pixel_count[256] = { 0 };
 140     float pixel_pro[256] = { 0 };
 141     int threshold = 0;
 142     uchar* data = (uchar*)src_image->imageData;
 143     for (int i = 0; i < src_image->height; i++) {
 144         for (int j = 0; j < src_image->width; j++) {
 145             pixel_count[(int)data[i * src_image->width + j]]++;
 146         }
 147     }
 148     for (int i = 0; i < 256; i++) {
 149         pixel_pro[i] = (float)pixel_count[i] / (src_image->height * src_image->width);
 150     }
 151     for (int i = 0; i < 256; i++) {
 152         w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
 153         for (int j = 0; j < 256; j++) {
 154             if (j <= i) {
 155                 w0 += pixel_pro[j];
 156                 u0_temp += j * pixel_pro[j];
 157             }
 158             else {
 159                 w1 += pixel_pro[j];
 160                 u1_temp += j * pixel_pro[j];
 161             }
 162         }
 163         u0 = u0_temp / w0;
 164         u1 = u1_temp / w1;
 165         delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));
 166         if (delta_temp > delta_max) {
 167             delta_max = delta_temp;
 168             threshold = i;
 169         }
 170     }
 171     return threshold;
 172 }
 173 int otsu(Mat src_image) {
 174     double w0 = 0.0;
 175     double w1 = 0.0;
 176     double u0_temp = 0.0;
 177     double u1_temp = 0.0;
 178     double u0 = 0.0;
 179     double u1 = 0.0;
 180     double delta_temp = 0.0;
 181     double delta_max = 0.0;
 182 
 183     int pixel_count[256] = { 0 };
 184     float pixel_pro[256] = { 0 };
 185     int threshold = 0;
 186     
 187     for (int i = 0; i < src_image.size().height; i++) {
 188         uchar* data = src_image.ptr<uchar>(i);
 189         for (int j = 0; j < src_image.size().width; j++) {
 190             pixel_count[int(data[j])]++;
 191         }
 192     }
 193     for (int i = 0; i < 256; i++) {
 194         pixel_pro[i] = (float)pixel_count[i] / (src_image.size().height * src_image.size().width);
 195     }
 196     for (int i = 0; i < 256; i++) {
 197         w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
 198         for (int j = 0; j < 256; j++) {
 199             if (j <= i) {
 200                 w0 += pixel_pro[j];
 201                 u0_temp += j * pixel_pro[j];
 202             }
 203             else {
 204                 w1 += pixel_pro[j];
 205                 u1_temp += j * pixel_pro[j];
 206             }
 207         }
 208         u0 = u0_temp / w0;
 209         u1 = u1_temp / w1;
 210         delta_temp = (float)(w0 * w1 * pow((u0 - u1), 2));
 211         
 212         if (delta_temp > delta_max) {
 213             delta_max = delta_temp;
 214             threshold = i;
 215         }
 216     }
 217     return threshold;
 218 }
 219 
 220 //图像二值化
 221 int imageBinarization(IplImage* src_image) {
 222     IplImage* binImg = cvCreateImage(cvGetSize(src_image), src_image->depth, src_image->nChannels);
 223     CvScalar s;
 224     int ave = 0;
 225     int binThreshold = otsu(src_image);
 226     for (int i = 0; i < src_image->height; i++) {
 227         for (int j = 0; j < src_image->width; j++) {
 228             s = cvGet2D(src_image, i, j);
 229             ave = int((s.val[0] + s.val[1] + s.val[2]));
 230             if (ave < 3 * binThreshold) {    //取反 ave < binThreshold
 231                 s.val[0] = 0xff;
 232                 s.val[1] = 0xff;
 233                 s.val[2] = 0xff;
 234                 cvSet2D(src_image, i, j, s);
 235             }
 236             else {
 237                 s.val[0] = 0x00;
 238                 s.val[1] = 0x00;
 239                 s.val[2] = 0x00;
 240                 cvSet2D(src_image, i, j, s);
 241             }
 242         }
 243     }
 244     cvCopy(src_image, binImg);
 245     cvSaveImage(bined, binImg);
 246     //cvShowImage("binarization", binImg);
 247     //waitKey(0);
 248     return binThreshold;
 249 }
 250 int imageBinarization(Mat src_image) {
 251     Mat binImg = Mat::zeros(src_image.size().height, src_image.size().width, CV_8UC3);
 252     int ave = 0;
 253     int binThreshold = otsu(src_image);
 254     for (int i = 0; i < src_image.size().width; i++) {
 255         for (int j = 0; j < src_image.size().height; j++) {
 256             ave = src_image.at<Vec3b>(j, i)[0] + src_image.at<Vec3b>(j, i)[1] + src_image.at<Vec3b>(j, i)[2];
 257             if (ave < 3 * binThreshold) {
 258                 binImg.at<Vec3b>(j, i)[0] = 0xff;
 259                 binImg.at<Vec3b>(j, i)[1] = 0xff;
 260                 binImg.at<Vec3b>(j, i)[2] = 0xff;
 261             }
 262             else {
 263                 binImg.at<Vec3b>(j, i)[0] = 0;
 264                 binImg.at<Vec3b>(j, i)[1] = 0;
 265                 binImg.at<Vec3b>(j, i)[2] = 0;
 266             }
 267         }
 268     }
 269     imwrite("./data/otsu.jpg", binImg);
 270     //imshow("", binImg);
 271     //waitKey();
 272     return binThreshold;
 273 }
 274 
 275 //获取灰度分布直方图
 276 CvHistogram* getHistogram(const char* fileName) {
 277     IplImage* src = cvLoadImage(fileName);
 278     IplImage* gray_plane = cvCreateImage(cvGetSize(src), 8, 1);
 279     cvCvtColor(src, gray_plane, CV_BGR2GRAY);
 280 
 281     int hist_size = 256;
 282     int hist_height = 256;
 283     float range[] = { 0, 255 };
 284     float* ranges[] = { range };
 285     CvHistogram* gray_hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY, ranges, 1);
 286     cvCalcHist(&gray_plane, gray_hist, 0, 0);
 287     cvNormalizeHist(gray_hist, 1.0);
 288 
 289     int scale = 2;
 290     IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale, hist_height), 8, 3);
 291     cvZero(hist_image);
 292     float max_value = 0;
 293     cvGetMinMaxHistValue(gray_hist, 0, &max_value, 0, 0);
 294 
 295     for (int i = 0; i<hist_size; i++) {
 296         float bin_val = cvQueryHistValue_1D(gray_hist, i);
 297         int intensity = cvRound(bin_val*hist_height / max_value);
 298         cvRectangle(hist_image,
 299             cvPoint(i*scale, hist_height - 1),
 300             cvPoint((i + 1)*scale - 1, hist_height - intensity),
 301             CV_RGB(255, 255, 255));
 302     }
 303 
 304     //cvNamedWindow("GraySource", 1);
 305     //cvShowImage("GraySource", gray_plane);
 306     //cvNamedWindow("H-S Histogram", 1);
 307     //cvShowImage("H-S Histogram", hist_image);
 308 
 309     cvWaitKey(0);
 310 
 311     return gray_hist;
 312 }
 313 CvHistogram* getHistogram(IplImage* src) {
 314     IplImage* gray_plane = cvCreateImage(cvGetSize(src), 8, 1);
 315     cvCvtColor(src, gray_plane, CV_BGR2GRAY);
 316     int hist_size = 256;
 317     int hist_height = 256;
 318     float range[] = { 0, 255 };
 319     float* ranges[] = { range };
 320     CvHistogram* gray_hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY, ranges, 1);
 321     cvCalcHist(&gray_plane, gray_hist, 0, 0);
 322     cvNormalizeHist(gray_hist, 1.0);
 323 
 324     int scale = 2;
 325     IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale, hist_height), 8, 3);
 326     cvZero(hist_image);
 327     float max_value = 0;
 328     cvGetMinMaxHistValue(gray_hist, 0, &max_value, 0, 0);
 329 
 330     for (int i = 0; i<hist_size; i++) {
 331         float bin_val = cvQueryHistValue_1D(gray_hist, i);
 332         int intensity = cvRound(bin_val*hist_height / max_value);
 333         cvRectangle(hist_image,
 334             cvPoint(i*scale, hist_height - 1),
 335             cvPoint((i + 1)*scale - 1, hist_height - intensity),
 336             CV_RGB(255, 255, 255));
 337     }
 338     return gray_hist;
 339 }
 340 
 341 //数字转换成特定位数的字符串
 342 inline void i2s(string& str, int i, int len = 5) {
 343     stringstream ss;
 344     ss << setw(len) << setfill('0') << i;
 345     str = ss.str();
 346 }
 347 
 348 //图像切割
 349 void jpgSplit(string fileName) {
 350     Mat curSample = imread(fileName);
 351     const int spt = 14;
 352     vector<Mat> part;
 353     int s = 0;
 354     for (int i = 0; i < 28; i += spt) {
 355         for (int j = 0; j < 28; j += spt) {
 356             part.push_back(curSample(Rect(i, j, spt, spt)));
 357             imshow("A", part[part.size() - 1]);
 358             waitKey();
 359             stringstream ss;
 360             string tmp;
 361             ss << s++; ss >> tmp;
 362             tmp = tmp + ".jpg";
 363             imwrite(tmp, part[part.size() - 1]);
 364         }
 365     }
 366 }
 367 
 368 //伽马校对
 369 void gammaCorrection(IplImage& cData) {
 370     for (int i = 0; i < cData.height; i++) {
 371         for (int j = 0; j < cData.width; j++) {
 372             CvScalar s = cvGet2D(&cData, i, j);
 373             s.val[0] = sqrt(s.val[0]);
 374             s.val[1] = sqrt(s.val[1]);
 375             s.val[2] = sqrt(s.val[2]);
 376         }
 377     }
 378 }
 379 void gammaCorrection(Mat& cData) {
 380     for (int i = 0; i < cData.size().height; i++) {
 381         for (int j = 0; j < cData.size().width; j++) {
 382             for (int k = 0; k < 3; k++) {
 383                 cData.at<Vec3b>(i, j)[k] = uchar(sqrt(cData.at<Vec3b>(i, j)[k]));
 384             }
 385         }
 386     }
 387 }
 388 
 389 //图片尺寸修改
 390 void imgResize(const string fileName, Mat& dst, int height, int width) {
 391     IplImage* src = cvLoadImage(fileName.c_str());
 392     if (src->height != height || src->width != width) {
 393         IplImage* reSizedMat = NULL;
 394         CvSize ImgSize;
 395         ImgSize.width = width;
 396         ImgSize.height = height;
 397         reSizedMat = cvCreateImage(ImgSize, src->depth, src->nChannels);
 398         cvResize(src, reSizedMat, CV_INTER_AREA);
 399         dst = Mat(reSizedMat, 0);
 400     }
 401     else dst = Mat(src, 0);
 402 }
 403 void imgResize(IplImage* src, Mat& dst, int height, int width) {
 404     if (src->height != height || src->width != width) {
 405         IplImage* reSizedMat = NULL;
 406         CvSize ImgSize;
 407         ImgSize.width = width;
 408         ImgSize.height = height;
 409         reSizedMat = cvCreateImage(ImgSize, src->depth, src->nChannels);
 410         cvResize(src, reSizedMat, CV_INTER_AREA);
 411         dst = Mat(reSizedMat, 0);
 412     }
 413     else dst = Mat(src, 0);
 414 }
 415 void imgResize(Mat& msrc, Mat& dst, int height, int width) {
 416     IplImage _src(msrc);
 417     IplImage* src = &_src;
 418     if (src->height != height || src->width != width) {
 419         IplImage* reSizedMat = NULL;
 420         CvSize ImgSize;
 421         ImgSize.width = width;
 422         ImgSize.height = height;
 423         reSizedMat = cvCreateImage(ImgSize, src->depth, src->nChannels);
 424         cvResize(src, reSizedMat, CV_INTER_AREA);
 425         dst = Mat(reSizedMat, 0);
 426     }
 427     else dst = Mat(src, 0);
 428 }
 429 
 430 //定位并切割数字的精确位置
 431 void getROI(Mat& src, Mat& dst, int binThreshold) {
 432     size_t aimRGB = 0x00;    //黑色0x00 白色0xff
 433     int sx = 0, sy = 0, ex = src.rows, ey = src.cols;
 434     for (int i = 0; i != src.rows; i++) {
 435         int avg = 0;
 436         for (int j = 0; j != src.cols; j++) {
 437             avg += src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2];
 438         }
 439         avg = avg / src.cols / 3;
 440         if (avg > aimRGB) {
 441             sx = i;
 442             break;
 443         }
 444     }
 445     for (int i = 0; i != src.cols; i++) {
 446         int avg = 0;
 447         for (int j = 0; j != src.rows; j++) {
 448             avg += src.at<Vec3b>(j, i)[0] + src.at<Vec3b>(j, i)[1] + src.at<Vec3b>(j, i)[2];
 449         }
 450         avg = avg / src.rows / 3;
 451         if (avg > aimRGB) {
 452             sy = i;
 453             break;
 454         }
 455     }
 456     for (int i = src.rows - 1; i != 0; i--) {
 457         int avg = 0;
 458         for (int j = 0; j != src.cols; j++) {
 459             avg += src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2];
 460         }
 461         avg = avg / src.cols / 3;
 462         if (avg > aimRGB) {
 463             ex = i;
 464             break;
 465         }
 466     }
 467     for (int i = src.cols - 1; i != 0; i--) {
 468         int avg = 0;
 469         for (int j = 0; j != src.rows; j++) {
 470             avg += src.at<Vec3b>(j, i)[0] + src.at<Vec3b>(j, i)[1] + src.at<Vec3b>(j, i)[2];
 471         }
 472         avg = avg / src.rows / 3;
 473         if (avg > aimRGB) {
 474             ey = i;
 475             break;
 476         }
 477     }
 478     dst = src(Range(sx, ex), Range(sy, ey));
 479 }
 480 
 481 //获取文件
 482 void getFile(string dirName, int num) {
 483     static int sum = 0;
 484     string tmp;
 485     string fileName;
 486     string snum;
 487     stringstream ss;
 488     int cur = 1;
 489     tmp.clear();
 490     ss << num; ss >> snum;
 491     ifstream fileRead;
 492     for (; cur <= nImgNum; cur++) {
 493         i2s(tmp, cur);
 494         fileName = dirNames[num] + snum + "_" + tmp + jpg;
 495         fileRead.open(fileName);
 496         if (!fileRead) {
 497             break;
 498         }
 499         fileRead.close();
 500         Mat sample = imread(fileName);
 501         samples.push_back(sample);
 502 #ifdef GETFILEONETIME
 503         break;
 504 #endif
 505     }
 506     cout << "number : " << num << ". Get samples. Total : " << samples.size() << endl << "Now training..." << endl;
 507 }
 508 
 509 //求得特征向量组
 510 void preTrain() {
 511     int num = 0;
 512     for (int i = 0; i != samples.size(); i++) {
 513         if (i != 0 && i % nImgNum == 0) {
 514             num++;
 515         }
 516         Mat trainImg = Mat::zeros(ImgWidth, ImgHeight, CV_8UC3);
 517         resize(samples[i], trainImg, cv::Size(ImgWidth, ImgHeight), 0, 0, INTER_CUBIC);
 518         HOGDescriptor *hog = new HOGDescriptor(
 519             cvSize(ImgWidth, ImgHeight), cvSize(14, 14), cvSize(7, 7), cvSize(7, 7), bin);
 520         descriptors.clear();
 521         hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0));
 522         int n = 0;
 523         for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
 524             dat_mat.at<float>(i, n++) = *iter;
 525         }
 526         res_mat.at<float>(i, 0) = float(num);
 527     }
 528 }
 529 
 530 //读取mnist文件
 531 int ReverseInt(int i) {
 532     unsigned char ch1, ch2, ch3, ch4;
 533     ch1 = i & 255;
 534     ch2 = (i >> 8) & 255;
 535     ch3 = (i >> 16) & 255;
 536     ch4 = (i >> 24) & 255;
 537     return((int)ch1 << 24) + ((int)ch2 << 16) + ((int)ch3 << 8) + ch4;
 538 }
 539 void read_Mnist(string filename, vector<cv::Mat> &vec) {
 540     ifstream file(filename, ios::binary);
 541     if (file.is_open()) {
 542         int magic_number = 0;
 543         int number_of_images = 0;
 544         int n_rows = 0;
 545         int n_cols = 0;
 546         file.read((char*)&magic_number, sizeof(magic_number));
 547         magic_number = ReverseInt(magic_number);
 548         file.read((char*)&number_of_images, sizeof(number_of_images));
 549         number_of_images = ReverseInt(number_of_images);
 550         file.read((char*)&n_rows, sizeof(n_rows));
 551         n_rows = ReverseInt(n_rows);
 552         file.read((char*)&n_cols, sizeof(n_cols));
 553         n_cols = ReverseInt(n_cols);
 554 
 555         for (int i = 0; i < number_of_images; ++i) {
 556             cv::Mat tp = cv::Mat::zeros(n_rows, n_cols, CV_8UC1);
 557             for (int r = 0; r < n_rows; ++r) {
 558                 for (int c = 0; c < n_cols; ++c) {
 559                     unsigned char temp = 0;
 560                     file.read((char*)&temp, sizeof(temp));
 561                     tp.at<uchar>(r, c) = (int)temp;
 562                 }
 563             }
 564             vec.push_back(tp);
 565         }
 566     }
 567 }
 568 void read_Mnist_Label(string filename, vector<int> &vec) {
 569     ifstream file(filename, ios::binary);
 570     if (file.is_open()) {
 571         int magic_number = 0;
 572         int number_of_images = 0;
 573         int n_rows = 0;
 574         int n_cols = 0;
 575         file.read((char*)&magic_number, sizeof(magic_number));
 576         magic_number = ReverseInt(magic_number);
 577         file.read((char*)&number_of_images, sizeof(number_of_images));
 578         number_of_images = ReverseInt(number_of_images);
 579 
 580         for (int i = 0; i < number_of_images; ++i) {
 581             unsigned char temp = 0;
 582             file.read((char*)&temp, sizeof(temp));
 583             vec[i] = (int)temp;
 584         }
 585     }
 586 }
 587 void readTrained() {
 588     samples.clear();
 589     labels.clear();
 590     dat_mat = Mat::zeros(10 * nImgNum, 324, CV_32FC1);
 591     res_mat = Mat::zeros(10 * nImgNum, 1, CV_32FC1);
 592     labels = vector<int>(nImgNum);
 593 
 594     string filename_train_images = "./mnist/train-images.idx3-ubyte";
 595     string filename_train_labels = "./mnist/train-labels.idx1-ubyte";
 596 
 597     read_Mnist(filename_train_images, samples);
 598     read_Mnist_Label(filename_train_labels, labels);
 599     if (samples.size() != labels.size()) {
 600         cout << "parse MNIST train file error" << endl;
 601         cout << samples.size() << " != " << labels.size() << endl;
 602         exit(EXIT_FAILURE);
 603     }
 604     for (int i = 0; i != nImgNum; i++) {
 605         Mat trainImg = Mat::zeros(ImgWidth, ImgHeight, CV_8UC3);
 606         resize(samples[i], trainImg, cv::Size(ImgWidth, ImgHeight), 0, 0, INTER_CUBIC);
 607         HOGDescriptor *hog = new HOGDescriptor(
 608             cvSize(ImgWidth, ImgHeight), cvSize(14, 14), cvSize(7, 7), cvSize(7, 7), bin);
 609         descriptors.clear();
 610         hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0));
 611         int n = 0;
 612         for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
 613             dat_mat.at<float>(i, n++) = *iter;
 614         }
 615         res_mat.at<float>(i, 0) = float(labels[i]);
 616     }
 617     cout << dat_mat.size() << endl;
 618     knn.train(dat_mat, res_mat, Mat(), false, 2);
 619 }
 620 
 621 //knn训练模块
 622 void knnTrain() {
 623 #ifdef SAVETRAINED
 624     //knn training;
 625     samples.clear();
 626     dat_mat = Mat::zeros(10 * nImgNum, 324, CV_32FC1);
 627     res_mat = Mat::zeros(10 * nImgNum, 1, CV_32FC1);
 628     for (int i = 0; i != 10; i++) {
 629         getFile(dirNames[i], i);
 630     }
 631     preTrain();
 632     cout << "------ Training finished. -----" << endl << endl;
 633     knn.train(dat_mat, res_mat, Mat(), false, 2);
 634 
 635 #ifdef SAVEASTXT
 636     cout << "Here are " << dat_mat.size().height << " eigenvectors. " << endl;
 637     ofstream fileWrite("./trained/knnTrained.dat");
 638     for (int i = 0; i < dat_mat.size().height; i++) {
 639         for (int j = 0; j < dat_mat.size().width; j++) {
 640             fileWrite << dat_mat.at<float>(i, j) << " ";
 641         }
 642     }
 643     fileWrite.close();
 644 
 645     fileWrite.open("./trained/result.dat");
 646     for (int i = 0; i < res_mat.size().height; i++) {
 647         fileWrite << res_mat.at<float>(i, 0) << " ";
 648     }
 649 #endif
 650 
 651 #else
 652     readTrained();
 653 #endif
 654 }
 655 
 656 //用作准确度统计的knn测试
 657 void selfknnTest() {
 658     //knn test
 659     cout << endl << "--- KNN test mode : ---" << endl;
 660     int tCnt = 10000;
 661     int tAc = 0;
 662 
 663     const string testRootDir("./test/");
 664     const string testDir[11] = {
 665         testRootDir + "0/", testRootDir + "1/", testRootDir + "2/",
 666         testRootDir + "3/", testRootDir + "4/", testRootDir + "5/",
 667         testRootDir + "6/", testRootDir + "7/", testRootDir + "8/",
 668         testRootDir + "9/"
 669     };
 670     for (int i = 0; i != 10; i++) {
 671         cout << "Now test : " << i << endl;
 672         string tmp;
 673         string fileName;
 674         string snum;
 675         stringstream ss;
 676         int cur = 1;
 677         tmp.clear();
 678         ss << i; ss >> snum;
 679         ifstream fileRead;
 680         for (; cur <= testNum; cur++) {
 681             i2s(tmp, cur + 5000);
 682             fileName = testDir[i] + snum + "_" + tmp + jpg;
 683             fileRead.open(fileName);
 684             if (!fileRead) {
 685                 break;
 686             }
 687             fileRead.close();
 688 
 689             Mat test_mat;
 690             test_mat = imread(fileName);
 691 
 692             Mat testVec;
 693             descriptors.clear();
 694             testVec = Mat::zeros(1, 324, CV_32FC1);
 695             HOGDescriptor *hog = new HOGDescriptor(
 696                 cvSize(ImgWidth, ImgHeight), cvSize(14, 14), cvSize(7, 7), cvSize(7, 7), bin);
 697             hog->compute(test_mat, descriptors, Size(1, 1), Size(0, 0));
 698             int n = 0;
 699             for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
 700                 testVec.at<float>(0, n++) = float(*iter);
 701             }
 702             float result = knn.find_nearest(testVec, 1);
 703             if (result == float(i)) {
 704                 tAc++;
 705             }
 706             //cout << result << endl;
 707             //imshow("", test_mat);
 708             //waitKey();
 709 
 710             test_mat.~Mat();
 711         }
 712     }
 713 
 714     cout << endl << endl << "Total number of test samples : " << tCnt << endl;
 715 
 716     cout << "Accuracy : " << float(float(tAc) / float(tCnt)) * 100 << "%" << endl;
 717 }
 718 
 719 //识别模块
 720 int recongnition(IplImage* src) {
 721     Mat dst;
 722     imgResize(src, dst, ImgHeight, ImgWidth);
 723     Mat testVec;
 724     descriptors.clear();
 725     testVec = Mat::zeros(1, 324, CV_32FC1);
 726     HOGDescriptor *hog = new HOGDescriptor(
 727         cvSize(ImgWidth, ImgHeight), cvSize(14, 14), cvSize(7, 7), cvSize(7, 7), bin);
 728     hog->compute(dst, descriptors, Size(1, 1), Size(0, 0));
 729     int n = 0;
 730     for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
 731         testVec.at<float>(0, n++) = float(*iter);
 732     }
 733     float result = knn.find_nearest(testVec, 1);
 734     return int(result);
 735     //imshow("", dst);
 736     //waitKey();
 737 }
 738 int recongnition(const string fileName) {
 739     Mat dst;
 740     imgResize(fileName, dst, ImgHeight, ImgWidth);
 741     Mat testVec;
 742     descriptors.clear();
 743     testVec = Mat::zeros(1, 324, CV_32FC1);
 744     HOGDescriptor *hog = new HOGDescriptor(
 745         cvSize(ImgWidth, ImgHeight), cvSize(14, 14), cvSize(7, 7), cvSize(7, 7), bin);
 746     hog->compute(dst, descriptors, Size(1, 1), Size(0, 0));
 747     int n = 0;
 748     for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
 749         testVec.at<float>(0, n++) = float(*iter);
 750     }
 751     float result = knn.find_nearest(testVec, 1);
 752     return int(result);
 753     //imshow("", dst);
 754     //waitKey();
 755 }
 756 int recongnition(Mat src) {
 757     Mat dst;
 758     imgResize(src, dst, ImgHeight, ImgWidth);
 759     Mat testVec;
 760     descriptors.clear();
 761     testVec = Mat::zeros(1, 324, CV_32FC1);
 762     HOGDescriptor *hog = new HOGDescriptor(
 763         cvSize(ImgWidth, ImgHeight), cvSize(14, 14), cvSize(7, 7), cvSize(7, 7), bin);
 764     hog->compute(dst, descriptors, Size(1, 1), Size(0, 0));
 765     int n = 0;
 766     for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
 767         testVec.at<float>(0, n++) = float(*iter);
 768     }
 769     float result = knn.find_nearest(testVec, 1);
 770     return int(result);
 771     //imshow("", dst);
 772     //waitKey();
 773 }
 774 
 775 //去噪
 776 typedef class ImgDenoising {
 777 public:
 778     ImgDenoising() = default;
 779     ImgDenoising(Mat& ss, int nn, int mm, int bb) :
 780         src(ss), n(nn), m(mm), binThreshold(bb) {
 781         int dt = max(n, m);
 782         G = new int*[dt];
 783         for (int i = 0; i < dt; i++) {
 784             G[i] = new int[dt];
 785         }
 786     }
 787     Mat src;
 788     int** G;
 789     int n, m, cnt, binThreshold;
 790     bool ok(int i, int j) {
 791         return G[i][j] == -1 && i > 0 && j > 0 && i < n && j < m;
 792     }
 793     void _dfs(int r, int c) {
 794         static int dx[4] = { 0, 0, 1, -1 };
 795         static int dy[4] = { 1, -1, 0, 0 };
 796         if (G[r][c] == -1) G[r][c] = cnt;
 797         else return;
 798         for (int i = 0; i < 4; i++) {
 799             int x = r + dx[i];
 800             int y = c + dy[i];
 801             if (ok(x, y)) _dfs(x, y);
 802         }
 803     }
 804     void imgDenoising() {
 805         memset(G, -1, sizeof(G));
 806         cnt = 1;
 807         n = src.rows;
 808         m = src.cols;
 809         for (int i = 0; i < n; i++) {
 810             for (int j = 0; j < m; j++) {
 811                 int rgb = (src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2]) / 3;
 812                 if (rgb < binThreshold) G[i][j] = -1;
 813             }
 814         }
 815         for (int i = 0; i < src.rows; i++) {
 816             for (int j = 0; j < src.cols; j++) {
 817                 if (G[i][j] == -1) {
 818                     _dfs(i, j);
 819                     cnt++;
 820                 }
 821             }
 822         }
 823         cout << cnt << endl;
 824     }
 825 }ImgDenoising;
 826 
 827 //连通域切分
 828 const int inf = 0x7f7f;
 829 bool ok(int** G, int n, int m, int i, int j) {
 830     return G[i][j] == 0 && i >= 0 && j >= 0 && i < n && j < m;
 831 }
 832 void _dfs(int** G, int n, int m, int r, int c, int cnt) {
 833     static int dx[4] = { 0, 0, 1, -1 };
 834     static int dy[4] = { 1, -1, 0, 0 };
 835     G[r][c] = cnt;
 836     for (int i = 0; i < 4; i++) {
 837         int x = r + dx[i];
 838         int y = c + dy[i];
 839         if (ok(G, n, m, x, y)) _dfs(G, n, m, x, y, cnt);
 840     }
 841 }
 842 int devideNum(Mat& src, int binThreshold, int** G) {
 843     int n = src.rows;
 844     int m = src.cols;
 845     int dt = max(n, m);
 846     int cnt = 1;
 847     G = new int*[dt];
 848     for (int i = 0; i < dt; i++) {
 849         G[i] = new int[dt];
 850         for (int j = 0; j < m; j++) {
 851             G[i][j] = inf;
 852         }
 853     }
 854     for (int i = 0; i < n; i++) {
 855         for (int j = 0; j < m; j++) {//试卷白底RGB=(0xff,0xff,0xff)
 856             int rgb = (src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2]) / 3;
 857             if (rgb > binThreshold) G[i][j] = 0;
 858         }
 859     }
 860     for (int i = 0; i < src.rows; i++) {
 861         for (int j = 0; j < src.cols; j++) {
 862             if (G[i][j] == 0) {
 863                 _dfs(G, n, m, i, j, cnt);
 864                 cnt++;
 865             }
 866         }
 867     }
 868     return cnt - 1;
 869 }
 870 void saveNums(Mat& src, vector<Mat>& nums) {
 871 
 872 }
 873 
 874 //获取直线[rho,theta]
 875 vector<Vec2f> getLines(const char* fileName) {
 876     Mat src = cvLoadImage(fileName);
 877     Mat img;
 878     cvtColor(src, img, CV_RGB2GRAY);
 879     GaussianBlur(img, img, Size(3, 3), 0, 0);
 880     Canny(img, img, 100, 200, 3);
 881     vector<Vec2f> lines, dre;
 882     lines.clear(); dre.clear();
 883     HoughLines(img, lines, 1.0, CV_PI / 180, 300, 0, 0);
 884     sort(lines.begin(), lines.end(), [](Vec2f i, Vec2f j){
 885         if (i.val[0] == j.val[0]) return i.val[1] < j.val[1];
 886         return i.val[0] < j.val[0];
 887     });
 888     //去重复,排序后记下第一个,然后设定阈值。记录的值和遍历到的作差,小于阈值则不放入容器中。
 889     double rho = lines[0].val[0];
 890     double threshold = 50;
 891     dre.push_back(lines[0]);
 892     for (int i = 1; i < lines.size(); i++) {
 893         if (lines[i].val[0] - rho <= threshold) continue;
 894         dre.push_back(lines[i]); rho = lines[i].val[0];
 895     }
 896     para.clear();  vert.clear();
 897     //通过计算几何学的运算求出交点。
 898     //分别丢入横线para和竖线vert容器中。
 899     for (int i = 0; i < dre.size(); i++) {
 900         float rho = dre[i][0], theta = dre[i][1];
 901         Point pt1, pt2;
 902         double a = cos(theta), b = sin(theta);
 903         double x0 = a * rho, y0 = b * rho;
 904         pt1.x = cvRound(x0 + 3500 * (-b));
 905         pt1.y = cvRound(y0 + 3500 * (a));
 906         pt2.x = cvRound(x0 - 3500 * (-b));
 907         pt2.y = cvRound(y0 - 3500 * (a));
 908         //横线para: 1<=theta<=2
 909 
 910         if (dre[i].val[1] >= 1 && dre[i].val[1] <= 2) para.push_back(Line(pt1, pt2));
 911         else vert.push_back(Line(pt1, pt2));
 912     }
 913     sort(vert.begin(), vert.end(), [](Line p, Line q){    //调整竖线顺序
 914         if (p.a.x == q.a.x) return p.a.y < q.a.y;
 915         return p.a.x < q.a.x;
 916     });
 917     sort(para.begin(), para.end(), [](Line p, Line q){    //调整横线顺序
 918         if (p.a.y == q.a.y) return p.a.x > q.a.x;
 919         return p.a.x > q.a.y;
 920     });
 921     //imwrite("./data/houghlines.jpg", img);
 922     //imshow("Y", img);
 923     //waitKey();
 924     return dre;
 925 }
 926 
 927 //测试霍夫变换的函数
 928 void houghTest(vector<Vec2f> lines, string picName) {
 929     ofstream fileWrite("./data/lines.txt");
 930     Mat dst = imread(picName);
 931     //霍夫变换后,y=kx+b -> rho=x*cos(theta)+y*sin(theta)
 932     for (size_t i = 0; i < lines.size(); i++) {
 933         float rho = lines[i][0], theta = lines[i][1];
 934         fileWrite << rho << " " << theta << endl;
 935         Point pt1, pt2;
 936         double a = cos(theta), b = sin(theta);
 937         double x0 = a * rho, y0 = b * rho;
 938         pt1.x = cvRound(x0 + 10000 * (-b));
 939         pt1.y = cvRound(y0 + 10000 * (a));
 940         pt2.x = cvRound(x0 - 10000 * (-b));
 941         pt2.y = cvRound(y0 - 10000 * (a));
 942         line(dst, pt1, pt2, Scalar(0, 0, 255), 1, CV_AA);
 943 
 944     }
 945     imwrite("./data/withline.jpg", dst);
 946     //imshow("A", dst);
 947     //waitKey();
 948     fileWrite.close();
 949 }
 950 
 951 //获取学号,分数栏内多项信息。
 952 void getMessage(Message& paperMessage, string picName, size_t binThreshold) {
 953     vector<vector<Point>> cross; cross.clear();    //[para][vert]
 954     // 霍夫线变换公式推导:
 955     // 设直线 y = k*x+b
 956     // k = cosθ / sinθ, b = ρ / sinθ
 957     // 则直线: y = (cosθ / sinθ) * x + ρ / cosθ
 958     // 化简: ρ = x * cosθ + y * sinθ
 959     // 竖: ρ = y * sinθ, y = ρ / cosθ {θ=0}
 960     // 横: ρ = x * cosθ, x = ρ / sinθ {θ=π/2}
 961 
 962     // 求横线竖线的笛卡尔积集,cross[i][j],横线i和竖线j交点
 963     Mat dst;
 964     dst = imread(picName);
 965     for (int i = 0; i < para.size(); i++) {
 966         cross.push_back(vector<Point>());
 967         for (int j = 0; j < vert.size(); j++) {
 968             //求交点
 969             Point cp = getCross(para[i], vert[j]);
 970             cross[i].push_back(cp);
 971         }
 972     }
 973     //获取到十个分数外加总分的表格边界,后面需要处理线之间距离
 974     //这里简单处理,假设图片完整,那么收集到的横竖线个数是一定的。排序列后找对应交点提取表格。
 975     vector<Line> bound; bound.clear();
 976     vector<Mat> sheet;
 977     for (int i = 4; i < para.size() - 1; i++) {
 978         int beginvert = 1;
 979         bound.push_back(Line(cross[i][beginvert], cross[i][beginvert + 1]));
 980         //line(dst, cross[i][beginvert], cross[i][beginvert+1], Scalar(0, 0, 255), 1, CV_AA);
 981         //line(dst, cross[i][beginvert], cross[i+1][beginvert], Scalar(0, 0, 255), 1, CV_AA);
 982         //line(dst, cross[i][beginvert+1], cross[i+1][beginvert+1], Scalar(0, 0, 255), 1, CV_AA);
 983     }
 984     for (int i = 0; i < bound.size() - 1; i++) {
 985         Mat tmp = dst(Rect(
 986             bound[i].a.x+20,
 987             bound[i].a.y+5, 
 988             bound[i + 1].b.x - bound[i].a.x-80,
 989             bound[i + 1].b.y - bound[i].a.y-5));
 990         sheet.push_back(tmp);
 991     }
 992     imwrite("./data/pointed.jpg", dst);
 993 
 994     char t = '0';
 995     for (int i = 0; i < sheet.size(); i++) {
 996         //getROI(sheet[i], sheet[i], binThreshold);
 997         imgResize(sheet[i], sheet[i], ImgHeight, ImgWidth);
 998         int score = recongnition(sheet[i]);
 999         paperMessage.score[i] = score;
1000         paperMessage.sum += score;
1001         imwrite(string(string("./data/tmp/sheet")+t+string(".jpg")), sheet[i]);
1002         t++;
1003     }
1004     Mat tmp = dst(Rect(
1005         bound[0].a.x + 20,
1006         bound[0].a.y + 5,
1007         bound[10].b.x - bound[0].a.x - 80,
1008         bound[10].b.y - bound[0].a.y - 5));
1009     cout << "Result :" << endl;
1010     for (int i = 0; i < 10; i++) {
1011         cout << "Problem ID: " << i + 1 << ". " << "Score : " << paperMessage.score[i] << endl;
1012     }
1013     imgResize(tmp, tmp, 450, 100);
1014     cout << "Sum : " << paperMessage.sum << endl;
1015     imshow("", tmp);
1016     waitKey();
1017 }
1018 
1019 #ifdef MAIN_CPP_START
1020 int main() {
1021     knnTrain();
1022     ifstream fileOpenTest;
1023     while (1) {
1024         string fileName;
1025         cout << "Please input the image file name :  ";
1026         cin >> fileName;
1027         fileOpenTest.open(fileName);
1028         if (!fileOpenTest) {
1029             cout << "This file doesn't exist. Please try again." << endl;
1030             continue;
1031         }
1032         fileOpenTest.close();
1033         IplImage* img = cvLoadImage(fileName.c_str());
1034         int binThreshold = imageBinarization(img);
1035         Mat src(img, 0), dst;
1036         blur(src, src, Size(3, 3));
1037         //ImgDenoising idi(src, src.rows, src.cols, binThreshold);//在这里统计连通块数并且处理掉噪点
1038         //idi.imgDenoising();
1039 
1040         //图像连通域切分,结果保存在G中
1041         int** G = NULL;
1042         int comDom = devideNum(src, binThreshold, G);
1043         getROI(src, dst, binThreshold);
1044         imgResize(dst, dst, ImgHeight, ImgWidth);
1045         cout << "It's number : " << recongnition(dst) << endl;
1046         imshow("", dst);
1047         waitKey();
1048     }
1049 
1050     //knnTrain();
1051     //selfknnTest();
1052     return 0;
1053 }
1054 
1055 #else
1056 int main() {
1057     knnTrain();
1058     //selfknnTest();
1059     Mat src;
1060     src = cvLoadImage("./data/qq.jpg");
1061     size_t binThreshold = imageBinarization(src);
1062 
1063     string picName("./data/otsu.jpg");
1064     houghTest(getLines(picName.c_str()), picName);
1065     Message paperMessage;
1066     getMessage(paperMessage, picName, binThreshold);
1067     return 0;
1068 }
1069 
1070 #endif

现行的试卷封面并获取学生题目得分信息以及学号信息的原型系统

测试分类结果

原文地址:https://www.cnblogs.com/kirai/p/6579815.html