<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          太贊了!用 JS 實(shí)現(xiàn)了識別網(wǎng)頁驗(yàn)證碼的功能!

          共 17482字,需瀏覽 35分鐘

           ·

          2021-04-17 07:05

          點(diǎn)擊上方藍(lán)字關(guān)注前端瓶子君,從此前端進(jìn)階不再難

          文章轉(zhuǎn)載自:LeoNaN,文末有原文鏈接。

          很高興大家喜歡!Github:leonof/imgRecJs[1],剛剛上傳,代碼還需要完善~因?yàn)橛胁簧偻瑢W(xué)表示訓(xùn)練和識別有疑問,我做了個小接口放在最后,可以方便大家先把流程走通。

          后續(xù)會更新:將 js 代碼等打包成 chrome 擴(kuò)展程序,這樣就可以讓瀏覽器自動識別,完全傻瓜式使用啦~!(更新啦:利用 chrome 擴(kuò)展,讓瀏覽器執(zhí)行我們的腳本[2]

          其實(shí)整篇文章難度不高,網(wǎng)上也有很多 java、c 等的代碼。只是當(dāng)時我寫代碼的時候,沒有找到純 js 可以用的代碼和庫,不能打包成 chrome 擴(kuò)展,用起來還是不太方便的。所以在驗(yàn)證了思路的可行性后,我就大致寫下來,給他人以方便吧。

          目前有多種驗(yàn)證碼識別思路,限于能力有限,我只好采用了最簡單的機(jī)器學(xué)習(xí)。目標(biāo)驗(yàn)證碼也比較簡單,如:

          (含字母也一樣)

          。識別控制速度在 0.1 秒以內(nèi)的話,正確率在 99.99999%(因?yàn)橐恢笔亲R別正確哈哈哈)。

          在動手之前,先梳理一下大致思路,方便比較獨(dú)立的同學(xué)自己嘗試完成代碼:

          1、先分析網(wǎng)頁 DOM 結(jié)構(gòu),載入驗(yàn)證碼圖片。

          2、將圖片畫到 canvas 上,拿到圖片的像素?cái)?shù)據(jù)。

          3、先后對圖片進(jìn)行二值化、腐蝕膨脹、切割、旋轉(zhuǎn)、縮放處理。

          4、記錄處理后的單個數(shù)字的二值化數(shù)據(jù),并人工錄入真實(shí)數(shù)字。

          5、重復(fù)訓(xùn)練。

          6、識別時,用處理后的圖像與庫中數(shù)據(jù)對比,取得最相近的數(shù)據(jù),得到真實(shí)數(shù)字。

          (以下優(yōu)化)

          7、數(shù)據(jù)量大時,可以取前幾個相似數(shù)據(jù),并按權(quán)重從中選出最可能的數(shù)字,以提高準(zhǔn)確度。

          8、也可查找到相似度足夠高時停止搜索,取其作為最后識別結(jié)果,以提升效率。

          大神們可以直接去寫了,我這低級簡單的代碼會遭你們嘲笑的。。。比較急于求成的同學(xué)也可以不用看了,回頭直接拿 demo 去修改吧!

          ====================================================================

          好吧既然你看到這里了,我就盡量說的清楚明白一點(diǎn)。

          在動手之前,我簡單模擬一下需要輸入驗(yàn)證碼的網(wǎng)站,效果如下:

          好吧,是真的簡單…點(diǎn)擊圖片可以更換驗(yàn)證碼,輸入框用來輸入,按鈕模擬提交,如下:

          我們就假裝他作為我們要自動識別的目標(biāo)。

          一、分析網(wǎng)頁 DOM 結(jié)構(gòu),載入驗(yàn)證碼圖片。

          我們可以看到,驗(yàn)證碼的 url 是:img/0.jpg。我這里的 url 會變化,是為了模擬更換驗(yàn)證碼的過程。但實(shí)際上,由于驗(yàn)證碼絕大多數(shù)為后臺生成的,所以地址是固定的。那么我們很容易就可以拿到圖片數(shù)據(jù):new 一個 Image,賦值 url 即可(直接 get 到 img 元素也行)。參考代碼:

          var img = document.getElementById("img");

          二、將圖片畫到 canvas 上,拿到圖片的像素?cái)?shù)據(jù)。

          要將圖片畫到 canvas 上,首先要創(chuàng)建一個 canvas 并初始化。參考代碼:

          var canvas1 = document.createElement("canvas");
          document.getElementsByTagName("body")[0].appendChild(canvas1);
          canvas1.style.backgroundColor = "cornsilk";
          var ctx1 = canvas1.getContext("2d");

          隨后,將圖片繪制上去。參考代碼:

          ctx1.drawImage(img,0,0,img.width,img.height);

          然后我們就可以利用 canvas,拿到圖片的像素?cái)?shù)據(jù)。參考代碼:

          var imgData = ctx1.getImageData(0,0,WIDTH,HEIGHT);

          三、先后對圖片進(jìn)行二值化、腐蝕膨脹、切割、旋轉(zhuǎn)、縮放處理。

          這部分是圖像識別的重點(diǎn),直接影響到識別準(zhǔn)確率和速度。復(fù)雜的驗(yàn)證碼還應(yīng)加上去躁等處理過程。比如可以檢測貫穿的橫線并消除,或者將顏色高度統(tǒng)一的背景去掉等等。我們的圖片幾乎沒有干擾,只有簡單的旋轉(zhuǎn)和縮放,故直接進(jìn)行二值化操作(二值化也能去掉少量的干擾)。

          1、二值化操作的思路是:計(jì)算圖片的平均灰度作為閾值,比閾值大的置為純黑,反之純白。參考代碼:

          function toHex(fromImgData){//二值化圖像
              var fromPixelData = fromImgData.data;
              var greyAve = 0;
              for(var j=0;j<WIDTH*HEIGHT;j++){
                  var r = fromPixelData[4*j];
                  var g = fromPixelData[4*j+1];
                  var b = fromPixelData[4*j+2];
                  greyAve += r*0.3 + g*0.59 + b*0.11;
              }
              greyAve /= WIDTH*HEIGHT;//計(jì)算平均灰度值。
              for(j=0;j<WIDTH*HEIGHT;j++){
                  r = fromPixelData[4*j];
                  g = fromPixelData[4*j+1];
                  b = fromPixelData[4*j+2];
                  var grey = r*0.333 + g*0.333 + b*0.333;//取平均值。
                  grey = grey>greyAve?255:0;
                  fromPixelData[4*j] = grey;
                  fromPixelData[4*j+1] = grey;
                  fromPixelData[4*j+2] = grey;
              }
              return fromImgData;
          }//二值化圖像

          二值化后,效果如圖:

          可以發(fā)現(xiàn),簡單的背景色是可以去掉的。

          二值化處理之后,就可以將圖片轉(zhuǎn)換成數(shù)組(存 0 或 1)來保存了。參考代碼如下:

          function toXY(fromImgData){
              var result = new Array(HEIGHT);
              var fromPixelData = fromImgData.data;
              for(var j=0;j<HEIGHT;j++){
                  result[j] = new Array(WIDTH);
                  for(var k=0;k<WIDTH;k++){
                      var r = fromPixelData[4*(j*WIDTH+k)];
                      var g = fromPixelData[4*(j*WIDTH+k)+1];
                      var b = fromPixelData[4*(j*WIDTH+k)+2];

                      result[j][k] = (r+g+b)>500?0:1;//賦值0、1給內(nèi)部數(shù)組
                  }
              }
              return result;
          }//圖像轉(zhuǎn)數(shù)組

          2、接下來是腐蝕、膨脹。腐蝕的基本思路在于,將所有白色周圍的像素都置成白色,以此來消除游離的個別黑色像素點(diǎn)噪聲。膨脹正好相反,將黑色周圍置成黑色,消除數(shù)字內(nèi)部的個別白色。同時,腐蝕、膨脹的操作可以讓圖片更加平滑。參考代碼:

          function corrode(fromArray){
              for(var j=1;j<fromArray.length-1;j++){
                  for(var k=1;k<fromArray[j].length-1;k++){
                      if(fromArray[j][k]==1&&fromArray[j-1][k]+fromArray[j+1][k]+fromArray[j][k-1]+fromArray[j][k+1]==0){
                          fromArray[j][k] = 0;
                      }
                  }
              }
              return fromArray;
          }//腐蝕(簡單)

          function expand(fromArray){
              for(var j=1;j<fromArray.length-1;j++){
                  for(var k=1;k<fromArray[j].length-1;k++){
                      if(fromArray[j][k]==0&&fromArray[j-1][k]+fromArray[j+1][k]+fromArray[j][k-1]+fromArray[j][k+1]==4){
                          fromArray[j][k] = 1;
                      }
                  }
              }
              return fromArray;
          }//膨脹(簡單)

          由于我們的圖片背景干擾不是很強(qiáng)烈,所以基本看不出差別。不過對于計(jì)算機(jī)來說,還是有不同的喲~尤其是背景復(fù)雜的圖片,這一步很好用。

          3、切割。

          由于我們的圖片內(nèi)各數(shù)字沒有粘連,所以切割時只需要從上至下,從左至右掃描圖片,發(fā)現(xiàn)圖片某一豎行均為白色,就切一刀。有粘連的驗(yàn)證碼比較困難,暫時不討論了。參考代碼:

          function split(fromArray,count){
              var numNow = 0;
              var status = false;
              var w = fromArray[0].length;
              for(var k=0;k<w;k++) {//遍歷圖像
                  var sumUp = 0;
                  for (var j=0;j<fromArray.length;j++) //檢測整列是否有圖像
                      sumUp += fromArray[j][k];
                  if(sumUp == 0){//切割
                      for (j=0;j<fromArray.length-1;j++)
                          fromArray[j].remove(k);
                      w --;
                      k --;
                      status = false;
                      continue;
                  }
                  else{//切換狀態(tài)
                      if(!status)
                          numNow ++;
                      status = true;
                  }
                  if(numNow!=count){//不是想要的數(shù)字
                      for (j=0;j<fromArray.length-1;j++)
                          fromArray[j].remove(k);
                      w --;
                      k --;
                  }
              }
              return fromArray;
          }//切割,獲取特定數(shù)字

          切割后,左右的空白因?yàn)槎急磺辛耍蜎]有了。但是上下仍然存在空白,所以進(jìn)行處理。這里比較簡單,就不放代碼了,思路和切割類似,但簡單很多。

          4、旋轉(zhuǎn)、縮放。

          其實(shí)旋轉(zhuǎn)不是必要的。沒有旋轉(zhuǎn)的步驟,可以用更多的數(shù)據(jù)量訓(xùn)練來彌補(bǔ)。同理,縮放也不是必須的。先大致講一下思路:旋轉(zhuǎn)和縮放都再次利用了 canvas,將圖片畫上去之后,利用 canvas 的方法操作圖片旋轉(zhuǎn)或縮放,之后再把數(shù)據(jù)拿下來,就像我們最開始讀圖片時做的一樣。旋轉(zhuǎn)時,取順時針逆時針各 90 度,取左右寬度最窄的角度,當(dāng)作數(shù)字站立的旋轉(zhuǎn)角度。縮放時,直接按預(yù)設(shè)長寬畫圖即可。這里我就只寫了縮放。處理后再轉(zhuǎn)換回?cái)?shù)組形式。參考代碼:

          function zoomToFit(fromArray){
              var imgD = fromXY(fromArray);
              var w = lastWidth;
              var h = lastHeight;
              var tempc1 = document.createElement("canvas");
              var tempc2 = document.createElement("canvas");
              tempc1.width = fromArray[0].length;
              tempc1.height = fromArray.length;
              tempc2.width = w;
              tempc2.height = h;
              var tempt1 = tempc1.getContext("2d");
              var tempt2 = tempc2.getContext("2d");
              tempt1.putImageData(imgD,0,0,0,0,tempc1.width,tempc1.height);
              tempt2.drawImage(tempc1,0,0,w,h);
              var returnImageD = tempt2.getImageData(0,0,WIDTH,HEIGHT);
              fromArray = toXY(returnImageD);
              fromArray.length = h;
              for(var i=0;i<h;i++)
                  fromArray[i].length = w;
              return fromArray;
          }//尺寸歸一化

          處理后效果如圖:

          四、記錄處理后的單個數(shù)字的二值化數(shù)據(jù),并人工錄入真實(shí)數(shù)字。

          到這里,圖像處理就搞定了,后面的工作就比較簡單了。我們把上一步得到的數(shù)組和真實(shí)的數(shù)字一起保存起來。這個過程可以有很多方法。我當(dāng)時采取了大家一起錄入的方式,所以搭建了 PHP+MySQL 的服務(wù)器,用數(shù)據(jù)庫存儲。這塊就不詳述了,大家各顯神威。

          五、重復(fù)訓(xùn)練

          為了方便訓(xùn)練,我直接在頁面里增加了手動輸入的地方,提交后刷新驗(yàn)證碼,繼續(xù)提交。提交 20 個驗(yàn)證碼(20*4=80 個數(shù)字)后,便經(jīng)常可以正確識別出 4 位驗(yàn)證碼,在單個數(shù)字的數(shù)據(jù)量在 300 左右時(大約需要 300/4=75 個驗(yàn)證碼),識別效率已經(jīng)在 95%以上。在 500 左右時已經(jīng)基本見不到錯誤識別的情況了,這時候已經(jīng)可以寫代碼實(shí)現(xiàn)自我訓(xùn)練了。此時識別一次大約需要 0.06 秒。

          六、識別時,用處理后的圖像與庫中數(shù)據(jù)對比,取得最相近的數(shù)據(jù),得到真實(shí)數(shù)字。

          這塊也比較簡單。訓(xùn)練完成后,我將數(shù)據(jù)庫數(shù)據(jù)導(dǎo)出,保存成了一個大的數(shù)組,直接用 js 就可以讀了。識別時遍歷所有的數(shù)據(jù),按像素點(diǎn)逐一比較。由于尺寸做了歸一化,所以直接數(shù)有多少像素匹配即可。匹配數(shù)量最多的即為識別出的結(jié)果。我只找到了最開始寫的 PHP 代碼,先放一下吧,有點(diǎn)懶得再寫 js 了…:

          function check($str)
          {
              $str = str_split($str,1);
              $length = count($str);
              $tempNum = 0;
              $tempSimmiar = 0;
              $query = "SELECT * FROM numkeys";
              $sth = execSql($query);
              while ($RES = $sth->fetch()) {
                  $thisSimmiar = 0;
                  $thisFeature = str_split($RES["feature"],1);
                  $thisNum = $RES["resultnum"];
                  for($i=0;$i<$length;$i++){
                      if($thisFeature[$i]==$str[$i]){
                          $thisSimmiar ++;
                      }
                  }
                  if($thisSimmiar>$tempSimmiar){
                      $tempSimmiar = $thisSimmiar;
                      $tempNum = $thisNum;
                  }
              }
              return $tempNum;
          }

          七、優(yōu)化部分

          這塊就大家自己看著來吧,因?yàn)槲业膱D片不是很復(fù)雜,數(shù)據(jù)量也不是很大(千條級別),所以也沒啥優(yōu)化的必要,每次識別大約 0.1 秒吧。所以我只是沒事干,做了之前大綱里寫了那兩個優(yōu)化。其實(shí)我感覺主要的優(yōu)化方向還是圖像處理那塊,盡量減少干擾,才能提高效率,也能檢測更復(fù)雜的驗(yàn)證碼。

          PS:訓(xùn)練和識別的接口:

          訓(xùn)練:POST 發(fā)送 username(用戶名)、password(密碼)、n1(第一個數(shù)組)、n2、n3

          、n4、num(真實(shí)四位字符)至 http://www.leonszone.cn/test/yanzhengma/train.php[3]。參考代碼:

          function sendData({
              var str = prompt("請輸入驗(yàn)證碼:""");
              if(!str)
                  return false;
              postData = {//整合數(shù)據(jù)包
                  username'pdgzfx',
                  password'pdgzfx',
                  nums: str,
                  n1: numsArray[0],
                  n2: numsArray[1],
                  n3: numsArray[2],
                  n4: numsArray[3]
              };
              $.ajax({
                  url'http://www.leonszone.cn/test/yanzhengma/train.php',
                  type'POST',
                  data: postData,
                  successfunction (data{
                      console.log(data);
                      setTimeout(function ({
                          location.reload();
                      },1000);
                  }
              });
          }

          識別:POST 發(fā)送 username(用戶名)、password(密碼)、n1(第一個數(shù)組)、n2、n3、n4 至 http://www.leonszone.cn/test/yanzhengma/check.php[4]。參考代碼:

          function getData({
              postData = {//整合數(shù)據(jù)包
                  username'pdgzfx',
                  password'pdgzfx',
                  nums'help!!!',
                  n1: numsArray[0],
                  n2: numsArray[1],
                  n3: numsArray[2],
                  n4: numsArray[3]
              };
              $.ajax({
                  url'http://www.leonszone.cn/test/yanzhengma/check.php',
                  type'POST',
                  data: postData,
                  successfunction (data{
                      $("#Vercode").val(data);
                      console.log(data);
                  }
              });
          }

          注冊用戶名密碼(防止大家的庫混淆):POST 或 GET 發(fā)送 username(用戶名)、password(密碼)至 http://www.leonszone.cn/test/yanzhengma/regist.php[5]。參考代碼:

          function getData({
              postData = {//整合數(shù)據(jù)包
                  username'pdgzfx',
                  password'pdgzfx',
                  };
              $.ajax({
                  url'http://www.leonszone.cn/test/yanzhengma/regist.php',
                  type'POST',
                  data: postData,
                  successfunction (data{
                      console.log(data); } }); }

          或直接瀏覽器訪問:http://www.leonszone.cn/test/yanzhengma/regist.php\?username= 你的用戶名 \&amp;amp;password=[6]你的密碼

          好累,先休息下,看看有沒有人看吧…(我感覺應(yīng)該沒多少人= = 還真的有人!!!)

          參考資料

          [1]

          leonof/imgRecJs: https://link.zhihu.com/?target=https%3A//github.com/leonof/imgRecJs

          [2]

          利用 chrome 擴(kuò)展,讓瀏覽器執(zhí)行我們的腳本: https://zhuanlan.zhihu.com/p/28641172

          [3]

          http://www.leonszone.cn/test/yanzhengma/train.php: https://link.zhihu.com/?target=http%3A//www.leonszone.cn/test/yanzhengma/train.php

          [4]

          http://www.leonszone.cn/test/yanzhengma/check.php: https://link.zhihu.com/?target=http%3A//www.leonszone.cn/test/yanzhengma/check.php

          [5]

          http://www.leonszone.cn/test/yanzhengma/regist.php: https://link.zhihu.com/?target=http%3A//www.leonszone.cn/test/yanzhengma/regist.php

          [6]

          http://www.leonszone.cn/test/yanzhengma/regist.php?username= 你的用戶名 &amp;amp;password=: https://link.zhihu.com/?target=http%3A//www.leonszone.cn/test/yanzhengma/regist.php%3Fusername%3Dleon%26password%3D1

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端算法源碼編程群,每日一刷(工作日),每題瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 74
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品乱码久久久久久蜜桃欧美 | 99在线这里只有精品 | 成人香蕉网 | 五月丁香五月婷婷 | 99热综合在线 |