machine learning, image

canyouhack.it Captcha Challenge 1, 2

qkqhxla1 2015. 5. 4. 19:00
2. Tilt your head.

들어가보면 기울어진 글자가 보인다.(매번 글자와 기울어진 정도 변경.)


이 기울어진 글자를 해석해서 2초안에 

/Content/Challenges/Captcha/Captcha2.php?text=답 과같은 형식으로 보내면 답이 포함된 이미지가 출력된다고 한다. 어떻게 할까 생각하다가 기울어진 글자의 각도를 계산해서, 돌아간 각도만큼 원래 각도대로 돌려준 후 python ocr 모듈인 pytesser로 인식하기로 했다. 각도 계산은 모든 경우에 계산하게 하기는 너무 복잡했다. 그래서 270~90도 사이의 값만 인식하게 하고,(즉 6시방향~12시방향 사이로 기울어진 글자.)

나머지 너무 돌아가서 글자가 거꾸로되거나 이런건 포기했다.(이것까지 하는건 오바인듯.)


내가 푼 방법론은, 우선 사진파일을 다운로드받아서 배경은 검게칠하고, 글자는 하얗게 칠한 후 두글자 이상의 윤곽선을 찾는다.(opencv의 윤곽선 찾는 함수가 있다. 윤곽선의 넓이 구하는 함수가 있어서 일정한 넓이 이상의 윤곽선 찾는게 가능함)



윤곽선을 찾은 예시이다. 여기서는 VI가 잡혔다. 윤곽선으로 잡힌 사각형의 두 점의 좌표로 기울기를 구한다. (위 사진에서 파랑색 점찍은 두 부분.) 두 점의 기울기는 해당 글자의 기울기라고 보면 된다. 

그런데 글자를 오른쪽으로 기울여야 하는지, 왼쪽으로 기울여야 하는지 모른다. 그래서 하나는 오른쪽으로 기울이고, 다른 하나는 왼쪽으로 기울인다. 이러한 이미지 두개를 만든다. 




이렇게 이미지 두개가 나올텐데,이걸 pytesser로 인식한다. 그런데 답은 항상 영어 대문자로만 이루어져있으므로 인식했을때 영어이면서, 영어 대문자로만 이루어져있는건 답으로 인식해서 보내면 된다.



잘 인식이 되며, 제대로 보낸 후에는 답이 담긴 사진이 있는데 사진에서 답을 찾아서 쓰면 된다.

뭔가 쉽게 설명했는데 코딩하기 되게 복잡했다.. 정확도도 조금은 낮아서 몇번 돌려야 성공한다.

# -*- encoding: cp949 -*-
import urllib2, Image, cv2
import numpy as np
import math
import pytesser
 
session = 본인 세션
 
def download_photo(filename,answer):
    file_path = "%s%s" % ("C:\\Users\\Ko\\Documents\\Visual Studio 2012\\Projects\\PythonApplication37\\", filename)
    downloaded_image = file(file_path, "wb")
       
    req = urllib2.Request('http://canyouhack.it/Content/Challenges/Captcha/Captcha2.php'+answer)
    req.add_header('Cookie',session)
    image_on_web = urllib2.urlopen(req)
    while True:
        buf = image_on_web.read()
        if len(buf) == 0:
            break
        downloaded_image.write(buf)
    downloaded_image.close()
    image_on_web.close()
    return file_path

while True:
    #download_photo('captcha.png','') #사진을 다운받는다.
    im = Image.open('captcha.png').convert('RGB') #RGB 모드로 변경 후
    for i in range(im.size[1]):
        for j in range(im.size[0]):
            if im.getpixel((j,i)) != (204,204,204): #배경은 검은색으로,
                im.putpixel((j,i),(255,255,255))
            else: #글자는 흰색으로 칠해준다.
                im.putpixel((j,i),(0,0,0))
    im.save('captcha1.png') #이걸 captcha1.png로 저장한다.
 
    im = cv2.imread('captcha1.png') #opencv로 연다.
    imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) #grayscale모드 : 검은색,흰색, 회색 모드로 이미지를 염.
    ret,thresh = cv2.threshold(imgray,127,255,0)
    contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    sum = 0; cnts = 0

    for cnt in contours: #contours에는 윤곽선의 모임이 들어있다.
        if cv2.contourArea(cnt) > 200: #윤곽선의 크기가 200이상인것들만 가져온다. 대충 200정도면 적당하니 잘 된다.
            rect = cv2.minAreaRect(cnt)
            box = cv2.cv.BoxPoints(rect)
            box = np.int0(box)
            #for i in range(4):
            #    print '(x,y)[%d] = (%s,%s)' %(i,box[i][0],box[i][1])
            #print
            zero_one = math.sqrt((box[1][0]-box[0][0])**2+(box[1][1]-box[0][1])**2) #맨 아래의 점부터 왼쪽 위의 점 사이의 거리.
            one_two = math.sqrt((box[2][0]-box[1][0])**2+(box[2][1]-box[1][1])**2) #왼쪽 위의 점부터 오른쪽 위의 점 사이의 거리.
            #print 'z =',zero_one,'ot =',one_two
            if (zero_one > one_two) and (zero_one > 40 or one_two > 40): #두 점간의 거리중 하나가 40은 넘어야된다. 40이 넘으면 두글자 이상 인식했다고 볼 수 있다.
                #print '\n기울기 =',float(box[1][1]-box[0][1])/float(box[0][0]-box[1][0])
                sum += float(box[1][1]-box[0][1])/float(box[0][0]-box[1][0]); cnts += 1 #sum에다가 점간의 거리를 더해놓는다. 이걸 나중에 평균을 내서 기울기를 구한다.
            elif zero_one > 40 or one_two > 40:
                #print '\n기울기 =',float(box[0][1]-box[3][1])/float(box[3][0]-box[0][0])
                sum += float(box[0][1]-box[3][1])/float(box[3][0]-box[0][0]); cnts += 1
            cv2.drawContours(im,[box],0,(0,255,0),2)
            cv2.imshow('result',im)
            cv2.waitKey(0)
 
    r = sum/cnts if cnts !=0 else 0 #평균을 구해서 r에다가 넣는다. 0인경우는 인식을 못한 경우... 여러번 돌려봐야 한다.
    if r==0:
        print '인식 실패'
        continue
    for i in range(2):
        if i==0:
            r1 = math.atan(-r)*180/math.pi
        else:
            r1 = 180 + r1
        print '기울일 각도 =',r1
 
        img = cv2.imread('captcha1.png',0) #기울기를 구했으니 원래대로 회전하기 위한 알고리즘.
        rows,cols = img.shape
        M = cv2.getRotationMatrix2D((cols/2,rows/2),r1,1) #r만큼 회전한다.
        dst = cv2.warpAffine(img,M,(cols,rows))
        cv2.imwrite('captcha%d.png' %(i+2),dst) 
        answer = pytesser.image_to_string(Image.open('captcha%d.png' %(i+2))).strip() #회전한 이미지를 저장한값에서 문자열을 추출해낸다.
        if answer.isupper() and answer.isalpha():
            print answer #답을 출력하고
            download_photo('answer.png','?text='+answer) #답을 보내면서 답이 사진에 써있으므로 사진도 같이 다운로드받아온다.
            cv2.imshow('answer',cv2.imread('answer.png',0)) #답 확인을 위해 다운로드받은 사진을 연다.
            cv2.waitKey(0)
            exit(1)

1. As easy as it gets
앞에서도 많이 했고, 쉽고 간단한 것이기에 별 설명은 적지 않는다.
# -*- encoding: cp949 -*-
import urllib2, Image, cv2
import numpy as np
import math
import pytesser

session = 본인 세션

def download_photo(filename,answer):
    file_path = "%s%s" % ("C:\\Users\\Ko\\Documents\\Visual Studio 2012\\Projects\\PythonApplication37\\", filename)
    downloaded_image = file(file_path, "wb")
      
    req = urllib2.Request('http://canyouhack.it/Content/Challenges/Captcha/Captcha1.php'+answer)
    req.add_header('Cookie',session)
    image_on_web = urllib2.urlopen(req)
    while True:
        buf = image_on_web.read()
        if len(buf) == 0:
            break
        downloaded_image.write(buf)
    downloaded_image.close()
    image_on_web.close()
    return file_path

req = urllib2.Request('http://canyouhack.it/Hacking-Challenges/Captcha-Challenges/As-easy-as-it-gets/')
req.add_header('Cookie',session)
urllib2.urlopen(req).read()

download_photo('captcha.png','')
im = Image.open('captcha.png').convert('RGB')
for i in range(im.size[1]): #색깔보정
    for j in range(im.size[0]):
        if im.getpixel((j,i)) == (204,204,204):
            im.putpixel((j,i),(255,255,255))
        else:
            im.putpixel((j,i),(0,0,0))

im.save('captcha.png')
answer = pytesser.image_to_string(Image.open('captcha.png')).strip()
print answer
download_photo('captcha2.png','?text='+answer)
cv2.imshow('answer',cv2.imread('captcha2.png',0))
cv2.waitKey(0)