1 캡챠(Captcha)

Captcha를 사용해서 RPA 봇은 막을 수 있지만, 값싼 노동력을 이용하여 mechanical turk와 같은 서비스를 사용할 경우 우회도 가능하고 RPA 봇에 딥러닝 기법을 적용시켜 엔진을 장착시킬 경우 역시 무력화시킬 수 있다.

2 캡챠 데이터셋 [1]

캡챠 원본 데이터는 Wilhelmy, Rodrigo & Rosas, Horacio. (2013)에서 다운로드 받을 수 있고, 캐글 CAPTCHA Images Version 2 CAPTCHA Images 데이터를 확인가능하다. 캡챠 원본 데이터를 가지고 작업을 진행한다. 다운로드 받아 압축을 풀어 제대로 가져왔는지 확인해 본다.

2.1 헬로 월드

먼저 제대로 압축을 풀고 데이터를 준비했는지 magick 팩키지 image_read() 함수로 불러 읽어온다.

library(tidyverse)
library(magick)

sample <- image_read("data/captcha_dataset/226md.png")

sample

2.2 라벨 데이터

원본 이미지 데이터에서 라벨 데이터를 준비한다.

library(glue)

file_names <- list.files("data/captcha_dataset/")

captcha_labels <- str_extract(file_names, "[\\w\\d]+?(?=\\.)")

captcha_df <- tibble(label = captcha_labels, 
       captcha = glue("data/captcha_dataset/{file_names}"))

captcha_df
# A tibble: 1,070 x 2
   label captcha                       
   <chr> <glue>                        
 1 226md data/captcha_dataset/226md.png
 2 22d5n data/captcha_dataset/22d5n.png
 3 2356g data/captcha_dataset/2356g.png
 4 23mdg data/captcha_dataset/23mdg.png
 5 23n88 data/captcha_dataset/23n88.png
 6 243mm data/captcha_dataset/243mm.png
 7 244e2 data/captcha_dataset/244e2.png
 8 245y5 data/captcha_dataset/245y5.png
 9 24f6w data/captcha_dataset/24f6w.png
10 24pew data/captcha_dataset/24pew.png
# … with 1,060 more rows

3 캡챠 딥러닝

CNN 신경망 모형을 개발하여 챕챠 이미지를 제대로 학습했는지 훈련/시험 데이터를 통해 확인한다.

4 모형 성능평가

randomized_character <- function(label_string) {
  string_characters <- str_split(label_string, "") %>% unlist
  random_character <- sample(string_characters, 1)
  character_pos <- which(string_characters == random_character)
  string_characters[character_pos] <- sample(LETTERS, 1)
  output_string <- str_flatten(string_characters)
  return(output_string)
}

# for(i in 1:10) {  randomized_character("ywn6f") }

captcha_trained <- captcha_df %>% 
  mutate(answer = rbinom(nrow(captcha_df), 1, prob=0.9))  %>% 
  mutate(predicted = ifelse(answer == 1, label, map_chr(label, randomized_character))) 

captcha_trained
# A tibble: 1,070 x 4
   label captcha                        answer predicted
   <chr> <glue>                          <int> <chr>    
 1 226md data/captcha_dataset/226md.png      1 226md    
 2 22d5n data/captcha_dataset/22d5n.png      1 22d5n    
 3 2356g data/captcha_dataset/2356g.png      1 2356g    
 4 23mdg data/captcha_dataset/23mdg.png      1 23mdg    
 5 23n88 data/captcha_dataset/23n88.png      1 23n88    
 6 243mm data/captcha_dataset/243mm.png      1 243mm    
 7 244e2 data/captcha_dataset/244e2.png      1 244e2    
 8 245y5 data/captcha_dataset/245y5.png      0 24DyD    
 9 24f6w data/captcha_dataset/24f6w.png      1 24f6w    
10 24pew data/captcha_dataset/24pew.png      1 24pew    
# … with 1,060 more rows

6 캡챠 tesseract 모형

tesseract 팩키지 ocr() 함수를 사용해서 captcha를 깨보자. 이미지 전처리 작업을 수행하고 나서, tesseract 화이트리스트를 정의한 후에 이를 ocr() 함수에 넣어 텍스트를 추출한다.

library(magick)
library(tesseract)


alphabet <- paste(letters, collapse="")

whitelist <- glue::glue("1234567890{alphabet}")

break_captcha <- function(image) {
  
  captcha_orig <- image_read(image) # 226md
  
  img_processed <- captcha_orig %>% 
    image_convert(colorspace = 'gray') %>%
    image_threshold(threshold = "50%", type = "white")
  
  captcha_text <- tesseract::ocr(img_processed, engine = tesseract(language = "eng",
                                  options = list(tessedit_char_whitelist = whitelist)))  
  return(captcha_text)
}

break_captcha("data/captcha_dataset/23n88.png")
[1] "723n83\n"
captcha_df <- captcha_df %>% 
  mutate(text = map_chr(captcha, break_captcha)) %>% 
  mutate(text = str_remove(text, "\n")) %>% 
  mutate(matching = ifelse(label == text, 1, 0))

captcha_df %>% 
  summarise(accuracy = mean(matching)) 
# A tibble: 1 x 1
  accuracy
     <dbl>
1   0.0467

6.1 성공한 사례

잘 추출된 captcha 이미지를 시각적으로 살펴보자

library(slickR)

broken_captcha <- captcha_df %>% 
  filter(matching == 1)

captcha_num <- htmlwidgets::JS("function(slick,index) {
                            return '<a>'+(index+1)+'</a>';
                       }")

captcha_opts <- settings(
  dots = TRUE,
  initialSlide = 0,
  slidesToShow = 10, 
  slidesToScroll = 10, 
  focusOnSelect = TRUE,
  customPaging = captcha_num,
  arrows = FALSE)

slickR(obj = broken_captcha$captcha, height = 100, width = "100%") %synch%
  ( slickR(broken_captcha$text, slideType = 'p') + captcha_opts ) 

6.2 실패한 사례

실패한 captcha 이미지는 많기 때문에 일부 추출하여 원인 파악에 사용한다.

library(slickR)

failed_captcha <- captcha_df %>% 
  filter(matching == 0) %>% 
  sample_n(100)

slickR(obj = failed_captcha$captcha, height = 100, width = "100%") %synch%
  ( slickR(failed_captcha$text, slideType = 'p') + captcha_opts ) 

1. Wilhelmy R, Rosas H. Captcha dataset. 2013.