本项目使用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.}
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.}
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.
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. }
有疑问欢迎私信讨论。