挂海论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
 友情提示:文字/图片广告均非网站意见,请担保交易勿直接付款,由此产生的责任自负
玩游戏来117游戏网(H5不下载也能玩手游传奇,吃鸡,竞技都有)不懂社区·好资源不错过·各位资源站大佬欢迎来采集搬运IOS签名/udid证书出售/送证书加群1040456405 ██【我要租此广告位】██
... .
查看: 3223|回复: 3
打印 上一主题 下一主题

[转载] 发一个 验证码识别技术帖

[复制链接]
4中级会员
520/600

520

积分

223

主题

5

听众
已帮网友解决0 个问题
好评
0
贡献
297
海币
3998
交易币
0
跳转到指定楼层
楼主
发表于 2015-2-10 00:20:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
提醒:若下载的软件是收费的"请不要付款",可能是骗子,请立即联系本站举报,执意要付款被骗后本站概不负责。(任何交易请走第三方中介,请勿直接付款交易以免被骗!切记).

友情提示:文字/图片广告均非本站意见,请担保交易勿直接付款,由此产生的责任自负!!!↑↑


发一个 验证码识别技术帖
验证码识别是一项特殊的技术,任何一个公开的验证码识别代码都会很快的失效。
因为代码的公开后相关网站都会很快的更改验证码。
所以下面我只会介绍其原理。

在这里讨论验证码识别技术纯粹基于技术研究目的。
公开此技术也是为了让更多的网站采取更有效的防范措施。
禁止任何人利用这里介绍的验证码识别技术滥发垃圾信息。


本文介绍的验证码识别适用于比较复杂的图片验证码,也是大多数网站采用的方法。
有一些网站的验证码极简单,例如在网页中直接显示验证码字符而不是图片,或者图片的文件名直接就是验证码上的字符。
或者有其他规律可循,或者有其他明显的漏洞可以利用(例如通过改写访问验证码页面的源代码使验证码不刷新)。

这一类的验证码识别极其简单,只要熟练掌握web库、element库的函数即可,不需要使用下面介绍的方法。
一、下载验证码样本

打开c:\test文件夹,选“查看缩略图”,
然后重复运行下面的LAScript脚本,每运行一次,就查看c:\test下自动生成的图片,把图片上的字符改为文件名.
例如图片上面显示5,就把文件名改为5.jpg.

如果变化比较复杂的验证码,可以对每个字符多用几个样本,第一个字符为验证码字符,第二个字符可以为任意字符。
例如:5a.jpg , 5b.jpg , 5c.jpg ...........等等。  
样本多就会识别能力就越强。

img = image.new();

--下载图像,没有后缀名要显示指定*.bmp格式
img:getURL("http://www.***.com/test.asp","*.png");
assert(img:ok(),"下载验证码失败");

img:Crop(4 ,3 , 56 ,18 )
img:save("c:\\test\\test.jpg") --保存到硬盘


--折分图片,指定一行四列
img2,img3,img4,img5 = img:split(1,4);

img2:save("c:\\test\\0001.jpg")
img3:save("c:\\test\\0002.jpg")
img4:save("c:\\test\\0003.jpg")
img5:save("c:\\test\\0004.jpg")

image.del(img);


如何确定图片后缀名  
在整个验证码识别过程中,格式与后缀名一定不能搞错,否则就会失败。
通常:asp的验证码是bmp格式,php的验证码是png格式,其他验证码很多是jpg格式。
简单的,在验证码上右键点选“图片另存为”,就可以看到格式(不一定准确)。


另外,你可以用UltraEdit等以二进制方式打开看文件头部

首先下载:
str = web.getURL("http://www.***.com/test.asp")
string.save( str,"c:\\test.bin")

然后用UE打开test.bin看文件头部(第一行)

jpg文件头部有 JFIF 字眼
png文件头部 有 PNG 字眼
gif文件头部有 GIF字眼
如果你搞不清楚,这时候就不要指定后缀名
img:getURL("http://vwww.***.com/test.asp","")
这样就可以下载了


二、生成验证码样本数据库

复制下面的代码并粘贴到fap程序的「脚本区块」内,然后点击"回放运行",最后再点击"读取源代码"。

你就可以在ApeML源代码最后面的「数据区块」中看到生成的验证码样本了。
将「数据区块」的内容复制需要使用验证码识别的fap模拟程序中覆盖「数据区块」即可。

local tkey ={A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0};

--在字典中添加所有数字键
for i =0,9,1 do
    tkey[ tostring(i) ] = 0;  
end;


--如果一个字符有多个样本,例如 5A.jpg 5B.jpg 5C.jpg
for k,v in pairs(tkey) do  
    if((#k)~=2)then --如果元素键名不是两位字符
        tkey[k.."A" ]=0;
        tkey[k.."B" ]=0;
        tkey[k.."C" ]=0;
        tkey[k]=nil;--删除单字符的键名
    end;
end;

--k参数为键,v参数表示值 一个典型的tkeyle迭代器回调函数
loadtkey = function(k,v)  
    local img = image.new();

    img:load("C:\\test\\"..k..".jpg");
    assert(img:ok(),"C:\\test\\"..k..".jpg".."\n不是有效的图片");

    img:bpp(1);
    img:bpp(24);
    --通过上面两句,轻松去掉验证码上的杂色杂点

    img:Crop( 1 , 0 , 9 , 10);--修剪单个字符
    img:median(2);--中值滤波进一步去杂点

    tkey[k]= string.encode( img:getBytes("*.jpg") , ""); --因为转换到字符串还是二进制,所以用base64进行编码
    image.del(img);
end;

--遍历表tkey的所有元素,调用loadtkey加载图片文件  
for k,v in pairs(tkey) do  
    loadtkey(k,v);
end;

--把所有图片保存到数据岛,
ape:saveTable(tkey,"验证码样本")

三、验证码识别
将下面的代码添加到fap模拟程序最前面的init脚本区块中即可

--从数据区块读取base64编码的图片数据
codekey = ape:loadTable("验证码样本");
local timg = {}; --这是一个图像数组,用来储存还原后的验证码样本的图片数据
--必须进行一个转换,因为codekey里面只是base64编码的普通字符串,而timg 将是真正的图片对象(二进制数据)

--还原到图片对象
toImage = function(k,v)
    local img = image.new();
    local str = string.decode( v ,"");--首先进行base64解码,将纯文本转换为二进制数据
    img:setBytes( str ,"*.jpg");--将二进制数据还原为图像
    timg[k] = img;  
end;

--载入验证码样本
tkey = ape:loadTable("验证码样本");
for k,v in pairs(tkey) do  --验证样本
    toImage(k,v); --转换为图像
end;

--转换图片验证码到字符串的函数
function ImgToString(img)
    function test(imgX) --test是一个被包含在函数中的内部函数
        sleep(0);  
        local limit = (60 * 20) + (60 * 20); --最小相似度 local关键字声明为局部变量
        local chr = "A"; --读取的字符


        --testimg是一个被包含在函数中的内部函数,作为table.foreach的回调函数,k参数表示键,v参数表示值
        testimg = function(k,v)

            --调用image.testXX()函数得出相似度,类似的函数还有image.testX() image.test()  
            local n = imgX:testXX(timg[k]);  
            if(n<limit)then --比较最小相似度
                  limit = n;
                  chr = k.."";
            end;
        end;

        --遍历timg表,并调用testimg函数
        for k,v in pairs(timg) do   
           testimg(k,v);   
        end;

        return string.left(chr,1); --返回读取到的字符串首字符(如果每个字符有多个样本)
    end;


    --修剪图片     
    image.Crop(img, 4 ,3 , 56 ,18 )
    img:bpp(1);
    img:bpp(24);
    --上面的过程必须与下载样本时的代码完全一致。

    --使用split函数分割图片
    local img2,img3,img4,img5 = img:split(1,4);
    win.messagePrint("正在检测图片,请稍候....");
    return test(img2)..test(img3)..test(img4)..test(img5);

end;
需要识别验证码的地方添加类似下面的代码:

img = image.new()
img:getURL("http://www.***.com/test.asp","*.jpg")  
--因为刷新了验证码与页面不一致,把验证码画到屏幕上
local x,y = mouse.getPos()
img:paint(x,y,60 ,20 )

local str = ImgToString(img);

--下面我们把验证码的每个字符都转换为大写,并控制键盘顺序按键
code1 = string.upper( string.sub(str,1,1) );  
code2 = string.upper( string.sub(str,2,2) );  
code3 = string.upper( string.sub(str,3,3) );  
code4 = string.upper( string.sub(str,4,4) );
key.press(100,code1,code2,code3,code4);

上面我们用了模拟按键的方法输入验证码。
实际上大多时候可以用更简单的方法,如下:

ele = wb:getEle("验证码控件名字");
ele:setAttribute("value",str)
为什么我的验证码与页面上不一样

因为我们使用img:getURL读取验证码时已经刷新了验证码。
所以验证码与页面上显示的并不一样,您只需要识别最新的验证码即可。

如何直接获取页面的上图片,而不是重新下载

有些验证码是绑定页面的,必须识别页面上的验证码才行。
那么可以使用image.capture函数直接抓屏屏幕上的图片即可。
请参考:image.capture函数。

更好的方法是使用ele:exec("Copy")函数直接拷贝页面上的图片到剪贴板。
然后使用 img:getClipBD() 获取图片。  
请参考:ele:exec("Copy")函数 img:getClipBD()函数

四、关于剪切图片


看上面的示意图,Crop就是选取绿色方框内的区域去清除绿色方框外面的区域.
必须保证里面的面积正好可以平均分成四块(假设这里是四个验证码字符)

这样以后调用 img:split(1,4) 就正好分成四个字符了
分成四份的小图片其宽度应当正好是上面的红色小方块的宽度。
高度与绿色方框一样,我这里画的参次不齐是为了让大家看清楚。

如果你Crop的参数值不对,那么split就出错了.
下载验证码图片以后,可以使用图像编辑软件打开高倍放大。

五、使用种子填充算法去除验证码上的干扰线
模拟精灵识别验证码的能用是强大的,一个函数即可以去除杂色杂点。


img:bpp(1)
img:bpp(24)
经过上面两句代码的处理,速度很快,所有背景、干扰点、杂色荡然无存。

但是有时候验证码中有大量的干扰线,并且位置随机变动的太历害,
这时候我们在处理验证码以前首先去除这些干扰线并准确的去除背景提取字符.

下面是一个模拟精灵初步处理后的验证码图片.已经去除了杂色、杂点.但是上面还是有干扰线.



一个可选的办法是用中值滤波再处理一下。img:median(2); 一个函数调用就可以,但
是这样虽然去掉了干扰线,原来的字符也被少量的破坏了。

下面是使用种子填充算法去除干扰线的源代码,不但能去除杂点,
而且可以去除周围的空白(提取位置随机变化的验证码),
稍加修改还能有更多的用途.

下面是自动处理以后的效果



下面是全部的源代码:

--[[
用一个table结构{x=0; y=0}表示图像上的「坐标点」
用一组点构成table结构表示图像上的一条「线」。所有相连的黑色的点被认为是一条「连通线」。
找出最长的一条「连通线」,被认为是字符,其他的认为是杂点。


算法原理与种子填充算法相似。

首先让用img:bpp函数处理为黑白图片,并初步去除杂色。

先找到一个黑点,创建一个表示「坐标点」对象,并添加到「连通线」中。
然后在黑点周围8个点中,再找黑色的点,找到就添加到「连通线」,这样一直递归下去
直到遍历图像所有点,可能有几块。

清除杂点使用方法
image.scan(img);

清除杂点并切去掉周围的空白
image.scan(img,true);
--]]
function image.scan(img,crop)

    --用一个table数组记录所有的「连通线」
     assert(img:ok(),"image.scan 的参数必须是一个有效的图片");

     local tlines ={};  

     --首先计算出图片的高度宽度,避免重复的调用
     local w = img:width();
     local h = img:height();


    --[[以table形式定义一个数组,对应图象中的每个点。
    作用相当一个开关,首先值为false,但黑点首次被遍历到时。把这个值变为true。
    下次,再找到这个点时忽略。避免重复加入连通线。   
    --]]
    local tchked ={};
    for i=0,w,1  do
        tchked={};
        for j=0,h,1  do
            tchked[j]=false;
        end;
    end;

    -----去噪
    img:bpp(1);
    img:bpp(24);

    --首先计算出各点的颜色值,避免在循环递归中重复的取
    local tcl={};
    for i=0,w,1  do
        tcl={};
        for j=0,h,1   do
            tcl[j]=img:getPos(i,j);
        end;
    end;


    --[[
    算点数函数
    参数x,y 坐标
    参数tab 所属连通线;
    --]]  
    local   function  seed(x,y,tab)

        ---出界了则返回
        if(x<0 or y<0 or x>w or y>h) then
            return;
        end;

        ---点的颜色为白色时,返回,不处理。
        if(tcl[x][y]==16777215)  then
            return;
        end;

        ---值为1,则计数加1,返回
        if ( tchked[x][y]) then
            return ;
        else
            table.insert(tab,{x=x,y=y} );--添加到连通线里
            tchked[x][y]=true;---当值为0时,把值置为1。
            seed(x+1,y-1,tab);
            seed(x,y-1,tab);
            seed(x-1,y-1,tab);
            seed(x-1,y,tab);
            seed(x+1,y,tab);
            seed(x-1,y+1,tab);
            seed(x,y+1,tab);
            return seed(x+1,y+1,tab); --这里可以用一个尾调用(参考教程中的函数部份),加快递归的速度。
        end;
    end;


    ---------------------------

    ----遍历图像中的所有点
    for i=0,w,1   do
        for j=0,h,1  do
            ---如果是黑色的点,而且没有被计过数,则调用seed函数。
            if(tcl[j]==0 and (not tchked[j])) then         
                local tab = {}
                seed(i,j,tab);
                table.insert(tlines,tab); --添加一条连通线

            end;
        end;
    end;

    --现在tlines 里记录了的有的连通线,我们现在需要根据连通线的长度排序   
    sproc =  function(l,l2)   
        return table.maxn(l) > table.maxn(l2);--长的连通线排到前面  
    end;
    table.sort(tlines,sproc)

    --把图像全部画成白色的点      
    for i=0,w,1  do
        for j=0,h,1  do
            img:setPos( i , j, 16777215);
        end;
    end;

    --然后把最长的一条连通线画上去
    for i,point in  ipairs(tlines[1])  do
        img:setPos( point.x, point.y , 0);   
    end;


    --如果需要去掉周围的空白
    if(crop)then
        local n = table.maxn(tlines[1])

        --排序最长连通线中的所有坐标点
        sproc =  function(pt,pt2)   
            return  (pt.x <pt2.x );--*左的排前面
        end;
        table.sort(tlines[1],sproc);
        local x,x2 = tlines[1][1].x, tlines[1][n].x;

        --排序最长连通线中的所有坐标点
        sproc =  function(pt,pt2)   
            return (pt.y <pt2.y );--*上的排前面
        end;
        table.sort(tlines[1],sproc);
        local y,y2 = tlines[1][1].y, tlines[1][n].y;

        img:Crop( x,y,x2+1,y2)
    end;

end;



联系我时,请说是在 挂海论坛 上看到的,谢谢!



上一篇:C语言 — 简单验证码识别程序
下一篇:通过调试寄存器不修改代码实现bt功能
免责声明:
1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关。一切关于该内容及资源商业行为与www.52ghai.com无关。

2、本站提供的一切资源内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。

3、本站信息来自第三方用户,非本站自制,版权归原作者享有,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

4、如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵犯你版权的,请邮件与我们联系删除(邮箱:xhzlw@foxmail.com),本站将立即改正。

3正式会员
112/300

112

积分

50

主题

3

听众
已帮网友解决0 个问题
好评
0
贡献
62
海币
1667
交易币
0
沙发
发表于 2015-3-10 12:45:07 | 只看该作者
垃圾内容,路过为证。

8

积分

3

主题

5

听众
已帮网友解决0 个问题
好评
0
贡献
5
海币
65
交易币
0
板凳
发表于 2015-7-20 18:20:32 | 只看该作者
Hacker 发表于 2015-3-10 12:45
垃圾内容,路过为证。

这个垃圾?怎么说? 我觉得还不错吧
6高级会员
1050/1100

1050

积分

346

主题

9

听众
已帮网友解决0 个问题
好评
0
贡献
704
海币
3347
交易币
0
地板
发表于 2015-7-31 01:44:24 | 只看该作者
对楼猪只能说,你太伟大了!!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

免责声明|Archiver|手机版|小黑屋|挂海论坛

GMT+8, 2025-4-5 02:00 , Processed in 0.091762 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.2

本站资源来自互联网用户收集发布,如有侵权请邮件与我们联系处理。xhzlw@foxmail.com

快速回复 返回顶部 返回列表