天天看點

python破解簡單驗證碼

此破解的驗證碼隻适合,字元顔色單一,且字元分割較明顯的驗證碼

破解驗證碼屬于機器學習分支的内容, 破解驗證碼方式有多重,這裡使用向量空間搜尋的方法

資料集來自于實驗樓。由版權原因請自行到https://www.shiyanlou.com/courses/364下載下傳,讀者可配合實驗内容進行了解

圖檔二值化即,将圖檔變成隻有黑白兩種顔色, 其他大部分在代碼中已解釋

#coding:utf-8

"""
    此程式隻适合紅色220跟227畫出來的字元,其他顔色不支援
    2018年5月2日21:55:57
    
"""


from PIL import Image
import hashlib
import time
import math
import os

class VectorCompare:
    """
    2 篇文檔所使用的相同的單詞越多,那這兩篇文章就越相似!但是這單詞太多怎麼辦,就由我們來選擇幾個關鍵單詞,選擇的單詞又被稱作特征,
    每一個特征就好比空間中的一個次元(x,y,z 等),一組特征就是一個矢量,每一個文檔我們都能得到這麼一個矢量,隻要計算矢量之間的夾角就能得到文章的相似度了。


    """
    #計算矢量大小, 即二範數
    def magnitude(self, concordance):
        total = 0
        for word, count in concordance.items():
            total += count ** 2
        return math.sqrt(total)

    #計算矢量之間的cos值, 即計算餘弦相似度
    def relation(self, concordance1, concordance2):
        relevance = 0
        topvalue = 0
        for word, count in concordance1.items():
            if word in concordance2:
                topvalue += count * concordance2[word]
        return topvalue / (self.magnitude(concordance1) * self.magnitude(concordance2))


def sort_his():
    """将圖檔排序, 友善選取顔色,去除噪聲"""
    his = im.histogram()
    values = {}

    for i in range(256):
        values[i] = his[i]

    hisSort = sorted(values.items(), key = lambda x: x[1], reverse = True)
    #擷取前10的顔色以及數量
    for j, k in hisSort[:10]:
        print(j, k)



def binaryzation(im):
    """将圖檔二值化"""
    #獲得一張全白的背景,即與im同樣大小,模式為P,像素全為255的圖檔
    im2 = Image.new("P", im.size, 255)

    #擷取圖像矩陣的行列
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            #擷取像素的RGB值
            pix = im.getpixel((x, y))
        #   if pix == 219:#用219畫出來不是字元的輪廓
            #220跟227是紅色,即想要的顔色(通過嘗試前面多種顔色的效果,對比輸入輸出圖檔)
            if pix == 220 or  pix == 227:
                #im2的白色背景上畫像素,顔色為0(黑色)
                im2.putpixel((x, y), 0)

    return im2

def cutImg(im2):
    """将圖檔進行縱向切割,得到每個字的圖檔"""
    inletter = False
    foundletter = False
    start = 0   #每個字元的橫坐标開始位置
    end = 0     #每個字元的橫坐标結束位置

    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
        #如果找到了不是白色像素的位置,記錄每行的x
        if  foundletter == False and inletter == True:
            foundletter = True
            start = x
        #z找到一列都為白色像素的為止,作為字元結尾
        if foundletter == True and inletter == False:
            foundletter = False
            end = x
            letters.append((start, end))

        inletter = False

    count = 0
    imgList = []
    for xStart, xEnd in letters:          #letter裡存放每個字元的開始與結束的橫坐标
        #crop參數為(Xstart,Ystart,Xend,Yend),然後對圖檔進行切割
        imgList.append(im2.crop(( xStart, 0, xEnd, im2.size[1])))
    """
    測試代碼:
    count = 0
    for xStart, xEnd in letters:
        m = hashlib.md5()
        im3 = im2.crop(( xStart, 0, xEnd, im2.size[1]))
        m.update("%s%s"%(time.time(), count))
        im3.save("./%s.gif"%(m.hexdigest()))
        count += 1
    """
    return imgList

#将圖檔轉換為矢量
def buildvector(im):
    d1 = {}
    count = 0
    for i in im.getdata():
        d1[count] = i
        count += 1
    return d1

def loadSet():
    """加載訓練集"""
    #答案
    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']
    #加載訓練集
    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})

    return imageset

def testImg():
    #建立一個向量類
    v = VectorCompare()
    imageset = loadSet()
    #打開測試圖檔
    im = Image.open("captcha.gif")
    #将圖檔轉換為8位像素模式,即平常RGB(0, 255)
    im.convert("P") 
    '''
                            測試: 列印圖的顔色直方圖(每位對應像素出現多少次)
                            print(im.histogram())
                            sort_his()
    '''
    #将圖檔二值化
    im2 = binaryzation(im)
    #将二值化的圖檔進行切割,得到單個字元
    imgList = cutImg(im2)

    for img in imgList:
        m = hashlib.md5()
        guess = []
        #周遊清單裡的所有字典
        for image in imageset:
            #擷取字典的鍵值,
            for x, y in image.items():
                if len(y) != 0:
                    guess.append( ( v.relation(y[0], buildvector(img)), x) )

        guess.sort(reverse=True)
        print("",guess[0])


if __name__ == '__main__':
    testImg()