文章目錄
- 摘要
- 前言
- 正文
- 一、研究內容的基本原理
- 二、所采用的研究方法及相關工具
- 三、專案的方案設計
- 四、核心代碼實作
- 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;//{(110,125),(135,150),,,,}
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
