opencv 練手項目:ISBN 號識別系統


首先需要說明的是,這個系統是我們大二下學期的二級項目。

正因為是二級項目,所以老師要求我們不能使用現成的庫(如 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\n", rtNums, rtNums * 1.0 / testImgNums); printf("准確個數:%4.d 准確率:%f\n", accNums, accNums * 1.0 / sunNums); waitKey(0); system("pause"); }

其中 “數據集” 和 “樣例” 兩個文件夾均在默認路徑(跟 cpp 文件放在一起)

鏈接:https://pan.baidu.com/s/1imCmPFalLL2Et1GeVRp7kA
提取碼:m1ga

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM