大家好,我是公眾號3分鐘學堂的郭立員~
在群里看他們聊天坑赡,因為最近聯(lián)眾打碼不能用了,遇到的過驗證碼問題,有沒有其他解決辦法。
說到這么一個驗證碼,如下圖所示:
驗證碼分兩部分,上部分是傾斜的數(shù)字,下面是4個選項,點擊和上部分相同數(shù)字的選項即為驗證成功。
原本通過這個驗證可以整體截圖對接打碼平臺认臊,然后平臺返回正確選項的位置坐標,腳本點擊此坐標就可以完成驗證了。
要是不用打碼平臺,本地識別可不可以解決呢?
這個驗證的兩部分識別難度是不一樣的竖般,上半部分難度高闭翩,下半部分難度低。
先從下半部分簡單的開始識別,這里我先測試smartocr命令,看看識別的準確率眯搭。
通過測試發(fā)現(xiàn)這個部分的識別率基本接近100%準確允耿,既然準確率非常高蚂蕴,那么就直接使用這個命令低散,不在測試其他的命令。
識別后的結果存入abcd這4個變量中骡楼,分別代表四個選項熔号。
接下來要識別難度大的上半部分,還是測試:
①測試smartocr命令:
正確結果是64217君编,識別結果是66484跨嘉,識別準確是20%
把5個文字分開又重新識別測試川慌,識別準確率依然很低吃嘿,所以smartocr命令可以pass掉了。
這部分之所以難識別梦重,因為數(shù)字是雙層的兑燥,并且每一個字都是傾斜的,關于這類數(shù)字的識別琴拧,我想到了這個軟件降瞳。
這是電腦端的識別,如果安卓端使用,可以把圖片用ftp傳遞電腦上識別挣饥,然后返回識別結果除师。
先看看識別效果:
5個數(shù)字只識別出4個數(shù)字,準確率也很一般扔枫,不過我嘗試把數(shù)字放到一行汛聚,再次識別,發(fā)現(xiàn)準確提高了短荐。
可以看到識別結果倚舀,5個數(shù)字識別正確了4個,準確率是80%忍宋,基本上達到可以用的程度痕貌。
那么怎么把這些數(shù)字放到一行,又是一個難題糠排?
我想到的思路是把每一個數(shù)字截圖出來舵稠,然后再合并到一個圖片上~思路有了我們開始著手去做。
①把每一個圖片的位置找出來
這里每個數(shù)字都不是粘連在一起入宦,那么先橫向把數(shù)字分塊:
分塊原理是縱向遍歷每一列顏色點柱查,如果在一列當中包含任意一個像素是數(shù)字的顏色值,就是有效區(qū)域云石,也就是上圖中紅框唉工,如果一整列都沒有一個符合的顏色點,那么就是非數(shù)字區(qū)域汹忠,也就是紅框以外的淋硝。
這是一個二維遍歷,先遍歷一列的顏色點宽菜,再遍歷所有列谣膳。
為了便于后續(xù)處理,做一個二值化的處理铅乡,具體處理方式是:以列為單位继谚,如果一列中包含數(shù)字的顏色點,記作1阵幸,如果不包含記作0花履,如圖所示:
要實現(xiàn)這個二值化的計算,代碼如下所示:
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段連續(xù)的數(shù)字1挚赊,說明驗證圖上有5個數(shù)字诡壁,那問題又來了,怎么提取每個數(shù)字對應連續(xù)1的位置荠割?
可以使用查找命令妹卿,先看示意圖:
示意圖中,先查找“01”就可以找到連續(xù)數(shù)字1的左側,再查找“10”就可以找到連續(xù)數(shù)字1的右側夺克,如果是只有一段連續(xù)數(shù)字1箕宙,那么一次查找即可,但是我們例子中有5段铺纽,所以需要循環(huán)查找扒吁,并且每次查找的起始位置都要在上次查找結果的基礎上向后偏移至少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
兩組數(shù)值都是5個室囊,分別代表5個數(shù)字左側和右側的位置雕崩。
易錯點來了:上面所得到的位置坐標,都是相對坐標融撞,相對的點是數(shù)字區(qū)域左上角的坐標:
還剩下每個數(shù)字的上下位置了盼铁,由于5個數(shù)字之間在縱向是沒有間隙的。
需要單個數(shù)字去獲取上下位置尝偎,方法還是剛剛那樣饶火,不同之處是要先遍歷單行顏色點,再逐行遍歷致扯。
在提醒一遍肤寝,寫代碼的時候還是要注意易錯點——相對坐標
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”抖僵,再次強調得到結果是相對坐標鲤看。
把所有的數(shù)字都按照這個方法獲取上下坐標,到此處我們已經可以得到每個數(shù)字的范圍坐標耍群,接下來就是截圖了义桂。
考慮到后面數(shù)字要拼接到一起,所以截圖時外延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
在文件夾里面已經把所有數(shù)字單獨截取出來了慷吊。
截圖部分的內容已經完成,下面開始把所有圖片拼接到一起曹抬。
第一步:獲取所有圖片的尺寸溉瓶,并存入數(shù)組中
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張圖中最高的高度就是合成圖的高度。
一組數(shù)字比較大小赖临,可以用冒泡法胞锰,即相鄰兩個數(shù)字比較,前面數(shù)字大于后面數(shù)字兢榨,兩個數(shù)字調換位置,如果前面數(shù)字小于后面數(shù)字,兩個數(shù)字位置不變吵聪,這樣所有大的數(shù)字都被放到后面凌那,那么最后一個數(shù)字就是最大的數(shù)字。
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"
第五步:把每張數(shù)字圖片的顏色數(shù)據(jù)吟逝,都賦值給上面的圖片帽蝶。
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
這期文章實現(xiàn)的功能很簡單,但是思考的邏輯過程還是比較復雜的块攒,另外在群里問怎么把兩張圖合并在一起的同學可以來領教程了励稳。
寫到這里有點累了,找到正確答案的識別和比對囱井,不想寫了驹尼,說個大概思路:
(1)比對數(shù)字個數(shù),上部分是5個數(shù)字庞呕,只有選項B/C滿足新翎,排除2個錯誤答案
(2)因為選項的識別準確率非常高,我假定它是完全準確的住练。上部分的數(shù)字和選項比對這么幾個維度地啰,并且用打分形式記錄
①單個數(shù)字對比,一個相同數(shù)字(+5分)
②同位數(shù)字比對讲逛,比如第一位都是6亏吝,每對一位(+10分)
③連續(xù)位數(shù)相同,這個情況比較多盏混,先判斷所有位相同的情況顺呕,如果這個滿足,直接認定為正確答案括饶,后續(xù)是1位不同株茶,2位不同,分值依次降低图焰。
就我們這個例子來說启盛,它非常簡單,幾個選項相差很大技羔,所以這個比對就簡單很多僵闯。
=正文完=