machine learning, image

Breaking Wechall Crackcha using tensorflow(keras) 1. collecting the data.

qkqhxla1 2018. 12. 31. 19:26

예전. 대략 3년전에 http://qkqhxla1.tistory.com/382?category=602369 글에서 wechall 의 캡챠를 깨기 위해 많은 노력을 했었다. 그러다 도저히 방법론 자체를 잘 모르겠어서... 포기했다. 


최근에 tensorflow를 공부하던 도중 keras를 사용해서 숫자 인식하는 코드가 많은것을 보고.. tensorflow도 공부해볼겸 다시 도전했다. 바로 이전글에서 이것저것 삽질하면서, 또 스스로 데이터를 수집하고 트레이닝을 시켜보면서 깨달은건 어떤 알고리즘을 사용하느냐보다는, 좋은 데이터가 '많이' 들어갔을때 좋은 결과를 낸다는거다. 


엄청난 삽질을 해가며 데이터를 가공하고, 넣고, 예측 테스트를 했는데 예측률이 90%가 넘었을때의 기쁨은 진짜 어떤걸로도 표현할 수가 없을것 같다. 최근 몇달동안중 프로그래밍으로 이뤄낸 가장 기뻤던 성과인것 같다. wechall의 crackcha문제의 링크는 아래에 있다.(안보일시 로그인하면됨.)


https://www.wechall.net/challenge/crackcha/index.php


문제 분석을 다시한번 하자면 

요러한 이미지가 나오는데, 이걸 인식해서 

https://www.wechall.net/challenge/crackcha/answer.php?answer=DZUHZ 처럼 보내주면 된다. 30분안에 468개의 캡챠를 깨야 한다.(틀려도 상관없음) 1800초에 468개니 약 3.8초에 1개씩 잘 보내면 된다. 문제는 1문제당 문자가 5개라는거다. 3.8초당 한번씩 5개를 동시에 정확하게 인식해야한다는게 어렵다. 이미 3년전에 엄청난 삽질을 하고도 실패했기에, 장기적이지만 정확도와 효율성을 높여서 문제에 접근하기로 결심했다. 


첫번째 할 일은 데이터 수집이다. 저번에는 생각도 하기싫지만 반 수작업으로 어거지로 데이터를 모았었다. 이번에는 데이터 수집 자동화부터 제대로 만들어보기로 결심했다. 


첫번째로 Wechall에서 reset이나 답을 보내거나 문제를 다운받기위해 SiteHandler이라는 클래스를 만들었다. 아래의 WC에는 로그인후 본인의 쿠키값을 넣어준다.init_site()에서는 reset.php를 호출해서 문제 진행상황을 리셋시켜준다.

download_photo에서는 문제 이미지를 다운로드해서 저장한다. get_right_answer에서는 일부러 틀린 답(aaaaa)를 보내 맞는 답을 가져온다. 틀릴 경우 Your answer (NXBNN) was wrong. Correct would have been NXHNN.와 같은 포맷으로 답이 출력되기에, 맞는 부분을 정규식이나 그런걸로 잘라서 가져오면 된다. send_predicted_answer에서는 나중에 예측 후 맞는 값을 보낼거다.


그다음으로 bfsDenoiser이다. 나중에 돌릴때는 속도가 더 중요해서 빼버렸고, 빼도 결과 차이가 크게 안났지만.. 데이터 수집할때 이 코드가 있으므로 그냥 썼다. 첫번째로 grouping_with_bfs에서는 bfs알고리즘을 사용해서 점들을 그룹핑한다. 위,아래,좌,우,대각선 4방향안에 점이 있으면 같은 그룹으로 인식한다. 간혹 이미지를 preprocess한 후에 이미지 끝부분에 점같은게 있어서.. 이걸 지워버리려고 이 방법을 썼다. denoising_small_dot에서는 가장 큰 그룹의 이미지가 중앙에 있는 숫자라고 가정하고, 나머지 작은 그룹들은 그냥 배경과 같은 0으로 칠해버렸다. 


그리고 메인 클래스인 ImageDataMaker이다. 생성자에서 위의 SiteHandler()을 만들어두고 쓴다. make_empty_image에서는 새로 저장할 경로의 이미지 이름을 받아서 42*row, 42*column의 크기로 만든다. 42*42로 만드는 이유는 Wechall의 한 글자가 42*42의 크기이기 때문이다.

get_coordinate에서는 이미지를 수집해서 종류별로 저장할건데,(A는 A대로, B는 B대로 등.) 첫번째 자리에 이미 이미지가 있으면 그 다음 자리에 이미지를 붙여야 하기 때문에 좌표를 저장해 둔다. 좌표는 파일로 쓰여지며, get_coordinate에서는 이미지를 쓸 좌표의 row와 column을 가져온다. is_data_collecting_finish에서는 모든 대문자를 돌면서 이미지의 좌표가 마지막 라인까지 다 쓰여졌는지를 확인한다. 모든 A~Z까지 이미지가 저장되었으면 True를 리턴한다.

write_character_to_empty_page에서는 해당 이미지의 좌표에 다운로드한 이미지를 그린다.

image_denoising, image_thresholding, preprocess_image함수는 이전에 삽질했던 링크의 preprocessing을 그대로 가져와서 클래스화했다. 

나머지 함수는 main과 같이 설명하겠다.


main()함수에서는 Wechall사이트를 초기화한다. 그러고 위에서 설명한 is_data_collecting_finish.즉 모든 알파벳 데이터가 원하는 만큼 수집되기전까지 반복문이 계속 돈다. 

download_and_process_image에서는 이미지를 다운로드받아 전처리를 하고 전처리에 성공하면 전처리된 이미지를 저장해둔다. 그후 이 이미지에 맞는 답을 가져온 후(get_right_answer) 좌표를 가져와서 하나하나 페이지에 그린다. last_row, last_column이 20,100이므로 각각 알파벳을 20행 * 100열개(2000개)만큼 가져온다는 뜻이다. ./data폴더에 저장되며, 다 저장된 후 아래처럼 저장된다. (다 모으는데 이틀 걸렸다..)



예시로 A.png를 열어보면 다음과 같다. row, column은 위에서 설정한대로 20, 100. 즉 2000개다.


A.txt는 마지막까지 갔으므로 20, 0으로 기록되어 있다. 이 이미지들은 전부 모아주는 함수가 combine_all_images이다. 저 함수를 실행시키면 A부터 Z까지 모든 이미지를 합친후 새로 그린다. 그린 후 아래와 같다. (일부만 캡쳐해서 올림.)

이제 내가 원하는대로 조립했으니 이 이미지를 가져와서 가공하면 된다