成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

太囂張了!他竟用Python繞過了“驗證碼”

開發 后端 開發工具
很多網站登錄都需要輸入驗證碼,如果要實現自動登錄就不可避免的要識別驗證碼。本文以一個真實網站的驗證碼為例,實現了基于 KNN 的驗證碼識別。

 [[250250]]

 

 

準備工作

 

 


這里我們使用 OpenCV 做圖像處理,所以需要安裝下面兩個庫:

  1. pip3 install opencv-python  
  2. pip3 install numpy  

 

識別原理

 

 

我們采取一種有監督式學習的方法來識別驗證碼,包含以下幾個步驟:

  • 圖片處理:對圖片進行降噪、二值化處理。

  • 切割圖片:將圖片切割成單個字符并保存。

  • 人工標注:對切割的字符圖片進行人工標注,作為訓練集。

  • 訓練數據:用 KNN 算法訓練數據。

  • 檢測結果:用上一步的訓練結果識別新的驗證碼。

 

下面我們來逐一介紹每一步的過程,并給出具體的代碼實現。

 

 

圖片處理

 


先來看一下我們要識別的驗證碼是長什么樣的:

上圖可以看到,字符做了一些扭曲變換。仔細觀察,還可以發現圖片中間的部分添加了一些顆粒化的噪聲。

 

我們先讀入圖片,并將圖片轉成灰度圖,代碼如下:

  1. import cv2 
  2.  
  3. im = cv2.imread(filepath) 
  4. im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 

經過上面的處理,我們的彩色圖片變成了下面這樣:

將圖片做二值化處理,代碼如下:

  1. ret, im_inv = cv2.threshold(im_gray,127,255,cv2.THRESH_BINARY_INV) 

127 是我們設定的閾值,像素值大于 127 被置成了 0,小于 127 的被置成了 255。處理后的圖片變成了這樣:

接下來,我們應用高斯模糊對圖片進行降噪。高斯模糊的本質是用高斯核和圖像做卷積,代碼如下:

  1. kernel = 1/16*np.array([[1,2,1], [2,4,2], [1,2,1]]) 
  2. im_blur = cv2.filter2D(im_inv,-1,kernel) 

降噪后的圖片如下:

上圖可以看到一些顆粒化的噪聲被平滑掉了。降噪后,我們對圖片再做一輪二值化處理:

  1. ret, im_res = cv2.threshold(im_blur,127,255,cv2.THRESH_BINARY) 

現在圖片變成了這樣:

好了,接下來,我們要開始切割圖片了。

 

 

切割圖片

 


這一步是所有步驟里最復雜的一步。我們的目標是把最開始的圖片切割成單個字符,并把每個字符保存成如下的灰度圖:

首先我們用 OpenCV 的 findContours 來提取輪廓:

  1. im2, contours, hierarchy = cv2.findContours(im_res, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 

我們把提取的輪廓用矩形框起來,畫出來是這樣的:

可以看到,每個字符都被檢測出來了。但這只是理想情況,很多時候,相鄰字符有粘連的會被識別成同一個字符,比如像下面的情況:

要處理這種情況,我們就要對上面的圖片做進一步的分割。字符粘連會有下面幾種情況,我們逐一來看下該怎么處理。

 

①4 個字符被識別成 3 個字符

這種情況,對粘連的字符輪廓,從中間進行分割,代碼如下:

  1. result = [] 
  2. for contour in contours: 
  3.     x, y, w, h = cv2.boundingRect(contour) 
  4.     if w == w_max: # w_max是所有contonur的寬度中最寬的值 
  5.         box_left = np.int0([[x,y], [x+w/2,y], [x+w/2,y+h], [x,y+h]]) 
  6.         box_right = np.int0([[x+w/2,y], [x+w,y], [x+w,y+h], [x+w/2,y+h]]) 
  7.         result.append(box_left) 
  8.         result.append(box_right) 
  9.     else
  10.         box = np.int0([[x,y], [x+w,y], [x+w,y+h], [x,y+h]]) 
  11.         result.append(box) 

分割后,圖片變成了這樣:

②4 個字符被識別成 2 個字符

 

4 個字符被識別成 2 個字符有下面兩種情況:

對第一種情況,對于左右兩個輪廓,從中間分割即可。對第二種情況,將包含了 3 個字符的輪廓在水平方向上三等分。

 

具體代碼如下:

  1. result = [] 
  2. for contour in contours: 
  3.     x, y, w, h = cv2.boundingRect(contour) 
  4.     if w == w_max and w_max >= w_min * 2: 
  5.         # 如果兩個輪廓一個是另一個的寬度的2倍以上,我們認為這個輪廓就是包含3個字符的輪廓 
  6.         box_left = np.int0([[x,y], [x+w/3,y], [x+w/3,y+h], [x,y+h]]) 
  7.         box_mid = np.int0([[x+w/3,y], [x+w*2/3,y], [x+w*2/3,y+h], [x+w/3,y+h]]) 
  8.         box_right = np.int0([[x+w*2/3,y], [x+w,y], [x+w,y+h], [x+w*2/3,y+h]]) 
  9.         result.append(box_left) 
  10.         result.append(box_mid) 
  11.         result.append(box_right) 
  12.     elif w_max < w_min * 2: 
  13.         # 如果兩個輪廓,較寬的寬度小于較窄的2倍,我們認為這是兩個包含2個字符的輪廓 
  14.         box_left = np.int0([[x,y], [x+w/2,y], [x+w/2,y+h], [x,y+h]]) 
  15.         box_right = np.int0([[x+w/2,y], [x+w,y], [x+w,y+h], [x+w/2,y+h]]) 
  16.         result.append(box_left) 
  17.         result.append(box_right) 
  18.     else
  19.         box = np.int0([[x,y], [x+w,y], [x+w,y+h], [x,y+h]]) 
  20.         result.append(box) 

分割后的圖片如下:

③4 個字符被識別成 1 個字符

這種情況對輪廓在水平方向上做四等分即可,代碼如下:

  1. result = [] 
  2. contour = contours[0] 
  3. x, y, w, h = cv2.boundingRect(contour) 
  4. box0 = np.int0([[x,y], [x+w/4,y], [x+w/4,y+h], [x,y+h]]) 
  5. box1 = np.int0([[x+w/4,y], [x+w*2/4,y], [x+w*2/4,y+h], [x+w/4,y+h]]) 
  6. box2 = np.int0([[x+w*2/4,y], [x+w*3/4,y], [x+w*3/4,y+h], [x+w*2/4,y+h]]) 
  7. box3 = np.int0([[x+w*3/4,y], [x+w,y], [x+w,y+h], [x+w*3/4,y+h]]) 
  8. result.extend([box0, box1, box2, box3]) 

分割后的圖片如下:

對圖片分割完成后,我們將分割后的單個字符的圖片存成不同的圖片文件,以便下一步做人工標注。

 

存取字符圖片的代碼如下:

  1. for box in result: 
  2.     cv2.drawContours(im, [box], 0, (0,0,255),2) 
  3.     roi = im_res[box[0][1]:box[3][1], box[0][0]:box[1][0]] 
  4.     roistd = cv2.resize(roi, (30, 30)) # 將字符圖片統一調整為30x30的圖片大小 
  5.     timestamp = int(time.time() * 1e6) # 為防止文件重名,使用時間戳命名文件名 
  6.     filename = "{}.jpg".format(timestamp
  7.     filepath = os.path.join("char", filename) 
  8.     cv2.imwrite(filepath, roistd) 

字符圖片保存在名為 char 的目錄下面,這個目錄里的文件大致是長這樣的(文件名用時間戳命名,確保不會重名):

接下來,我們開始標注數據。

 

 

人工標注

 


這一步是所有步驟里最耗費體力的一步了。為節省時間,我們在程序里依次打開 char 目錄中的每張圖片,鍵盤輸入字符名,程序讀取鍵盤輸入并將字符名保存在文件名里。

 

代碼如下:

  1. files = os.listdir("char"
  2. for filename in files: 
  3.     filename_ts = filename.split(".")[0] 
  4.     patt = "label/{}_*".format(filename_ts) 
  5.     saved_num = len(glob.glob(patt)) 
  6.     if saved_num == 1: 
  7.         print("{} done".format(patt)) 
  8.         continue 
  9.     filepath = os.path.join("char", filename) 
  10.     im = cv2.imread(filepath) 
  11.     cv2.imshow("image", im) 
  12.     key = cv2.waitKey(0) 
  13.     if key == 27: 
  14.         sys.exit() 
  15.     if key == 13: 
  16.         continue 
  17.     char = chr(key
  18.     filename_ts = filename.split(".")[0] 
  19.     outfile = "{}_{}.jpg".format(filename_ts, char
  20.     outpath = os.path.join("label", outfile) 
  21.     cv2.imwrite(outpath, im) 

這里一共標注了大概 800 張字符圖片,標注的結果存在名為 label 的目錄下,目錄下的文件是這樣的(文件名由原文件名+標注名組成):

接下來,我們開始訓練數據。

 

 

訓練數據

 


首先,我們從 label 目錄中加載已標注的數據:

  1. filenames = os.listdir("label"
  2. samples = np.empty((0, 900)) 
  3. labels = [] 
  4. for filename in filenames: 
  5.     filepath = os.path.join("label", filename) 
  6.     label = filename.split(".")[0].split("_")[-1] 
  7.     labels.append(label) 
  8.     im = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE) 
  9.     sample = im.reshape((1, 900)).astype(np.float32) 
  10.     samples = np.append(samples, sample, 0) 
  11. samples = samples.astype(np.float32) 
  12. unique_labels = list(set(labels)) 
  13. unique_ids = list(range(len(unique_labels))) 
  14. label_id_map = dict(zip(unique_labels, unique_ids)) 
  15. id_label_map = dict(zip(unique_ids, unique_labels)) 
  16. label_ids = list(map(lambda x: label_id_map[x], labels)) 
  17. label_ids = np.array(label_ids).reshape((-1, 1)).astype(np.float32) 

接下來,訓練我們的模型:

  1. model = cv2.ml.KNearest_create() 
  2. model.train(samples, cv2.ml.ROW_SAMPLE, label_ids) 

訓練完,我們用這個模型來識別一下新的驗證碼。

 

 

檢測結果

 


下面是我們要識別的驗證碼:

對于每一個要識別的驗證碼,我們都需要對圖片做降噪、二值化、分割的處理(代碼和上面的一樣,這里不再重復)。

 

假設處理后的圖片存在變量 im_res 中,分割后的字符的輪廓信息存在變量 boxes 中,識別驗證碼的代碼如下:

  1. for box in boxes: 
  2.     roi = im_res[box[0][1]:box[3][1], box[0][0]:box[1][0]] 
  3.     roistd = cv2.resize(roi, (30, 30)) 
  4.     sample = roistd.reshape((1, 900)).astype(np.float32) 
  5.     ret, results, neighbours, distances = model.findNearest(sample, k = 3) 
  6.     label_id = int(results[0,0]) 
  7.     label = id_label_map[label_id] 
  8.     print(label) 

運行上面的代碼,可以看到程序輸出:

圖片中的驗證碼被成功地識別出來。我們測試了下識別的準確率,取 100 張驗證碼圖片(存在 test 目錄下)進行識別,識別的準確率約為 82%。

 

看到有人說用神經網絡識別驗證碼,準確率可以達到 90% 以上,下次有機會可以嘗試一下。

 

完整代碼已上傳 GitHub,所有訓練數據、測試數據、已標注圖片都已上傳百度網盤,后臺回復“驗證碼”可獲取地址。

 

責任編輯:武曉燕 來源: Python與數據分析
相關推薦

2022-05-11 07:41:31

Python驗證碼

2017-05-16 14:18:08

2017-05-18 09:36:11

大數據爬蟲驗證碼

2013-06-19 10:19:59

2009-02-09 14:17:36

2009-08-11 14:05:28

JSP驗證碼

2020-11-16 07:28:53

驗證碼

2015-03-23 17:58:04

驗證碼倒計時并行

2022-02-11 07:10:15

驗證碼

2015-09-21 15:31:05

php實現驗證碼

2017-12-21 07:38:19

2021-01-19 10:29:34

短信驗證碼密碼

2021-08-02 12:29:15

Python爬蟲網站

2024-01-29 08:32:10

Python驗證碼識別

2011-11-02 12:43:33

2015-03-17 09:28:04

2011-11-02 16:46:41

2019-06-18 07:12:25

驗證碼漏洞加密

2009-08-13 10:47:29

C#創建驗證碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品国产a级 | 亚洲福利一区二区 | 91麻豆精品国产91久久久久久久久 | 国产精品一区二区久久久久 | 国产亚韩 | 亚洲日产精品 | 亚洲不卡av在线 | 中文字幕一区二区三区在线乱码 | 九九热在线免费观看 | 欧美视频免费 | h视频在线免费看 | 黄色网络在线观看 | 国产69精品久久久久777 | 国产精品99久久久久久动医院 | 欧美亚洲成人网 | 人成精品 | 亚洲不卡一 | 亚洲精品二区 | jav成人av免费播放 | 久久久成 | 精品中文字幕一区二区 | 欧美高清免费 | 成人亚洲精品久久久久软件 | 中文字幕av网址 | 国产精品欧美一区二区 | 日韩精品一二三区 | 精品视频999 | 亚洲国产自产 | 久久久久亚洲 | 中文字幕一区二区三区四区五区 | 精品二区 | 欧美一区日韩一区 | 国产在线精品一区二区 | 国产高清精品一区二区三区 | 欧洲亚洲视频 | 婷婷综合色 | 日本不卡一区二区三区在线观看 | 国产亚洲精品一区二区三区 | 久久久久黄 | 欧美一区二区三区的 | 久久亚洲精品视频 |