燕山大学数据结构与算法课程实践——ISBN号识别系统的设计与开发

项目背景

        ISBN 号是国际标准书号的简称,它是国际标准化组织于 1972 年公布的一项国际通用的出版物统一编号方法。所有正规出版的普通图书版权页都有 ISBN 号, ISBN international standard of book number 几个英文字母的缩写,即国际标准书号。这个号码印刷在每本图书封底( 或护封 ) 的右下角,由一组用四个分割线“-”隔开的 13 个数字。例如: ISBN 978-7-111-47818-8 。其中, ISBN987 代表是中国, 7 代表分类, 111代表出版社编号,47818 代表本书在出版社所有出版书中的编号, 9 是校验码,即:用 1 分别乘 ISBN 的前12 位中的奇数位,用 3 乘以偶数位,乘积之和以 10 为模,用 10 减去此模,即可得到校验位的值,其值范围为 0-9 。例如: S=9*1+7*3+8*1+7*3+1*1+1*3+1*1+4*3+7*1+8*3+1*1+8*3=132 132%10=2 10-2=8 因此校验码即为 8 。在该码的下方有一个条形码,条形码的下方写着 13 个数字,与 ISBN 后面的数字是相同的。

任务:开发一套ISBN号识别系统

        给定 100 张包含 ISBN 号的图片,建议在 VS + OpenCV 环境下完成对给定的 ISBN 图像的识别, 每幅图像的文件名为图中的 ISBN ,例如 ISBN978-7-111-47818-8.jpg. 你可以自由参考现有的文献中的方法,从设计的原理、技术方案到程序实现,给出你能识别出的ISBN 的正确率和准确率。例如:如果有 100 幅图片,你能识别出 90 幅,那么你的系统识别的正确率是 90% ;每一张照片有 13 个需要识别的数字, 100 张照片共有 1300 个需要识别的数字,你的系统识别出了 1000 个,那识别系统的准确率是 1000/1300=76.92%

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

        本项目使用vs2019和OpenCV4开发,在对其进行图像的转正,然后灰度值转化,对图像进行去噪处理,再进行图像二值化的转化,对图书ISBN编号进行垂直方向数字分割,切割后的每个字符图片与模板进行匹配,实现对ISBN的快速识别。最后,计算出识别出的ISBN相应的正确率和准确率。

项目的方案设计

        通过图像预处理读入图像,对其进行灰度值转化和二值化,将二值化图片进行倾斜校正,校正之后寻找到ISBN号所在区域,对源图片进行分割获得彩色ISBN号,再将其转换为二值图像,寻找ISBN号字符的边界后截取每个字符,截取后的字符通过调整最后与模板进行对比,差值最小的模板号即为读取的ISBN 号,与正确的ISBN号比较得到项目的正确率和准确率。

核心代码的实现

图像预处理:读入图片、转化为灰度图和二值图

1.int rightNums = 0, acNums = 0, sumNums=0; //1:正确的个数;  2:正确的字符个数  3:总共的字符个数  
2.  
3.    string path1 = "C:/Users/Administrator/Desktop/二级项目/ISBN/ISBN测试数据--100幅图"; //文件路径  
4.  
5.    vector<String> imgPaths;  
6.    glob(path1, imgPaths, false); //1:文件路径  2:输出数组  3:递归遍历所有图片  
7.    int imgNums = imgPaths.size(); //图片总个数  
8.  
9.    for (int i = 0; i < imgNums; i++)  
10.    {  
11.        Mat Img = imread(imgPaths[i]); //读入图片  
12.        if (Img.empty()) //图片不存在  
13.        {  
14.            cout << imgPaths[i] << "  Not Loaded" << endl;  
15.            continue;  
16.        }  
17.  
18.        //对图片的大小进行统一调整  
19.        double width = 400; //宽度  
20.        double height = width * Img.rows / Img.cols; //高度  
21.        resize(Img, Img, Size(width, height)); //重新对图片大小进行调整。1:源图像  2:输出图像  3:图像大小  
22.  
23.        //将原图转化为灰度图再转化为二值图 该二值图像中,黑色设为1,白色设为0(关键!!!)  
24.        //---转化为灰度图  
25.        Mat erImg;  
26.        cvtColor(Img,erImg, COLOR_BGR2GRAY); //将图片转化为灰度图  
27.        //COLOR_BGR2GRAY的原理  GRAY = B * 0.114 + G * 0.587 + R * 0.299  
28.  
29.        threshold(erImg, erImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);//将灰度图转化为二值图  
30.        //1:源图片  2:输出图片  3:阈值1  4:域指2  5:方式  
31.        //自动设置阈值的方法OTSU  当点大于阈值设置为0(白)  小于阈值设置为255(黑)  

图像的旋转

1.//获取旋转角度  
2.double getAngles(Mat inImg)  
3.{  
4.    //计算垂直方向导数  
5.    Mat tempImg;  
6.    Sobel(inImg, tempImg, -1, 0, 1, 5);  
7.    //它可以用来对图像进行边缘检测, 或者用来计算某个像素点的法线向量。  
8.    //参数说明:1,输入图像;2,输出图像,需要有和原图一样的尺寸和类型;  
9.    //3,图像的深度;4,x方向上的差分阶数;5,y方向上的差分阶数;  
10.    //6,int类型ksize,有默认值3,表示Sobel核的大小;必须取1,3,5或7  
11.  
12.    //直线检测  
13.    vector<Vec2f> lines;  
14.    HoughLines(tempImg, lines, 1, CV_PI / 180, 180);  
15.    //参数说明:1:源图像;   
16.    //2:InputArray类型的lines,经过调用HoughLines函数后储存了霍夫线变换检测到线条的输出矢量  
17.    //3:以像素为单位的距离精度   4:以弧度为单位的角度精度  
18.    //5:累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值  
19.  
20.    //计算旋转角度  
21.    float angle = 0.0;  
22.    for (int i = 0; i < lines.size(); i++)  
23.    {  
24.        float theta = lines[i][1];  
25.        angle += theta;  
26.    }  
27.  
28.    if (lines.size() == 0) //未i检测到直线  
29.    {  
30.        angle = CV_PI / 2;  
31.    }  
32.    else //检测到直线,取平均值  
33.    {  
34.        angle = angle / lines.size();  
35.    }  
36.    return angle;  
37.}  

寻找ISBN号所在区域

1.//寻找ISBN所在行  
2.void FindISBNRows(Mat inputImg, int boundary, int top, int bottom, int minsize, int& startindex, int& endindex)  
3.{  
4.  
5.    //边缘检测,方便找到梯度大的地方,忽略梯度小的地方  
6.    Mat canImg;  
7.    //去噪(均值滤波)  
8.    blur(inputImg, canImg, Size(3, 3));//1:源图像  2:输出图像  3:内核大小  
9.  
10.    Canny(canImg, canImg, boundary, boundary * 2, 3);  
11.    //参数说明:1,输入。2,输出。3,阈值1.4.阈值2。5,sobel核大小。  
12.    //高于和靠近阈值2的点会被认为是边界     
13.    //注:低于阈值1的像素点会被认为不是边缘;高于阈值2的像素点会被认为是边缘;  
14.    //在阈值1和阈值2之间的像素点, 若与第2步得到的边缘像素点相邻,则被认为是边缘,否则被认为不是边缘  
15.  
16.    //寻找上边界  
17.    for (int i = top; i < bottom; i++)  
18.    {  
19.  
20.        if (canImg.at<uchar>(i, 0) != 0) //有像素点存在  
21.        {  
22.            startindex = i;  //上边界  
23.            break;   
24.        }  
25.    }  
26.  
27.    //寻找下边界  
28.    for (int i = bottom; i >= top; i--)  
29.    {  
30.        if (canImg.at<uchar>(i, 0) != 0)  
31.        {  
32.            endindex = i;  //下边界  
33.            break;  
34.        }  
35.    }  
36.  
37.  
38.    //范围过小,调整阈值再次寻找  
39.    if (abs(endindex - startindex) < minsize)  
40.    {  
41.        boundary -= 10; //缩小阈值重新寻找  
42.        if (boundary <= 0)  
43.        {  
44.            startindex = top;  
45.            endindex = bottom;  
46.            return;  
47.        }  
48.        FindISBNRows(inputImg, boundary, top, bottom, minsize, startindex, endindex);  
49.    }  
50.  
51.  
52.}  

截取ISBN号所在区域转化为二值图

1.//弥补旋转缺失的区域  
2.        Mat BG = Mat(Img.rows, Img.cols, CV_8UC1, Scalar(255));  
3.        warpAffine(BG, BG, M, Img.size());  
4.        bitwise_not(BG, BG);  
5.        Mat turnImg; //彩色图片  
6.        warpAffine(Img, turnImg, M, Img.size());  
7.        Img.copyTo(turnImg, BG); //弥补旋转确实的区域  
8.  
9.//截取ISBN所在的区域  
10.        Mat isbnImg = Mat(turnImg, Range(start1, end1), Range(0, turnImg.cols));  //截取的是原图的那一部分  
11.  
12.        //调整大小  
13.        width = 900;  
14.        height = width * isbnImg.rows / isbnImg.cols;  
15.        resize(isbnImg, isbnImg, Size(width, height));  
16.  
17.//转化为二值图  
18.        erImg = Mat();  
19.        cvtColor(isbnImg, erImg, COLOR_BGR2GRAY);  
20.        threshold(erImg, erImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);  
21.

截取字符和模板匹配,并将正确的ISBN和识别出的ISBN输出

1.//寻找每个字符的位置  
2.void findChar(Mat inputImg, vector<float>& p)  //记录每个字符的左右边界列坐标  
3.{  
4.    int boundary = 0;    //阈值                     
5.    for (int j = 1; j < inputImg.cols - 1; j++)  
6.    {  
7.        if (inputImg.at<uchar>(0, j) > boundary && inputImg.at<uchar>(0, j - 1) <= boundary)   //1是黑色  
8.        {  
9.            p.push_back(j - 1);   //左边缘,是图形的左半边轮廓装入数组中  
10.        }  
11.        else if (inputImg.at<uchar>(0, j) > boundary && inputImg.at<uchar>(0, j + 1) <= boundary)  
12.        {  
13.            p.push_back(j + 1);   //右边缘,是图形的右半边轮廓装入数组中  
14.        }  
15.    }  
16.}  
17.  
18.//差值函数  
19.int CalcImg(Mat inputImg) {  
20.    int nums = 0;  
21.    for (int i = 0; i < inputImg.rows; i++) {  
22.        for (int j = 0; j < inputImg.cols; j++) {  
23.            if (inputImg.at<uchar>(i, j) != 0) {  
24.                nums += inputImg.at<uchar>(i, j);  
25.            }  
26.        }  
27.    }  
28.    return nums;  
29.}  
30.  
31.//模板匹配  
32.bool cmp(pair<int, int>x, pair<int, int>y)    //按照从小到大的顺序排序  
33.{  
34.    return x.second < y.second;  
35.}  
36.  
37.  
38.//模板匹配函数  
39.char CheckImg(Mat inputImg) {  
40.    string wjlj = "样例/*.jpg";  
41.    vector<String> wjm;  
42.    glob(wjlj, wjm, false); //读入模板名  
43.    int wjmlen = wjm.size();  
44.  
45.    pair<int, int>* nums = new pair<int, int>[wjmlen];  
46.    for (int i = 0; i < wjmlen; i++) {  
47.        nums[i].first = i;  
48.        Mat numImg = imread(wjm[i], 0);//  
49.        Mat delImg;  
50.        absdiff(numImg, inputImg, delImg);//计算两个数组差的绝对值  
51.        nums[i].second = CalcImg(delImg);  
52.    }  
53.  
54.    sort(nums, nums + wjmlen,cmp); //-------------------------------------------------  
55.  
56.    int index = nums[0].first / 2;  
57.    switch (index) {  
58.        case 0:  
59.        case 1:  
60.        case 2:  
61.        case 3:  
62.        case 4:  
63.        case 5:  
64.        case 6:  
65.        case 7:  
66.        case 8:  
67.        case 9:  
68.            return index + '0';   //如果是数字就return该数字  
69.        case 10:  
70.            return 'I';  
71.        case 11:  
72.            return 'S';  
73.        case 12:  
74.            return 'B';  
75.        case 13:  
76.            return 'N';  
77.        case 14:  
78.            return 'X';  
79.        default:  
80.            return ' ';  
81.    }  
82.}  

计算正确率和准确率并输出

1.//计算准确率  
2.        sumNums += cmpData.length();  
3.        int acOfone = 0;  
4.        for (int a = 0; a < cmpData.length(); a++) {  
5.            if (res[a] == cmpData[a]) {  
6.                acNums++;  
7.                acOfone++;  
8.            }  
9.        }  
10.        cout << "正确识别的个数为:" << acOfone << endl;  
11.  
12.        //计算正确率  
13.        if (res == cmpData) {  
14.            rightNums++;  
15.            cout << "Yes" << endl;  
16.        }  
17.        else {  
18.            cout << "No" << endl;  
19.        }  
20.  
21.  
22.        if (i == imgNums-1)  
23.        {  
24.            cout << endl;  
25.            /*printf("正确个数:%4.d 正确率:%f\n", rightNums, rightNums * 1.0 / 100); 
26.            printf("准确个数:%4.d 准确率:%f\n", acNums, acNums * 1.0 / sumNums);*/  
27.            cout << setprecision(6) << fixed << "正确总数比:" << rightNums << "/" << imgNums << " " << "       准确率" << rightNums * 1.0 / imgNums << endl;  
28.            cout << setprecision(6) << fixed << "正确字符总比数:" << acNums << "/" << sumNums << " " << "精确率" << acNums * 1.0 / sumNums;  
29.            //waitKey(0);  
30.            cout << endl;  
31.        }  

运行结果

 

有疑问欢迎私信讨论。

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shallen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值