燕山大学课程实践项目报告:ISBN号识别系统的设计与开发 您所在的位置:网站首页 项目识别的概念 燕山大学课程实践项目报告:ISBN号识别系统的设计与开发

燕山大学课程实践项目报告:ISBN号识别系统的设计与开发

2024-07-16 07:38| 来源: 网络整理| 查看: 265

文章目录 摘要前言正文一、研究内容的基本原理二、所采用的研究方法及相关工具三、项目的方案设计四、核心代码实现1.读取图片2.将原图转化为灰度图3.去噪处理4.迭代法求阈值5.水平投影确定行6.竖直投影确定列7.找最小矩形框8.模板匹配 字符识别 五、项目测试六、研究结果并讨论 结论附页开发环境项目源码下载源代码

摘要

国际标准书号ISBN由13位数字组成。前三位数字代表图书,中间的9个数字分为三组,分表示组号、出版社号和书序号,最后一个数字是校验码从1968年英国的“标准书号”(SBN)开始。其优点主要体现在:国际标准书号是机读的编码,从图书的生产到发行、销售始终如一,对图书的发行系统起了很大的作用:它的引入使图书的定购、库存控制、账目和输出过程等任何图书业的分支程序都简化了。我们小组设计出的这个ISBN编号识别系统利用机器视觉图像处理技术可以识别不同的 ISBN 号并将其读取出来,为事后相应的管理统筹工作提供可靠的辅助。

本系统开发采用了 VS2019+opencv的开发环境,C++语言实现。该项目流程为:读取->转化为灰度图->中值滤波去噪->二值化->水平投影确定行->竖直投影确定列 ->模板匹配字符识别->输出。

[关键词] 课程实践项目;数字识别;灰度转化;中值滤波降噪; 二值转化;字符分割

前言

随着现代社会的高速发展,人民的生活水平也在不断提高,阅读书籍的需求也在不断上涨,这对书籍管理水平提出了更高要求,而ISBN编号能极大的方便对书籍的管理,随之而来的问题是:人工识别ISBN码的效率太低了,需要计算机来代替我们做这些工作。但计算机不能直接识别图片中的内容,所以要求我们对图片进行一系列的操作,使它变得易于被识别。这些操作包括:转化为灰度图,中值滤波去噪,二值化,水平投影分割行,竖直投影切割列,找最小矩形框,最后得到单个字符的形式,之后进行数字识别。这里数字识别主要方法有:模板匹配法,神经网络法以及划线法。本小组采用模板匹配法来对字符进行识别。下面就分别介绍对图片的一系列操作和模板匹配的方法。

正文 一、研究内容的基本原理

本次的ISBN编号识别采用模板匹配的方法,在进行识别之前需要对图片进行一系列预处理:原图转灰度图 灰度图转二值图 对二值图进行切割 找到切割后的最小矩形框。模板匹配这种方法具体来说,就是把所有可能出现的每一个字符情况都找一定数量(我们用的有2~4个)的模板,当要识别未知字符时需要与所有模板一一比对,找到最接近的模板进行匹配。这里就有一个衡量标准的问题,即:怎样算“最接近”?我们用的是“找不同的”方法,即先将模板与待识别的字符图片调成一样大小(40X60),再对比对应位置的像素值是否相同,统计两幅图片不同的像素点的个数。如果不同点的数量越少,就认为该模板与待识别的字符越接近;因而最接近自然就是不同的像素点个数最少的模板。

二、所采用的研究方法及相关工具

我们小组在拿到课题后一起讨论弄,清楚了这个项目的重难点,采用了组长分工、小组成员讨论的方式进行该项目工程。分别完成各自部分的功能设计和代码实现,最后再一起细致的改入整个大项目中,也就是先分,再治,最后整合的过程。 我们采用的工具是 VS2019+opencv4.00,在工具统一的情况下进行工作和开发。

三、项目的方案设计

对于项目的整体设计方案,我们采用的是如下方法: 1,首先用glob()函数找到文件中每张图片的路径,根据路径找到将图片保存到Mat对象中,然后开始对每一张图片进行处理。 2,调整图像为统一大小以便进行处理,并将原彩色图片转化成灰度图。 3,对灰度图进行中值滤波降噪处理,以提高识别率。 4,灰度图二值化,采用迭代法求阈值。 5,对图像进行切割。将图像切割成只有最上面一部分的情况,也就是只有 ISBN 号的图片。 6,将第5步切出来的图片进一步切割成为单个数字图像的图片,并进行储存。 7,从上一步得到的单个字符的图片得到最小矩形框,并截出最小矩形的。 8,将切割好的数字与准备好的数字模板进行比对,匹配差值最小的数字。最后输出正确率和准确率。

四、核心代码实现 1.读取图片 //读取 ISBN 图片 string testImgPath = "数据集/*"; vector testImgFN;//必须String glob(testImgPath, testImgFN, false); int testImgNums = testImgFN.size(); for (int index =0; index int row = inputImg.rows; int col = inputImg.cols; outputImg.create(row, col, CV_8UC1); for (int i = 0; i double sum = 0; //得到三个通道的像素值 int b = inputImg.at(i, j)[0]; int g = inputImg.at(i, j)[1]; int r = inputImg.at(i, j)[2]; //利用灰度化公式将彩色图像三个通道的像素值转化为灰度图像单通道的像素值 sum = b * 0.114 + g * 0.587 + r * 0.299; outputImg.at(i, j) = static_cast(sum); } } }

代码讲解: 先用两个变量row和col分别来表示原图中每行每列的像素点,再用.create的方法创建一个和原图大小一样的图片,然后用两个循环语句根据灰度化公式将原彩色图像中每三个通道对应的像素点的像素值转化为灰度图中对应的像素点的像素值,由于定义的sum是double类型的,所以还需要使用 static_cast(sum)将sum转换为uchar类型,再赋值给灰度图的各个像素点,从而得到一个和原图大小一样的灰度图。

3.去噪处理 //冒泡排序对非边界值与其八邻域的进行排序,找到中值 int BubbletoMedian(vector& a) { int median;//找到中值 for (int i = 0; i if (a[j] > a[j + 1]) { int temp; temp = a[j + 1]; a[j + 1] = a[j]; a[j] = temp; } } } median = a[4]; return median; } //去噪处理 void denoising(Mat gray1, Mat& grayImg) { //声明一个与灰度图gray1行数,列数都相同的图grayImg grayImg = Mat(gray1.rows, gray1.cols, CV_8UC1); vectortemp(9); //声明动态数组temp[] //定义九个方向 int dx[9] = { 1,-1,1,-1,-1,0,1,0,0 }; int dy[9] = { 1,-1,-1,1,0,1,0,-1,0 }; for (int i = 0;i //边缘部分不做处理 if (i == 0 || i == gray1.rows - 1 || j == 0 || j == gray1.cols - 1) { grayImg.at(i, j) = gray1.at(i, j); } else { for (int k = 0;k //1.求灰度直方图 vectorhisto(256); for (int i = 0;i histo[grayImg.at(i, j)]++; } } //2.根据上面的直方图 用迭代法求阈值 int count0, count1;//count0,count1分别是大于t0和小于t0的像素点的个数 int t0 = 127,t=0; //t0是初始的阈值,t是每一次经过迭代运算后的阈值 当t=t0时认为找到 int z0, z1; //z0,z1分别是大于t0和小于t0的像素值的总和 while (1) { count0=count1=z0 = z1= 0; for (int i=0;i count0+= histo[i]; z0 += i * histo[i]; } else { count1 += histo[i]; z1 += i * histo[i]; } } t = (z0/count0 + z1/count1)/2; if (t0==t) break; else t0 = t; } theThreshold = t0; }

代码讲解:

此处用的是用迭代法求阈值,首先求灰度图的直方图,得到每个像素值对应的像素点的个数,用vectorhisto(256)数组来盛它,它的下标表示像素值,它的值表示每个像素值所对应的像素点的个数。然后再用迭代法来求得阈值,有六个变量,其中t0是初始的阈值,我们最初将像素值范围(0-255)的中心点127赋值给它,t表示经过一次迭代运算得到的阈值,z0表示大于t0的像素值的总和,z1表示小于t0的像素值的总和,count0表示像素值大于t0的像素点的个数,count1表示像素值小于t0的像素点的个数。每经过一次迭代运算,就会得到一个t( t = (z0/count0 + z1/count1)/2;z0/count0是像素值大于t0的所有像素点的平均像素值,z1/count1是像素值小于t0的所有像素点的平均像素值,而(z0/count0 + z1/count1)/2就是新得到的阈值。)将t和t0进行比较,若t=t0,则得到该图像的阈值就为t0,否则令t0等于t,继续进行迭代运算,直至t=t0。

5.水平投影确定行 //水平投影找到行 pair SelectRow2(Mat inputImg) { pairp; //记录ISBN所在的上界(p.first)和下届(p.second) vectorarr(inputImg.rows); //存储每行的水平投影的结果 for (int i = 0;i p.first = i;break; } } for (int i = p.first;i p.second = i;break; } } //有些图片不规则,图中没有全零行单独处理 if (p.second-p.first for (int j=0;j //用theCol[j] >= 3判断 适当把截取范围取大一点 if (theCol[j] >= 3 && num % 2 == 0) { num++; p.first = j;j += 2; } else if (theCol[j] == 0 && num % 2 != 0) { num++; p.second = j; a.push_back(p);j += 2; } } }

讲解:在上一步得到的ISBN所在行后,重新在原图上截取出ISBN所在行再经灰度转化,降噪处理,二值化后进行竖直投影。这里的竖直投影类比上一步的水平投影,我们找的是每一列像素值不为零的像素点的个数。然后根据二值图竖直方向投影结果的数字特征分辨出每一个字符的左边界和右边界。那么这种数字特征是什么?左右边界又如何区分呢?数字特征和上一步类似,即找全零列。正常情况下(我们组的二值图是黑底白字的,因此背景像素值是0,数字像素值是255)开始遇见的都是全0列,所以第一次出现的非零列就是第一个字符的左边界(num=0);紧接着第一次出现的全零行就是右边界(num=1),然后来到了第一个字符和第二个字符的间隙,又都是全0列;在此次出现非列0的就是第二个字符的左边界(num=2)…后面的以此类推,因而当num是偶数时为左边界,num是奇数时是右边界。

7.找最小矩形框 void findMinRectangle(Mat inputImg, int& st, int& ed, int& let, int& righ) { //inputImg是输入的二值图 for (int i = 0;i if (inputImg.at(i, j) != 0) { st = i; break; } }//找到第一行不全为0的行,并将该行作为截取的上界 if (inputImg.at(i, j) != 0) { break; } } for (int i = inputImg.rows-1;i >= 0;i--) { int j; for (j = 0;j ed = i; break; } } //对矩阵的行反向遍历找到最后一行不全为0的行,并将该行作为截取的下界 if (inputImg.at(i, j) != 0) { break; } } for (int j = 0;j if (inputImg.at(i, j) != 0) { let = j; break; } }//找到第一列不全为0的列,并将该列作为截取的左界 if (inputImg.at(i, j) != 0) { break; } } for (int j = inputImg.cols - 1;j >= 0;j--) { int i; for (i = 0;i righ = j; break; } }//对矩阵的列反向遍历找到最后一列不全为0的列,并将该列作为截取的右界 if (inputImg.at(i, j) != 0) { break; } } }

讲解:利用for循环对inputImg的行和列进行正向与反向的遍历,通过找到不全为0的行和列找到inputImg二值图的上界st,下界ed,左界let,右界righ,并根据st,ed,let,righ对inputImg进行截取,找到截取出的数字或字符的最小矩形框。

8.模板匹配 字符识别 //两张图做差(统计两张图片不同点像素点的个数) double absDi(Mat inputImg, Mat sampleImg) { //记录两张图不同的像素点的个数 double diffNums = 0;double sameNums = 0; for (int i=0;i //对应位置的像素值不同 则diffnums++ if (inputImg.at(i, j) != sampleImg.at(i, j)) diffNums++; else sameNums++; } } return diffNums; } //字符识别 char recognition(Mat inputImg,int k) { string sampleImgPath = "样例3"; vector sampleImgFN; glob(sampleImgPath, sampleImgFN, false); int sampleImgNums = sampleImgFN.size(); vectornums(sampleImgNums+1); for (int i = 0; i string st = to_string(k) + ".jpg"; imwrite("D:\\二级项目图片\\"+to_string(ch) +"."+ st, inputImg); } //计算修正角度 double GetTurnTheta(Mat inputImg) { //计算垂直方向导数 Mat yImg; Sobel(inputImg, yImg, -1, 0, 1, 5); //直线检测 vectorlines; HoughLines(yImg, lines, 1, CV_PI / 180, 180); //计算旋转角度 float thetas = 0; for (int i = 0; i //未检测到直线 thetas = CV_PI / 2; } else {//检测到直线,取平均值 thetas /= lines.size(); } return thetas; } /*-------------------------------------------------------------------------------------------------------------------------- Function: originalImgToGrayImg Description:将原图转化成灰度图 --------------------------------------------------------------------------------------------------------------------------- Calls:NONE Called By: main Table Accessed: NONE Table Updated: NONE --------------------------------------------------------------------------------------------------------------------------- Input: 第一个参数:原图,Mat 第二个参数:转出的灰度图,Mat Output : 灰度图 Return : None Others: NONE --------------------------------------------------------------------------------------------------------------------------*/ void originalImgToGrayImg(Mat inputImg, Mat& outputImg) { int row = inputImg.rows; int col = inputImg.cols; outputImg.create(row, col, CV_8UC1); for (int i = 0; i double sum = 0; //得到三个通道的像素值 int b = inputImg.at(i, j)[0]; int g = inputImg.at(i, j)[1]; int r = inputImg.at(i, j)[2]; sum = b * 0.114 + g * 0.587 + r * 0.299; outputImg.at(i, j) = static_cast(sum); } } } //冒泡排序对非边界值与其八邻域的进行排序,找到中值 int BubbletoMedian(vector& a) { int median; for (int i = 0; i if (a[j] > a[j + 1]) { int temp; temp = a[j + 1]; a[j + 1] = a[j]; a[j] = temp; } } } median = a[4]; return median; } //去噪处理 void denoising(Mat gray1, Mat& grayImg) { grayImg = Mat(gray1.rows, gray1.cols, CV_8UC1); vectortemp(9); //定义九个方向 int dx[9] = { 1,-1,1,-1,-1,0,1,0,0 }; int dy[9] = { 1,-1,-1,1,0,1,0,-1,0 }; for (int i = 0;i //边缘部分不做处理 if (i == 0 || i == gray1.rows - 1 || j == 0 || j == gray1.cols - 1) { grayImg.at(i, j) = gray1.at(i, j); } else { for (int k = 0;k //1.求灰度直方图 vectorhisto(256); for (int i = 0;i histo[grayImg.at(i, j)]++; } } //2.根据上面的直方图 用迭代法求阈值 int count0, count1;//count0,count1分别是大于t0和小于t0的像素点的个数 int t0 = 127,t=0; //t0是初始的阈值,t是每一次经过迭代运算后的阈值 当t=t0时认为找到 int z0, z1; //z0,在z1分别是大于t0和小于t0的像素值的总和 while (1) { count0=count1=z0 = z1= 0; for (int i=0;i count0+= histo[i]; z0 += i * histo[i]; } else { count1 += histo[i]; z1 += i * histo[i]; } } t = (z0/count0 + z1/count1)/2; if (t0==t) break; else t0 = t; } theThreshold = t0-10; } // 根据灰度直方图得到二值图 Mat toBinaryGraph( Mat grayImg) { int theThreshold; //求阈值 getThreshold(grayImg, theThreshold); Mat binG=Mat(grayImg.rows,grayImg.cols, CV_8UC1); for (int i = 0;i if (grayImg.at(i, j) //水平投影 for (int j = 0;j 10) { p.first = i;break; } } for (int i = p.first;i p.second = i;break; } } //有些图片不规则,图中没有全零行单独处理 if (p.second-p.first for (int j = 0; j a.first = i; find = true; break; } if (find)break; } } for (int i = a.first + 1; i //0表示黑 255表示白 if (inputImg.at(i, j) == 255) arr[i]++;//这里是在水平投影 } if (arr[i] == 0) { a.second = i;break; } } //有些图片不规则,图中没有全零行模糊处理 if (a.second - a.first for (int j=0;j //用theCol[j] >= 3判断 适当把截取范围取大一点 if (theCol[j] >= 3 && num % 2 == 0) { num++; p.first = j;j += 2; } else if (theCol[j] == 0 && num % 2 != 0) { num++; p.second = j; a.push_back(p);j += 2; } } } //找最小矩形框 void findMinRectangle(Mat inputImg, int& st, int& ed, int& le, int& ri) { vectorhProjectoin(inputImg.rows); vectorvProjectoin(inputImg.cols); for (int i=0;i if (inputImg.at(i, j) != 0) { hProjectoin[i]++;vProjectoin[j]++; } } } st = le = 0;ed = inputImg.rows;ri = inputImg.cols; for (int i = 0;i = 0;i--) if (vProjectoin[i] != 0)le = i; } //两张图做差(统计两张图片不同点像素点的个数) double absDi(Mat inputImg, Mat sampleImg) { //记录两张图不同的像素点的个数 double diffNums = 0;double sameNums = 0; for (int i=0;i //对应位置的像素值不同 则diffnums++ if (inputImg.at(i, j) != sampleImg.at(i, j)) diffNums++; else sameNums++; } } return diffNums; } //字符识别 char recognition(Mat inputImg,int k) { string sampleImgPath = "样例3"; vector sampleImgFN; glob(sampleImgPath, sampleImgFN, false); int sampleImgNums = sampleImgFN.size(); vectornums(sampleImgNums+1); for (int i = 0; i int rtNums = 0, accNums = 0, sunNums = 0;//分别代表:正确的数量,被准确识别的字符的数量,要识别的字符的总和 //读取 ISBN 图片 string testImgPath = "数据集B/*"; vector testImgFN;//必须cv的String glob(testImgPath, testImgFN, false); int testImgNums = testImgFN.size(); for (int index =12; index (110,125),(135,150)。。。。} sliptCol(binImg2,a); int st, ed, le, ri; string result = ""; for (int i = 0;i //切出最小矩形的 Mat minImg = Mat(subImg, Range(st, ed+1), Range(le, ri+1)); imshow("最小矩形", minImg); //模板匹配得到字符 char ch = recognition(minImg,index); if (ch >= '0' && ch if (testImgFN[index][i] >= '0' && testImgFN[index][i] cmpData += testImgFN[index][i]; } } cout rtNums++; cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有