├── .gitignore ├── README.md └── get_codepng.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | *.o 6 | 7 | # Packages 8 | #*.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | logs 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | # virtual env 39 | .venv 40 | 41 | # Editors 42 | *.swp 43 | .ropeproject 44 | 45 | dataviz/data/ 46 | dlib/shape_predictor_68_face_landmarks.dat 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # captacha 2 | -------------------------------------------------------------------------------- /get_codepng.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import sys 3 | import os 4 | import time 5 | import numpy as np 6 | import cv2 7 | 8 | TRAIN_DIR = "train" 9 | TEST_DIR = "test" 10 | 11 | # convert contours to boxes 12 | # each box is a rectangle consisting of 4 points 13 | # if there is connected characters, split the contour 14 | def get_rect_box(contours): 15 | ws = [] 16 | valid_contours = [] 17 | for contour in contours: 18 | x, y, w, h = cv2.boundingRect(contour) 19 | if w < 7: 20 | continue 21 | valid_contours.append(contour) 22 | ws.append(w) 23 | 24 | w_min = min(ws) 25 | w_max = max(ws) 26 | 27 | result = [] 28 | if len(valid_contours) == 4: 29 | for contour in valid_contours: 30 | x, y, w, h = cv2.boundingRect(contour) 31 | box = np.int0([[x,y], [x+w,y], [x+w,y+h], [x,y+h]]) 32 | result.append(box) 33 | elif len(valid_contours) == 3: 34 | for contour in valid_contours: 35 | x, y, w, h = cv2.boundingRect(contour) 36 | if w == w_max: 37 | box_left = np.int0([[x,y], [x+w/2,y], [x+w/2,y+h], [x,y+h]]) 38 | box_right = np.int0([[x+w/2,y], [x+w,y], [x+w,y+h], [x+w/2,y+h]]) 39 | result.append(box_left) 40 | result.append(box_right) 41 | else: 42 | box = np.int0([[x,y], [x+w,y], [x+w,y+h], [x,y+h]]) 43 | result.append(box) 44 | elif len(valid_contours) == 2: 45 | for contour in valid_contours: 46 | x, y, w, h = cv2.boundingRect(contour) 47 | if w == w_max and w_max >= w_min * 2: 48 | box_left = np.int0([[x,y], [x+w/3,y], [x+w/3,y+h], [x,y+h]]) 49 | box_mid = np.int0([[x+w/3,y], [x+w*2/3,y], [x+w*2/3,y+h], [x+w/3,y+h]]) 50 | box_right = np.int0([[x+w*2/3,y], [x+w,y], [x+w,y+h], [x+w*2/3,y+h]]) 51 | result.append(box_left) 52 | result.append(box_mid) 53 | result.append(box_right) 54 | elif w_max < w_min * 2: 55 | box_left = np.int0([[x,y], [x+w/2,y], [x+w/2,y+h], [x,y+h]]) 56 | box_right = np.int0([[x+w/2,y], [x+w,y], [x+w,y+h], [x+w/2,y+h]]) 57 | result.append(box_left) 58 | result.append(box_right) 59 | else: 60 | box = np.int0([[x,y], [x+w,y], [x+w,y+h], [x,y+h]]) 61 | result.append(box) 62 | elif len(valid_contours) == 1: 63 | contour = valid_contours[0] 64 | x, y, w, h = cv2.boundingRect(contour) 65 | box0 = np.int0([[x,y], [x+w/4,y], [x+w/4,y+h], [x,y+h]]) 66 | box1 = np.int0([[x+w/4,y], [x+w*2/4,y], [x+w*2/4,y+h], [x+w/4,y+h]]) 67 | box2 = np.int0([[x+w*2/4,y], [x+w*3/4,y], [x+w*3/4,y+h], [x+w*2/4,y+h]]) 68 | box3 = np.int0([[x+w*3/4,y], [x+w,y], [x+w,y+h], [x+w*3/4,y+h]]) 69 | result.extend([box0, box1, box2, box3]) 70 | elif len(valid_contours) > 4: 71 | for contour in valid_contours: 72 | x, y, w, h = cv2.boundingRect(contour) 73 | box = np.int0([[x,y], [x+w,y], [x+w,y+h], [x,y+h]]) 74 | result.append(box) 75 | result = sorted(result, key=lambda x: x[0][0]) 76 | return result 77 | 78 | # process image including denoise, thresholding 79 | def process_im(im): 80 | rows, cols, ch = im.shape 81 | im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 82 | ret, im_inv = cv2.threshold(im_gray,127,255,cv2.THRESH_BINARY_INV) 83 | kernel = 1/16*np.array([[1,2,1], [2,4,2], [1,2,1]]) 84 | im_blur = cv2.filter2D(im_inv,-1,kernel) 85 | ret, im_res = cv2.threshold(im_blur,127,255,cv2.THRESH_BINARY) 86 | return im_res 87 | 88 | # split captcha code into single characters 89 | def split_code(filepath): 90 | filename = filepath.split("/")[-1] 91 | filename_ts = filename.split(".")[0] 92 | im = cv2.imread(filepath) 93 | im_res = process_im(im) 94 | 95 | im2, contours, hierarchy = cv2.findContours(im_res, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 96 | 97 | boxes = get_rect_box(contours) 98 | if len(boxes) != 4: 99 | print(filepath) 100 | 101 | for box in boxes: 102 | cv2.drawContours(im, [box], 0, (0,0,255),2) 103 | roi = im_res[box[0][1]:box[3][1], box[0][0]:box[1][0]] 104 | roistd = cv2.resize(roi, (30, 30)) 105 | timestamp = int(time.time() * 1e6) 106 | filename = "{}.jpg".format(timestamp) 107 | filepath = os.path.join("char", filename) 108 | cv2.imwrite(filepath, roistd) 109 | 110 | #cv2.imshow("image", im) 111 | #cv2.waitKey(0) 112 | #cv2.destroyAllWindows() 113 | 114 | # split all captacha codes in training set 115 | def split_all(): 116 | files = os.listdir(TRAIN_DIR) 117 | for filename in files: 118 | filename_ts = filename.split(".")[0] 119 | patt = "label/{}_*".format(filename_ts) 120 | saved_chars = glob.glob(patt) 121 | if len(saved_chars) == 4: 122 | print("{} done".format(filepath)) 123 | continue 124 | filepath = os.path.join(TRAIN_DIR, filename) 125 | split_code(filepath) 126 | 127 | # label data in training set 128 | # input character manually for each image 129 | def label_data(): 130 | files = os.listdir("char") 131 | for filename in files: 132 | filename_ts = filename.split(".")[0] 133 | patt = "label/{}_*".format(filename_ts) 134 | saved_num = len(glob.glob(patt)) 135 | if saved_num == 1: 136 | print("{} done".format(patt)) 137 | continue 138 | filepath = os.path.join("char", filename) 139 | im = cv2.imread(filepath) 140 | cv2.imshow("image", im) 141 | key = cv2.waitKey(0) 142 | if key == 27: 143 | sys.exit() 144 | if key == 13: 145 | continue 146 | char = chr(key) 147 | filename_ts = filename.split(".")[0] 148 | outfile = "{}_{}.jpg".format(filename_ts, char) 149 | outpath = os.path.join("label", outfile) 150 | cv2.imwrite(outpath, im) 151 | 152 | def analyze_label(): 153 | files = os.listdir("label") 154 | label_count = {} 155 | for filename in files: 156 | label = filename.split(".")[0].split("_")[1] 157 | label_count.setdefault(label, 0) 158 | label_count[label] += 1 159 | print(label_count) 160 | 161 | # load all data in training set 162 | def load_data(): 163 | filenames = os.listdir("label") 164 | samples = np.empty((0, 900)) 165 | labels = [] 166 | for filename in filenames: 167 | filepath = os.path.join("label", filename) 168 | label = filename.split(".")[0].split("_")[-1] 169 | labels.append(label) 170 | im = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE) 171 | sample = im.reshape((1, 900)).astype(np.float32) 172 | samples = np.append(samples, sample, 0) 173 | samples = samples.astype(np.float32) 174 | unique_labels = list(set(labels)) 175 | unique_ids = list(range(len(unique_labels))) 176 | label_id_map = dict(zip(unique_labels, unique_ids)) 177 | id_label_map = dict(zip(unique_ids, unique_labels)) 178 | label_ids = list(map(lambda x: label_id_map[x], labels)) 179 | label_ids = np.array(label_ids).reshape((-1, 1)).astype(np.float32) 180 | return [samples, label_ids, id_label_map] 181 | 182 | # identify captcha image 183 | def get_code(im): 184 | [samples, label_ids, id_label_map] = load_data() 185 | model = cv2.ml.KNearest_create() 186 | model.train(samples, cv2.ml.ROW_SAMPLE, label_ids) 187 | 188 | im_res = process_im(im) 189 | im2, contours, hierarchy = cv2.findContours(im_res, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 190 | 191 | boxes = get_rect_box(contours) 192 | if len(boxes) != 4: 193 | print("cannot get code") 194 | 195 | result = [] 196 | for box in boxes: 197 | roi = im_res[box[0][1]:box[3][1], box[0][0]:box[1][0]] 198 | roistd = cv2.resize(roi, (30, 30)) 199 | sample = roistd.reshape((1, 900)).astype(np.float32) 200 | ret, results, neighbours, distances = model.findNearest(sample, k = 3) 201 | label_id = int(results[0,0]) 202 | label = id_label_map[label_id] 203 | result.append(label) 204 | return result 205 | 206 | # identify captcha image in test set 207 | def test_data(): 208 | test_files = os.listdir("test") 209 | total = 0 210 | correct = 0 211 | for filename in test_files: 212 | filepath = os.path.join("test", filename) 213 | im = cv2.imread(filepath) 214 | preds = get_code(im) 215 | chars = filename.split(".")[0] 216 | print(chars, preds) 217 | for i in range(len(chars)): 218 | if chars[i] == preds[i]: 219 | correct += 1 220 | total += 1 221 | print(correct/total) 222 | 223 | if __name__ == "__main__": 224 | test_data() 225 | --------------------------------------------------------------------------------