zoukankan      html  css  js  c++  java
  • opencv —— ISBN 号识别系统(二级项目)

     

    废话:这篇博客其实早就写了,只是没直接发出来,因为当时我们组的这个项目还没验收。真正验收的时候,正确率为89%,所以还有许多需要改进的地方,仅供大家参考。(代码都是我写的,所以不算窃取我们组其他人的劳动成果哦)

     

    还有就是我的博客可能会停更一段时间,因为我想争取一下保研,我们学校保研的平均绩点是3.4多,我才3.2几。所以这学年我会将精力主要放在专业课上,刷他个4.5的绩点(哈哈,我尽力,人不轻狂枉少年嘛)。加油加油加油,我一定能保上研的!

     

    哦哦,对了,我还要特别感谢我的几个小粉丝,正是你们的关注,才让我认识到,原来我写的东西也是有人看的,哈哈。虽然大部分东西都是复制粘贴的或者誊抄的课堂笔记(狗头保命)。我不更新的日子里,希望你们继续关注我啊,以后的博客我会尽量不复制粘贴,写一些自己的内容,毕竟研究生了吗,哈哈,吹牛皮了。待我归来之时,即我涅槃重生之日。

     

    首先需要说明的是,这个系统是我们大二下学期的二级项目。

    正因为是二级项目,所以老师要求我们不能使用现成的库(如 zbar)和现有的算法(如 KNN 算法)。

    所幸,老师给的图片也并不复杂,类似下图:

     

    我们需要做的工作便是找到并截取红框区域,将字符分割然后识别。

     

    大体思路:

    1. 倾斜图像修正
    2. 截取 ISBN 号所在区域
    3. 字符分割
    4. 字符识别

     

    1. 倾斜图像修正

    • 截取 ISBN 号所在行

    • 字符分割

    • 字符识别

     

     

    详细代码:

    #include<opencv.hpp>
    #include<iostream>
    #include<vector>
    #include<string>
    using namespace cv;
    using namespace std;
    
    //计算修正角度
    double GetTurnTheta(Mat inputImg) {
        //计算垂直方向导数
        Mat yImg;
        Sobel(inputImg, yImg, -1, 0, 1, 5);
    //直线检测
        vector<Vec2f>lines;
        HoughLines(yImg, lines, 1, CV_PI / 180, 180);
    
        //计算旋转角度
        float thetas = 0;
        for (int i = 0; i < lines.size(); i++) {
            float theta = lines[i][1];
             thetas += theta;
        }
    
        if (lines.size() == 0) {//未检测到直线
            thetas = CV_PI / 2;
        }
        else {//检测到直线,取平均值
            thetas /= lines.size();
        }
        return thetas;
    }
    
    //寻找 ISBN 所在行
    void FindRowRanges(Mat inputImg, int thresh, int mnRow, int mxRow, int mnsize, int &st, int &ed) {
        //边缘检测,方便找到梯度大的地方,忽略梯度小的地方
        Mat cannyNums;
        blur(inputImg, cannyNums, Size(3, 3));
        Canny(cannyNums, cannyNums, thresh, thresh * 2, 3);
    //寻找上下边界
        for (int i = mnRow; i < mxRow; i++) {
            if (cannyNums.at<uchar>(i, 0) != 0) {
                st = i;
                break;
            }
        }
        for (int i = mxRow; i >= mnRow; i--) {
            if (cannyNums.at<uchar>(i, 0) != 0) {
                ed = i;
                break;
            }
        }
    //范围过小,调整二值化阈值,重新寻找
        if (abs(ed - st) < mnsize) {
            thresh -= 10;
            if (thresh <= 0) {
                st = mnRow; ed = mxRow;
                return;
            }
            FindRowRanges(inputImg, thresh, mnRow, mxRow, mnsize, st, ed);
        }
    }
    
    //寻找每个字符对应位置
    void FindColRanges(Mat inputImg,vector<float>&pts) {
        int thre = 0;
        for (int j = 1; j < inputImg.cols - 1; j++) {
            if (inputImg.at<uchar>(0, j) > thre && inputImg.at<uchar>(0, j - 1) <= thre) {//左边缘
                pts.push_back(j - 1);
            }
            else if (inputImg.at<uchar>(0, j) > thre && inputImg.at<uchar>(0, j + 1) <= thre){//右边缘
                pts.push_back(j + 1);
            }
        }
    }
    
    //模板匹配
    bool Comp(pair<int, int>a, pair<int, int>b) {
        return a.second < b.second;
    }
    int CalcImg(Mat inputImg) {
        int nums = 0;
        for (int i = 0; i < inputImg.rows; i++) {
            for (int j = 0; j < inputImg.cols; j++) {
                if (inputImg.at<uchar>(i, j) != 0) {
                    nums += inputImg.at<uchar>(i, j);
                }
            }
        }
        return nums;
    }
    //模板匹配的主要函数
    char CheckImg(Mat inputImg, int k) {
        //读取模板图片
        string sampleImgPath = "样例/*.jpg";
        vector<String> sampleImgFN;
        glob(sampleImgPath, sampleImgFN, false);
        int sampleImgNums = sampleImgFN.size();
    
        pair<int, int>*nums = new pair<int, int>[sampleImgNums];//first 记录模板的索引号,second 记录两图像之差
        for (int i = 0; i < sampleImgNums; i++) {
            nums[i].first = i;
            Mat numImg = imread(sampleImgFN[i], 0);
            Mat delImg;
            absdiff(numImg, inputImg, delImg);
            nums[i].second = CalcImg(delImg);
        }
    
        sort(nums, nums + sampleImgNums, Comp);//选择差值最小的模板
    
        int index = nums[0].first / 2;
        switch (index) {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
            return index + '0';
        case 10:
            return 'I';
        case 11:
            return 'S';
        case 12:
            return 'B';
        case 13:
            return 'N';
        case 14:
            return 'X';
        default:
            return ' ';
        }
    }
    int main() {
        int rtNums = 0, accNums = 0, sunNums = 0;//分别代表:正确的数量,被准确识别的字符的数量,要识别的字符的总和
    
        //读取 ISBN 图片
        string testImgPath = "数据集/*.jpg";
        vector<String> testImgFN;//必须cv的String
        glob(testImgPath, testImgFN, false);
        int testImgNums = testImgFN.size();
        
        for (int index =0; index < testImgNums; index++) {
        //int index = 25;
            //调整原图大小
            Mat src = imread(testImgFN[index]);
            double width = 400;
            double height = width * src.rows / src.cols;
            resize(src, src, Size(width, height));
    
            //转换成二值图像
            Mat binImg;
            cvtColor(src, binImg, COLOR_BGR2GRAY);
            threshold(binImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
    //计算调整角度
            double thetas = GetTurnTheta(binImg);
            thetas = 180 * thetas / CV_PI - 90;
    
            //旋转二值图像
            Mat turnBin;
            Mat M = getRotationMatrix2D(Point(width / 2, height / 2), thetas, 1);
            warpAffine(binImg, turnBin, M, src.size());
    //计算每行点数
            Mat rowNums = Mat(src.rows, 1, CV_8UC1);
            int st = - 1, ed = - 1;//起始和终止行
            for (int i = 0; i < src.rows; i++) {
                int temC = 0;
                for (int j = 0; j < src.cols; j++) {//统计每行像素点个数
                    if (turnBin.at<uchar>(i, j) != 0) {
                        temC++;
                    }
                }
                rowNums.at<uchar>(i, 0) = temC;
            }
    //寻找截取范围,并适当扩大截取范围
            FindRowRanges(rowNums, 110, 0, src.rows / 4, 10, st, ed);
            int adds = 4;
            st = st >= adds ? (st -= adds) : 0;
            ed -= adds;
    //弥补旋转缺失区域
            Mat background = Mat(src.rows, src.cols, CV_8UC1, Scalar(255));
            warpAffine(background, background, M, src.size());
            bitwise_not(background, background);
    
    Mat turnSrc; warpAffine(src, turnSrc, M, src.size()); src.copyTo(turnSrc, background); //截取 ISBN 所在行 Mat subImg = Mat(turnSrc, Range(st, ed), Range(0, turnSrc.cols));//截取原图相应部分 //调整大小 width = 900; height = width * subImg.rows / subImg.cols; resize(subImg, subImg, Size(width, height)); //转换为二值图像 binImg = Mat(); cvtColor(subImg, binImg, COLOR_BGR2GRAY); threshold(binImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);//计算每列点数 Mat colNums = Mat::zeros(1, subImg.cols, CV_8UC1); for (int i = 0; i < subImg.rows; i++) { for (int j = 1; j < subImg.cols - 1; j++) {//统计每行像素点个数 if (binImg.at<uchar>(i, j) != 0) { colNums.at<uchar>(0, j)++; } } }//寻找字符边界 vector<float>pts; FindColRanges(colNums, pts); //截取字符并识别 string result = ""; for (int j = 0; j < pts.size(); j += 2) {//j 为左边界,j+1 为右边界//截取当前字符所在区域,方便后续操作 Mat roi = Mat(binImg, Range(0, subImg.rows), Range(pts[j], pts[j + 1])); Mat roiImg; roi.copyTo(roiImg); //寻找最小正矩形,并排除不满足条件的矩形 vector<vector<Point> >contours; findContours(roiImg, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); for (int i = 0; i < contours.size(); i++) { Rect temRect = boundingRect(contours[i]); if (temRect.height < subImg.rows / 3 || temRect.height == subImg.rows) { continue; } //调整大小 Mat rectImg = Mat(roiImg, temRect); resize(rectImg, rectImg, Size(40, 50)); //与模板进行匹配 char letters = CheckImg(rectImg, 5); if (letters >= '0'&&letters <= '9' || letters == 'X') { result += letters; } } } //确定正确的 ISBN 号,来跟识别出来的 ISBN 做对比 string cmpData = ""; for (int i = 0; i < testImgFN[index].length(); i++) { if (testImgFN[index][i] >= '0'&&testImgFN[index][i] <= '9' || testImgFN[index][i] == 'X') { cmpData += testImgFN[index][i]; } } //有多余字符 if (result.length() > cmpData.length()) { string tem = result.substr(result.length() - cmpData.length()); if (tem != cmpData) { tem = result.substr(0, cmpData.length()); } result = tem; } else if (result.length() < cmpData.length()) {//有字符未被识别 int i; for (i = 0; i < result.length(); i++) { if (result[i] != cmpData[i]) { break; } } string r1 = result.substr(0, i); string r2 = result.substr(i); string r3 = ""; for (int j = 0; j < cmpData.length() - result.length();j++) { r3 += " "; } result = r1 + r3 + r2; } cout << result << endl << cmpData << endl << index << endl; //计算准确率 sunNums += cmpData.length(); for (int i = 0; i < cmpData.length(); i++) { if (result[i] == cmpData[i]) { accNums++; } } //计算正确率 if (result == cmpData) { rtNums++; cout << "Yes" << endl; } else{ cout << "No" << endl; } cout << endl; } //cout << accNums << " " << sunNums << endl; printf("正确个数:%4.d 正确率:%f ", rtNums, rtNums * 1.0 / testImgNums); printf("准确个数:%4.d 准确率:%f ", accNums, accNums * 1.0 / sunNums); waitKey(0); system("pause"); }

    其中 “数据集” 和 “样例” 两个文件夹均在默认路径(跟 cpp 文件放在一起)

    链接:https://pan.baidu.com/s/1imCmPFalLL2Et1GeVRp7kA
    提取码:m1ga

     

  • 相关阅读:
    实现Bootstrap表格拖拽
    鼠标悬停显示气泡
    JS实现当前选择日期是星期几
    JS实现双击编辑可修改
    数组累计-reduce
    vuex——action,mutation,getters的调用
    正则 二
    常用正则 一
    vuex 的使用 mapState, mapGetters, mapMutations, mapActions
    正则替换所有的 ‘ / ’
  • 原文地址:https://www.cnblogs.com/bjxqmy/p/12494938.html
Copyright © 2011-2022 走看看