AI-機器學習-森林隨機算法-驗證碼實戰實例

注意

本文純粹給未接觸過機器學習的同學看到,高手請繞路

背景

本人接觸了一個項目,需要登錄web系統。使用robotframework + python 做自動化測試,發現開發同學設置了驗證碼,首頁面根本就沒法登錄啊,而且還是變形的,使用基本的python庫識別效果不好。於是本人查詢了多位大俠的網站和文章,發現識別沒有這麼難,只是寫的還不夠白,所以我這次寫的白一點,讓大家看的更清楚一些。

本文只識別與上述這種不太複雜的圖片,太複雜的,特徵向量選取就比較麻煩

另外,

1、本文不介紹理論,直接使用爲主,快速入門爲目標

2、本文未考慮圖像干擾點清理的過程

涉及的技術內容概念

樣本:

對於本文來說,就是大量圖片,和未來待識別的內容完全一致的東西,根據這些圖片的內容,經過學習,生成學習後的model,用於後續的,預測,例如:

驗證集(預測集)

和上述完全一致,是一類東西,上述的用於學習model,輸出model文件後,那這個在用於預測下屬的圖片

 

最小樣本集:

實際上,機器學習,不是用上面的學習的,是要經過處理的,最終目標是識別某單個字符圖片的字符是什麼內容

觀察上述的樣本,有兩個明顯可以看出來的問題:

例如  一個圖片內容是“3456”,最終需要分解成四張小圖片去訓練

樣本來源(學習集和驗證集)

各顯其能吧,我是找開發要了他們原始的驗證碼和圖片生成代碼

樣本預處理(圖片數據清晰)

對於字符識別處理,基本都採用灰度處理,把字符變成了最終0和255兩個值,其餘的RGB信息全部去除

樣本特徵:

針對這樣的一個圖片,要數字化,才能去調用機器學習算法學習

一般使用向量標識

data = [[1,2,3,4,5,6,7,8,9,0], [1,2,3,4,5,6,7,8,9,0] ,[1,2,3,4,5,6,7,8,9,0] ,[1,2,3,4,5,6,7,8,9,0]] 

上面是就是一個list,有四個元素,其中每個元素對應於一個10維空間的一個向量,每個向量描述一個字符

上述向量代表的值,沒對應什麼呢,對應於一一映射的另一個list

label = [3,4,5,6]

data和label 是兩個一一對應的list,分別對應圖片,及圖片的字符是什麼內容

 

學習模型(算法)

這個就是大家經常聽到的,學習算法是什麼,本文不講理論,只是讓大家快速用起來

所以使用最簡單的隨機森林算法RandomForestClassifier(),並使用其默認參數,而且會發現,即使默認參數,識別率就相當高

學習完成,會生成一個model文件

輸入是data和label

模型預測

使用上述生成model,就可以

整個過程圖

涉及的全部函數(後續再分解說)

公共函數

read_captcha_image(path)

讀取樣本圖像

get_croped_imgs(img)

我用它把圖片無效部分做了初步裁剪

def get_pure_image(image)

把無效的空格部分做進一步處理

image_transfer_to_gray

圖像二值化

get_feature(image, image_width = 27, image_height = 30)

這部分是圖片特數字化,向量化

詳細代碼和說明如下:

from PIL import Image
import os
import numpy as np
def read_captcha_image(path):
    """
    1、獲取樣本圖像
    2、大量樣本圖片時要用到numpy類型,否則會出現打開太多資源不足的情況
    :param path:
    :return:
    """
    image_array = []
    image_label = []
    file_list = os.listdir(path)    # 獲取captcha文件
    for file in file_list:
        fp = open(path + '/' + file,'rb')
        image = Image.open(fp)   # 打開圖片,以句柄方式
        image = np.array(image)
        fp.close()
        image = Image.fromarray(image)
        file_name = file.split(".")[0]  #獲取文件名,此爲圖片標籤
        image_array.append(image)
        image_label.append(file_name)
    return image_array, image_label
def get_croped_imgs(img):
    """
    按照圖片的特點,進行切割,這個要根據具體的驗證碼來進行工作.
    另外要把無關的數據切割掉
    :param img:單個驗證碼image
    :return:字符list,來源於字符中的圖片
    """
    # 先截斷左側無效的部分(box目前看是[)這樣的關係)
    img = img.crop(box=(12,0, 120, 30))
    child_img_list = []
    for i in range(4):
        x = i * 27
        y = 30
        child_img = img.crop(box=(x,0, x+27, y))
        child_img_list.append(child_img)
    return child_img_list
def get_feature(image, image_width = 27, image_height = 30):
    """
    1、特徵就是數字化
    2、圖片字符特徵有很多種方法數字化,例如黑點佔全部等比例,某個區域的黑點佔全部的比例等等
    本函數的實現方式是,參考了部分網上的部分實現,以及擴充了部分實現
    以每行的黑色點數量,每列的黑色點數量,每圈的點數量(本例12圈),作爲向量
    所以向量維數是(x+y+12)
    :param image (單個圖像)
    :return:feature (單個特徵)
    """
    # 裁剪空白
    image = get_pure_image(image)
    # 標準化圖像格式
    image = image.resize((image_width, image_height))
    #再二值化
    image_list = []
    image_list.append(image)
    image_list = image_transfer_to_gray(image_list, 100)# resize之後還需要二值化
    #取回第一個圖片
    image = image_list[0]
   ###################################################
    feature = []  # 計算特徵
    # 計算行特徵
    for x in range(image_width):
        feature_width = 0
        for y in range(image_height):
            if image.getpixel((x, y)) == 0:
                feature_width += 1
        feature.append(feature_width)
    # 計算列特徵
    for y in range(image_height):
        feature_height = 0
        for x in range(image_width):
            if image.getpixel((x, y)) == 0:
                feature_height += 1
        feature.append(feature_height)

    # 計算圈特徵
    for index in range(12):
        feature_circle = 0
        # 上,下兩條邊
        for y in [index, image_height - index - 1]:
            for x in range(index, image_width - index):
                if image.getpixel((x, y)) == 0:
                    feature_circle += 1
        # # 左,右兩條邊
        for x in [index, image_width - index - 1]:
            for y in range(index, image_height - index):
                if image.getpixel((x, y)) == 0:
                    feature_circle += 1
        feature.append(feature_circle)
    return feature
def get_pure_image(image):
    """
    純粹的裁剪圖片無效的部分
    把單個字符的圖片的無效空白處儘可能出掉,確保字符的上下左右都頂到邊緣
    這樣能提升一定的準確率
    :param image:
    :return:
    """
    dict_coordinate = {'x1': 0,'x2': image.size[0]-1, 'y1': 0,'y2': image.size[1]-1}

    for y in range(0,image.size[1]):
        for x in range(0,image.size[0]):
            pix = image.getpixel((x, y))

            if pix == 0: #等於0位黑色
                dict_coordinate['y1'] = y
                break
        if dict_coordinate['y1'] != 0:
            break
#######################################################################
    for y in range(image.size[1]-1,0,-1 ):
        for x in range (0,image.size[0]):
            pix = image.getpixel((x, y))
            if pix == 0:
                dict_coordinate['y2'] = y + 1
                break
        if dict_coordinate['y2'] != image.size[1]-1:
            break
###############################################################################
    for x in range(0,image.size[0]):  # 遍歷所有像素,將灰度超過閾值的像素轉變爲255(白)
        for y in range(0,image.size[1]):
            pix = image.getpixel((x, y))
            if pix == 0:
                dict_coordinate['x1'] = x
                break
        if dict_coordinate['x1'] != 0:
            break
###############################################################################

    for x in range(image.size[0]-1,0,-1 ):
        for y in range(0,image.size[1]):
            pix = image.getpixel((x, y))
            if pix == 0:
                dict_coordinate['x2'] = x + 1
                break
        if dict_coordinate['x2'] != image.size[0]-1:
            break

    image = image.crop(box=(dict_coordinate['x1'],dict_coordinate['y1'],dict_coordinate['x2'],dict_coordinate['y2']))



    return  image
def image_transfer_to_gray(image_arry, threshold_grey = 100):
    """
    二值化圖片:
    1、因爲圖片的顏色不重要,所以把圖片RGB,變成了一維的數字,由0到255標識
    2、再經過變化,把圖片變成單一的0和255兩個值
    :param image_arry:  圖像list,每個元素爲一副圖像
    :return: image_arry:  二值化後的圖像list
    """
    image_gray = []
    for i, image in enumerate(image_arry):
        image = image.convert('L') # 轉換爲灰度圖像,即RGB 3 維 變爲 1維
        im_sub = Image.new("L", image.size, 255)
        for y in range(image.size[1]): # 遍歷所有像素,將灰度超過閾值的像素轉變爲255(白)
            for x in range(image.size[0]):
                pix = image.getpixel((x, y))
                if int(pix) > threshold_grey:  # 灰度閾值 默認100
                    im_sub.putpixel((x, y), 255)
                else:
                    im_sub.putpixel((x, y), 0)
        image_gray.append(im_sub)
    return image_gray

學習模型主程序

"""
訓練
輸入:圖片
輸出:字符
"""

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import  cross_val_score
from sklearn.externals import joblib
from content_indentify.indentify_common import  *


def trainModel(data, label,model_path):
    """
    參數格式如下:圖片向量list和label list
    data = [[0,0,0],[1,1,1],[2,2,2],[1,1,1],[2,2,2],[3,3,3],[1,1,1],[4,4,4]]
    label = [0,1,2,1,2,3,1,4]
    :param data:
    :param label:
    :return:
    """
    print("開始模型擬合 >>>>>>>>>>>>>>>>>>>>>>")
    model_learn = RandomForestClassifier()
    scores = cross_val_score(model_learn, data, label, cv=10) # 交叉檢驗,計算模型平均準確率
    print("model_learn: ", scores.mean())
    model_learn.fit(data, label) # 擬合模型
    joblib.dump(model_learn, model_path)  # 模型保存到本地,以後預測就用這個了
    print("模型保存完畢,恭喜!")

    return model_learn
def data_generate(src_path, dir_path):
# 獲取目錄下的列表,每個元素,帶有圖片對象list和名字list
    data = [] #圖片向量化
    label = [] #label
    #獲取樣本圖像
    images_info =  read_captcha_image(src_path)
    images = list(images_info[0])
    image_names = images_info[1]
    #分解成四個單獨小圖片
    for index1, image in enumerate (images):
        image_list_cut =  get_croped_imgs(image)  # 把左側空白裁減掉
        for index2 , image in enumerate(image_list_cut):
            temp = []
            temp.append(image)
            # 第一步灰階處理
            image_temp_list = image_transfer_to_gray(temp)
            # 第二步,取特徵值,數字化
            data_to_predict = get_feature(image_temp_list[0])
            data.append(data_to_predict)
            label.append(image_names[index1][index2])
    return data, label



#待學習的樣本的位置
sample = r"C:\Users\user\Pictures\sample"
#圖像樣本數據化
data, label = data_generate(sample,r"C:\Users\user\Pictures\sample_data")
#生成模型並保存,待以後使用
trainModel(data,label,"g:/model")


識別驗證碼主程序

這個前期的讀取圖片,處理圖片,和模型學習完全一致

只是後面,使用已學習的model來預測

from sklearn.externals import joblib
from content_indentify.indentify_common import  *
def test_captcha_sample(path):
    # 獲取目錄下的列表,每個元素,帶有圖片對象list和名字list
    images_info =  read_captcha_image(path)
    total_number = float(len(images_info[1]))
    sucess_number = 0.0
    images = list(images_info[0])
    #分解成四個單獨小圖片
    model = joblib.load("g:/model")
    for index1, image in enumerate (images):
        image_list_cut =  get_croped_imgs(image)  # 只處理文件夾裏的第一張圖片
        data_set =[]
        for index2 , image in enumerate(image_list_cut):
            temp = []
            temp.append(image)
            # 第一步灰階處理
            image_temp_list = image_transfer_to_gray(temp)
            # 第二步,取特徵值
            data_to_predict = get_feature(image_temp_list[0])
            data_set.append(data_to_predict)
            #使用模型進行預測,返回預測值
            target_list = model.predict(data_set)
            target = "".join(target_list)
        if target == images_info[1][index1][0:4]:
            sucess_number += 1.0
    print("success_rate = ",sucess_number/total_number)



path = r"C:\Users\user\Pictures\驗證集"
test_captcha_sample(path)

 

 

最終效果

G:\pics 有三種圖片

執行之後,返回驗證碼實際數據如下

['4aQ3', 'Bd4X', 'CT4m']  即使是默認也很高,在這種字符變形的情況下,識別率在99.8%

 

如果大家想練手的話,我提供一個集合給大家使用:

樣本集和驗證集文件路徑

https://download.csdn.net/download/weixin_41357300/12117690

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章