驗證碼破解技術四部曲之使用K近鄰算法(三)

前言

在上一節中,咱們使用了google的開源OCR庫來對字符進行識別,這一節以及下一節咱們將要使用機器學習算法來識別驗證碼。本節的代碼都在https://github.com/nladuo/captcha-break/tree/master/csdn能夠找到。php

下載驗證碼

在這一節中,將要對CSDN下載的驗證碼進行破解,就是在http://download.csdn.net/下載東西的時候,短期內下載次數過多彈出來的驗證碼。

python

作機器學習的第一個步驟就是採集數據,構建訓練樣本。首先,來看一下CSDN下載中出現的驗證碼。

captcha1captcha2

git

在每次刷新的時候,會有以上這兩種驗證碼出現。在本節中,爲了方便學習K近鄰算法(簡稱爲:KNN),選擇第二種來進行破解,由於第二種的字母分割十分容易,每一個字母的位置都是固定的。

github

因爲兩種驗證碼的圖片大小不同,因此能夠使用圖片大小來判斷哪一個是第一種驗證碼,哪一個是第二種驗證碼,這裏使用python進行驗證碼下載。算法

# coding:utf-8
import requests
import uuid
from PIL import Image
import os

url = "http://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325"
for i in range(100):
    resp = requests.get(url)
    filename = "./captchas/" + str(uuid.uuid4()) + ".png"
    with open(filename, 'wb') as f:
        for chunk in resp.iter_content(chunk_size=1024):
            if chunk:  # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
        f.close()
    im = Image.open(filename)
    if im.size != (48, 20):
        os.remove(filename)
    else:
        print filename

分割字符

下載事後,就須要對字母進行分割。機器學習雖然牛逼,可是也須要對樣本進行預處理,這裏的預處理就是把字母分割出來,而且分割成一樣的尺寸。分割的方式能夠使用代碼分割,固然也能夠經過人用PS等工具進行手動分割。
數組

我這裏使用代碼分割,字母分割的代碼在spliter文件夾下,我使用了boost庫來來讀取全部下載的驗證碼,對圖片進行二值化後,進行定點分割,能夠看到分割好的字母以下。

letters

機器學習

以後,須要人工對字母進行分類,分類好的圖片見recognizer/dataset,我這裏每一個字母須要6個樣本,10個字母,總共60個樣本。

dataset

函數

算法原理

K近鄰算法的定義十分簡單,在百度百科上有這樣的解釋:若是一個樣本在特徵空間中的k個最類似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。

工具

也就是說,須要找到要識別的字母在訓練樣本中K個最近的字母,而後找出這K個字母中最多的是某個類的?要識別的圖片也就是該類的。學習

實現KNN

計算距離

首先,先定義一下距離如何計算,這裏能夠用各類數學上的距離,歐式距離、馬氏距離等等。。

因爲咱們的圖片已經進行了二值化,爲了簡便起見,這裏把兩張圖片的距離定義爲:兩張圖片灰度不一樣的像素點個數。也就是逐個比較圖片的相對位置上的灰度值,若是不相同,距離就加一。

int count_distance(Mat mat1, Mat mat2)
{
    assert(mat1.size().height == mat2.size().height);
    assert(mat1.size().width == mat2.size().width);
    assert(mat1.channels() == 1 && mat2.channels() == 1);

    int distance = 0;

    for(int i = 0; i < mat1.size().width; i++){
        for(int j = 0; j < mat1.size().height; j++){
            if(mat1.at<uchar>(j, i) != mat2.at<uchar>(j, i)){//不相等就加1
                distance++;
            }
        }
    }
    return distance;
}

加載數據

數據的加載須要一個圖片數組和一個標籤數組,來記錄圖片數組相應位置的類別。

加載樣本數據:

void load_dataset(Mat dataset[])
{
    string dataset_dir = "../recognizer/dataset/";
    for(int i = 0; i < 6*10; i++){
        char buffer[255];
        sprintf(buffer, "%d", i/6);
        string image_path = dataset_dir + string(buffer);
        sprintf(buffer, "%d", i%6 + 1);
        image_path += string(buffer) + ".png";
        dataset[i] = imread(image_path, CV_LOAD_IMAGE_GRAYSCALE);
    }
}

加載樣本數據標籤:

void create_labels(int labels[])
{
    for(int i = 0; i < 6*10; i++){
        labels[i] = i/6;
    }
}

算法實現

加載完數據後,就能夠開始實現KNN分類了。

一、計算輸入圖片和全部其餘圖片的距離

int distances[6*10];
int sorted_distances[6*10];
//count distances
for(int i = 0; i < 6*10 ;i++){
    distances[i] = count_distance(letter, dataset[i]);
    sorted_distances[i] = distances[i];
}

二、對距離進行排序

sort(sorted_distances, sorted_distances+6*10);

三、獲取K個距離最近的圖片的類別

int* k_nearest = new int[k];
for(int i = 0; i < k; i++){
    for(int j = 0; j < 6*10 ; j++){
        if(distances[j] == sorted_distances[i]){
            k_nearest[i] = labels[j];
            break;
        }
    }
}

四、利用map記錄全部類別中出現k_nearest的次數

map<int, int> labels_map;
for(int i = 0; i < k; i++){
    if(labels_map.find(k_nearest[i]) == labels_map.end())
        labels_map[k_nearest[i]] = 0;
    else
        labels_map[k_nearest[i]]++;
}

五、獲得出現最多的類別

int max_label = -1;
labels_map[max_label] = -1;
map<int,int>::iterator it;
for(it=labels_map.begin();it!=labels_map.end();++it){
    if(it->second > labels_map[max_label]){
        max_label = it->first;
    }
}
delete[] k_nearest;
return max_label;

識別驗證碼

最後,咱們把驗證碼的4個字母分割出來,再進行K近鄰分類,就能夠獲得識別結果了。

void recognize(string path, Mat dataset[], int labels[])
{
    Mat test_image = imread(path, CV_LOAD_IMAGE_GRAYSCALE);
    threshold(test_image, test_image, 100, 255, cv::THRESH_BINARY);
    Range col_ranges[4] = {
        Range(5, 5+8),
        Range(14, 14+8),
        Range(23, 23+8),
        Range(32, 32+8)
    };
    cout<<"Result:";
    for(int i = 0; i < 4; i++){
        Mat letter = test_image.colRange(col_ranges[i]);
        cout << knn_classify(letter, dataset, labels, 5);
    }
    cout<<endl;
}

效果

識別圖片:

captchas


識別結果:

captchas_result

練習

經過以上,咱們破解了CSDN下載的第二種驗證碼,第一種驗證碼的識別過程也是能夠使用KNN的,可是第一種和第二種的分割字母的方式不一樣,讀者能夠嘗試使用opencv的findCountours函數對字母進行分割,或者使用垂直投影的方式進行分割,須要注意的是第一種驗證碼有一個黑色的邊框,若是不處理會影響findCountours函數的效果。

相關文章
相關標籤/搜索