Skip to content
This repository was archived by the owner on Nov 18, 2022. It is now read-only.

Commit 7616be7

Browse files
committed
Update to add support for captcha with english letter, implement 5/6 digits classifier and data augmentation.
1 parent 7d6a95d commit 7616be7

40 files changed

+963
-216
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,4 @@ venv.bak/
113113
/data
114114
/test
115115
/logs
116+
/developing

README.md

Lines changed: 283 additions & 96 deletions
Large diffs are not rendered by default.

captcha_gen.py

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
from random import randint
33
import csv
44
import numpy as np
5-
FONTPATH = ["./data/times-bold.ttf", "./data/courier-bold.ttf"]
5+
FONTPATH = ["./data/font/times-bold.ttf", "./data/font/courier-bold.ttf"]
6+
ENGSTR = "ABCDEFGHJKLMNPQRSTUVWXYZ" # 沒有O和I
7+
LETTERSTR = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ"
68

79

810
class rect:
@@ -41,27 +43,35 @@ def draw(self, image, overlay):
4143

4244

4345
class captchatext:
44-
def __init__(self, priority, offset):
45-
self.number = randint(0, 9)
46+
def __init__(self, priority, offset, captchalen, engletter, ENGNOLIMIT):
47+
self.engletter = engletter
48+
if ENGNOLIMIT:
49+
engletter = True if randint(1, 34) <= 24 else False
50+
if engletter:
51+
self.letter = ENGSTR[randint(0, len(ENGSTR) - 1)]
52+
else:
53+
self.letter = str(randint(0, 9))
4654
self.color = [randint(10, 140) for _ in range(3)]
4755
self.angle = randint(-55, 55)
4856
self.priority = priority
4957
self.offset = offset
5058
self.next_offset = 0
59+
self.captchalen = captchalen
5160

5261

5362
def draw(self, image):
5463
color = (self.color[0], self.color[1], self.color[2], 255)
5564
font = ImageFont.truetype(FONTPATH[randint(0, 1)], randint(25, 27) * 10)
56-
text = Image.new("RGBA", (150, 300), (0, 0, 0, 0))
65+
text = Image.new("RGBA", (font.getsize(self.letter)[0], 300), (0, 0, 0, 0))
5766
textdraw = ImageDraw.Draw(text)
58-
textdraw.text((0, 0), str(self.number), font=font, fill=color)
67+
textdraw.text((0, 0), self.letter, font=font, fill=color)
5968
text = text.rotate(self.angle, expand=True)
6069
text = text.resize((int(text.size[0] / 10), int(text.size[1] / 10)))
61-
base = int(self.priority * (200 / 6))
62-
rand_min = (offset - base - 2) if (offset - base - 2) >= -15 else -15
70+
base = int(self.priority * (200 / self.captchalen))
71+
rand_min = (self.offset - base - 4) if (self.offset - base - 4) >= -15 else -15
6372
rand_min = 0 if self.priority == 0 else rand_min
64-
rand_max = (33 - text.size[0]) if self.priority == 5 else (33 - text.size[0] + 10)
73+
avg_dp = int(200 / self.captchalen)
74+
rand_max = (avg_dp - text.size[0]) if self.priority == self.captchalen - 1 else (avg_dp - text.size[0] + 10)
6575
try:
6676
displace = randint(rand_min, rand_max)
6777
except:
@@ -71,34 +81,43 @@ def draw(self, image):
7181
image.paste(text, location, text)
7282

7383

74-
outputcsv = open('./data/train_set/train.csv', 'w', encoding = 'utf8', newline = '')
75-
numberlist = []
76-
status = 1
77-
for index in range(1, 50001, 1):
78-
numberstr = ""
79-
bgcolor = [randint(180, 250) for _ in range(3)]
80-
captcha = Image.new('RGBA', (200, 60), (bgcolor[0], bgcolor[1], bgcolor[2], 255))
81-
rectlist = [rect() for _ in range(32)]
82-
for obj in rectlist:
83-
obj.draw(image=captcha, overlay=False)
84+
def generate(GENNUM, SAVEPATH, ENGP=25, FIVEP=0, ENGNOLIMIT=False, filename="train"):
85+
captchacsv = open(SAVEPATH + "captcha_{:s}.csv".format(filename), 'w', encoding = 'utf8', newline = '')
86+
lencsv = open(SAVEPATH + "len_{:s}.csv".format(filename), 'w', encoding = 'utf8', newline = '')
87+
letterlist = []
88+
lenlist = []
89+
for index in range(1, GENNUM + 1, 1):
90+
captchastr = ""
91+
captchalen = 5 if randint(1, 100) <= FIVEP else 6
92+
engat = randint(0, captchalen - 1) if randint(1, 100) <= ENGP else -1
93+
bgcolor = [randint(180, 250) for _ in range(3)]
94+
captcha = Image.new('RGBA', (200, 60), (bgcolor[0], bgcolor[1], bgcolor[2], 255))
95+
rectlist = [rect() for _ in range(32)]
96+
for obj in rectlist:
97+
obj.draw(image=captcha, overlay=False)
98+
offset = 0
99+
for i in range(captchalen):
100+
newtext = captchatext(i, offset, captchalen, (True if engat == i else False), ENGNOLIMIT)
101+
newtext.draw(image=captcha)
102+
offset = newtext.next_offset
103+
captchastr += str(newtext.letter)
104+
letterlist.append([str(index), captchastr])
105+
lenlist.append([str(index), captchalen])
106+
for obj in rectlist:
107+
obj.draw(image=captcha, overlay=True)
108+
captcha.convert("RGB").save(SAVEPATH + str(index).zfill(len(str(GENNUM))) + ".jpg", "JPEG")
109+
writer = csv.writer(captchacsv)
110+
writer.writerows(letterlist)
111+
writer = csv.writer(lencsv)
112+
writer.writerows(lenlist)
113+
captchacsv.close()
114+
lencsv.close()
84115

85-
offset = 0
86-
for i in range(6):
87-
newtext = captchatext(i, offset)
88-
newtext.draw(image=captcha)
89-
offset = newtext.next_offset
90-
numberstr += str(newtext.number)
91-
numberlist.append([str(index), numberstr])
92116

93-
for obj in rectlist:
94-
obj.draw(image=captcha, overlay=True)
95-
96-
captcha.convert("RGB").save("./data/train_set/" + str(index) + ".jpg", "JPEG")
97-
98-
if (index / 50000) >= (status * 0.01):
99-
print("...." + str(status) + "%")
100-
status += 1
101-
102-
writer = csv.writer(outputcsv)
103-
writer.writerows(numberlist)
104-
outputcsv.close()
117+
if __name__ == "__main__":
118+
generate(50000, "./data/56_imitate_train_set/", ENGP=100, FIVEP=50, ENGNOLIMIT=True, filename="train")
119+
generate(10240, "./data/56_imitate_vali_set/", ENGP=100, FIVEP=50, ENGNOLIMIT=True, filename="vali")
120+
generate(50000, "./data/5_imitate_train_set/", ENGP=100, FIVEP=100, ENGNOLIMIT=True, filename="train")
121+
generate(10240, "./data/5_imitate_vali_set/", ENGP=100, FIVEP=100, ENGNOLIMIT=True, filename="vali")
122+
generate(50000, "./data/6_imitate_train_set/", ENGP=100, FIVEP=0, ENGNOLIMIT=True, filename="train")
123+
generate(10240, "./data/6_imitate_vali_set/", ENGP=100, FIVEP=0, ENGNOLIMIT=True, filename="vali")

captcha_scrawl.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import shutil
2+
import requests
3+
import time
4+
SAVEPATH = "./data/manual_label/"
5+
url = 'http://railway1.hinet.net/ImageOut.jsp'
6+
for i in range(1, 3000):
7+
response = requests.get(url, stream=True)
8+
with open(SAVEPATH + str(i) + '.jpg', 'wb') as out_file:
9+
shutil.copyfileobj(response.raw, out_file)
10+
del response
11+
time.sleep(0.5)

data_augment.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from keras.preprocessing.image import ImageDataGenerator
2+
from PIL import Image
3+
import numpy as np
4+
import csv
5+
6+
outputcsv = open('./data/6_real_train_set/captcha_train.csv', 'w', encoding = 'utf8', newline = '') # 輸出csv
7+
inputcsv = open('./data/manual_label/captcha_vali.csv', 'r', encoding = 'utf8')
8+
data = [np.array(Image.open('./data/manual_label/' + row[0] + ".jpg")) for row in csv.reader(inputcsv) if len(row[1]) == 6] # 只讀答案是6位的
9+
inputcsv = open('./data/manual_label/captcha_vali.csv', 'r', encoding = 'utf8')
10+
oldanswer = [row[1] for row in csv.reader(inputcsv) if len(row[1]) == 6] # 只讀答案是6位的
11+
answer = []
12+
datagen = ImageDataGenerator(rotation_range=5,shear_range=0.2,zoom_range=0.05,fill_mode='nearest')
13+
index, augmentindex, oldanswerindex = 0, 0, 0
14+
for img in data:
15+
for batch in datagen.flow(np.asarray([img]), batch_size=1):
16+
index += 1
17+
augmentindex += 1
18+
batch = batch.reshape((60,200,3))
19+
Image.fromarray(np.uint8(batch)).convert("RGB").save("./data/6_real_train_set/" + str(index) + ".jpg", "JPEG")
20+
answer.append((str(index), oldanswer[oldanswerindex]))
21+
if augmentindex >= 50: # 每張產生50個
22+
oldanswerindex += 1
23+
augmentindex = 0
24+
break
25+
csv.writer(outputcsv).writerows(answer)

demo_cnn.py

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,87 @@
11
from keras.models import load_model
22
from keras.models import Model
3-
from PIL import Image, ImageEnhance
3+
from keras import backend as K
4+
from PIL import Image
45
import numpy as np
6+
import os
7+
import csv
8+
LETTERSTR = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ"
59

6-
validate_data = np.stack([(np.array(Image.open("./data/8dataset/8-" + str(index) +".jpg")))/255.0 for index in range(1, 11, 1)])
7-
model = load_model("./data/cnn_model.h5")
8-
prediction = model.predict(validate_data)
9-
resultlist = ["" for _ in range(10)]
10-
for predict in prediction:
11-
for index in range(10):
12-
resultlist[index] += str(np.argmax(predict[index]))
13-
for result in resultlist:
14-
print(result)
10+
11+
def toonehot(text):
12+
labellist = []
13+
for letter in text:
14+
onehot = [0 for _ in range(34)]
15+
num = LETTERSTR.find(letter)
16+
onehot[num] = 1
17+
labellist.append(onehot)
18+
return labellist
19+
20+
21+
print("Loading test data...")
22+
testcsv = open('./data/manual_label/captcha_test.csv', 'r', encoding = 'utf8')
23+
test_data = np.stack([np.array(Image.open("./data/manual_label/" + row[0] + ".jpg"))/255.0 for row in csv.reader(testcsv)])
24+
testcsv = open('./data/manual_label/captcha_test.csv', 'r', encoding = 'utf8')
25+
test_label = [row[1] for row in csv.reader(testcsv)]
26+
print("Loading model...")
27+
K.clear_session()
28+
model = None
29+
model5 = load_model("./data/model/imitate_5_model.h5")
30+
model6 = load_model("./data/model/imitate_6_model.h5")
31+
model56 = load_model("./data/model/real_56_model.h5")
32+
print("Predicting...")
33+
prediction56 = [6 if arr[0] > 0.5 else 5 for arr in model56.predict(test_data)] # 5/6碼分類
34+
prediction5 = model5.predict(test_data) # 5碼
35+
prediction6 = model6.predict(test_data) # 6碼
36+
37+
# 以下計算各個模型各個字元辨識率等等,有點亂,以後有空再整理
38+
total, total5, total6 = len(prediction56), 0, 0
39+
correct5, correct6, correct56, correct = 0, 0, 0, 0
40+
correct5digit, correct6digit = [0 for _ in range(5)], [0 for _ in range(6)]
41+
totalalpha, correctalpha = len([1 for ans in test_label for char in ans if char.isalpha()]), 0
42+
for i in range(total):
43+
checkcorrect = True
44+
if prediction56[i] == len(test_label[i]):
45+
correct56 += 1
46+
else:
47+
checkcorrect = False
48+
if prediction56[i] == 5:
49+
total5 += 1
50+
allequal = True
51+
for char in range(5):
52+
if LETTERSTR[np.argmax(prediction5[char][i])] == test_label[i][char]:
53+
correct5digit[char] += 1
54+
correctalpha += 1 if LETTERSTR[np.argmax(prediction5[char][i])].isalpha() else 0
55+
else:
56+
allequal = False
57+
if allequal:
58+
correct5 += 1
59+
else:
60+
checkcorrect = False
61+
else:
62+
total6 += 1
63+
allequal = True
64+
for char in range(6):
65+
if LETTERSTR[np.argmax(prediction6[char][i])] == test_label[i][char]:
66+
correct6digit[char] += 1
67+
correctalpha += 1 if LETTERSTR[np.argmax(prediction6[char][i])].isalpha() else 0
68+
else:
69+
allequal = False
70+
if allequal:
71+
correct6 += 1
72+
else:
73+
checkcorrect = False
74+
if checkcorrect:
75+
correct += 1
76+
77+
print("5 or 6 model acc:{:.4f}%".format(correct56/total*100)) # 5/6模型acc
78+
print("---------------------------")
79+
print("5digits model acc:{:.4f}%".format(correct5/total5*100)) # 5模型acc
80+
for i in range(5):
81+
print("digit{:d} acc:{:.4f}%".format(i+1, correct5digit[i]/total5*100)) # 5模型各字元acc
82+
print("---------------------------")
83+
print("6digits model acc:{:.4f}%".format(correct6/total6*100)) # 6模型acc
84+
for i in range(6):
85+
print("digit{:d} acc:{:.4f}%".format(i+1, correct6digit[i]/total6*100)) # 6模型各字元acc
86+
print("---------------------------")
87+
print("alpha acc:{:.4f}%".format(correctalpha/totalalpha*100)) # 整體英文字acc

demo_online.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from selenium import webdriver
2+
from selenium.common.exceptions import TimeoutException
3+
from selenium.webdriver.common.by import By
4+
from selenium.webdriver.support.ui import WebDriverWait
5+
from selenium.webdriver.support import expected_conditions as EC
6+
import numpy as np
7+
from PIL import Image
8+
from keras.models import load_model, Model
9+
import time
10+
import random
11+
IDNumber = "X123456789" # 填入你的身分證字號
12+
model = None
13+
model5 = load_model("./data/model/imitate_5_model.h5") # 辨識5碼的Model
14+
model6 = load_model("./data/model/imitate_6_model.h5") # 辨識6碼的Model
15+
model56 = load_model("./data/model/real_56_model.h5") # 辨識是5碼or6碼的Model
16+
LETTERSTR = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ"
17+
driver = webdriver.Chrome("./data/chromedriver.exe") # chromedriver 路徑
18+
correct, wrong = 0, 0
19+
20+
for _ in range(1000):# 跑1000次
21+
driver.get('http://railway1.hinet.net/Foreign/TW/ecsearch.html')
22+
id_textbox = driver.find_element_by_id('person_id')
23+
id_textbox.send_keys(IDNumber)
24+
button = driver.find_element_by_css_selector('body > div.container > div.row.contents > div > form > div > div.col-xs-12 > button')
25+
button.click()
26+
driver.save_screenshot('tmp.png')
27+
location = driver.find_element_by_id('idRandomPic').location
28+
x, y = location['x'] + 5, location['y'] + 5
29+
img = Image.open('tmp.png')
30+
captcha = img.crop((x, y, x+200, y+60))
31+
captcha.convert("RGB").save('captcha.jpg', 'JPEG')
32+
# check is 5 or 6 digits
33+
p56 = model56.predict(np.stack([np.array(Image.open('captcha.jpg'))/255.0]))[0][0]
34+
if p56 > 0.5:
35+
model = model6
36+
else:
37+
model = model5
38+
prediction = model.predict(np.stack([np.array(Image.open('captcha.jpg'))/255.0]))
39+
answer = ""
40+
for predict in prediction:
41+
answer += LETTERSTR[np.argmax(predict[0])]
42+
captcha_textbox = driver.find_element_by_id('randInput')
43+
captcha_textbox.send_keys(answer)
44+
driver.find_element_by_id('sbutton').click()
45+
if "亂數號碼錯誤" in driver.page_source:
46+
wrong += 1
47+
else:
48+
correct += 1
49+
print("{:.4f}% (Correct{:d}-Wrong{:d})".format(correct/(correct+wrong)*100, correct, wrong))
50+
time.sleep(3)

readme_img/captcha_sample1.jpg

3.13 KB
Loading

readme_img/captcha_sample2.jpg

3.22 KB
Loading

readme_img/captcha_sample3.jpg

3.56 KB
Loading

readme_img/captcha_sample4.jpg

3.47 KB
Loading
File renamed without changes.
File renamed without changes.
File renamed without changes.

readme_img/csv.png

16.4 KB
Loading

readme_img/dataaugmentation.png

737 KB
Loading

readme_img/generate.png

157 KB
Loading

readme_img/head.gif

1.4 MB
Loading

readme_img/imitate6.png

288 KB
Loading

readme_img/imitate6_tensorboard.png

98.3 KB
Loading

readme_img/imitate_result.png

13 KB
Loading
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

readme_img/old/5.PNG

51.3 KB
Loading

readme_img/old/6.PNG

49.5 KB
Loading

readme_img/old/7.PNG

48.3 KB
Loading
File renamed without changes.
File renamed without changes.

train_cnn.py

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)