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

课题背景及意义:

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

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

课题相关研究情况概述:

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

主要研究内容:

准备工作:

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

2、Opencv环境配置,尝试使用caffe+cuda未果,最终,机器学习部分使用的是Opencv的machine 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手册,查找opencv的ml库内有什么现成的模型,最终选定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、通过几何学运算获取横竖线段两两交点,将交点保存。公式推导:

  1. 104 // (p1-p0)X(p2-p0)=0
  2. 105 // (p3-p0)X(p2-p0)=0
  3. 106
  4. 107 // (y1-y2)x0+(x2-x1)y0+x1y2-x2y1=0
  5. 108 // (y3-y4)x0+(x4-x3)y0+x3y4-x4y3=0
  6. 109
  7. 110 // a1x0+b1y0+c1=0
  8. 111 // a2x0+b2y0+c2=0
  9. 112
  10. 113 // x0=(c1*b2-c2*b1)/(a2*b1-a1*b2)
  11. 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[];
  4. size_t sum;
  5. Message() {
  6. studentID.clear();
  7. memset(score, -, sizeof(score));
  8. sum = ;
  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 = ; //HOG特征提取时方向数
  50. const int ImgWidth = ; //图片宽度
  51. const int ImgHeight = ; //图片高度
  52. const int nImgNum = ; //训练图片数量(每个数字)
  53. const int testNum = ; //测试样例图片数量(每个数字)
  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[] = {
  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[];
  77. size_t sum;
  78. Message() {
  79. studentID.clear();
  80. memset(score, -, sizeof(score));
  81. sum = ;
  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[] = { };
  140. float pixel_pro[] = { };
  141. int threshold = ;
  142. uchar* data = (uchar*)src_image->imageData;
  143. for (int i = ; i < src_image->height; i++) {
  144. for (int j = ; j < src_image->width; j++) {
  145. pixel_count[(int)data[i * src_image->width + j]]++;
  146. }
  147. }
  148. for (int i = ; i < ; i++) {
  149. pixel_pro[i] = (float)pixel_count[i] / (src_image->height * src_image->width);
  150. }
  151. for (int i = ; i < ; i++) {
  152. w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = ;
  153. for (int j = ; j < ; 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), ));
  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[] = { };
  184. float pixel_pro[] = { };
  185. int threshold = ;
  186.  
  187. for (int i = ; i < src_image.size().height; i++) {
  188. uchar* data = src_image.ptr<uchar>(i);
  189. for (int j = ; j < src_image.size().width; j++) {
  190. pixel_count[int(data[j])]++;
  191. }
  192. }
  193. for (int i = ; i < ; i++) {
  194. pixel_pro[i] = (float)pixel_count[i] / (src_image.size().height * src_image.size().width);
  195. }
  196. for (int i = ; i < ; i++) {
  197. w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = ;
  198. for (int j = ; j < ; 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), ));
  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 = ;
  225. int binThreshold = otsu(src_image);
  226. for (int i = ; i < src_image->height; i++) {
  227. for (int j = ; j < src_image->width; j++) {
  228. s = cvGet2D(src_image, i, j);
  229. ave = int((s.val[] + s.val[] + s.val[]));
  230. if (ave < * binThreshold) { //取反 ave < binThreshold
  231. s.val[] = 0xff;
  232. s.val[] = 0xff;
  233. s.val[] = 0xff;
  234. cvSet2D(src_image, i, j, s);
  235. }
  236. else {
  237. s.val[] = 0x00;
  238. s.val[] = 0x00;
  239. s.val[] = 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 = ;
  253. int binThreshold = otsu(src_image);
  254. for (int i = ; i < src_image.size().width; i++) {
  255. for (int j = ; j < src_image.size().height; j++) {
  256. ave = src_image.at<Vec3b>(j, i)[] + src_image.at<Vec3b>(j, i)[] + src_image.at<Vec3b>(j, i)[];
  257. if (ave < * binThreshold) {
  258. binImg.at<Vec3b>(j, i)[] = 0xff;
  259. binImg.at<Vec3b>(j, i)[] = 0xff;
  260. binImg.at<Vec3b>(j, i)[] = 0xff;
  261. }
  262. else {
  263. binImg.at<Vec3b>(j, i)[] = ;
  264. binImg.at<Vec3b>(j, i)[] = ;
  265. binImg.at<Vec3b>(j, i)[] = ;
  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), , );
  279. cvCvtColor(src, gray_plane, CV_BGR2GRAY);
  280.  
  281. int hist_size = ;
  282. int hist_height = ;
  283. float range[] = { , };
  284. float* ranges[] = { range };
  285. CvHistogram* gray_hist = cvCreateHist(, &hist_size, CV_HIST_ARRAY, ranges, );
  286. cvCalcHist(&gray_plane, gray_hist, , );
  287. cvNormalizeHist(gray_hist, 1.0);
  288.  
  289. int scale = ;
  290. IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale, hist_height), , );
  291. cvZero(hist_image);
  292. float max_value = ;
  293. cvGetMinMaxHistValue(gray_hist, , &max_value, , );
  294.  
  295. for (int i = ; 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 - ),
  300. cvPoint((i + )*scale - , hist_height - intensity),
  301. CV_RGB(, , ));
  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();
  310.  
  311. return gray_hist;
  312. }
  313. CvHistogram* getHistogram(IplImage* src) {
  314. IplImage* gray_plane = cvCreateImage(cvGetSize(src), , );
  315. cvCvtColor(src, gray_plane, CV_BGR2GRAY);
  316. int hist_size = ;
  317. int hist_height = ;
  318. float range[] = { , };
  319. float* ranges[] = { range };
  320. CvHistogram* gray_hist = cvCreateHist(, &hist_size, CV_HIST_ARRAY, ranges, );
  321. cvCalcHist(&gray_plane, gray_hist, , );
  322. cvNormalizeHist(gray_hist, 1.0);
  323.  
  324. int scale = ;
  325. IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale, hist_height), , );
  326. cvZero(hist_image);
  327. float max_value = ;
  328. cvGetMinMaxHistValue(gray_hist, , &max_value, , );
  329.  
  330. for (int i = ; 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 - ),
  335. cvPoint((i + )*scale - , hist_height - intensity),
  336. CV_RGB(, , ));
  337. }
  338. return gray_hist;
  339. }
  340.  
  341. //数字转换成特定位数的字符串
  342. inline void i2s(string& str, int i, int len = ) {
  343. stringstream ss;
  344. ss << setw(len) << setfill('') << i;
  345. str = ss.str();
  346. }
  347.  
  348. //图像切割
  349. void jpgSplit(string fileName) {
  350. Mat curSample = imread(fileName);
  351. const int spt = ;
  352. vector<Mat> part;
  353. int s = ;
  354. for (int i = ; i < ; i += spt) {
  355. for (int j = ; j < ; j += spt) {
  356. part.push_back(curSample(Rect(i, j, spt, spt)));
  357. imshow("A", part[part.size() - ]);
  358. waitKey();
  359. stringstream ss;
  360. string tmp;
  361. ss << s++; ss >> tmp;
  362. tmp = tmp + ".jpg";
  363. imwrite(tmp, part[part.size() - ]);
  364. }
  365. }
  366. }
  367.  
  368. //伽马校对
  369. void gammaCorrection(IplImage& cData) {
  370. for (int i = ; i < cData.height; i++) {
  371. for (int j = ; j < cData.width; j++) {
  372. CvScalar s = cvGet2D(&cData, i, j);
  373. s.val[] = sqrt(s.val[]);
  374. s.val[] = sqrt(s.val[]);
  375. s.val[] = sqrt(s.val[]);
  376. }
  377. }
  378. }
  379. void gammaCorrection(Mat& cData) {
  380. for (int i = ; i < cData.size().height; i++) {
  381. for (int j = ; j < cData.size().width; j++) {
  382. for (int k = ; k < ; 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, );
  400. }
  401. else dst = Mat(src, );
  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, );
  412. }
  413. else dst = Mat(src, );
  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, );
  426. }
  427. else dst = Mat(src, );
  428. }
  429.  
  430. //定位并切割数字的精确位置
  431. void getROI(Mat& src, Mat& dst, int binThreshold) {
  432. size_t aimRGB = 0x00; //黑色0x00 白色0xff
  433. int sx = , sy = , ex = src.rows, ey = src.cols;
  434. for (int i = ; i != src.rows; i++) {
  435. int avg = ;
  436. for (int j = ; j != src.cols; j++) {
  437. avg += src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[];
  438. }
  439. avg = avg / src.cols / ;
  440. if (avg > aimRGB) {
  441. sx = i;
  442. break;
  443. }
  444. }
  445. for (int i = ; i != src.cols; i++) {
  446. int avg = ;
  447. for (int j = ; j != src.rows; j++) {
  448. avg += src.at<Vec3b>(j, i)[] + src.at<Vec3b>(j, i)[] + src.at<Vec3b>(j, i)[];
  449. }
  450. avg = avg / src.rows / ;
  451. if (avg > aimRGB) {
  452. sy = i;
  453. break;
  454. }
  455. }
  456. for (int i = src.rows - ; i != ; i--) {
  457. int avg = ;
  458. for (int j = ; j != src.cols; j++) {
  459. avg += src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[];
  460. }
  461. avg = avg / src.cols / ;
  462. if (avg > aimRGB) {
  463. ex = i;
  464. break;
  465. }
  466. }
  467. for (int i = src.cols - ; i != ; i--) {
  468. int avg = ;
  469. for (int j = ; j != src.rows; j++) {
  470. avg += src.at<Vec3b>(j, i)[] + src.at<Vec3b>(j, i)[] + src.at<Vec3b>(j, i)[];
  471. }
  472. avg = avg / src.rows / ;
  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 = ;
  484. string tmp;
  485. string fileName;
  486. string snum;
  487. stringstream ss;
  488. int cur = ;
  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 = ;
  512. for (int i = ; i != samples.size(); i++) {
  513. if (i != && i % nImgNum == ) {
  514. num++;
  515. }
  516. Mat trainImg = Mat::zeros(ImgWidth, ImgHeight, CV_8UC3);
  517. resize(samples[i], trainImg, cv::Size(ImgWidth, ImgHeight), , , INTER_CUBIC);
  518. HOGDescriptor *hog = new HOGDescriptor(
  519. cvSize(ImgWidth, ImgHeight), cvSize(, ), cvSize(, ), cvSize(, ), bin);
  520. descriptors.clear();
  521. hog->compute(trainImg, descriptors, Size(, ), Size(, ));
  522. int n = ;
  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, ) = float(num);
  527. }
  528. }
  529.  
  530. //读取mnist文件
  531. int ReverseInt(int i) {
  532. unsigned char ch1, ch2, ch3, ch4;
  533. ch1 = i & ;
  534. ch2 = (i >> ) & ;
  535. ch3 = (i >> ) & ;
  536. ch4 = (i >> ) & ;
  537. return((int)ch1 << ) + ((int)ch2 << ) + ((int)ch3 << ) + 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 = ;
  543. int number_of_images = ;
  544. int n_rows = ;
  545. int n_cols = ;
  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 = ; i < number_of_images; ++i) {
  556. cv::Mat tp = cv::Mat::zeros(n_rows, n_cols, CV_8UC1);
  557. for (int r = ; r < n_rows; ++r) {
  558. for (int c = ; c < n_cols; ++c) {
  559. unsigned char temp = ;
  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 = ;
  572. int number_of_images = ;
  573. int n_rows = ;
  574. int n_cols = ;
  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 = ; i < number_of_images; ++i) {
  581. unsigned char temp = ;
  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( * nImgNum, , CV_32FC1);
  591. res_mat = Mat::zeros( * nImgNum, , 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 = ; i != nImgNum; i++) {
  605. Mat trainImg = Mat::zeros(ImgWidth, ImgHeight, CV_8UC3);
  606. resize(samples[i], trainImg, cv::Size(ImgWidth, ImgHeight), , , INTER_CUBIC);
  607. HOGDescriptor *hog = new HOGDescriptor(
  608. cvSize(ImgWidth, ImgHeight), cvSize(, ), cvSize(, ), cvSize(, ), bin);
  609. descriptors.clear();
  610. hog->compute(trainImg, descriptors, Size(, ), Size(, ));
  611. int n = ;
  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, ) = float(labels[i]);
  616. }
  617. cout << dat_mat.size() << endl;
  618. knn.train(dat_mat, res_mat, Mat(), false, );
  619. }
  620.  
  621. //knn训练模块
  622. void knnTrain() {
  623. #ifdef SAVETRAINED
  624. //knn training;
  625. samples.clear();
  626. dat_mat = Mat::zeros( * nImgNum, , CV_32FC1);
  627. res_mat = Mat::zeros( * nImgNum, , CV_32FC1);
  628. for (int i = ; i != ; i++) {
  629. getFile(dirNames[i], i);
  630. }
  631. preTrain();
  632. cout << "------ Training finished. -----" << endl << endl;
  633. knn.train(dat_mat, res_mat, Mat(), false, );
  634.  
  635. #ifdef SAVEASTXT
  636. cout << "Here are " << dat_mat.size().height << " eigenvectors. " << endl;
  637. ofstream fileWrite("./trained/knnTrained.dat");
  638. for (int i = ; i < dat_mat.size().height; i++) {
  639. for (int j = ; 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 = ; i < res_mat.size().height; i++) {
  647. fileWrite << res_mat.at<float>(i, ) << " ";
  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 = ;
  661. int tAc = ;
  662.  
  663. const string testRootDir("./test/");
  664. const string testDir[] = {
  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 = ; i != ; i++) {
  671. cout << "Now test : " << i << endl;
  672. string tmp;
  673. string fileName;
  674. string snum;
  675. stringstream ss;
  676. int cur = ;
  677. tmp.clear();
  678. ss << i; ss >> snum;
  679. ifstream fileRead;
  680. for (; cur <= testNum; cur++) {
  681. i2s(tmp, cur + );
  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(, , CV_32FC1);
  695. HOGDescriptor *hog = new HOGDescriptor(
  696. cvSize(ImgWidth, ImgHeight), cvSize(, ), cvSize(, ), cvSize(, ), bin);
  697. hog->compute(test_mat, descriptors, Size(, ), Size(, ));
  698. int n = ;
  699. for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
  700. testVec.at<float>(, n++) = float(*iter);
  701. }
  702. float result = knn.find_nearest(testVec, );
  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)) * << "%" << 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(, , CV_32FC1);
  726. HOGDescriptor *hog = new HOGDescriptor(
  727. cvSize(ImgWidth, ImgHeight), cvSize(, ), cvSize(, ), cvSize(, ), bin);
  728. hog->compute(dst, descriptors, Size(, ), Size(, ));
  729. int n = ;
  730. for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
  731. testVec.at<float>(, n++) = float(*iter);
  732. }
  733. float result = knn.find_nearest(testVec, );
  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(, , CV_32FC1);
  744. HOGDescriptor *hog = new HOGDescriptor(
  745. cvSize(ImgWidth, ImgHeight), cvSize(, ), cvSize(, ), cvSize(, ), bin);
  746. hog->compute(dst, descriptors, Size(, ), Size(, ));
  747. int n = ;
  748. for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
  749. testVec.at<float>(, n++) = float(*iter);
  750. }
  751. float result = knn.find_nearest(testVec, );
  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(, , CV_32FC1);
  762. HOGDescriptor *hog = new HOGDescriptor(
  763. cvSize(ImgWidth, ImgHeight), cvSize(, ), cvSize(, ), cvSize(, ), bin);
  764. hog->compute(dst, descriptors, Size(, ), Size(, ));
  765. int n = ;
  766. for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) {
  767. testVec.at<float>(, n++) = float(*iter);
  768. }
  769. float result = knn.find_nearest(testVec, );
  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 = ; 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] == - && i > && j > && i < n && j < m;
  792. }
  793. void _dfs(int r, int c) {
  794. static int dx[] = { , , , - };
  795. static int dy[] = { , -, , };
  796. if (G[r][c] == -) G[r][c] = cnt;
  797. else return;
  798. for (int i = ; i < ; 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, -, sizeof(G));
  806. cnt = ;
  807. n = src.rows;
  808. m = src.cols;
  809. for (int i = ; i < n; i++) {
  810. for (int j = ; j < m; j++) {
  811. int rgb = (src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[]) / ;
  812. if (rgb < binThreshold) G[i][j] = -;
  813. }
  814. }
  815. for (int i = ; i < src.rows; i++) {
  816. for (int j = ; j < src.cols; j++) {
  817. if (G[i][j] == -) {
  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] == && i >= && j >= && i < n && j < m;
  831. }
  832. void _dfs(int** G, int n, int m, int r, int c, int cnt) {
  833. static int dx[] = { , , , - };
  834. static int dy[] = { , -, , };
  835. G[r][c] = cnt;
  836. for (int i = ; i < ; 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 = ;
  847. G = new int*[dt];
  848. for (int i = ; i < dt; i++) {
  849. G[i] = new int[dt];
  850. for (int j = ; j < m; j++) {
  851. G[i][j] = inf;
  852. }
  853. }
  854. for (int i = ; i < n; i++) {
  855. for (int j = ; j < m; j++) {//试卷白底RGB=(0xff,0xff,0xff)
  856. int rgb = (src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[] + src.at<Vec3b>(i, j)[]) / ;
  857. if (rgb > binThreshold) G[i][j] = ;
  858. }
  859. }
  860. for (int i = ; i < src.rows; i++) {
  861. for (int j = ; j < src.cols; j++) {
  862. if (G[i][j] == ) {
  863. _dfs(G, n, m, i, j, cnt);
  864. cnt++;
  865. }
  866. }
  867. }
  868. return cnt - ;
  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(, ), , );
  880. Canny(img, img, , , );
  881. vector<Vec2f> lines, dre;
  882. lines.clear(); dre.clear();
  883. HoughLines(img, lines, 1.0, CV_PI / , , , );
  884. sort(lines.begin(), lines.end(), [](Vec2f i, Vec2f j){
  885. if (i.val[] == j.val[]) return i.val[] < j.val[];
  886. return i.val[] < j.val[];
  887. });
  888. //去重复,排序后记下第一个,然后设定阈值。记录的值和遍历到的作差,小于阈值则不放入容器中。
  889. double rho = lines[].val[];
  890. double threshold = ;
  891. dre.push_back(lines[]);
  892. for (int i = ; i < lines.size(); i++) {
  893. if (lines[i].val[] - rho <= threshold) continue;
  894. dre.push_back(lines[i]); rho = lines[i].val[];
  895. }
  896. para.clear(); vert.clear();
  897. //通过计算几何学的运算求出交点。
  898. //分别丢入横线para和竖线vert容器中。
  899. for (int i = ; i < dre.size(); i++) {
  900. float rho = dre[i][], theta = dre[i][];
  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 + * (-b));
  905. pt1.y = cvRound(y0 + * (a));
  906. pt2.x = cvRound(x0 - * (-b));
  907. pt2.y = cvRound(y0 - * (a));
  908. //横线para: 1<=theta<=2
  909.  
  910. if (dre[i].val[] >= && dre[i].val[] <= ) 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 = ; i < lines.size(); i++) {
  933. float rho = lines[i][], theta = lines[i][];
  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 + * (-b));
  939. pt1.y = cvRound(y0 + * (a));
  940. pt2.x = cvRound(x0 - * (-b));
  941. pt2.y = cvRound(y0 - * (a));
  942. line(dst, pt1, pt2, Scalar(, , ), , 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 = ; i < para.size(); i++) {
  966. cross.push_back(vector<Point>());
  967. for (int j = ; 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 = ; i < para.size() - ; i++) {
  978. int beginvert = ;
  979. bound.push_back(Line(cross[i][beginvert], cross[i][beginvert + ]));
  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 = ; i < bound.size() - ; i++) {
  985. Mat tmp = dst(Rect(
  986. bound[i].a.x+,
  987. bound[i].a.y+,
  988. bound[i + ].b.x - bound[i].a.x-,
  989. bound[i + ].b.y - bound[i].a.y-));
  990. sheet.push_back(tmp);
  991. }
  992. imwrite("./data/pointed.jpg", dst);
  993.  
  994. char t = '';
  995. for (int i = ; 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[].a.x + ,
  1006. bound[].a.y + ,
  1007. bound[].b.x - bound[].a.x - ,
  1008. bound[].b.y - bound[].a.y - ));
  1009. cout << "Result :" << endl;
  1010. for (int i = ; i < ; i++) {
  1011. cout << "Problem ID: " << i + << ". " << "Score : " << paperMessage.score[i] << endl;
  1012. }
  1013. imgResize(tmp, tmp, , );
  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 () {
  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, ), dst;
  1036. blur(src, src, Size(, ));
  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 ;
  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 ;
  1068. }
  1069.  
  1070. #endif

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

测试分类结果

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

  1. 调用获取学生信息的接口,保存到excel里面

    # 2.http: // doc.nnzhp.cn / index.php?s = / 6 & page_id = 14# 调用获取学生信息的接口,保存到excel里面 import requ ...

  2. HttpClient+Jsoup模拟登陆贺州学院教务系统,获取学生个人信息

    前言 注:可能学校的教务系统已经做了升级,当前的程序不知道还能不能成功获取信息,加上已经毕业,我的账户已经被注销,试不了,在这里做下思路跟过程的记录. 在我的毕业设计中”基于SSM框架贺州学院校园二手 ...

  3. 调用获取学生信息的接口,保存到excel里面的小程序

    # 2.http: // doc.nnzhp.cn / index.php?s = / 6 & page_id = 14# 调用获取学生信息的接口,保存到excel里面 import requ ...

  4. Day_11【集合】扩展案例1_遍历打印学生信息,获取学生成绩的最高分,获取成绩最高的学员,获取学生成绩的平均值,获取不及格的学员数量

    分析以下需求,并用代码实现: 1.按照以下描述完成类的定义 学生类 属性: 姓名name 年龄age 成绩score 行为: 吃饭eat() study(String content)(content ...

  5. 重学 Java 设计模式:实战访问者模式「模拟家长与校长,对学生和老师的不同视角信息的访问场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 能力,是你前行的最大保障 年龄会不断的增长,但是什么才能让你不 ...

  6. 简析Geoserver中获取图层列表以及各图层描述信息的三种方法

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 实际项目中需要获取到Geoserver中的图层组织以及各图层 ...

  7. [Java] 获取本月周次和日期时间段信息

    package com.wdcloud.monitoring.common; import java.text.SimpleDateFormat; import java.util.ArrayList ...

  8. 获取手机中已安装apk文件信息(PackageInfo、ResolveInfo)(应用图片、应用名、包名等)

    众所周知,通过PackageManager可以获取手机端已安装的apk文件的信息,具体代码如下: PackageManager packageManager = this.getPackageMana ...

  9. C#的百度地图开发(三)依据坐标获取位置、商圈及周边信息

    原文:C#的百度地图开发(三)依据坐标获取位置.商圈及周边信息 我们得到了百度坐标,现在依据这一坐标来获取相应的信息.下面是相应的代码 public class BaiduMap { /// < ...

随机推荐

  1. EM算法 大白话讲解

    假设有一堆数据点,它是由两个线性模型产生的.公式如下: 模型参数为a,b,n:a为线性权值或斜率,b为常数偏置量,n为误差或者噪声. 一方面,假如我们被告知这两个模型的参数,则我们可以计算出损失. 对 ...

  2. jq返回顶部

    今天发工资了,哎,更加坚定我要努力的学习,没资本,只能玩技术了.人呢,想的的开,才行,虽然有些不甘心,不过确实,现在的技术只值这个 价格.不过做到问心无愧就够了,不然人之贪婪,真的收也收不回.好了,今 ...

  3. css实现页面居中的一种方法

    在网页制作的过程中,为方便读者的阅读,会把网页内容限定在一个较小的方框中,并居中显示,如何实现这一功能呢? 1)把正文放在一个<div>标签中,只要这个标签居中整个网页就居中了. < ...

  4. 阿里云SSD等磁盘挂载方法(详细步骤完整版)

    1,根据提示购买一块,在阿里云管理磁盘的列表"更多"点击,选中"挂载": 2,进入远程实例(远程系统),管理-->存储-->磁盘管理,在右侧看到新挂 ...

  5. 成小胖学习ActiveMQ·基础篇

    过了个春节,回到公司的成小胖变成了成大胖.但是你们千万别以为他那个大肚子里面装的都是肥肉,里面的墨水也多了不少嘞,毕竟成小胖利用春节的半个月时间专心学习并研究了 ActiveMQ,嘿嘿……这不,为了检 ...

  6. Android EventBus 3.0 实例使用详解

    EventBus的使用和原理在网上有很多的博客了,其中泓洋大哥和启舰写的非常非常棒,我也是跟着他们的博客学会的EventBus,因为是第一次接触并使用EventBus,所以我写的更多是如何使用,源码解 ...

  7. iOS RunTime你知道了总得用一下

    说点题外话: 我刚来现在这家公司的时候,老板让我下载一个脉脉,上去找找自己的同行,多认识些同行.其实初衷的好的,但最近这两天我把它卸载了,不为别的,负能量太多!iOS这行自从2016就没景气过,在这行 ...

  8. SharePoint 2016 配置应用程序商店

    最近碰到一个新的需求,就是要给SharePoint配置应用程序商店,挺有意思的,就简单的配置和记录了一下,分享给大家. 其实应用程序商店之前感觉很鸡肋,但是用起来还是不错的.不喜勿喷,呵呵. 首先需要 ...

  9. 《经久不衰的Spring框架:@ResponseBody 中文乱码》

    问题背景 本文并不是介绍@ResponseBody注解,也不是中文乱码问题的大汇总笔记,这些网上都有很多内容了.这边仅对几年前,一个卡壳了挺久时间的问题的解决过程做一个记录,以警惕自己,达到自醒得目的 ...

  10. DevExpreess汉化使用方法及汉化包

    1.在程序入口加入代码: System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.Cultu ...