通過機器學習破解驗證碼圖片

背景

機器學習,即Machine Learning驮肉,屬于AI范疇拇勃,是深度學習的父集四苇。
通過如何用機器學習(ML)在15分鐘內(nèi)破解圖片驗證碼的一篇文章,看到了solving-captchas-code-examples項目方咆。作者通過神經(jīng)網(wǎng)絡訓練簡單的model來破解了形如下圖的圖片驗證碼:

eg1

正文

根據(jù)該example月腋,打算拿來練練手。當然不能止步于示例中的簡單驗證碼,隨手打開了幾個網(wǎng)頁榆骚,搜集了下如圖所示的彩色的圖片驗證碼:

rs0

選定了上述的帶數(shù)學算數(shù)的圖形驗證碼片拍,接下來開始上路了。
大致的思路是:識別圖片->訓練模型(model)->生產(chǎn)驗證

一妓肢、識別圖片

這個識別圖片不是OCR的意思捌省,是相當于你教會計算機去認識圖片中的數(shù)字。
比如职恳,你拿一張寫著數(shù)字1的圖片所禀,然后告訴計算機,這是數(shù)字1放钦。

這些基礎數(shù)據(jù)色徘,是作為下一步訓練模型的基礎的,大家都知道操禀,時下人工智能里褂策,訓練個好的模型至關重要。我曾一度認為颓屑,源代碼是否暴露或者開源已無關緊要斤寂,但是經(jīng)過了大數(shù)據(jù)的神經(jīng)網(wǎng)絡模型,才是真正的'核心源碼'揪惦。

所以遍搞,我們要獲取的驗證碼圖片的基礎數(shù)據(jù)當然越多越好。這里我的初衷是簡單實驗一下器腋,也不是打造一個專業(yè)的破解系統(tǒng)溪猿,所以通過爬蟲簡單保存了二三十張圖片,并按照圖片內(nèi)容手動重命名:

rs01

接下來纫塌,按照各個基礎數(shù)據(jù)圖片的文件名诊县,識別、拆分圖片中的各個字符措左,來分類依痊。這里沒有照搬example中的代碼,通過循環(huán)各個圖片怎披,來拆分圖片并分類存儲:

def main(counts, captcha_image_file):
    # 結果輸出dir
    OUTPUT_FOLDER = './result'
    # 基礎數(shù)據(jù)dir
    captcha_image_files = glob.glob(os.path.join('./subject/', "*"))

    filename = os.path.basename(captcha_image_file)
    captcha_correct_text = os.path.splitext(filename)[0]
    # 加載圖像并將其轉(zhuǎn)換成灰度級
    image = cv2.imread(captcha_image_file)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 在圖像周圍添加一些填充物
    gray = cv2.copyMakeBorder(gray, 1, 1, 1, 1, cv2.BORDER_REPLICATE)

    # 閾值圖像(轉(zhuǎn)換為純黑色和白色)
    # threshold:    輸入圖像,閾值,輸出圖像的最大值,閾值的類型,目標圖像
    # 轉(zhuǎn)換第一次胸嘁,有淺色情況所以下面轉(zhuǎn)第二次
    thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_TOZERO)[1]
    # 將圖片轉(zhuǎn)為黑白
    thresh = cv2.threshold(thresh, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]


    # 找到圖像的輪廓(連續(xù)像素塊)
    contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # OpenCV版本適應
    contours = contours[0] if imutils.is_cv2() else contours[1]
    letter_image_regions = []

    # 遍歷輪廓,并提取字母
    for contour in contours:
        # 獲取包含輪廓的矩形
        (x, y, w, h) = cv2.boundingRect(contour)
        
        # 比較輪廓的寬度和高度來檢測字母
        if w / h > 1.25:
            # 輪廓太寬,把它分成兩個字母區(qū)域
            half_width = int(w / 2)
            letter_image_regions.append((x, y, half_width, h))
            letter_image_regions.append((x + half_width, y, half_width, h))
        else:
            # 正常字母
            letter_image_regions.append((x, y, w, h))

    # 根據(jù)X坐標對檢測到的字母圖像進行排序钳枕,以確保我們從左到右處理它們缴渊,因此我們將正確的圖像與正確的字母匹配。
    letter_image_regions = sorted(letter_image_regions, key=lambda x: x[0])

    # Save image
    retStr = ''
    nCnt = 0
    for letter_bounding_box, letter_text in zip(letter_image_regions, captcha_correct_text):
        nCnt += 1
        # 抓取圖像中字母的坐標
        x, y, w, h = letter_bounding_box
        # 從原始圖像中提取具有邊緣的2像素邊緣的字母
        letter_image = gray[y - 2:y + h + 2, x - 2:x + w + 2]
        
        # 獲取文件夾以保存圖像
        save_path = os.path.join(OUTPUT_FOLDER, letter_text)
        # 如果輸出目錄不存在鱼炒,則創(chuàng)建它
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        # 將字母圖像寫入文件
        count = counts.get(letter_text, 1)
        p = os.path.join(save_path, "{}.png".format(str(count).zfill(6)))
        retStr += letter_text
        cv2.imwrite(p, letter_image)
        
        counts[letter_text] = count + 1

    return (retStr)

上述是主函數(shù),我又寫了遍歷基礎圖片目錄的方法衔沼,來循環(huán)調(diào)用它:

dicT = {}
for (i, captcha_image_file) in enumerate(captcha_image_files):
    a = main(dicT, captcha_image_file)
    a = list(a)
    try:
        # 這里做個打印
        if len(a)>=3:
            if a[1] == 'x':
                print(a[0]+'*'+a[2]+'='+str((int(a[0])*int(a[2]))))
            else:
                print(a[0]+'+'+a[2]+'='+str((int(a[0])+int(a[2]))))
    except:
        raise

執(zhí)行后,將基礎數(shù)據(jù)分門別類到result目錄:

rs1
rs11

上圖即按照各個字符,拆分圖片指蚁,每個子文件夾下菩佑,是該字符的各種樣子。
比如./result/9/目錄下凝化,即各個形態(tài)的數(shù)字9稍坯。

二、訓練模型

訓練模型這里搓劫,使用的是keras庫瞧哟。摘一下文檔中的簡述:

Keras 是一個用 Python 編寫的高級神經(jīng)網(wǎng)絡 API,它能夠以 TensorFlow, CNTK, 或者 Theano 作為后端運行枪向。Keras 的開發(fā)重點是支持快速的實驗勤揩。能夠以最小的時延把你的想法轉(zhuǎn)換為實驗結果,是做好研究的關鍵秘蛔。
如果你在以下情況下需要深度學習庫陨亡,請使用 Keras:

  • 允許簡單而快速的原型設計(由于用戶友好,高度模塊化深员,可擴展性)负蠕。
  • 同時支持卷積神經(jīng)網(wǎng)絡和循環(huán)神經(jīng)網(wǎng)絡,以及兩者的組合倦畅。
  • 在 CPU 和 GPU 上無縫運行

該步驟處理的就是./result/下的各個形態(tài)的字符文件了遮糖。
其大致思路是,先將各個圖片統(tǒng)一大小叠赐,然后加入到keras的model進行訓練并生成一個model文件止吁。
該部分代碼同example類似,可以當做utils來使用燎悍。

LETTER_IMAGES_FOLDER = "result"
MODEL_FILENAME = "captcha_model.hdf5"
MODEL_LABELS_FILENAME = "model_labels.dat"


# 初始化
data = []
labels = []

# 循環(huán)result中的圖片
for image_file in paths.list_images(LETTER_IMAGES_FOLDER):
    # 讀取圖片轉(zhuǎn)灰度
    image = cv2.imread(image_file)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 重置圖片大小,統(tǒng)一為20x20 pixel
    image = resize_to_fit(image, 20, 20)
    # 在原image數(shù)組的3位置添加數(shù)據(jù)盼理,是為了給keras用
    image = np.expand_dims(image, axis=2)
    # 獲取文件名
    label = image_file.split(os.path.sep)[-2]
    # 加入到數(shù)組以備后續(xù)訓練模型
    data.append(image)
    labels.append(label)

# 下述模型訓練的代碼未做改動
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
(X_train, X_test, Y_train, Y_test) = train_test_split(data, labels, test_size=0.25, random_state=0)
lb = LabelBinarizer().fit(Y_train)
Y_train = lb.transform(Y_train)
Y_test = lb.transform(Y_test)
# one-hot編碼保存映射文件
with open(MODEL_LABELS_FILENAME, "wb") as f:
    pickle.dump(lb, f)

model = Sequential()

model.add(Conv2D(20, (5, 5), padding="same", input_shape=(20, 20, 1), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Conv2D(50, (5, 5), padding="same", activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(500, activation="relu"))
model.add(Dense(13, activation="softmax"))
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
model.fit(X_train, Y_train, validation_data=(X_test, Y_test), batch_size=13, epochs=10, verbose=1)

model.save(MODEL_FILENAME)

運行后輸出的captcha_model.hdf5文件即所需的model模型文件谈山。

rs2

三、開始驗證

驗證是宏怔,首先按照第一步開頭的方式奏路,載入圖片。因為第一步我做了些改動來識別彩色的數(shù)字驗證碼臊诊,所以此刻也需將要破解的圖片鸽粉,做相同處理:

MODEL_FILENAME = "captcha_model.hdf5"
MODEL_LABELS_FILENAME = "model_labels.dat"
# 要破解的圖片文件夾所在目錄
CAPTCHA_IMAGE_FOLDER = "sc"

# 載入映射文件和模型
with open(MODEL_LABELS_FILENAME, "rb") as f:
    lb = pickle.load(f)
model = load_model(MODEL_FILENAME)

接下里使用模型來識別目標圖片:

captcha_image_files = list(paths.list_images(CAPTCHA_IMAGE_FOLDER))
captcha_image_files = np.random.choice(captcha_image_files, size=(1,), replace=False)
print("要識別的圖:\n"+str(captcha_image_files))
# loop over the image paths
for image_file in captcha_image_files:
    # Load the image and convert it to grayscale
    image = cv2.imread(image_file)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    image = cv2.copyMakeBorder(image, 1, 1, 1, 1, cv2.BORDER_REPLICATE)
    # 轉(zhuǎn)換第一次,有淺色情況所以下面轉(zhuǎn)第二次
    thresh = cv2.threshold(image, 200, 255, cv2.THRESH_TOZERO)[1]
    # 將圖片轉(zhuǎn)為黑白
    thresh = cv2.threshold(thresh, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    # 找到圖像的輪廓(連續(xù)像素塊)
    contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # OpenCV版本適應
    contours = contours[0] if imutils.is_cv2() else contours[1]
    # 遍歷四個輪廓中的每一個并提取每個輪廓中的字母抓艳,這里封裝統(tǒng)一函數(shù)触机,不作顯示,詳見example
    letter_image_regions = contours_regions(contrours)
    # 輸出demo,做輪廓的標記
    output = cv2.merge([image] * 3)
    predictions = []
    for letter_bounding_box in letter_image_regions:
        x, y, w, h = letter_bounding_box
        letter_image = image[y - 2:y + h + 2, x - 2:x + w + 2]
        # 同樣的resize
        letter_image = resize_to_fit(letter_image, 20, 20)
        letter_image = np.expand_dims(letter_image, axis=2)
        letter_image = np.expand_dims(letter_image, axis=0)
        prediction = model.predict(letter_image)
        letter = lb.inverse_transform(prediction)[0]
        predictions.append(letter)
        cv2.rectangle(output, (x - 2, y - 2), (x + w + 4, y + h + 4), (0, 255, 0), 1)
        cv2.putText(output, letter, (x - 5, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 255, 0), 2)

# 至此predictions中即為破解解析后的各個字符的list了,接下來是一些便利性的打印儡首,不作展示
print(predictions)

效果如圖:

rs3

總結

  • 通過cv2讀入并轉(zhuǎn)換圖片片任,這部分在blog中有不少示例了,所以用的相對熟練蔬胯。這也是能通過示例example的黑白字母驗證碼引申到彩色的算式驗證碼上的原因所在对供;
  • keras是第一次使用,基本上是照搬的示例了氛濒,用的還比較順手产场。雖不是第一次訓練model,但也是第一次這么流程清晰的使用神經(jīng)網(wǎng)絡舞竿;
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末京景,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子炬灭,更是在濱河造成了極大的恐慌醋粟,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件重归,死亡現(xiàn)場離奇詭異米愿,居然都是意外死亡,警方通過查閱死者的電腦和手機鼻吮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門育苟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人椎木,你說我怎么就攤上這事违柏。” “怎么了香椎?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵漱竖,是天一觀的道長。 經(jīng)常有香客問我畜伐,道長馍惹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任玛界,我火速辦了婚禮万矾,結果婚禮上,老公的妹妹穿的比我還像新娘慎框。我一直安慰自己良狈,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布笨枯。 她就那樣靜靜地躺著薪丁,像睡著了一般遇西。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窥突,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天努溃,我揣著相機與錄音,去河邊找鬼阻问。 笑死梧税,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的称近。 我是一名探鬼主播第队,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刨秆!你這毒婦竟也來了凳谦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤衡未,失蹤者是張志新(化名)和其女友劉穎尸执,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缓醋,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡如失,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了送粱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褪贵。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抗俄,靈堂內(nèi)的尸體忽然破棺而出脆丁,到底是詐尸還是另有隱情,我是刑警寧澤动雹,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布槽卫,位于F島的核電站,受9級特大地震影響胰蝠,放射性物質(zhì)發(fā)生泄漏晒夹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一姊氓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喷好,春花似錦翔横、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽效览。三九已至,卻和暖如春荡短,著一層夾襖步出監(jiān)牢的瞬間丐枉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工掘托, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瘦锹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓闪盔,卻偏偏與公主長得像弯院,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泪掀,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容