大家好,我是公众号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位不同,分值依次降低。
就我们这个例子来说,它非常简单,几个选项相差很大,所以这个比对就简单很多。
=正文完=