1. 程式人生 > >實驗樓Python破解驗證碼

實驗樓Python破解驗證碼

  本人大二,因為Python結業考試專案,又想要學習機器學習方向,但是由於接觸時間不長,選擇了實驗樓的Python破解驗證碼這個專案作為我的專案,

我在原來的基礎上加了一些程式碼用於完善,並且對功能如何實現記錄在此,第一次接觸到影象識別的專案。

  這是專案需要的檔案連結:https://pan.baidu.com/s/1qoJ5qvU9idmH0v7dnFkMCw   提取碼:h3gv

  總體思想是將驗證碼變成黑白,然後切割成單字元,再與準備好的訓練集相互比對,將相似度最高的字元輸出。

  第一步,先對一個驗證碼進行處理,,①目標是將圖片儘量簡化成黑白,②然後切割出單字元,對此使用的是PIL的Image庫。

  ①匯入圖片,轉換成8位畫素的圖片

#載入圖片並且轉換成8位畫素
im = Image.open("./captcha.gif")
im.convert("P")

  我們需要知道驗證碼的顏色,拾色器工具是一種方法,但是我們通過資料說話,通過列印直方圖 print(im.histogram()) 可以返回如下列表

[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]

  列表每一個元素代表在圖片中含有對應位的顏色的畫素的數量。(白色255,黑色是0)

  接下來進行排序

his = im.histogram()
values = {}
#將顏色作為鍵,出現次數作為值,形成字典
for i in range(256):
    values[i] = his[i]
#對字典進行排序,排序根據字典的值(x[0]是字典的鍵),從大到小排序
for j,k in sorted(values.items(),key=lambda x:x[1],reverse = True)[:10]:
    print(j,k)

  即可得到以下

255 625
212 365
220 186
219 135
169 132
227 116
213 115
234 21
205 18
184 15

  220與227是我們所需要的紅色,於是我們可以建立一個相同大小的純白色的圖片,將符合的顏色變為黑色0

 (其實這裡也就表現了這個程式的第一個侷限性,顏色要人為判斷,並且每一個字元都要顏色統一)

# 構造一個純白的等大小的圖片im2
im2 = Image.new("P", im.size, 255)
#遍歷載入的圖片,對每個畫素點判斷是否符合要求
for x in range(im.size[1]):         #im.size[1]是垂直畫素數
    for y in range(im.size[0]):     #im.size[0]是水平畫素數
        pix = im.getpixel((y, x))       #獲取每一個畫素點的顏色紙
        if pix == 220 or pix == 227:    #判斷是否符合220或者227
            im2.putpixel((y, x), 0)     #符合則變成黑色

  之後用im2.show(),可以看到這個很符合我們的想法

  ②然後我們需要切割出單個字元,實驗樓裡面說:“由於例子比較簡單,我們對其進行縱向切割:”,恕我剛剛接觸時間不長,還不太能瞭解這句話後面的深度

  具體做法就是縱向從左到右“一刀刀往下切”

  一個變數判斷是否切到了黑色的畫素點,切到則轉換“刀”為切到字元的狀態並且記錄當前的水平位置,

如果沒有切到黑色畫素點,但是“刀”依舊是切到字元的狀態,則重置“刀”為未切到字元的狀態並且當前的記錄水平位置,

第一次記錄的位置到第二次記錄的位置一定有一個字元。

inletter = False  #判斷是否切割到了字元
foundletter = False     #未切到字元的狀態記錄
start = 0       #記錄開始的x值
end = 0          #記錄結束的x值
letters = []    #記錄切割到的字元
#縱向切割記錄資料
for x in range(im2.size[0]):    #遍歷水平的畫素點
    for y in range(im2.size[1]): #同一水平值下遍歷垂直的(用刀切)
        pix = im2.getpixel((x, y))  #獲取畫素點顏色
        if pix != 255:           #碰到黑色就位切到了
            inletter = True
    # 切到但是刀的狀態是沒有切到,則轉換刀的狀態為切到
    if foundletter == False and inletter == True:
        foundletter = True
        start = x

    #如果上面if沒有成立,則下面的if不會發生,所以letters一定會記錄到2個不同的值

    #沒有切到但是刀的狀態是切到了,則轉換刀的狀態為未切到
    if foundletter == True and inletter == False:
        foundletter = False
        end = x
        letters.append((start, end))
    #重置為未切到字元
    inletter = False

  列印letters,符合預期

[(6, 14), (15, 25), (27, 35), (37, 46), (48, 56), (57, 67)]

 然後將記錄到的資料,對圖片進行切割

#切割字元
for letter in letters:
    #引數一個四元組,四個元素依次是左上角的x和y值與右下角的x和y值
    im3 = im2.crop((letter[0], 0, letter[1], im.size[1]))

  然後可以遍歷儲存im3為.gif格式,可以得到6個圖片                    都是單獨的字元了,至此第一步完成

  接下來就是核心的第二步,怎麼把每一個字元輸出對應的數字?

  首先是實驗樓給出的論文網站http://ondoc.logand.com/d/2697/pdf,

  “也說了這個這個方法的優缺點:

  • 不需要大量的訓練迭代
  • 不會訓練過度
  • 你可以隨時加入/移除錯誤的資料檢視效果
  • 很容易理解和編寫成程式碼
  • 提供分級結果,你可以檢視最接近的多個匹配
  • 對於無法識別的東西只要加入到搜尋引擎中,馬上就能識別了。

  當然它也有缺點,例如分類的速度比神經網路慢很多,它不能找到自己的方法解決問題等等。”

  然後實驗樓只是簡單的介紹了一下原理,並未詳細說明,為此我通讀了整篇論文,來說說我的一點理解。

  有1篇講貓和狗和鼠的文章,但是我想知道這篇文章主要講的是哪個動物,為此我將“貓”,“狗”,“鼠”這幾個特徵性的單詞作為我的重點關注物件,並建立一個一個三維空間,x軸對應“貓”這個單詞出現的次數,同理y軸對應“狗”,z軸對應“鼠”。首先先用於第一篇文章,“貓”出現一次,x就加1,“狗”出現一次,y就加1,“鼠”出現一次,z就加1,那麼整篇文章遍歷完了,就一定在三維空間中有一個向量(x1,y1,z1)對應出現次數,然後將這個向量投影在x,y,z軸的值,最大的值對應的軸就是這篇文章出現最多的單詞了,也應該是這篇文章主要講的動物了。

  在x軸上的投影為在向量大小為定值時,夾角越小,餘弦越大,則投影越大,所以我們不用計算出具體的投影的值,問題轉化成了求夾角的餘弦即可。

  兩向量的夾角公式帶入相應的數值即可得到即可知道和x軸夾角,同理與y軸夾角與z軸夾角之後只需要找出最大的餘弦值,對應的字元就是最相關的。

  這是基本向量空間搜尋引擎理論的含義,然後將這個方法用於圖片,會變得更加的複雜,但是核心思想並未改變。

  然後我們照著改一下。

  有1個未知字元(字母或者數字)的圖片,但是我想知道這個字元講的是哪個字元,為此我找了26+10個對應不同字元的圖片作為我的已知的標準,將標準圖片的每個畫素點的顏色作為我的重點關注物件,再並建立一個n維向量,每一維則對應一個標準圖片的畫素點位置,我只要把未知圖片每一個畫素點的顏色值值代入,以及標準圖片每一個畫素點的顏色值代入,最後一定有2個向量表示未知圖片和這一個標準圖片,然後求未知圖片與這一標準圖片的向量的夾角的餘弦值,然後用同樣方法做36遍,再按照相似度從大到小排序即可,最大餘弦值的對應的已知字元應該最接近。這有個要求就是我們的標準的畫素點的數量和未知的圖片畫素點數量要想接近,這應該涉及到了資料預處理的問題,我還沒有學,但是我覺得如果畫素點數量差距變大,會很影響判斷的,所以以下,我們當我們的資料都很好的預處理了。

  總的來說,和原本的區別就是我們的重點關注物件變多了,以及我們的標準也不只是單單的座標軸了,標準也變成一個向量。綜上我們一共需要完成①圖片變成向量②計算向量大小③計算夾角

  ①圖片準換成向量,我們定義成為一個函式,返回一個字典,鍵就是畫素點位置(0,1,2,3...),值就是畫素點顏色(0,255)

#圖片轉換成向量,字典化圖片
def buildvector(im):#引數是字元的圖片
    d1 = {}         #字典記錄畫素點位置和對應的顏色
    count =0        #用來增加畫素點位置
    for i  in im.getdata(): #i就是從0開始對應的顏色值
        d1[count] = i   #把顏色值作為值加入字典
        count +=1       
    return d1       #返回{畫素點位置:顏色}的字典

  ②計算向量大小以及③計算夾角同時放在一個類裡面

import math
#
比較向量相似度的類 class VectorCompare: #計算向量大小,即求餘弦的分母的元素 def magnitude (self,concordance): total = 0 for word,count in concordance.items():#word畫素點位置,count對應的顏色(0或255) total += count**2 return math.sqrt(total) #求出向量的模的大小 #計算向量的夾角 def relation(self,concordance1,concordance2): #輸入兩個字典化圖片 topvalue =0 #求餘弦公式的分子 for word,count in concordance1.items(): #需要很好的資料預處理 if word in concordance2: # 每一維度(畫素點),兩向量的顏色值(0或255)相乘,求出餘弦公式分子 topvalue += count *concordance2[word] all_magnitude = self.magnitude(concordance1)*self.magnitude(concordance2) #求餘弦公式的分母 relevance = topvalue/all_magnitude #求出餘弦 return relevance #返回相關性

  之後就是要用圖片轉換向量函式先把我的標準訓練集先完成,而標準訓練集就是iconset資料夾下的檔案,我們需要從iconset資料夾裡把每一個圖片和資料夾的名字一一對應上,所以我們需要用os庫來獲取檔名

#訓練集名字
iconset = ['0','1','2','3','4','5','6','7','8','9',
           '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']

#載入訓練集
imageset = []   #[{正確名字1:[字典化圖片]}, {正確名字2:[字典化圖片]}, {正確名字3:[字典化圖片]}。。。]
#字典化iconset裡面圖片
for letter in iconset:  #遍歷iconset所有要訓練的名字
    for img in os.listdir("./iconset/%s"%(letter)): #遍歷所有iconset裡面的資料夾
        temp = []       #列表用來記錄字典化圖片
        if img != "Thumbs.db" and img!= ".DS_Store":    #不需要訓練的檔案
            temp.append(buildvector(Image.open("./iconset/%s/%s"%(letter,img))))  #生成字典化圖片
        imageset.append({letter:temp})  #將訓練的名字和字典化圖片再對應

  最後一步,把我們之前切的im3逐一遍歷,再排序出相似度最高的對應的正確名字,最後打印出所有字串

#判斷單個字元的相似度
str = ""          #列印字串
for letter in letters:
    im3 = im2.crop((letter[0], 0, letter[1], im.size[1]))
    guess = []  #記錄和所有訓練集的資料,用來排序
    for image in imageset:  #和所有訓練集的資料進行遍歷
        for x, y in image.items():  #x是正確名字,y是對應的[字典化圖片]
            if len(y) != 0: #y不為空,除去是Thumbs.db和.DS_Store訓練出來的空列表
                guess.append((v.relation(y[0], buildvector(im3)), x))#y[0]就是字典化圖片
    guess.sort(reverse=True) #從大到小排序
    str += "{}".format(guess[0][1]) #相似度最高的字元加到字串裡
print(str)  #列印

  列印結果為

7s9t9j

  這一個到此為止成功。實驗樓的專案至此結束

  所有程式碼

from PIL import Image
import math
import os

#比較向量相似度的類
class VectorCompare:
    #計算向量大小,即求餘弦的分母的元素
    def magnitude (self,concordance):
        total = 0
        for word,count in concordance.items():#word畫素點位置,count對應的顏色(0或255)
            total += count**2
        return math.sqrt(total)         #求出向量的模的大小

    #計算向量的夾角
    def relation(self,concordance1,concordance2):   #輸入兩個字典化圖片
        topvalue =0                                #求餘弦公式的分子
        for word,count in concordance1.items():     #需要很好的資料預處理
            if word in concordance2:
                # 同一維度(畫素點),兩向量的顏色值(0或255)相乘,求出餘弦公式分子
                topvalue += count *concordance2[word]
        all_magnitude = self.magnitude(concordance1)*self.magnitude(concordance2)   #求餘弦公式的分母
        relevance = topvalue/all_magnitude      #求出餘弦
        return relevance            #返回相關性

#圖片轉換成向量,字典化圖片
def buildvector(im):#引數是字元的圖片
    d1 = {}         #字典記錄畫素點位置和對應的顏色
    count =0        #用來增加畫素點位置
    for i  in im.getdata(): #i就是從0開始對應的顏色值
        d1[count] = i   #把顏色值作為值加入字典
        count +=1
    return d1       #返回{畫素點位置:顏色}的字典

#例項化
v = VectorCompare()
#訓練集名字
iconset = ['0','1','2','3','4','5','6','7','8','9',
           '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']

#載入訓練集
imageset = []   #[{正確名字1:[字典化圖片]}, {正確名字2:[字典化圖片]}, {正確名字3:[字典化圖片]}。。。]
#字典化iconset裡面圖片
for letter in iconset:  #遍歷iconset所有要訓練的名字
    for img in os.listdir("./iconset/%s"%(letter)): #遍歷所有iconset裡面的資料夾
        temp = []       #列表用來記錄字典化圖片
        if img != "Thumbs.db" and img!= ".DS_Store":    #不需要訓練的檔案
            temp.append(buildvector(Image.open("./iconset/%s/%s"%(letter,img))))  #生成字典化圖片
        imageset.append({letter:temp})  #將訓練的名字和字典化圖片再對應


# 載入圖片並且轉換成8位畫素
im = Image.open("./captcha.gif")
im.convert("P")
# 構造一個純白的等大小的圖片im2
im2 = Image.new("P", im.size, 255)
# 遍歷載入的圖片,對每個畫素點判斷是否符合要求
for x in range(im.size[1]):  # im.size[1]是垂直畫素數
    for y in range(im.size[0]):  # im.size[0]是水平畫素數
        pix = im.getpixel((y, x))  # 獲取每一個畫素點的顏色紙
        if pix == 220 or pix == 227:  # 判斷是否符合220或者227
            im2.putpixel((y, x), 0)  # 符合則變成黑色

inletter = False  # 判斷是否切割到了字元
foundletter = False  # 未切到字元的狀態記錄
start = 0  # 記錄開始的x值
end = 0  # 記錄結束的x值
letters = []  # 記錄切割到的字元
# 縱向切割記錄資料
for x in range(im2.size[0]):  # 遍歷水平的畫素點
    for y in range(im2.size[1]):  # 同一水平值下遍歷垂直的(用刀切)
        pix = im2.getpixel((x, y))  # 獲取畫素點顏色
        if pix != 255:  # 碰到黑色就位切到了
            inletter = True
    # 切到但是刀的狀態是沒有切到,則轉換刀的狀態為切到
    if foundletter == False and inletter == True:
        foundletter = True
        start = x

    # 如果上面if沒有成立,則下面的if不會發生,所以letters一定會記錄到2個不同的值

    # 沒有切到但是刀的狀態是切到了,則轉換刀的狀態為未切到
    if foundletter == True and inletter == False:
        foundletter = False
        end = x
        letters.append((start, end))
    # 重置為未切到字元
    inletter = False

# 判斷單個字元的相似度
str = ""  # 列印字串
for letter in letters:
    im3 = im2.crop((letter[0], 0, letter[1], im.size[1]))
    guess = []  # 記錄和所有訓練集的資料,用來排序
    for image in imageset:  # 和所有訓練集的資料進行遍歷
        for x, y in image.items():  # x是正確名字,y是對應的[字典化圖片]
            if len(y) != 0:  # y不為空,除去是Thumbs.db和.DS_Store訓練出來的空列表
                guess.append((v.relation(y[0], buildvector(im3)), x))  # y[0]就是字典化圖片
    guess.sort(reverse=True)  # 從大到小排序
    str += "{}".format(guess[0][1])  # 相似度最高的字元加到字串裡

  

  之後便要對所有的examples資料夾下的驗證碼都進行訓練,看看準確度如何

  從載入圖片到最後的判斷字元都放入一個for迴圈語句當中

for listname in os.listdir("./examples"):

  以及驗證碼圖片的載入也要修改為

    if listname != "Thumbs.db" and listname != ".DS_Store":
        im = Image.open("./examples/%s" % (listname))
        im.convert("P")

  下面的所有程式碼都要這個if條件下才能實施,全部再縮排一行

  當我再次列印輸出的時候顯示的驗證碼結果是

0q1dp0
0q3tje
24alb0p
47j17b
4wwfa
5dwvo
5t0qh
75rc1qp
7s9t9j
bibfkf
bf5te
9f2luc
9tmxf
9to1tkp
akfvav
aro2hz
b17lzh
b3rk8h
b3ufl9
pbmk5jx
2mybt
cw0qy
cfyrg
eb0qy3
etg5z
fnt5x
phd0qli
ivusjv
jfte2
zttiq
k0qg4l
k6e2ir
w0qlk
w7k5z
l9felg
lz73a7
t1sge
n67dmb
nlrzo7
tmisv
f15jnd
fmiunq
qfwix9
r2lvkd
r6r12e
718ft
t6khw
ibrjc
puc1rdk
v63gde
7f54eg
xfnrsn

  有長有短,但是驗證碼的長度應當是6個字元,對錯我也並不知曉,所以我開始著手準備

我在迴圈前加了一系列變數用來記錄我所疑惑的

success =0            #記錄正確匹配個數
fail = 0                #記錄失敗的個數
success_name_list=[]        #記錄正確匹配的名字
fail_name_list =[]          #記錄失敗的名字
wrong_length_name = []        #記錄失敗的錯誤長度的名字
wrong_letter_count_dict= {}       #記錄失敗的對應的字母錯誤並累加記錄次數
correct_name_list = []          #記錄錯誤所對應的正確名字,列表下標對應,對比容易

  然後我在每一次迴圈剛開始的時候都記錄下當前驗證碼的正確名字,也就是圖片名

correct_name = listname[:6]  # 記錄正確的檔名 ,用來判斷是否正確

  然後就是對結果str進行判斷,並記錄相關資料

        if str == correct_name: #正確
            success += 1        #正確次數加1
            success_name_list.append(str)   #記錄正確的名字
        else:
            fail +=1    #錯誤次數加1
            fail_name_list.append(str)  #記錄錯誤名字
            correct_name_list.append(correct_name)  #同時記錄對應的正確名字用來進一步分析

  最後就是將相關資料彙總分析,我盡我能力全分析了,過程具體註釋也就不詳細寫了,

count =0 #錯誤名字列表和對應的正確名字的下標一一對應
#統計出錯誤的原因
for letters in fail_name_list:
    # 長度不統一
    if len(letters) != len(correct_name_list[count]):
        wrong_length_name.append(letters)
        count+=1

    #長度統一,但是識別錯誤
    else:
        index =0
        for letter in letters:
            if letter != correct_name_list[count][index]:
                wrong_letter_count_dict[letter] = wrong_letter_count_dict.get(letter,0)+1
                index+=1
            else:
                index+=1
        count+=1


success_rate = success/(success+fail)       #成功率
#列印總數,成功和失敗的數量,以及成功率
print("total count = {}\n"
      "success = {}, failed = {} \n"
       " success_rate = {}\n".format(success+fail,success,fail,success_rate))

#列印成功的驗證碼的名字
print("Success Trainning name:")
for i in range(len(success_name_list)):
    print(success_name_list[i])


wrong_length_count = len(wrong_length_name)                 #去除長度識別錯誤的數量
success_rate = success/(success+fail-wrong_length_count)    #去除長度錯誤的圖片後的成功率
#列印錯誤長度的驗證碼的數量
print("\nWrong Length count:{}".format(wrong_length_count))
print("total count without wrong length= {}\n"
      "success = {}, failed = without wrong length = {} \n"
       "success_rate without wrong length= {:.4f}\n".format(success+fail-wrong_length_count,
                                                         success,
                                                         fail-wrong_length_count,
                                                         success_rate))

#將字母識別錯誤>1的輸出,用來表示標準樣本的錯誤
wrong_letter_count_list = sorted(wrong_letter_count_dict.items(),
                                 key = lambda x:x[1],
                                 reverse =True)
for letter in wrong_letter_count_list:
    if letter[1] >1:
        print("Need more {} to train".format(letter[0]))

  最後執行一下

total count = 52
success = 13, failed = 39 
 success_rate = 0.25

Success Trainning name:
0q3tje
47j17b
7s9t9j
9f2luc
b3rk8h
b3ufl9
k6e2ir
nlrzo7
qfwix9
r2lvkd
r6r12e
v63gde
xfnrsn

Wrong Length count:25
total count without wrong length= 27
success = 13, failed = without wrong length = 14 
success_rate without wrong length= 0.4815

Need more f to train
Need more b to train
Need more v to train
Need more 7 to train

  其他的資料不多說,我後來列印了錯誤字元的字典,發現“f”錯了4次我很好奇為什麼,然後開啟訓練集一看

好嘛,根本沒有小寫“f”的訓練集,網上的訓練集也不靠譜啊

  最後總體說說我有可能需要改進的地方,首先是之前說到的顏色,要我手動輸入,而且還必須統一顏色,弄得不好還可能要多出一個字元,可是如果要解決這個要k鄰近?或者涉及到神經網路了,我才剛看了一點書。。。路漫漫其修遠兮。其次,根據上面的資料可以看到很多辨別失敗的是因為長度辨識錯誤,也就是字元的畫素點重合在一起了,會把兩個字符合成一個字元判斷,我現在想不到能用什麼辦法來解決這個問題。智商不夠用。

  真正的最後附上我的全部程式碼

from PIL import Image
import math
import os


#比較向量相似度的類
class VectorCompare:
    #計算向量大小,即求餘弦的分母的元素
    def magnitude (self,concordance):
        total = 0
        for word,count in concordance.items():#word畫素點位置,count對應的顏色(0或255)
            total += count**2
        return math.sqrt(total)         #求出向量的模的大小

    #計算向量的夾角
    def relation(self,concordance1,concordance2):   #輸入兩個字典化圖片
        topvalue =0                                #求餘弦公式的分子
        for word,count in concordance1.items():     #需要很好的資料預處理
            if word in concordance2:
                # 同一維度(畫素點),兩向量的顏色值(0或255)相乘,求出餘弦公式分子
                topvalue += count *concordance2[word]
        all_magnitude = self.magnitude(concordance1)*self.magnitude(concordance2)   #求餘弦公式的分母
        relevance = topvalue/all_magnitude      #求出餘弦
        return relevance            #返回相關性

#圖片轉換成向量,字典化圖片
def buildvector(im):#引數是字元的圖片
    d1 = {}         #字典記錄畫素點位置和對應的顏色
    count =0        #用來增加畫素點位置
    for i  in im.getdata(): #i就是從0開始對應的顏色值
        d1[count] = i   #把顏色值作為值加入字典
        count +=1
    return d1       #返回{畫素點位置:顏色}的字典

#例項化
v = VectorCompare()
#訓練集名字
iconset = ['0','1','2','3','4','5','6','7','8','9',
           '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']

#載入訓練集
imageset = []   #[{正確名字1:[字典化圖片]}, {正確名字2:[字典化圖片]}, {正確名字3:[字典化圖片]}。。。]
#字典化iconset裡面圖片
for letter in iconset:  #遍歷iconset所有要訓練的名字
    for img in os.listdir("./iconset/%s"%(letter)): #遍歷所有iconset裡面的資料夾
        temp = []       #列表用來記錄字典化圖片
        if img != "Thumbs.db" and img!= ".DS_Store":    #不需要訓練的檔案
            temp.append(buildvector(Image.open("./iconset/%s/%s"%(letter,img))))  #生成字典化圖片
        imageset.append({letter:temp})  #將訓練的名字和字典化圖片再對應

success =0            #記錄正確匹配個數
fail = 0                #記錄失敗的個數
success_name_list=[]        #記錄正確匹配的名字
fail_name_list =[]          #記錄失敗的名字
wrong_length_name = []        #記錄失敗的錯誤長度的名字
wrong_letter_count_dict= {}       #記錄失敗的對應的字母錯誤並累加記錄次數
correct_name_list = []          #記錄錯誤所對應的正確名字,列表下標對應對比容易
for listname in os.listdir("./examples"):

    correct_name = listname[:6]  # 記錄正確的檔名 ,用來比較
    # 載入圖片並且轉換成8位畫素
    if listname != "Thumbs.db" and listname != ".DS_Store":
        im = Image.open("./examples/%s" % (listname))
        im.convert("P")
        # 構造一個純白的等大小的圖片im2
        im2 = Image.new("P", im.size, 255)
        # 遍歷載入的圖片,對每個畫素點判斷是否符合要求
        for x in range(im.size[1]):  # im.size[1]是垂直畫素數
            for y in range(im.size[0]):  # im.size[0]是水平畫素數
                pix = im.getpixel((y, x))  # 獲取每一個畫素點的顏色紙
                if pix == 220 or pix == 227:  # 判斷是否符合220或者227
                    im2.putpixel((y, x), 0)  # 符合則變成黑色

        inletter = False  # 判斷是否切割到了字元
        foundletter = False  # 未切到字元的狀態記錄
        start = 0  # 記錄開始的x值
        end = 0  # 記錄結束的x值
        letters = []  # 記錄切割到的字元
        # 縱向切割記錄資料
        for x in range(im2.size[0]):  # 遍歷水平的畫素點
            for y in range(im2.size[1]):  # 同一水平值下遍歷垂直的(用刀切)
                pix = im2.getpixel((x, y))  # 獲取畫素點顏色
                if pix != 255:  # 碰到黑色就位切到了
                    inletter = True
            # 切到但是刀的狀態是沒有切到,則轉換刀的狀態為切到
            if foundletter == False and inletter == True:
                foundletter = True
                start = x

            # 如果上面if沒有成立,則下面的if不會發生,所以letters一定會記錄到2個不同的值

            # 沒有切到但是刀的狀態是切到了,則轉換刀的狀態為未切到
            if foundletter == True and inletter == False:
                foundletter = False
                end = x
                letters.append((start, end))
            # 重置為未切到字元
            inletter = False

        # 判斷單個字元的相似度
        str = ""  # 列印字串
        for letter in letters:
            im3 = im2.crop((letter[0], 0, letter[1], im.size[1]))
            guess = []  # 記錄和所有訓練集的資料,用來排序
            for image in imageset:  # 和所有訓練集的資料進行遍歷
                for x, y in image.items():  # x是正確名字,y是對應的[字典化圖片]
                    if len(y) != 0:  # y不為空,除去是Thumbs.db和.DS_Store訓練出來的空列表
                        guess.append((v.relation(y[0], buildvector(im3)), x))  # y[0]就是字典化圖片
            guess.sort(reverse=True)  # 從大到小排序
            str += "{}".format(guess[0][1])  # 相似度最高的字元加到字串裡

        if str == correct_name: #正確
            success += 1        #正確次數加1
            success_name_list.append(str)   #記錄正確的名字
        else:
            fail +=1    #錯誤次數加1
            fail_name_list.append(str)  #記錄錯誤名字
            correct_name_list.append(correct_name)  #同時記錄對應的正確名字用來進一步分析


count =0 #錯誤名字列表和對應的正確名字的下標一一對應
#統計出錯誤的原因
for letters in fail_name_list:
    # 長度不統一
    if len(letters) != len(correct_name_list[count]):
        wrong_length_name.append(letters)
        count+=1

    #長度統一,但是識別錯誤
    else:
        index =0
        for letter in letters:
            if letter != correct_name_list[count][index]:
                wrong_letter_count_dict[letter] = wrong_letter_count_dict.get(letter,0)+1
                index+=1
            else:
                index+=1
        count+=1


success_rate = success/(success+fail)       #成功率
#列印總數,成功和失敗的數量,以及成功率
print("total count = {}\n"
      "success = {}, failed = {} \n"
       " success_rate = {}\n".format(success+fail,success,fail,success_rate))

#列印成功的驗證碼的名字
print("Success Trainning name:")
for i in range(len(success_name_list)):
    print(success_name_list[i])


wrong_length_count = len(wrong_length_name)                 #去除長度識別錯誤的數量
success_rate = success/(success+fail-wrong_length_count)    #去除長度錯誤的圖片後的成功率
#列印錯誤長度的驗證碼的數量
print("\nWrong Length count:{}".format(wrong_length_count))
print("total count without wrong length= {}\n"
      "success = {}, failed = without wrong length = {} \n"
       "success_rate without wrong length= {:.4f}\n".format(success+fail-wrong_length_count,
                                                         success,
                                                         fail-wrong_length_count,
                                                         success_rate))

#將字母識別錯誤>1的輸出,用來表示標準樣本的錯誤
wrong_letter_count_list = sorted(wrong_letter_count_dict.items(),
                                 key = lambda x:x[1],
                                 reverse =True)
for letter in wrong_letter_count_list:
    if letter[1] >1:
        print("Need more {} to train".format(letter[0]))