项目背景

        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 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 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(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(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& p)  //记录每个字符的左右边界列坐标  

3.{  

4.    int boundary = 0;    //阈值                     

5.    for (int j = 1; j < inputImg.cols - 1; j++)  

6.    {  

7.        if (inputImg.at(0, j) > boundary && inputImg.at(0, j - 1) <= boundary)   //1是黑色  

8.        {  

9.            p.push_back(j - 1);   //左边缘,是图形的左半边轮廓装入数组中  

10.        }  

11.        else if (inputImg.at(0, j) > boundary && inputImg.at(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(i, j) != 0) {  

24.                nums += inputImg.at(i, j);  

25.            }  

26.        }  

27.    }  

28.    return nums;  

29.}  

30.  

31.//模板匹配  

32.bool cmp(pairx, pairy)    //按照从小到大的顺序排序  

33.{  

34.    return x.second < y.second;  

35.}  

36.  

37.  

38.//模板匹配函数  

39.char CheckImg(Mat inputImg) {  

40.    string wjlj = "样例/*.jpg";  

41.    vector wjm;  

42.    glob(wjlj, wjm, false); //读入模板名  

43.    int wjmlen = wjm.size();  

44.  

45.    pair* nums = new pair[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.        }  

运行结果

 

有疑问欢迎私信讨论。

相关链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: