背景
機器學習,即Machine Learning驮肉,屬于AI范疇拇勃,是深度學習的父集四苇。
通過如何用機器學習(ML)在15分鐘內(nèi)破解圖片驗證碼的一篇文章,看到了solving-captchas-code-examples項目方咆。作者通過神經(jīng)網(wǎng)絡訓練簡單的model來破解了形如下圖的圖片驗證碼:
正文
根據(jù)該example月腋,打算拿來練練手。當然不能止步于示例中的簡單驗證碼,隨手打開了幾個網(wǎng)頁榆骚,搜集了下如圖所示的彩色的圖片驗證碼:
選定了上述的帶數(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)容手動重命名:
接下來纫塌,按照各個基礎數(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目錄:
上圖即按照各個字符,拆分圖片指蚁,每個子文件夾下菩佑,是該字符的各種樣子。
比如./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模型文件谈山。
三、開始驗證
驗證是宏怔,首先按照第一步開頭的方式奏路,載入圖片。因為第一步我做了些改動來識別彩色的數(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)
效果如圖:
總結
- 通過cv2讀入并轉(zhuǎn)換圖片片任,這部分在blog中有不少示例了,所以用的相對熟練蔬胯。這也是能通過示例example的黑白字母驗證碼引申到彩色的算式驗證碼上的原因所在对供;
- keras是第一次使用,基本上是照搬的示例了氛濒,用的還比較順手产场。雖不是第一次訓練model,但也是第一次這么流程清晰的使用神經(jīng)網(wǎng)絡舞竿;