大二的时候写的一个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、通过几何学运算获取横竖线段两两交点,将交点保存。公式推导:
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
现行的试卷封面并获取学生题目得分信息以及学号信息的原型系统
测试分类结果