一、前言
本實(shí)驗(yàn)將通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)講解破解驗(yàn)證碼的原理,將學(xué)習(xí)和實(shí)踐以下知識(shí)點(diǎn):
Python基本知識(shí)
PIL模塊的使用
二、實(shí)例詳解
安裝 pillow(PIL)庫(kù):
1
2
3
4
5
6
7
8
|
$ sudo apt - get update $ sudo apt - get install python - dev $ sudo apt - get install libtiff5 - dev libjpeg8 - dev zlib1g - dev \ libfreetype6 - dev liblcms2 - dev libwebp - dev tcl8. 6 - dev tk8. 6 - dev python - tk $ sudo pip install pillow |
下載實(shí)驗(yàn)用的文件:
1
2
3
|
$ wget http: / / labfile.oss.aliyuncs.com / courses / 364 / python_captcha. zip $ unzip python_captcha. zip $ cd python_captcha |
這是我們實(shí)驗(yàn)使用的驗(yàn)證碼 captcha.gif
提取文本圖片
在工作目錄下新建 crack.py 文件,進(jìn)行編輯。
1
2
3
4
5
6
7
8
9
|
#-*- coding:utf8 -*- from PIL import Image im = Image. open ( "captcha.gif" ) #(將圖片轉(zhuǎn)換為8位像素模式) im = im.convert( "P" ) #打印顏色直方圖 print im.histogram() |
輸出:
1
|
[ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 2 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 2 , 1 , 0 , 0 , 0 , 2 , 0 , 0 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 2 , 0 , 0 , 0 , 1 , 2 , 0 , 1 , 0 , 0 , 1 , 0 , 2 , 0 , 0 , 1 , 0 , 0 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 3 , 1 , 3 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 3 , 2 , 132 , 1 , 1 , 0 , 0 , 0 , 1 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 15 , 0 , 1 , 0 , 1 , 0 , 0 , 8 , 1 , 0 , 0 , 0 , 0 , 1 , 6 , 0 , 2 , 0 , 0 , 0 , 0 , 18 , 1 , 1 , 1 , 1 , 1 , 2 , 365 , 115 , 0 , 1 , 0 , 0 , 0 , 135 , 186 , 0 , 0 , 1 , 0 , 0 , 0 , 116 , 3 , 0 , 0 , 0 , 0 , 0 , 21 , 1 , 1 , 0 , 0 , 0 , 2 , 10 , 2 , 0 , 0 , 0 , 0 , 2 , 10 , 0 , 0 , 0 , 0 , 1 , 0 , 625 ] |
顏色直方圖的每一位數(shù)字都代表了在圖片中含有對(duì)應(yīng)位的顏色的像素的數(shù)量。
每個(gè)像素點(diǎn)可表現(xiàn)256種顏色,你會(huì)發(fā)現(xiàn)白點(diǎn)是最多(白色序號(hào)255的位置,也就是最后一位,可以看到,有625個(gè)白色像素)。紅像素在序號(hào)200左右,我們可以通過(guò)排序,得到有用的顏色。
1
2
3
4
5
6
7
8
|
his = im.histogram() values = {} for i in range ( 256 ): values[i] = his[i] for j,k in sorted (values.items(),key = lambda x:x[ 1 ],reverse = True )[: 10 ]: print j,k |
輸出:
1
2
3
4
5
6
7
8
9
10
|
255 625 212 365 220 186 219 135 169 132 227 116 213 115 234 21 205 18 184 15 |
我們得到了圖片中最多的10種顏色,其中 220 與 227 才是我們需要的紅色和灰色,可以通過(guò)這一訊息構(gòu)造一種黑白二值圖片。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#-*- coding:utf8 -*- from PIL import Image im = Image. open ( "captcha.gif" ) im = im.convert( "P" ) im2 = Image.new( "P" ,im.size, 255 ) for x in range (im.size[ 1 ]): for y in range (im.size[ 0 ]): pix = im.getpixel((y,x)) if pix = = 220 or pix = = 227 : # these are the numbers to get im2.putpixel((y,x), 0 ) im2.show() |
得到的結(jié)果:
提取單個(gè)字符圖片
接下來(lái)的工作是要得到單個(gè)字符的像素集合,由于例子比較簡(jiǎn)單,我們對(duì)其進(jìn)行縱向切割:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
inletter = False foundletter = False start = 0 end = 0 letters = [] for y in range (im2.size[ 0 ]): for x in range (im2.size[ 1 ]): pix = im2.getpixel((y,x)) if pix ! = 255 : inletter = True if foundletter = = False and inletter = = True : foundletter = True start = y if foundletter = = True and inletter = = False : foundletter = False end = y letters.append((start,end)) inletter = False print letters |
輸出:
1
|
[( 6 , 14 ), ( 15 , 25 ), ( 27 , 35 ), ( 37 , 46 ), ( 48 , 56 ), ( 57 , 67 )] |
得到每個(gè)字符開始和結(jié)束的列序號(hào)。
1
2
3
4
5
6
7
8
9
10
|
import hashlib import time count = 0 for letter in letters: m = hashlib.md5() im3 = im2.crop(( letter[ 0 ] , 0 , letter[ 1 ],im2.size[ 1 ] )) m.update( "%s%s" % (time.time(),count)) im3.save( "./%s.gif" % (m.hexdigest())) count + = 1 |
(接上面的代碼)
對(duì)圖片進(jìn)行切割,得到每個(gè)字符所在的那部分圖片。
AI 與向量空間圖像識(shí)別
在這里我們使用向量空間搜索引擎來(lái)做字符識(shí)別,它具有很多優(yōu)點(diǎn):
- 不需要大量的訓(xùn)練迭代
- 不會(huì)訓(xùn)練過(guò)度
- 你可以隨時(shí)加入/移除錯(cuò)誤的數(shù)據(jù)查看效果
- 很容易理解和編寫成代碼
- 提供分級(jí)結(jié)果,你可以查看最接近的多個(gè)匹配
- 對(duì)于無(wú)法識(shí)別的東西只要加入到搜索引擎中,馬上就能識(shí)別了。
當(dāng)然它也有缺點(diǎn),例如分類的速度比神經(jīng)網(wǎng)絡(luò)慢很多,它不能找到自己的方法解決問(wèn)題等等。
向量空間搜索引擎名字聽上去很高大上其實(shí)原理很簡(jiǎn)單。拿文章里的例子來(lái)說(shuō):
你有 3 篇文檔,我們要怎么計(jì)算它們之間的相似度呢?2 篇文檔所使用的相同的單詞越多,那這兩篇文章就越相似!但是這單詞太多怎么辦,就由我們來(lái)選擇幾個(gè)關(guān)鍵單詞,選擇的單詞又被稱作特征,每一個(gè)特征就好比空間中的一個(gè)維度(x,y,z 等),一組特征就是一個(gè)矢量,每一個(gè)文檔我們都能得到這么一個(gè)矢量,只要計(jì)算矢量之間的夾角就能得到文章的相似度了。
用 Python 類實(shí)現(xiàn)向量空間:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import math class VectorCompare: #計(jì)算矢量大小 def magnitude( self ,concordance): total = 0 for word,count in concordance.iteritems(): total + = count * * 2 return math.sqrt(total) #計(jì)算矢量之間的 cos 值 def relation( self ,concordance1, concordance2): relevance = 0 topvalue = 0 for word, count in concordance1.iteritems(): if concordance2.has_key(word): topvalue + = count * concordance2[word] return topvalue / ( self .magnitude(concordance1) * self .magnitude(concordance2)) |
它會(huì)比較兩個(gè) python 字典類型并輸出它們的相似度(用 0~1 的數(shù)字表示)
將之前的內(nèi)容放在一起
還有取大量驗(yàn)證碼提取單個(gè)字符圖片作為訓(xùn)練集合的工作,但只要是有好好讀上文的同學(xué)就一定知道這些工作要怎么做,在這里就略去了。可以直接使用提供的訓(xùn)練集合來(lái)進(jìn)行下面的操作。
iconset目錄下放的是我們的訓(xùn)練集。
最后追加的內(nèi)容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#將圖片轉(zhuǎn)換為矢量 def buildvector(im): d1 = {} count = 0 for i in im.getdata(): d1[count] = i count + = 1 return d1 v = VectorCompare() iconset = [ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '0' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' ] #加載訓(xùn)練集 imageset = [] for letter in iconset: for img in os.listdir( './iconset/%s/' % (letter)): temp = [] if img ! = "Thumbs.db" and img ! = ".DS_Store" : temp.append(buildvector(Image. open ( "./iconset/%s/%s" % (letter,img)))) imageset.append({letter:temp}) count = 0 #對(duì)驗(yàn)證碼圖片進(jìn)行切割 for letter in letters: m = hashlib.md5() im3 = im2.crop(( letter[ 0 ] , 0 , letter[ 1 ],im2.size[ 1 ] )) guess = [] #將切割得到的驗(yàn)證碼小片段與每個(gè)訓(xùn)練片段進(jìn)行比較 for image in imageset: for x,y in image.iteritems(): if len (y) ! = 0 : guess.append( ( v.relation(y[ 0 ],buildvector(im3)),x) ) guess.sort(reverse = True ) print "",guess[ 0 ] count + = 1 |
得到結(jié)果
一切準(zhǔn)備就緒,運(yùn)行我們的代碼試試:
1
|
python crack.py |
輸出
1
2
3
4
5
6
|
( 0.96376811594202894 , '7' ) ( 0.96234028545977002 , 's' ) ( 0.9286884286888929 , '9' ) ( 0.98350370609844473 , 't' ) ( 0.96751165072506273 , '9' ) ( 0.96989711688772628 , 'j' ) |
是正解,干得漂亮。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
原文鏈接:https://zhuanlan.zhihu.com/p/24222942