Java應用OpenvCV指南其六:利用OpenCV實現的數字識別(驗證碼識別)
之前介紹了很多概念上的東西,這次讓我們來進行一次實際的應用。數字識別可以應用在許多領域,如數字型驗證碼的識別,車牌識別等領域。下面借我之前完成的一個數字驗證碼識別的小專案來簡單認識一下圖象識別領域的知識。
如果在閱讀這篇文章時有什麼疑問,可以參考一下之前的一些文章:
一、數字識別流程
1、圖象預處理
影象預處理,是將待檢測影象交給識別模組識別。在影象分析中,對輸入影象進行特徵提取、分割和匹配前所進行的必要處理。
影象預處理的主要目的是消除影象中無關的資訊,恢復有用的真實資訊,增強有關資訊的可檢測性和最大限度地簡化資料,從而提高特徵抽取、影象分割、匹配和識別的可靠性。
在識別數字的時候,預處理的最終目標一般都是生成一張原圖的清晰二值化圖象,所謂二值化就是將要識別的影象變成只有黑白兩種顏色的單通道圖片。經過這樣處理後的影象特徵突出,顏色簡單,可以大大的降低我們識別時的難度和複雜度。
二值化函式:
Imgproc.threshold(src, dst, thresh, maxval, type);
• 第一個引數,Mat型的src為待處理的原圖。
• 第二個引數,Mat型的dst為輸出的圖象。
• 第三個引數為閾值。
• 第四個引數表示最大值。
• 第五個引數表示二值運算的方式,引數數值說明如下:
enum
{
CV_THRESH_BINARY =0, /* value = value > threshold ? max_value : 0 */
CV_THRESH_BINARY_INV =1, /* value = value > threshold ? 0 : max_value */
CV_THRESH_TRUNC =2, /* value = value > threshold ? threshold : value */
CV_THRESH_TOZERO =3, /* value = value > threshold ? value : 0 */
CV_THRESH_TOZERO_INV =4, /* value = value > threshold ? 0 : value */
CV_THRESH_MASK =7,
CV_THRESH_OTSU =8 /* use Otsu algorithm to choose the optimal threshold value; combine the flag with one of the above CV_THRESH_* values */
};
不過相對於識別需要的內容,我們獲得的原圖一般都包含過多的圖象資訊,直接做二值化的話很難調參,即使勉強操作得出的圖象也幾乎無法進行識別,所以我們再二值化之前需要預先做一些處理儘量去除干擾要素。特別是驗證碼圖象,一般都會有一些干擾元素在裡面,很多情況下需要具體問題具體分析。
以我遇到的情況為例,我需要處理的是類似這樣的驗證碼:
圖上有許多噪點和弧形的彩色干擾線條,我在預處理時先寫了一個演算法儘可能的消除了這些干擾線,然後做了濾波,最後得出了類似這樣的效果:
這樣的話差不多就滿足識別的要求了,處理時注意方法的通用性,因為驗證碼是隨機生成的,所以我們需要保證處理後大多數的圖象效果都在一個水平線上,以保證我們識別的準確率。
具體的操作我們在後面面實際操作時再詳細介紹。
2、圖象的切割(選定ROI區域)
獲得二值化的圖象以後需要對圖中的數字進行切割,分割成一個個單獨的數字,由於二值化後 每個畫素點的值只有0和maxval兩種(單通道),還是以上圖為例,圖中黑色為0,白色為255。
先進行列掃描求每列的和。剛開始是都是黑色所以每列的和都是0,直到掃描到9的左邊緣的那列的時候因為有白色所以這列的和大於0,這時候記下當前位置left,然後接著掃描,接下來每列的和都大於0,直到9的右邊緣時候這列和右等於0,記下當前位置right,則right減去left則是3的寬度,高度仍為原圖的高度,這樣通過函式:
Rect rect=new Rect(x1,y1,x2,y2);
Mat Img1 =New Mat(img , rect);
可以截取出9
3、製作模板
我在這裡沒有使用機器學習的方式,由於數字形態沒有變化,所以使用了一種簡單有效的方式,就是製作與擷取部分一樣大小的模板(0123456789)與帶識別數字依次進行匹配,最相近的匹配結果即為識別出的數值。
4、數字識別
首先保證選出的ROI區域與模板大小一樣,然後讓需要匹配的圖和分別和10個模板相減,(讓兩個圖片對應畫素點值相減)然後求返回圖片的整個圖片的畫素點差值的絕對值的和,哪個模板匹配時候返回圖片的和最小則就可以得到結果。
Core.absdiff(src1, src2, dst);
• 第一個引數,src1第一個原陣列。
• 第二個引數,src2第二個原陣列。
• 第三個引數,dst輸出陣列。
函式 cvAbsDiff 可以計算兩個陣列差的絕對值
dst(I)c = abs(src1(I)c - src2(I)c).
所有陣列必須有相同的資料型別相同的大小(或ROI大小)
二、 識別例項
1、讀取圖片,並對干擾線進行初步處理
Mat img=Imgcodecs.imread("F:/workspace/opencv/train/29.jpg");
Mat end=new Mat();
//去除干擾線
for(x=0;x<img.rows();x++){
for(y=0;y<img.cols();y++){
double[] clone=img.get(x, y).clone(); //修改通道數值
double cb=clone[0];
double cg=clone[1];
double cr=clone[2];
double avg=(cb+cg+cr)/3;
if(!((cb>c&&cg>c&&cr>c)&&((avg>98)&&(avg<148)||(avg>153)&&(avg<196))&&((max(clone)-min(clone))<55))){
clone[0]=255;
clone[1]=254;
clone[2]=255;
img.put(x, y, clone);
}
}
}
Imgproc.bilateralFilter(img, end, a, a*2, a/2); //雙邊濾波
處理結果:
2、轉換為灰度圖,並用中值濾波對圖象進行進一步處理,最後將預處理過的圖象二值化。
Mat end2=new Mat();
Imgproc.cvtColor(end, end2, Imgproc.COLOR_BGR2GRAY); //灰度圖
Imgproc.medianBlur(end2, end2, 3); //中值濾波
Imgproc.threshold(end2, end2, 245, 255, 1); //二值化
處理結果:
3、通過掃描每列與每行點的值,擷取圖片上的數字
Rect area1=new Rect(x1,y1,a1,b1);
Rect area2=new Rect(x2,y2,a2,b2);
Rect area3=new Rect(x3,y3,a3,b3);
Mat num1=new Mat(end2,area1);
Mat num2=new Mat(end2,area2);
Mat num3=new Mat(end2,area3);
Rect的值為要擷取部分左上角和右下角的座標
擷取效果:
4、通過擷取的數字製作標準模板
純手工製作,不過步驟比較繁瑣單調。具體來說就是從擷取的數字中挑選讀取比較標準的數字,二值化後反覆通過get.clone()和put方法修改相應畫素點的顏色具體操作方式參考上面去除干擾線的方法,這裡就不再說明了。
模板:
5、數字識別
public static int getnum(Mat number){
int num=0;
int min=30000;
Mat model=new Mat();
Mat mid=new Mat();
Mat res=new Mat();
for(int i=0;i<=10;i++){
model=Imgcodecs.imread("F:/workspace/opencv/model/"+i+".jpg");
Imgproc.cvtColor(model, mid, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(mid, mid, 250, 255, 0); //二值化
Core.absdiff(number, mid, res); //兩個矩陣的差的絕對值
int now=getsum(res);
if(now<min){
min=now;
num=i;
}
}
if(num==10)
num=1;
return num;
}
識別運算子號:
boolean pls=false;
for(int i=45;i<130;i++){
for(int j=6;j<24;j++){
double[] can=end2.get(j, i).clone();
double s=can[0];
sum+=s;
}
if (sum>=510){
p=i;
break;
}
sum=0;
}
if(p>=50)
pls=true;
6、輸出結果
通過模擬登入的方式請求登入頁面驗證碼,然後下載圖片識別的方式測試例項80次,成功登入79次,識別率為98.75%