machine learning, image

python opencv rotated image recognition (hackthis captcha 4)

qkqhxla1 2015. 6. 28. 17:07

이전 문제풀이 : http://qkqhxla1.tistory.com/273


지금 풀 문제 링크 : https://www.hackthis.co.uk/levels/captcha/4

들어가보면 난이도가 2,3번보다 확 올라갔음을 알 수 있다.

이렇게 회전되있으며, 거기다가 색깔까지 입혀져있다. 2,3번은 내가 머신러닝의 트레이닝을 해서 풀었지만, 4번은 SIFT라는 회전에 강건한(어떠한 이미지가 회전되도 인식을 잘 하는 알고리즘) 방법을 써서 풀었다.


관련 자료 1http://opencv-python-tutroals.readthedocs.org/en/latest/py_tutorials/py_feature2d/py_matcher/py_matcher.html?highlight=orb

관련 자료 2 : http://stackoverflow.com/questions/20259025/module-object-has-no-attribute-drawmatches-opencv-python


이걸 참고해서 어떠한 회전된 이미지가 있으면 특징점을 이용해서 인식을 할 수 있다는 것을 알았다.

내가 푼 방법론은 기본 베이스 이미지를 2,3번 문제에서 가져와서 적절히 보정을 가한 후 하나하나 비교하면서 인식을 하는 것이다. (origin.png)

일단 이 이미지를 가져와서 grayscale모드로 변환하고 저장한다. SIFT는 기본적으로 같은 그림을 인식하는 알고리즘이기때문에 색깔이 다르면 인식을 못하는것같다. (실험해봄.) 그리고 저장한 후에 수작업으로 각각 12개의 이미지로 나눈 후, 하나씩 가져와서 매칭 연산을 했다. 

예시 1. 왼쪽의 그림이 똑바로된 그림이며, 오른쪽은 문제 이미지를 가져와서 3배로 크기를 늘린 후 SIFT로 매칭을 시도했을때의 그림이다. 첫번째에서는 특징점을 잘 찾았지만, 두번째는 하나밖에 못찾았다. 세번째는 해당 그림이 아닌데도 오탐지까지 하였다.

예시 2. 왼쪽의 약간 찡그린 표정이 3개가 있는데 대체로 잘 인식했다.

어쨋든 이런식으로 되는데 탐지율을 높이기 위해서 가중치라는걸 만들었다. 어떠한 이미지가 다른 이미지의 특징점을 찾았을때, 많이 찾으면 찾을수록 동일한 이미지일 확률이 높다는것에 기반을 두고 만들었다.


또 여기서 중요한게 이미지가 나타나는 위치도 랜덤이다. 그래서 동일한 이미지를 인식했는지 어떻게 아느냐의 문제는 이렇게 해결했다. 

1. 2차원리스트 vote를 만들어서 0으로 채워넣는다. ( vote = [[0 for j in range(13)] for i in range(10)] )

2. vote[i][0] 부분을 x좌표로 쓰기로 약속하고, 반복문을 돌면서 x좌표가 0이면(아직 x좌표를 입력 안했으면) 해당 인식된 좌표를 넣고, 가중치를 더해준다.

3. 어떠한 점이 나올시 x좌표목록을 가져와서 x좌표에서 30만큼의 절대값 안에 있으면 같은 이미지로 판별했다. 30이상 떨어져 있을때는 다른 이미지로 판별했다.


이런식으로 같은 이미지인지를 판별해서 가중치를 넣었다. 맨 마지막에는 나온 가중치의 합 중에서 가장 큰 합을 해당 이미지로 판별해서 미리 만들어놓은 dictonary에서 키 값으로 변환 후, 그걸 전송했다.


실제 문제를 풀 때에는 사진을 다운받아서 인식후 값을 보내고, 틀렸다 싶으면 다시 다운받아서.. 이런식으로 브루트포싱하듯이 했다.

# -*- encoding: cp949 -*-
import Image, cv2
import numpy as np
import urllib2
import cv2
from find_obj import filter_matches,explore_match
 
session = 본인 세션값
 
def download_photo(filename):
    file_path = "%s%s" % ("C:\\Users\\Ko\\Documents\\Visual Studio 2012\\Projects\\PythonApplication37\\", filename)
    downloaded_image = file(file_path, "wb")
        
    req = urllib2.Request('https://www.hackthis.co.uk/levels/extras/captcha4.php')
    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
 
my_size = 3
cv2.imwrite('origin1.png',cv2.imread('origin.png',0))
 
cut = [0,29,58,86,111,139,166,193,218,244,270,295,Image.open('origin1.png').size[0]]
 
for i in range(12):
    im = Image.open('origin1.png')
    im.crop((cut[i],0,cut[i+1],im.size[1])).save('origin%s.png' %str(i+2))
    im = Image.open('origin%s.png' %str(i+2))
    im.resize((im.size[0] * my_size,im.size[1] * my_size)).save('origin%s.png' %str(i+2))
 
while True:
    download_photo('trans.png')
    cv2.imwrite('trans1.png',cv2.imread('trans.png',0))
    im = Image.open('trans1.png')
    im.resize((im.size[0] * my_size,im.size[1] * my_size)).save('trans1.png')
 
    vote = [[0 for j in range(13)] for i in range(10)] #0 = x좌표, 1~12 = 각 표정에 대해서 vote된 수
    for i in range(2,14):
        try:
            #print 'i =',i
            img1 = cv2.imread('origin%s.png' %str(i),0)          # queryImage
            img2 = cv2.imread('trans1.png',0) # trainImage
 
            orb = cv2.SIFT()
 
            kp1, des1 = orb.detectAndCompute(img1,None)
            kp2, des2 = orb.detectAndCompute(img2,None)
 
            bf = cv2.BFMatcher()
 
            matches = bf.knnMatch(des1, trainDescriptors = des2, k = 2)
            p1, p2, kp_pairs = filter_matches(kp1, kp2, matches)
 
            weight = [1 for j in range(10)] #가중치
            for p in p2:
                for v in range(len(vote)):
                    if vote[v][0] == 0:
                        vote[v][0] = p[0]
                        if i==13:
                            vote[v][i-1] += 5 #하트는 찾을확률이 높으므로 그냥 5씩 더해줌.
                        else:
                            vote[v][i-1] += weight[v]
                            weight[v] += 1
                        break
                    if vote[v][0] != 0:
                        if vote[v][0]-30 < p[0] and p[0] < vote[v][0]+30:
                            if i==13:
                                vote[v][i-1] += 5 #하트는 5씩 더함.
                            else:
                                vote[v][i-1] += weight[v]
                                weight[v] += 1
                            break
            #for i in range(len(vote)):
            #    print vote[i]
            #print
 
            #explore_match('find_obj', img1,img2,kp_pairs)#cv2 shows image
 
            #cv2.waitKey()
            #cv2.destroyAllWindows()
        except: pass
    re = False
    for i in range(len(vote)):
        if vote[i][0] == 0:
            print '인식실패'
            re = True
            break
    if re: continue
 
    dic = {1:':D',2:':)',3:':p',4:':(',5:';)',6:'B)',7:':@',8:':o',9:':s',10:':|',11:':/',12:'<3'}
    vote = sorted(vote, key = lambda x:x[0])
    for i in range(len(vote)):
        print vote[i]
    answer = ''
    for i in range(len(vote)):
        answer += dic[vote[i].index(max(vote[i][1:]))]
    print answer
 
    req = urllib2.Request('https://www.hackthis.co.uk/levels/captcha/4','answer='+answer) #만들어진 이모티콘을 보내준다.
    req.add_header('cookie',session)
    page = urllib2.urlopen(req).read()
    if page.find('Incomplete') == -1:
        print 'success!!!!'
        Image.open('trans.png').show()
        exit(1)