如何「優(yōu)雅」地標(biāo)數(shù)據(jù)

最近想做一個識別驗證碼的程序乞榨。目標(biāo)其實很簡單熔酷,就是識別出某網(wǎng)站驗證碼的字母和數(shù)字孤紧。

驗證碼.png

這種類型的驗證碼已經(jīng)被做爛了,相應(yīng)的破解程序也很多拒秘。但我只是想學(xué)習(xí)消遣一下号显。

我已經(jīng)通過爬蟲收集了某網(wǎng)站的大量驗證碼圖片,并通過圖像處理的方法把字母和數(shù)字分割出來(好在這類驗證碼比較簡單躺酒,切割工作相對容易)押蚤。之后,便是要對這些圖片進(jìn)行標(biāo)記并訓(xùn)練羹应。我總共爬了 20000 張揽碘,每張上面有四個數(shù)字或字母,相當(dāng)于要對 80000 張圖片做標(biāo)記分類园匹。嗯雳刺,這很有趣!

需求分析

通過對原圖進(jìn)行處理分割后偎肃,我已經(jīng)得到如下的圖片數(shù)據(jù)(圖片尺寸 32 * 32煞烫,除了灰度圖,最好保留對應(yīng)的原圖):

image set.png

現(xiàn)在累颂,要將這些圖片分門別類。數(shù)字和字母,最多可以組合出 10 + 26 = 36 類紊馏,但仔細(xì)觀察數(shù)據(jù)后料饥,我發(fā)現(xiàn)有很多數(shù)字和字母壓根沒出現(xiàn)。通過粗略地掃描一下數(shù)據(jù)朱监,我統(tǒng)計出這個網(wǎng)站的驗證碼總共只使用了 23 類數(shù)字和字母岸啡。于是,我按照如下規(guī)則對圖片做了分類:

image_tag = {0: '3', 1: '5', 2: '6', 3: '7', 4: '8', 5: 'a', 6: 'c', 7: 'e', 8: 'f', 9: 'g', 10: 'h', 11: 'j', 12: 'k', 13: 'm', 14: 'n', 15: 'p', 16: 'r', 17: 's', 18: 't', 19: 'v', 20: 'w', 21: 'x', 22: 'y'}

將出現(xiàn)的數(shù)字和字母分為 23 類赫编。然后巡蘸,接下來的目標(biāo),就是把圖片分到如下 23 個文件夾中:

tag folder.png

實現(xiàn)思路

很多人都覺得標(biāo)數(shù)據(jù)這種事情很沒技術(shù)含量擂送,純屬「dirty work」悦荒。如果你只是單純地用肉眼把一張張圖片分到這些目錄里面,當(dāng)然顯得很「笨拙」嘹吨。而且搬味,仔細(xì)想想,80000 張圖片的分類蟀拷,(一個人)幾乎是不可能人工完成的碰纬。我們要用優(yōu)雅的方法來歸類。

這個優(yōu)雅的方法其實也很簡單问芬。分以下幾步進(jìn)行:

  1. 先人工挑出幾個或十幾個樣本悦析,訓(xùn)練一個分類器出來,這個分類器準(zhǔn)確率會很低此衅,但不要緊强戴;
  2. 再從原圖片中,選出幾十上百張炕柔,用剛才的分類器對它們進(jìn)行分類酌泰。由于分類器精度有限,需要從分類后的結(jié)果中挑出分錯的樣本匕累,然后人工將它們分到正確的目錄(這個工作比你自己去對上百張圖片做分類真的要輕松好多)陵刹;
  3. 用已經(jīng)分好類的數(shù)據(jù)繼續(xù)訓(xùn)練一個新的分類器,重復(fù)第 2 步直到數(shù)據(jù)都分類完(隨著分類器精度提高欢嘿,可以逐步增加待分類圖片的數(shù)量)衰琐;

這個方法雖然還是需要不少人工輔助,但總體來說炼蹦,比人工手動分類的效率實在高太多了羡宙。

具體實現(xiàn)

人工選取小樣本

要訓(xùn)練分類器,挑選樣本是必須的掐隐,我從分割的圖片中狗热,隨機(jī)挑出一兩百張钞馁,將它們分類到相應(yīng)的目錄內(nèi):

屏幕快照 2017-04-19 下午10.29.55.png

然后,我需要一個函數(shù)來讀取這些文件夾的數(shù)據(jù)匿刮,方便之后繼續(xù)訓(xùn)練僧凰。

'''讀取圖片數(shù)據(jù)文件,轉(zhuǎn)換成numpy格式熟丸,并保存'''
def maybe_pickle_data(all_image_folder, dest_folder, pickle_file, force=False):
    if os.path.exists(pickle_file) and force==False:
        print("data already pickled, pass")
        return

    image_folders = os.listdir(all_image_folder)
    train_image_data = []
    train_image_label = []

    for folder in image_folders:
        image_folder = os.path.join(all_image_folder, folder)
        if os.path.isdir(image_folder):
            print(image_folder)
            train_image_data.append(load_letter(image_folder))
            train_image_label.append(int(folder))

    # merge all the train data to ndarray
    train_dataset, train_label = merge_datasets(train_image_data, train_image_label)

    # randomize dataset and label
    train_dataset, train_label = randomize(train_dataset, train_label)

    # write to file
    with open(pickle_file, 'wb') as f:
        save = {
            'train_dataset': train_dataset,
            'train_labels': train_label,
        }
        pickle.dump(save, f, pickle.HIGHEST_PROTOCOL)

這個函數(shù)的主要工作是循環(huán)每一個目錄文件夾里的文件训措,將它們依次讀入,變成矩陣形式方便處理光羞,并通過 Pickle 保存成文件绩鸣。

這里主要用了其他幾個函數(shù)的功能:

  1. load_letter(image_folder)   # 讀取一個tag文件夾里的推按文件,并返回所有圖片數(shù)據(jù)的矩陣
    
  2. merge_datasets(train_image_data, train_image_label)   # 將所有類別的圖片數(shù)據(jù)合并成一個大的矩陣樣本數(shù)據(jù)
    

randomize(train_dataset, train_label) # 打亂訓(xùn)練數(shù)據(jù)



下面放點關(guān)鍵函數(shù)的代碼纱兑。

`load_letter()` 函數(shù)代碼如下呀闻,對圖片的讀取用了 `opencv`:

```python
'''讀取同種類別的圖片轉(zhuǎn)換成numpy數(shù)組'''
def load_letter(folder):
  image_files = os.listdir(folder)
  # image_size 為 32
  dataset = np.ndarray(shape=(len(image_files), image_size, image_size), dtype=np.float32)
  num_images = 0
  for image in image_files:
      image_file = os.path.join(folder, image)
      image_data = cv2.imread(image_file, cv2.IMREAD_GRAYSCALE)
      if image_data is None:
          continue
      if image_data.shape != (image_size, image_size):
          raise  Exception("%s  Unexpected image size: %s" % image_file, str(image_data.shape))
      dataset[num_images, :, :] = image_data
      num_images = num_images + 1

  dataset = dataset[0:num_images, :, :]

  return dataset

代碼比較簡單,就不多解釋了萍启。

merge_datasets() 函數(shù)代碼:

def merge_datasets(train_image_data, train_image_label):
    image_number = 0
    for image_datas in train_image_data:
        image_number = image_number + len(image_datas)
    #print(image_number)
    train_dataset, train_labels = make_array(image_number, image_size)

    image_number = 0
    
    # train_image_data 是所有圖片矩陣的list总珠,list每個元素對應(yīng)每個tag圖片的矩陣數(shù)據(jù)
    for label, image_datas in enumerate(train_image_data):
        for image_data in image_datas:
            train_dataset[image_number, :, :] = image_data
            train_labels[image_number] = train_image_label[label]
            image_number = image_number + 1
    #print(train_labels)
    return train_dataset, train_labels

訓(xùn)練分類器

好了,準(zhǔn)備好數(shù)據(jù)勘纯,我們需要訓(xùn)練一個分類器局服。簡單起見灾茁,這里選擇用 SVM官扣,并選用 sklearn 函數(shù)庫。

其實振定,可以直接把圖片矩陣轉(zhuǎn)換成一個向量進(jìn)行訓(xùn)練(32 * 32 —> 1 * 1024)堤结,但我們擁有的數(shù)據(jù)量太少唆迁,這樣效果較差。所以竞穷,我們先提取圖片的 HOG 特征再進(jìn)行訓(xùn)練:

bin_n = 16 # Number of bins

def hog(image):
    gx = cv2.Sobel(image, cv2.CV_32F, 1, 0)
    gy = cv2.Sobel(image, cv2.CV_32F, 0, 1)
    mag, ang = cv2.cartToPolar(gx, gy)
    bins = np.int32(bin_n*ang/(2*np.pi))    # quantizing binvalues in (0...16)
    bin_cells = bins[:16,:16], bins[16:,:16], bins[:16,16:], bins[16:,16:]
    mag_cells = mag[:16,:16], mag[16:,:16], mag[:16,16:], mag[16:,16:]
    hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
    hist = np.hstack(hists)     # hist is a 64 bit vector
    return hist

這個函數(shù)代碼摘自 opencv3 的文檔唐责,想了解代碼,請自行去官網(wǎng)閱讀文檔瘾带。

有了特征之后鼠哥,我們可以正式用 SVM 進(jìn)行訓(xùn)練了:

def train_svm(train_datasets, train_labels):
    x = np.ndarray(shape=(len(train_datasets), 64))
    y = np.ndarray(shape=(len(train_datasets)), dtype=np.int32)

    for index, image in enumerate(train_datasets):
        hist = np.float32(hog(image)).reshape(-1, 64)
        x[index] = hist
        y[index] = train_labels[index]

    model = svm.LinearSVC(C=1.0, multi_class='ovr', max_iter=1000)
    model.fit(x, y)
    return model

這個函數(shù)代碼一樣很簡單,如果看不懂看政,證明你需要熟悉 numpysklearn 函數(shù)庫的用法朴恳。

然后,我們需要選取圖片進(jìn)行預(yù)測分類允蚣∮谟保可以人工挑出個幾百上千張,放在一個預(yù)測目錄內(nèi)嚷兔。同時再開一個目錄文件夾如下:

test folder.png

這個 test 文件夾和先前人工分類的文件夾要分開森渐,因為之后還要人工對這里面的圖片除雜做入。最后,我們遍歷預(yù)測目錄內(nèi)的圖片章母,用 SVM 做預(yù)測母蛛,并將圖片放到預(yù)測結(jié)果對應(yīng)的文件夾里翩剪。

測試函數(shù)代碼如下:

def test_image(image_folder, result_folder, model):
    image_files = os.listdir(image_folder)
    
    for image in image_files:
        image_file = os.path.join(image_folder, image)
        image_data = cv2.imread(image_file, cv2.IMREAD_GRAYSCALE)

        if image_data is None:
            continue

        hist = np.float32(hog(image_data)).reshape(-1, 64)
        pred = model.predict(hist)

        shutil.copy(image_file, os.path.join(result_folder+"/"+str(int(pred)), image))

做完這一步乳怎,我們最關(guān)鍵,同時也是最優(yōu)雅的一步就完成了前弯。之后蚪缀,SVM 也幫不了你了。你需要依次打開每個文件夾恕出,看看里面的圖片有沒有分錯的询枚,然后人工矯正它們,最后把它們歸類到我們一開始挑選樣本分好類的文件夾里浙巫,后者這個文件夾的數(shù)據(jù)表示已經(jīng)分類好的金蜀。

如果運氣好的,這個初步訓(xùn)練好的 SVM 已經(jīng)稍微有點「聰明」了的畴≡ǔ看看我得到的分類結(jié)果:

good result.png

這個準(zhǔn)確率我已經(jīng)很欣慰了,基本上人工挑出幾張分錯的丧裁,剩下的都是同一類了护桦。

當(dāng)然,肯定有分的不好的情況:

bad result.png

對于這種煎娇,就是發(fā)揮你眼力的時候了二庵。基本上缓呛,之后所有的工作都是在這一堆類似的圖片里面找不同催享。當(dāng)然,你要相信這種情況會越來越少哟绊,因為隨著訓(xùn)練樣本逐漸增多因妙,SVM 的訓(xùn)練效果會越來越好。如果越到后面效果越差匿情,程序員兰迫,請你不要懷疑,一定是你的代碼出問題了炬称。

接下來我給出整個程序的主體部分:

if __name__ == '__main__':
    maybe_create_directory_1(image_real_tag_folder)
    maybe_create_directory_1(image_test_folder)
    maybe_pickle_data(image_real_tag_folder, image_dataset_folder,
                      image_dataset_folder + "/data.pickle", force=True)

    f = open(image_dataset_folder + "/data.pickle", 'rb')
    data = pickle.load(f)
    train_datasets = data['train_dataset']
    train_labels = data['train_labels']

    model = train_svm(train_datasets, train_labels)

    print("remove data in " + image_src_folder)
    remove_files(image_src_folder)
    print("copy data to " + image_src_folder + "...")
    copy_src_to_test(original_image_folder, image_src_folder)

    test_image(image_src_folder, image_test_folder, model)

main 函數(shù)就是上面幾個函數(shù)的結(jié)合汁果。之后,我們就是不斷地 run 一遍代碼玲躯,人工除雜精分類据德,再 run 一遍代碼鳄乏,再人工......循環(huán)往復(fù)直到數(shù)據(jù)分類完為止。

總結(jié)

這個方法可以節(jié)省你大量的體力活動棘利,有助于提高逼格橱野。雖然如此,這 80000 個樣本我還是生生花了一天半時間才分完善玫,工作量還是稍微超出預(yù)期水援。如果有小伙伴有逼格更高,更能提高生產(chǎn)效率的方法茅郎,望不吝賜教蜗元!

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市系冗,隨后出現(xiàn)的幾起案子奕扣,更是在濱河造成了極大的恐慌,老刑警劉巖掌敬,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惯豆,死亡現(xiàn)場離奇詭異,居然都是意外死亡奔害,警方通過查閱死者的電腦和手機(jī)楷兽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舀武,“玉大人拄养,你說我怎么就攤上這事∫眨” “怎么了瘪匿?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寻馏。 經(jīng)常有香客問我棋弥,道長,這世上最難降的妖魔是什么诚欠? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任顽染,我火速辦了婚禮,結(jié)果婚禮上轰绵,老公的妹妹穿的比我還像新娘粉寞。我一直安慰自己,他們只是感情好左腔,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布唧垦。 她就那樣靜靜地躺著,像睡著了一般液样。 火紅的嫁衣襯著肌膚如雪振亮。 梳的紋絲不亂的頭發(fā)上巧还,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音坊秸,去河邊找鬼麸祷。 笑死,一個胖子當(dāng)著我的面吹牛褒搔,可吹牛的內(nèi)容都是我干的阶牍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼站超,長吁一口氣:“原來是場噩夢啊……” “哼荸恕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起死相,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咬像,沒想到半個月后算撮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡县昂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年肮柜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒彰。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡审洞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出待讳,到底是詐尸還是另有隱情芒澜,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布创淡,位于F島的核電站痴晦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏琳彩。R本人自食惡果不足惜誊酌,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望露乏。 院中可真熱鬧碧浊,春花似錦、人聲如沸瘟仿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猾骡。三九已至瑞躺,卻和暖如春敷搪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幢哨。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工赡勘, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捞镰。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓闸与,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岸售。 傳聞我的和親對象是個殘疾皇子践樱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

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