大家好,我是公眾號3分鐘學堂的郭立員~
在群里看他們聊天,因為最近聯眾打碼不能用了,遇到的過驗證碼問題,有沒有其他解決辦法。
說到這么一個驗證碼,如下圖所示:
驗證碼分兩部分,上部分是傾斜的數字,下面是4個選項,點擊和上部分相同數字的選項即為驗證成功。
原本通過這個驗證可以整體截圖對接打碼平臺,然后平臺返回正確選項的位置坐標,腳本點擊此坐標就可以完成驗證了。
要是不用打碼平臺,本地識別可不可以解決呢?
這個驗證的兩部分識別難度是不一樣的,上半部分難度高,下半部分難度低。
先從下半部分簡單的開始識別,這里我先測試smartocr命令,看看識別的準確率。
通過測試發現這個部分的識別率基本接近100%準確,既然準確率非常高,那么就直接使用這個命令,不在測試其他的命令。
識別后的結果存入abcd這4個變量中,分別代表四個選項。
接下來要識別難度大的上半部分,還是測試:
①測試smartocr命令:
正確結果是64217,識別結果是66484,識別準確是20%
把5個文字分開又重新識別測試,識別準確率依然很低,所以smartocr命令可以pass掉了。
這部分之所以難識別,因為數字是雙層的,并且每一個字都是傾斜的,關于這類數字的識別,我想到了這個軟件。
這是電腦端的識別,如果安卓端使用,可以把圖片用ftp傳遞電腦上識別,然后返回識別結果。
先看看識別效果:
5個數字只識別出4個數字,準確率也很一般,不過我嘗試把數字放到一行,再次識別,發現準確提高了。
可以看到識別結果,5個數字識別正確了4個,準確率是80%,基本上達到可以用的程度。
那么怎么把這些數字放到一行,又是一個難題?
我想到的思路是把每一個數字截圖出來,然后再合并到一個圖片上~思路有了我們開始著手去做。
①把每一個圖片的位置找出來
這里每個數字都不是粘連在一起,那么先橫向把數字分塊:
分塊原理是縱向遍歷每一列顏色點,如果在一列當中包含任意一個像素是數字的顏色值,就是有效區域,也就是上圖中紅框,如果一整列都沒有一個符合的顏色點,那么就是非數字區域,也就是紅框以外的。
這是一個二維遍歷,先遍歷一列的顏色點,再遍歷所有列。
為了便于后續處理,做一個二值化的處理,具體處理方式是:以列為單位,如果一列中包含數字的顏色點,記作1,如果不包含記作0,如圖所示:
要實現這個二值化的計算,代碼如下所示:
TracePrintGetBinary(204,179,724,356,"2C4156")FunctionGetBinary(x1,y1,x2,y2,color)Dimbinary,line=""KeepCaptureForj = x1 To x2Fori = y1 To y2IfCmpColor(j, i, color, 0.9)=0 Then binary=1ExitForEndIfIfi = y2 Then binary=0EndIfNextline=line&binaryNextReleaseCaptureGetBinary=lineEndFunction
輸入結果:
結果中有5段連續的數字1,說明驗證圖上有5個數字,那問題又來了,怎么提取每個數字對應連續1的位置?
可以使用查找命令,先看示意圖:
示意圖中,先查找“01”就可以找到連續數字1的左側,再查找“10”就可以找到連續數字1的右側,如果是只有一段連續數字1,那么一次查找即可,但是我們例子中有5段,所以需要循環查找,并且每次查找的起始位置都要在上次查找結果的基礎上向后偏移至少1個位置。
代碼這樣寫:先查找01的
Dimline="00000000000000000000001111111111111111111111111111110000000000000000000000000000000000000000000001111111111111111111111111110000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000"dimleftarr=GetPositions(line,"01")TracePrintjoin(leftarr,"|")FunctionGetPositions(line,str)Dimarr(),i=0DoIfInStr(arr(i - 1) + 1, line, str) = 0 Then ExitDoElsearr(i)=InStr(arr(i - 1) + 1, line, str) + 1i=i+1EndIfLoopGetPositions=arrEndFunction
運算結果:
當前腳本第3行:23|98|188|308|426
把“01”改成“10”在運算一遍:
當前腳本第3行:53|125|227|331|457
兩組數值都是5個,分別代表5個數字左側和右側的位置。
易錯點來了:上面所得到的位置坐標,都是相對坐標,相對的點是數字區域左上角的坐標:
還剩下每個數字的上下位置了,由于5個數字之間在縱向是沒有間隙的。
需要單個數字去獲取上下位置,方法還是剛剛那樣,不同之處是要先遍歷單行顏色點,再逐行遍歷。
在提醒一遍,寫代碼的時候還是要注意易錯點——相對坐標
TracePrintGetBinary2(23+204,179,53+204,356,"2C4156")FunctionGetBinary2(x1,y1,x2,y2,color)Dimbinary,line=""KeepCaptureForj = y1 To y2Fori = x1 To x2IfCmpColor(i, j, color, 0.9)=0 Then binary=1ExitForEndIfIfi = x2 Then binary=0EndIfNextline=line&binaryNextReleaseCaptureGetBinary2=lineEndFunction
輸出結果:
當前腳本第1行:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111100000000000000000000000000000000000
獲取位置還是使用查找命令,查找“01”和“10”,再次強調得到結果是相對坐標。
把所有的數字都按照這個方法獲取上下坐標,到此處我們已經可以得到每個數字的范圍坐標,接下來就是截圖了。
考慮到后面數字要拼接到一起,所以截圖時外延5像素。
擴展計算方式:
加上原始范圍是x1,y1,x2,y2
擴展后的范圍是:x1-5,y1-5,x2+5,y2+5
最終代碼:
截圖(204,179,724,356,"2C4156")Function截圖(x1,y1,x2,y2,color)dimlines= Getbinary(x1,y1,x2,y2,color)TracePrintlinesdimleftarr= GetPositions(lines,"01")TracePrintjoin(leftarr,"|")dimrightarr= GetPositions(lines,"10")TracePrintjoin(rightarr,"|")Fori = 0 To UBOUND(leftarr)dimnewline= GetBinary2(leftarr(i)+x1, y1, rightarr(i)+x1, y2, color)TracePrintnewlineTracePrintleftarr(i)+x1TracePrintinstr(1,newline,"01")+y1TracePrintrightarr(i)+x1TracePrintinstr(1,newline,"10")+y1SnapShot("/sdcard/pictures/yzm/"&i&".png",leftarr(i)+x1-5,instr(1,newline,"01")+y1-5,rightarr(i)+x1+5,instr(1,newline,"10")+y1+5)TracePrint"--------------"NextEndFunctionFunctionGetBinary(x1,y1,x2,y2,color)Dimbinary,line=""KeepCaptureForj = x1 To x2Fori = y1 To y2IfCmpColor(j, i, color, 0.9)=0 Then binary=1ExitForEndIfIfi = y2 Then binary=0EndIfNextline=line&binaryNextReleaseCaptureGetBinary=lineEndFunctionFunctionGetBinary2(x1,y1,x2,y2,color)Dimbinary,line=""KeepCaptureForj = y1 To y2Fori = x1 To x2IfCmpColor(i, j, color, 0.9)=0 Then binary=1ExitForEndIfIfi = x2 Then binary=0EndIfNextline=line&binaryNextReleaseCaptureGetBinary2=lineEndFunctionFunctionGetPositions(line,str)Dimarr(),i=0DoIfInStr(arr(i - 1) + 1, line, str) = 0 Then ExitDoElsearr(i)=InStr(arr(i - 1) + 1, line, str) + 1i=i+1EndIfLoopGetPositions=arrEndFunction
在文件夾里面已經把所有數字單獨截取出來了。
截圖部分的內容已經完成,下面開始把所有圖片拼接到一起。
第一步:獲取所有圖片的尺寸,并存入數組中
DimPicArr()Fori = 0 To 4DimPath = "/sdcard/pictures/yzm/"&i&".png"Dim返回值 = Image.Size(Path)PicArr(i)={返回值[1],返回值[2]}NextDimjson=encode.tabletojson(PicArr)TracePrintjson
運算結果:
當前腳本第9行:[[41,50],[38,46],[50,51],[34,49],[42,49]]
第二步:因為是橫向拼接所有圖片,所以最終合成圖的寬度是所有圖片寬度之和。
Dim PicArr={{41,50},{38,46},{50,51},{34,49},{42,49}}Dim xFor i = 0 To 4? x=x+PicArr[i+1][1]NextTracePrint x
第三步:合成圖的高度,5張圖中最高的高度就是合成圖的高度。
一組數字比較大小,可以用冒泡法,即相鄰兩個數字比較,前面數字大于后面數字,兩個數字調換位置,如果前面數字小于后面數字,兩個數字位置不變,這樣所有大的數字都被放到后面,那么最后一個數字就是最大的數字。
Dim PicArr={{41,50},{38,46},{50,51},{34,49},{42,49}}Dim yFor i = 1 To 4? If PicArr[i][2] > PicArr[i+1][2] Then? ? PicArr[i+1][2]=PicArr[i][2]End IfNexty = PicArr[4][2]TracePrint y
第四步:做一個以驗證圖背景色顏色值的圖片
Dimx=205,y=51DimPixelData = Image.GetScreenData(1,1,x,y)Dimr,g,bDimbackground="C7D1DB"ColorToRGB(background,r,g,b)TracePrintr,g,b Forj = 1 To xFori = 1 To yPixelData[j][i][3]=rPixelData[j][i][2]=gPixelData[j][i][1]=bNextNextImage.SavePixelDataPixelData, "/sdcard/pictures/yzm/aa.png"
第五步:把每張數字圖片的顏色數據,都賦值給上面的圖片。
Dimx=205,y=51DimPicArr={{41,50},{38,46},{50,51},{34,49},{42,49}}DimPixelData =Image.GetPicData("/sdcard/pictures/yzm/aa.png")Forn = 0 To 4Ifn = 0 Then dimm = 0Elsem=m+PicArr[n][1]EndIfdimPixelDataword=Image.GetPicData("/sdcard/pictures/yzm/"&n&".png")Forj = 1 To PicArr[n+1][1]Fori = 1 To PicArr[n + 1][2]Fork = 1 To 3PixelData[m+j][i][k]=PixelDataword[j][i][k]NextNextNextNextImage.SavePixelDataPixelData, "/sdcard/pictures/yzm/ab.png"
完整代碼:
拼圖(4)Function拼圖(num)DimPicArr()Fori = 0 To numDimPath = "/sdcard/pictures/yzm/"&i&".png"Dim返回值 = Image.Size(Path)PicArr(i)={返回值[1],返回值[2]}NextDimjson=encode.tabletojson(PicArr)TracePrintjsonDimxFori = 0 To numx=x+PicArr[i+1][1]NextTracePrintxDimyFori = 1 To numIfPicArr[i][2] > PicArr[i+1][2] Then PicArr[i+1][2]=PicArr[i][2]EndIfNexty=PicArr[num][2]TracePrintyDimPixelData = Image.GetScreenData(1,1,x,y)Dimr,g,bDimbackground="C7D1DB"ColorToRGB(background,r,g,b)TracePrintr,g,b Forj = 1 To xFori = 1 To yPixelData[j][i][3]=rPixelData[j][i][2]=gPixelData[j][i][1]=bNextNextTracePrintx,yPicArr=Encode.JsonToTable(json)Forn = 0 To numIfn = 0 Then dimm = 0Elsem=m+PicArr[n][1]EndIfdimPixelDataword=Image.GetPicData("/sdcard/pictures/yzm/"&n&".png")Forj = 1 To PicArr[n+1][1]Fori = 1 To PicArr[n + 1][2]Fork = 1 To 3PixelData[m+j][i][k]=PixelDataword[j][i][k]NextNextNextNextImage.SavePixelDataPixelData, "/sdcard/pictures/yzm/ab.png"EndFunction
這期文章實現的功能很簡單,但是思考的邏輯過程還是比較復雜的,另外在群里問怎么把兩張圖合并在一起的同學可以來領教程了。
寫到這里有點累了,找到正確答案的識別和比對,不想寫了,說個大概思路:
(1)比對數字個數,上部分是5個數字,只有選項B/C滿足,排除2個錯誤答案
(2)因為選項的識別準確率非常高,我假定它是完全準確的。上部分的數字和選項比對這么幾個維度,并且用打分形式記錄
①單個數字對比,一個相同數字(+5分)
②同位數字比對,比如第一位都是6,每對一位(+10分)
③連續位數相同,這個情況比較多,先判斷所有位相同的情況,如果這個滿足,直接認定為正確答案,后續是1位不同,2位不同,分值依次降低。
就我們這個例子來說,它非常簡單,幾個選項相差很大,所以這個比對就簡單很多。
=正文完=