主頁 >  其他 > 燕山大學課程實踐專案報告:ISBN號識別系統的設計與開發

燕山大學課程實踐專案報告:ISBN號識別系統的設計與開發

2022-03-03 08:04:24 其他

文章目錄

  • 摘要
  • 前言
  • 正文
    • 一、研究內容的基本原理
    • 二、所采用的研究方法及相關工具
    • 三、專案的方案設計
    • 四、核心代碼實作
      • 1.讀取圖片
      • 2.將原圖轉化為灰度圖
      • 3.去噪處理
      • 4.迭代法求閾值
      • 5.水平投影確定行
      • 6.豎直投影確定列
      • 7.找最小矩形框
      • 8.模板匹配 字符識別
    • 五、專案測驗
    • 六、研究結果并討論
  • 結論
  • 附頁
    • 開發環境
    • 源代碼

摘要

國際標準書號ISBN由13位數字組成,前三位數字代表圖書,中間的9個數字分為三組,分表示組號、出版社號和書序號,最后一個數字是校驗碼從1968年英國的“標準書號”(SBN)開始,其優點主要體現在:國際標準書號是機讀的編碼,從圖書的生產到發行、銷售始終如一,對圖書的發行系統起了很大的作用:它的引入使圖書的定購、庫存控制、賬目和輸出程序等任何圖書業的分支程式都簡化了,我們小組設計出的這個ISBN編號識別系統利用機器視覺影像處理技術可以識別不同的 ISBN 號并將其讀取出來,為事后相應的管理統籌作業提供可靠的輔助,

本系統開發采用了 VS2019+opencv的開發環境,C++語言實作,該專案流程為:讀取->轉化為灰度圖->中值濾波去噪->二值化->水平投影確定行->豎直投影確定列 ->模板匹配字符識別->輸出,

[關鍵詞] 課程實踐專案;數字識別;灰度轉化;中值濾波降噪; 二值轉化;字符分割

前言

隨著現代社會的高速發展,人民的生活水平也在不斷提高,閱讀書籍的需求也在不斷上漲,這對書籍管理水平提出了更高要求,而ISBN編號能極大的方便對書籍的管理,隨之而來的問題是:人工識別ISBN碼的效率太低了,需要計算機來代替我們做這些作業,但計算機不能直接識別圖片中的內容,所以要求我們對圖片進行一系列的操作,使它變得易于被識別,這些操作包括:轉化為灰度圖,中值濾波去噪,二值化,水平投影分割行,豎直投影切割列,找最小矩形框,最后得到單個字符的形式,之后進行數字識別,這里數字識別主要方法有:模板匹配法,神經網路法以及劃線法,本小組采用模板匹配法來對字符進行識別,下面就分別介紹對圖片的一系列操作和模板匹配的方法,

正文

一、研究內容的基本原理

本次的ISBN編號識別采用模板匹配的方法,在進行識別之前需要對圖片進行一系列預處理:原圖轉灰度圖 灰度圖轉二值圖 對二值圖進行切割 找到切割后的最小矩形框,模板匹配這種方法具體來說,就是把所有可能出現的每一個字符情況都找一定數量(我們用的有2~4個)的模板,當要識別未知字符時需要與所有模板一一比對,找到最接近的模板進行匹配,這里就有一個衡量標準的問題,即:怎樣算“最接近”?我們用的是“找不同的”方法,即先將模板與待識別的字符圖片調成一樣大小(40X60),再對比對應位置的像素值是否相同,統計兩幅圖片不同的像素點的個數,如果不同點的數量越少,就認為該模板與待識別的字符越接近;因而最接近自然就是不同的像素點個數最少的模板,

二、所采用的研究方法及相關工具

我們小組在拿到課題后一起討論弄,清楚了這個專案的重難點,采用了組長分工、小組成員討論的方式進行該專案工程,分別完成各自部分的功能設計和代碼實作,最后再一起細致的改入整個大專案中,也就是先分,再治,最后整合的程序,
我們采用的工具是 VS2019+opencv4.00,在工具統一的情況下進行作業和開發,

三、專案的方案設計

對于專案的整體設計方案,我們采用的是如下方法:
1,首先用glob()函式找到檔案中每張圖片的路徑,根據路徑找到將圖片保存到Mat物件中,然后開始對每一張圖片進行處理,
2,調整影像為統一大小以便進行處理,并將原彩色圖片轉化成灰度圖,
3,對灰度圖進行中值濾波降噪處理,以提高識別率,
4,灰度圖二值化,采用迭代法求閾值,
5,對影像進行切割,將影像切割成只有最上面一部分的情況,也就是只有 ISBN 號的圖片,
6,將第5步切出來的圖片進一步切割成為單個數字影像的圖片,并進行儲存,
7,從上一步得到的單個字符的圖片得到最小矩形框,并截出最小矩形的,
8,將切割好的數字與準備好的數字模板進行比對,匹配差值最小的數字,最后輸出正確率和準確率,

四、核心代碼實作

1.讀取圖片

//讀取 ISBN 圖片
string testImgPath = "資料集/*";
vector<String> testImgFN;//必須String
glob(testImgPath, testImgFN, false);
int testImgNums = testImgFN.size();

for (int index =0; index < testImgNums; index++) {
 Mat src = imread(testImgFN[index]);
}

講解:這部分完成將圖片從檔案夾中讀取出到Mat物件中的功能,后面對圖片的操作就轉為對Mat物件的操作,具體來說就是先將檔案夾中圖片的路徑用golb()獲取并放在陣列vector testImgFN中,然后遍歷該陣列用imread()函式得到每一張圖片,

2.將原圖轉化為灰度圖

代碼實作:

void originalImgToGrayImg(Mat inputImg, Mat& outputImg)
{
	int row = inputImg.rows;
	int col = inputImg.cols;
	outputImg.create(row, col, CV_8UC1);
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			double sum = 0;
            //得到三個通道的像素值
			int b = inputImg.at<Vec3b>(i, j)[0];
		    int g = inputImg.at<Vec3b>(i, j)[1];
			int r = inputImg.at<Vec3b>(i, j)[2];
            //利用灰度化公式將彩色影像三個通道的像素值轉化為灰度影像單通道的像素值
			sum = b * 0.114 + g * 0.587 + r * 0.299;
			outputImg.at<uchar>(i, j) = static_cast<uchar>(sum);
		}
	}
}

代碼講解:
先用兩個變數row和col分別來表示原圖中每行每列的像素點,再用.create的方法創建一個和原圖大小一樣的圖片,然后用兩個回圈陳述句根據灰度化公式將原彩色影像中每三個通道對應的像素點的像素值轉化為灰度圖中對應的像素點的像素值,由于定義的sum是double型別的,所以還需要使用 static_cast(sum)將sum轉換為uchar型別,再賦值給灰度圖的各個像素點,從而得到一個和原圖大小一樣的灰度圖,

3.去噪處理

//冒泡排序對非邊界值與其八鄰域的進行排序,找到中值
int BubbletoMedian(vector<int>& a)
{
	int median;//找到中值
	for (int i = 0; i < 9; i++)
	{
		for (int j = 0; j < 9 - i - 1; j++)
		{
			if (a[j] > a[j + 1])
			{
				int temp;
				temp = a[j + 1];
				a[j + 1] = a[j];
				a[j] = temp;
			}
		}
	}
	median = a[4];
	return median;
}
//去噪處理
void denoising(Mat gray1, Mat& grayImg)
{
	//宣告一個與灰度圖gray1行數,列數都相同的圖grayImg 
	grayImg = Mat(gray1.rows, gray1.cols, CV_8UC1);
	vector<int>temp(9); //宣告動態陣列temp[]
	//定義九個方向
	int dx[9] = { 1,-1,1,-1,-1,0,1,0,0 };
	int dy[9] = { 1,-1,-1,1,0,1,0,-1,0 };
	for (int i = 0;i < gray1.rows;i++)
	{
		for (int j = 0;j < gray1.cols;j++)
		{
			//邊緣部分不做處理
	      if (i == 0 || i == gray1.rows - 1 || j == 0 || j == gray1.cols - 1)
			{
				grayImg.at<uchar>(i, j) = gray1.at<uchar>(i, j);
			}
			else
			{
				for (int k = 0;k < 9;k++)
				{
					//將非邊緣的像素點及其八鄰域的像素點存入陣列temp[]中  
					temp[k] = gray1.at<uchar>(i + dx[k], j + dy[k]);
				}
				//對陣列temp[]中的九個值進行冒泡排序求出九個值中的中值
				grayImg.at<uchar>(i, j) = BubbletoMedian(temp);
			}
		}
	}
}

講解:用中值濾波法對灰度圖gray1進行降噪處理,對gray1影像的邊緣不做處理賦值給grayImg;利用兩個一維陣列dx[],dy[],找到非邊緣值的八鄰域值,將這九個值存入動態陣列temp[9]中,利用冒泡排序的方法將這九個值從小到大進行排序,并回傳排序后的中值median,并且將中值median賦值給grayImg的相同位置,

4.迭代法求閾值

代碼實作:

void getGrayHistogram(Mat grayImg, int &theThreshold)
{
	//1.求灰度直方圖
    vector<int>histo(256);
    for (int i = 0;i < grayImg.rows;i++)
    {
        for (int j = 0;j < grayImg.cols;j++)
        {
            histo[grayImg.at<uchar>(i, j)]++;
        }
    }
	//2.根據上面的直方圖 用迭代法求閾值
    int count0, count1;//count0,count1分別是大于t0和小于t0的像素點的個數
	 
	int t0 = 127,t=0; //t0是初始的閾值,t是每一次經過迭代運算后的閾值 當t=t0時認為找到

	int z0, z1;      //z0,z1分別是大于t0和小于t0的像素值的總和
     while (1)
     {
         count0=count1=z0 = z1= 0;
         for (int i=0;i<histo.size();i++)
         {
             if (i<=t0)
             {
                 count0+= histo[i];
                 z0 += i * histo[i];
             }
             else
             {
				 count1 += histo[i];
				 z1 += i * histo[i];
             }
         }

         t = (z0/count0 + z1/count1)/2;
         if (t0==t) 
			break;
         else 
			t0 = t;
	}
	theThreshold = t0;
}

代碼講解:

此處用的是用迭代法求閾值,首先求灰度圖的直方圖,得到每個像素值對應的像素點的個數,用vectorhisto(256)陣列來盛它,它的下標表示像素值,它的值表示每個像素值所對應的像素點的個數,然后再用迭代法來求得閾值,有六個變數,其中t0是初始的閾值,我們最初將像素值范圍(0-255)的中心點127賦值給它,t表示經過一次迭代運算得到的閾值,z0表示大于t0的像素值的總和,z1表示小于t0的像素值的總和,count0表示像素值大于t0的像素點的個數,count1表示像素值小于t0的像素點的個數,每經過一次迭代運算,就會得到一個t( t = (z0/count0 + z1/count1)/2;z0/count0是像素值大于t0的所有像素點的平均像素值,z1/count1是像素值小于t0的所有像素點的平均像素值,而(z0/count0 + z1/count1)/2就是新得到的閾值,)將t和t0進行比較,若t=t0,則得到該影像的閾值就為t0,否則令t0等于t,繼續進行迭代運算,直至t=t0,

5.水平投影確定行

//水平投影找到行
pair<int, int> SelectRow2(Mat inputImg)
{
    pair<int, int>p;         //記錄ISBN所在的上界(p.first)和下屆(p.second)
    vector<int>arr(inputImg.rows); //存盤每行的水平投影的結果
	for (int i = 0;i < inputImg.rows;i++)
	{
        //水平投影
		for (int j = 0;j < inputImg.cols;j++)
            if (inputImg.at<uchar>(i, j) != 0)arr[i]++;
        //當此行的水平投影值大于指定閾值表示找到上屆 
        if (arr[i] > 10) {
            p.first = i;break;
           }
	}
	for (int i = p.first;i < inputImg.rows;i++)
	{
        //水平投影
		for (int j = 0;j < inputImg.cols;j++)
			if (inputImg.at<uchar>(i, j) != 0)arr[i]++;
        //當此行的水平投影值小于指定閾值表示找到下屆 
        if (arr[i] < 10) {
            p.second = i;break;
        }
	}
    //有些圖片不規則,圖中沒有全零行單獨處理
    if (p.second-p.first<=10) p.second = p.first+32;
    return p;
}

講解:找到ISBN所在行的操作是通過先做水平投影來實作的,具體來說就是:在上一步得到的二值圖的基礎上,統計每一行像素值不為零的像素點的個數然后再對統計出來的資料進行處理,也就是找到開始出現字符的行(ISBN的上界)和在這之后的第一次出現全零行的情況(ISBN的下屆),這里還有一個判斷,上下界之差<10則說明截取失敗,需要先截取一個大致范圍,再做處理,

6.豎直投影確定列

void sliptCol(Mat inputImg, vector<pair<int, int> >& a)
{   vector<int>theCol(inputImg.cols);
    //做豎直投影
    for (int i=0;i<inputImg.rows;i++)
    {
        for (int j=0;j<inputImg.cols;j++)
        {
            if (inputImg.at<uchar>(i, j) != 0) 
                theCol[j]++;
        }
    }
    //用nums區分走右邊界,num為偶數則代表左邊界 num 為奇數是右邊界
    int num = 0;pair<int, int>p;
	for (int j = 0;j < inputImg.cols;j++)
	{
        //用theCol[j] >= 3判斷 適當把截取范圍取大一點
        if (theCol[j] >= 3 && num % 2 == 0) 
        {
            num++;
            p.first = j;j += 2;
		}
        else if (theCol[j] == 0 && num % 2 != 0) 
        { 
            num++;
            p.second = j;
            a.push_back(p);j += 2;
        }
	}
    
}

講解:在上一步得到的ISBN所在行后,重新在原圖上截取出ISBN所在行再經灰度轉化,降噪處理,二值化后進行豎直投影,這里的豎直投影類比上一步的水平投影,我們找的是每一列像素值不為零的像素點的個數,然后根據二值圖豎直方向投影結果的數字特征分辨出每一個字符的左邊界和右邊界,那么這種數字特征是什么?左右邊界又如何區分呢?數字特征和上一步類似,即找全零列,正常情況下(我們組的二值圖是黑底白字的,因此背景像素值是0,數字像素值是255)開始遇見的都是全0列,所以第一次出現的非零列就是第一個字符的左邊界(num=0);緊接著第一次出現的全零行就是右邊界(num=1),然后來到了第一個字符和第二個字符的間隙,又都是全0列;在此次出現非列0的就是第二個字符的左邊界(num=2)…后面的以此類推,因而當num是偶數時為左邊界,num是奇數時是右邊界,

7.找最小矩形框

void findMinRectangle(Mat inputImg, int& st, int& ed, int& let, int& righ)
{
//inputImg是輸入的二值圖
    for (int i = 0;i < inputImg.rows;i++)
    {
        int j;
        for (j = 0;j < inputImg.cols;j++)
        {
            if (inputImg.at<uchar>(i, j) != 0)
            {
                st = i;
                break;
            }
        }//找到第一行不全為0的行,并將該行作為截取的上界
        if (inputImg.at<uchar>(i, j) != 0)
        {
            break;
        }
    }
    for (int i = inputImg.rows-1;i >= 0;i--)
    {
        int j;
        for (j = 0;j < inputImg.cols;j++)
        {
            if (inputImg.at<uchar>(i, j) != 0)
            {
                ed = i;
                break;
            }
      }
//對矩陣的行反向遍歷找到最后一行不全為0的行,并將該行作為截取的下界
        if (inputImg.at<uchar>(i, j) != 0)
        {
            break;
        }
    }
    for (int j = 0;j < inputImg.cols;j++)
    {
        int i;
        for (i = 0;i < inputImg.rows;i++)
        {
            if (inputImg.at<uchar>(i, j) != 0)
            {
                let = j;
                break;
            }
}//找到第一列不全為0的列,并將該列作為截取的左界
        if (inputImg.at<uchar>(i, j) != 0)
        {
            break;
        }
    }
    for (int j = inputImg.cols - 1;j >= 0;j--)
    {
        int i;
        for (i = 0;i < inputImg.rows;i++)
        {
            if (inputImg.at<uchar>(i, j) != 0)
            {
                righ = j;
                break;
            }
        }//對矩陣的列反向遍歷找到最后一列不全為0的列,并將該列作為截取的右界
        if (inputImg.at<uchar>(i, j) != 0)
        {
            break;
        }
    }
}

講解:利用for回圈對inputImg的行和列進行正向與反向的遍歷,通過找到不全為0的行和列找到inputImg二值圖的上界st,下界ed,左界let,右界righ,并根據st,ed,let,righ對inputImg進行截取,找到截取出的數字或字符的最小矩形框,

8.模板匹配 字符識別

//兩張圖做差(統計兩張圖片不同點像素點的個數)
double absDi(Mat inputImg, Mat sampleImg)
{
    //記錄兩張圖不同的像素點的個數
	double diffNums = 0;double sameNums = 0;
    for (int i=0;i<inputImg.rows;i++)
    {
        for (int j=0;j<inputImg.cols;j++)
        {
            //對應位置的像素值不同 則diffnums++
			if (inputImg.at<uchar>(i, j) != sampleImg.at<uchar>(i, j))
				diffNums++;
			else sameNums++;
        }
    }
    return diffNums;
}
//字符識別
char recognition(Mat inputImg,int k)
{
    string sampleImgPath = "樣例3";
    vector<String> sampleImgFN;
    glob(sampleImgPath, sampleImgFN, false);
    int sampleImgNums = sampleImgFN.size();
    vector<pair< double,int> >nums(sampleImgNums+1);
    for (int i = 0; i < sampleImgNums; i++) {
        nums[i].second= i;
        Mat numImg = imread(sampleImgFN[i], 0);
        //大小同一
        resize(numImg, numImg, Size(40, 60));
        resize(inputImg,inputImg,Size(40,60));
        nums[i].first = absDi(inputImg, numImg);
    }
	//imshow("圖片", inputImg);
	//waitKey();
	//排序 越小說明匹配度越高
	sort(&nums[0], &nums[sampleImgNums]);
    int index= nums[0].second;
	//截取模板的函式
	splitSample(index,k,inputImg);
	return sampleImgFN[index][sampleImgPath.size() + 1];
}

講解:字符識別用的是模板匹配這種方法實作的,具體來說就是把所有可能出現的每一個字符情況都找一定數量(我們用的在2~4個)的模板,當要識別未知字符時需要與所有模板一一比對,找到最接近的模板進行匹配,這里就有一個衡量標準的問題,即:怎樣算“最接近”?我們用的是“找不同的”方法,即先將模板與待識別的字符圖片調成一樣大小(40X60),再對比對應位置的像素值是否相同,統計兩幅圖片不同的像素點的個數,如果不同點的數量越少,就認為該模板與待識別的字符越接近;因而最接近自然就是不同的像素點個數最少的模板,上面兩個函式中absDi()就是用來統計不同的像素點個數的函式,而在 recognition()中就是通過找“最接近”達到模板匹配,字符識別的目的,

五、專案測驗

1.圖片讀取效果
在這里插入圖片描述

2.原圖轉灰度圖效果
在這里插入圖片描述
3.去噪效果
在這里插入圖片描述

4.二值化效果
在這里插入圖片描述

5.圖片旋轉效果
在這里插入圖片描述

6.截取所在行效果
在這里插入圖片描述

7.裂分割 最小矩形的效果
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

8.識別效果
在這里插入圖片描述

六、研究結果并討論

最終在不斷測驗用例,更換模板,調整代碼之后,我們將正確率和準確率提升到了一個比較令人滿意的程度,在規定時間內也完成了ISBN識別系統,這個程序中我們初步接觸了 opencv+VS 的開發環境,提高了 C++語言的應用能力,也開始了解計算機視覺方面的技術以及其研究前景,明白了小組通力合作的重要性,相信在以后的專案中我們會活用這次經驗來更好的迎接挑戰,

結論

在本專案中我們主要任務是開發ISBN識別系統,其中運用了簡單的計算機視覺技術,以實作數字識別的功能,在我們小組成員的不斷努力之后,我們對訓練集的測驗結果為:正確率達到了九成以上,識別效果可觀,可以說是較高質量的完成了這套 ISBN 識別系統的,這樣的效果離不開組內成員間的齊心協力,是我們互相的鼓勵讓我們不畏艱難險阻砥礪前行,在代碼有BUG的時候互相支持,我們能夠開發出這個ISBN識別系統,也源自我們永不放棄,越挫越勇的品質,下一步準備加一個從外界讀取圖片的系統,以達到自動識別書籍ISBN編號的功能,

附頁

開發環境

編譯環境:Windows10
編譯工具:VS2017
OpenCV庫版本:3.4.1

源代碼

#include<opencv.hpp>
#include<opencv2/opencv.hpp>
#include<iostream>
#include<vector>
#include<string>
using namespace cv;
using namespace std;
void splitSample(char ch,int k,Mat inputImg)
{
	string st =  to_string(k) + ".jpg";
    imwrite("D:\\二級專案圖片\\"+to_string(ch) +"."+ st, inputImg);
		
} 
//計算修正角度
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;
}
/*--------------------------------------------------------------------------------------------------------------------------
 Function: originalImgToGrayImg
 Description:將原圖轉化成灰度圖
 ---------------------------------------------------------------------------------------------------------------------------
 Calls:NONE
 Called By: main
 Table Accessed: NONE
 Table Updated:  NONE
 ---------------------------------------------------------------------------------------------------------------------------
  Input:
			第一個引數:原圖,Mat
			第二個引數:轉出的灰度圖,Mat
  Output : 灰度圖
  Return :  None
  Others:    NONE
--------------------------------------------------------------------------------------------------------------------------*/
void originalImgToGrayImg(Mat inputImg, Mat& outputImg)
{
	int row = inputImg.rows;
	int col = inputImg.cols;
	outputImg.create(row, col, CV_8UC1);
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			double sum = 0;
            //得到三個通道的像素值
			int b = inputImg.at<Vec3b>(i, j)[0];
		    int g = inputImg.at<Vec3b>(i, j)[1];
			int r = inputImg.at<Vec3b>(i, j)[2];
			sum = b * 0.114 + g * 0.587 + r * 0.299;
			outputImg.at<uchar>(i, j) = static_cast<uchar>(sum);
		}
	}
}
//冒泡排序對非邊界值與其八鄰域的進行排序,找到中值
int BubbletoMedian(vector<int>& a)
{
	int median;
	for (int i = 0; i < 9; i++)
	{
		for (int j = 0; j < 9 - i - 1; j++)
		{
			if (a[j] > a[j + 1])
			{
				int temp;
				temp = a[j + 1];
				a[j + 1] = a[j];
				a[j] = temp;
			}
		}
	}
	median = a[4];
	return median;
}
//去噪處理
void denoising(Mat gray1, Mat& grayImg)
{
	grayImg = Mat(gray1.rows, gray1.cols, CV_8UC1);
	vector<int>temp(9); //定義九個方向
	int dx[9] = { 1,-1,1,-1,-1,0,1,0,0 };
	int dy[9] = { 1,-1,-1,1,0,1,0,-1,0 };
	for (int i = 0;i < gray1.rows;i++)
	{
		for (int j = 0;j < gray1.cols;j++)
		{
			//邊緣部分不做處理
			if (i == 0 || i == gray1.rows - 1 || j == 0 || j == gray1.cols - 1)
			{
				grayImg.at<uchar>(i, j) = gray1.at<uchar>(i, j);
			}
			else
			{
				for (int k = 0;k < 9;k++)
				{
					temp[k] = gray1.at<uchar>(i + dx[k], j + dy[k]);
				}
				grayImg.at<uchar>(i, j) = BubbletoMedian(temp);
			}
		}
	}
}
//迭代法求閾值
void getThreshold(Mat grayImg, int &theThreshold)
{
	//1.求灰度直方圖
    vector<int>histo(256);
    for (int i = 0;i < grayImg.rows;i++)
    {
        for (int j = 0;j < grayImg.cols;j++)
        {
            histo[grayImg.at<uchar>(i, j)]++;
        }
    }
	//2.根據上面的直方圖 用迭代法求閾值
    int count0, count1;//count0,count1分別是大于t0和小于t0的像素點的個數
	 
	int t0 = 127,t=0; //t0是初始的閾值,t是每一次經過迭代運算后的閾值 當t=t0時認為找到

	int z0, z1;      //z0,在z1分別是大于t0和小于t0的像素值的總和
     while (1)
     {
         count0=count1=z0 = z1= 0;
         for (int i=0;i<histo.size();i++)
         {
             if (i<=t0)
             {
                 count0+= histo[i];
                 z0 += i * histo[i];
             }
             else
             {
				 count1 += histo[i];
				 z1 += i * histo[i];
             }
         }

         t = (z0/count0 + z1/count1)/2;
         if (t0==t) break;
         else t0 = t;
     }
     theThreshold = t0-10;
}
// 根據灰度直方圖得到二值圖
Mat toBinaryGraph( Mat grayImg)
{
    int theThreshold;
    //求閾值
	getThreshold(grayImg, theThreshold);
    Mat binG=Mat(grayImg.rows,grayImg.cols, CV_8UC1);
	for (int i = 0;i < grayImg.rows;i++)
	{
		for (int j = 0;j < grayImg.cols;j++)
		{
              if (grayImg.at<uchar>(i, j) < theThreshold)binG.at<uchar>(i, j) = 255;
               else binG.at<uchar>(i, j) = 0;
		}
	}
    return binG;
}
//水平投影找到行
pair<int, int> SelectRow2(Mat inputImg)
{
    pair<int, int>p;             //記錄ISBN所在的上界(p.first)和下屆(p.second)
    vector<int>arr(inputImg.rows); //存盤每行的水平投影的結果
	for (int i = 0;i < inputImg.rows;i++)
	{
        //水平投影
		for (int j = 0;j < inputImg.cols;j++)
            if (inputImg.at<uchar>(i, j) != 0)arr[i]++;
        //當此行的水平投影值大于指定閾值表示找到上屆 
        if (arr[i] > 10) {
            p.first = i;break;
        }
	}
	for (int i = p.first;i < inputImg.rows;i++)
	{
        //水平投影
		for (int j = 0;j < inputImg.cols;j++)
			if (inputImg.at<uchar>(i, j) != 0)arr[i]++;
        //當此行的水平投影值小于指定閾值表示找到下屆 
        if (arr[i] < 10) {
            p.second = i;break;
        }
	}
    //有些圖片不規則,圖中沒有全零行單獨處理
    if (p.second-p.first<=10) p.second = p.first+32;
    return p;
}
pair<int, int> SelectRow(Mat inputImg)
{
	pair<int, int>a;vector<int>arr(inputImg.rows);
	bool find = false;
	for (int i = 0; i < inputImg.rows; i++)
	{
		for (int j = 0; j < inputImg.cols; j++)
		{    //0表示黑 255表示白
			if (inputImg.at<uchar>(i, j) == 255)
			{
				a.first = i;
				find = true;
				break;
			}
			if (find)break;
		}
	}
	for (int i = a.first + 1; i < inputImg.rows; i++)
	{
		for (int j = 0; j < inputImg.cols; j++)
		{
			//0表示黑 255表示白
			if (inputImg.at<uchar>(i, j) == 255)
				arr[i]++;//這里是在水平投影
		}
		if (arr[i] == 0)
		{
			a.second = i;break;
		}
	}
	//有些圖片不規則,圖中沒有全零行模糊處理
	if (a.second - a.first <= 10) a.second = a.first + 32;
	return a;
}
//豎直投影分割列
void sliptCol(Mat inputImg, vector<pair<int, int> >& a)
{
    vector<int>theCol(inputImg.cols);
    //做豎直投影
    for (int i=0;i<inputImg.rows;i++)
    {
        for (int j=0;j<inputImg.cols;j++)
        {
            if (inputImg.at<uchar>(i, j) != 0) 
                theCol[j]++;
        }
    }
    //用nums區分走右邊界,num為偶數則代表左邊界 num 為奇數是右邊界
    int num = 0;pair<int, int>p;
	for (int j = 0;j < inputImg.cols;j++)
	{
        //用theCol[j] >= 3判斷 適當把截取范圍取大一點
        if (theCol[j] >= 3 && num % 2 == 0) 
        {
            num++;
            p.first = j;j += 2;
		}
        else if (theCol[j] == 0 && num % 2 != 0) 
        { 
            num++;
            p.second = j;
            a.push_back(p);j += 2;
        }
	}
    
}
//找最小矩形框
void findMinRectangle(Mat inputImg, int& st, int& ed, int& le, int& ri)
{
    vector<int>hProjectoin(inputImg.rows);
    vector<int>vProjectoin(inputImg.cols);
    for (int i=0;i<inputImg.rows;i++)
    {
        for (int j=0;j<inputImg.cols;j++)
        {
            if (inputImg.at<uchar>(i, j) != 0) { hProjectoin[i]++;vProjectoin[j]++; }
        }
    }
   st = le = 0;ed = inputImg.rows;ri = inputImg.cols;
	for (int i = 0;i < inputImg.rows;i++)
		if (hProjectoin[i] != 0) ed = i;
	for (int i = 0;i < inputImg.cols;i++)
		if (vProjectoin[i] != 0)ri = i;
	for (int i = inputImg.rows - 1;i >= 0;i--)
		if (hProjectoin[i] != 0) st = i;
	for (int i = inputImg.cols - 1;i >= 0;i--)
		if (vProjectoin[i] != 0)le = i;

}
//兩張圖做差(統計兩張圖片不同點像素點的個數)
double absDi(Mat inputImg, Mat sampleImg)
{
    //記錄兩張圖不同的像素點的個數
	double diffNums = 0;double sameNums = 0;
    for (int i=0;i<inputImg.rows;i++)
    {
        for (int j=0;j<inputImg.cols;j++)
        {
            //對應位置的像素值不同 則diffnums++
			if (inputImg.at<uchar>(i, j) != sampleImg.at<uchar>(i, j))
				diffNums++;
			else sameNums++;
        }
    }
    return diffNums;
}
//字符識別
char recognition(Mat inputImg,int k)
{
    string sampleImgPath = "樣例3";
    vector<String> sampleImgFN;
    glob(sampleImgPath, sampleImgFN, false);
    int sampleImgNums = sampleImgFN.size();
    vector<pair< double,int> >nums(sampleImgNums+1);
    for (int i = 0; i < sampleImgNums; i++) {
        nums[i].second= i;
        Mat numImg = imread(sampleImgFN[i], 0);
        //大小同一
        resize(numImg, numImg, Size(40, 60));
        resize(inputImg,inputImg,Size(40,60));
        nums[i].first = absDi(inputImg, numImg);
    }
	imshow("圖片", inputImg);
	waitKey();
	//排序 越小說明匹配度越高
	sort(&nums[0], &nums[sampleImgNums]);
    int index= nums[0].second;
	//截取模板的函式
	//splitSample(sampleImgFN[index][sampleImgPath.size() + 1],k,inputImg);
	return sampleImgFN[index][sampleImgPath.size() + 1];
   /* if (index >= 0 && index <= 9)
        return index+'0';
    else if (index == 10) return 'B';
    else if (index == 11) return 'I';
    else if (index == 12) return 'N';
    else if (index == 13) return 'S';
    else if (index == 14) return 'X';
    else return ' ';*/
}
int main()
{
    int rtNums = 0, accNums = 0, sunNums = 0;//分別代表:正確的數量,被準確識別的字符的數量,要識別的字符的總和
   //讀取 ISBN 圖片
    string testImgPath = "資料集B/*";
    vector<String> testImgFN;//必須cv的String
    glob(testImgPath, testImgFN, false);
    int testImgNums = testImgFN.size();

    for (int index =12; index < testImgNums; index++) {
        Mat src = imread(testImgFN[index]);
        //圖片大小統一
        double width = 700;
        double height = width * src.rows / src.cols;
        resize(src, src, Size(width, height));
        imshow("原圖",src);
        Mat gray1;
        //原圖轉灰度圖
        originalImgToGrayImg(src, gray1);
        imshow("灰度圖", gray1);

        //去噪處理
        denoising(gray1,gray1);
        imshow("去噪后的圖片", gray1);
        //二值化
        Mat binImg = toBinaryGraph(gray1);
        imshow("二值圖", binImg);
        //計算調整角度
        double thetas = GetTurnTheta(binImg);
        thetas = 180 * thetas / CV_PI - 90;

        //旋轉二值影像
        Mat bin;
        Mat M = getRotationMatrix2D(Point(width / 2, height / 2), thetas, 1);
        warpAffine(binImg, bin, M, binImg.size());
        imshow("旋轉后的二值圖", bin); 
		//waitKey();
		
        //旋轉原圖
		Mat bin2;
		warpAffine(src, bin2, M, src.size());
        imshow("旋轉的原圖", bin2);

        //從原圖上截取ISBN所在行
        Mat temp = Mat(bin2, Range(SelectRow2(bin).first, SelectRow2(bin).second+1), Range(0, bin.cols));
        imshow("原圖上的所在行",temp);
        //大小統一一
        resize(temp,temp, Size(60 * (temp.cols / temp.rows), 60));
        //轉灰度圖
        Mat gray2;
        originalImgToGrayImg(temp, gray2);
        //去噪處理
        denoising(gray2,gray2);
        //二值化
		Mat binImg2 = toBinaryGraph(gray2);
        imshow("原圖所在行二值化 ", binImg2); 
	     //waitKey();
        
        //分割ISBN所在行
        vector<pair<int, int> >a;//{110125),(135150),,,,}
        sliptCol(binImg2,a);

        int st, ed, le, ri;
        string result = "";
        for (int i = 0;i < a.size();i++)
        {
              //做列分割
              Mat subImg = Mat(binImg2, Range(0, binImg2.rows), Range(a[i].first, a[i].second+1));
              findMinRectangle(subImg, st, ed, le, ri);
              //ed - st > subImg.rows / 2如果最小矩形的高小于原來的一半 判斷為‘-’寬過小判斷為雜紋
              if (ed - st > subImg.rows / 2&&ri-le>3)
              {
                  //切出最小矩形的
                  Mat minImg = Mat(subImg, Range(st, ed+1), Range(le, ri+1));
                  imshow("最小矩形", minImg);
                  //模板匹配得到字符
                  char ch = recognition(minImg,index);

				  if (ch >= '0' && ch <= '9' || ch == 'X') {
					  result += ch;
					  cout << ch << " ";
				  }
                
              }
              
          }  cout << endl;
		  //確定正確的 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' ){//|| testImgFN[index][i] == 'I' || testImgFN[index][i] == 'S' || testImgFN[index][i] == 'B' || testImgFN[index][i] == 'N') {
				  cmpData += testImgFN[index][i];
			  }
		  }
          cout << cmpData << endl;
          sunNums += cmpData.length();
          cout << result <<endl<<index << endl;
          int times = min(cmpData.length(), result.length());
          bool flag = true;
          for (int i=0;i<times;i++)
          {
              if (cmpData[i] != result[i])
                  flag = false;
              else accNums++;
          }

          if (flag&&result.length()==cmpData.length()) { rtNums++; cout << "Yes" << endl; }
          else cout << "No" << endl;


    }
	printf("正確個數:%4.d 正確率:%f\n", rtNums, rtNums * 1.0 / testImgNums);
	printf("準確個數:%4.d 準確率:%f\n", accNums, accNums * 1.0 / sunNums);
	waitKey(0);
} 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/436394.html

標籤:AI

上一篇:【寶藏工具系列】神經網路可視化工具集合啦,秒級畫出漂亮的神經網路圖~

下一篇:【快速入門】YOLOv5目標檢測演算法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more