본문 바로가기

졸업프로젝트/OpenCV

[OpenCV] 답지 문제별로 자르기(1)

728x90

목표) 문제집 답지의 특정 페이지를 넣으면 각 문제별로 크롭하여 저장하기

 

input : 수능완성 답안지(png)

수능완성2021 (가)형 답지 - 2page
수능완성 2021 (가)형 답지 - 3page

 

output) 

(10번이전 일부 문제 포함) 10번 ~21번(약간 잘림) 까지의 이미지

10번

 

20번

 

아이디어)

1. 전체 페이지에서 윗단과아랫단 제거 -> 문제영역만 남기기 (trim이용)

2. 페이지를 왼쪽 부분과 오른쪽 부분으로 나눔 -> 중앙 라인 제거 

     * issue 1)  왼쪽과 오른쪽 부분의 가로 크기는 같아야 할 것 

       issue 2) 홀수 페이지와 짝수 페이지의 중앙 선 부분의 위치가 다름

3. "답" 이미지를 중심으로 일정 여백을 더하여 해당 부분을 자르면, 모든 문제에 대해서 잘라낼 수 있음 

"답"이미지

    * issue) 페이지의 맨 하단에 있는 "답"이미지가 없는 부분(문제의 시작부분 끝부분은 다음페이지에 있는 경우)은 잘라지지 않음 해결 필요 -> 예 ) 2페이지 12,15번 / 3페이지 18번, 21번 

4. 답이 다음 페이지에 이어지는 경우 다음 컷과 이어주기 

   ->예시 : 위 2,3페이지 사이에 걸쳐있는 15번과 같은 경우 2페이지에 존재하는 15번 부분과 다음페이지에 있는 "답"이미지 이전까지잘린 두 이미지를 합치기

   * issue) 이를 위해서는 읽는 순서가 (흡사 DFS TREE)

  1. 페이지 순서대로 ( 2p -> 3p )
  2. 페이지의 왼쪽의 윗쪽부터 아래쪽까지 ( 10번->11번->12번 (1) -> 12번(1) )
  3. 페이지의 오른쪽 윗쪽에서 아랫쪽까지 ( -> 12번(2) -> 13번 -> 14번 -> .... ) 

다음과 같아야만 다음컷과 이을 때 정확한 순서가 맞추어진다 12번(1)->12번(2) 

 

Image trim

  • 전체 페이지에서 윗단과아랫단 제거 -> 문제영역만 남기기 (trim이용)
  • 페이지를 왼쪽 부분과 오른쪽 부분으로 나눔 -> 중앙 라인 제거 
def imtrim(page,page_num):

    #페이지 번호 string에서 int형으로 변환 
    page_num = int(page_num)

    #짝수 페이지
    if (page_num%2 == 0): 
        left = page[150:-130, 100:855]
        right = page[150:-130,880:-137]


    #홀수 페이지
    else:
        left = page[150:-130, 145:900]
        right = page[150:-130,905:-112]

	# 왼쪽페이지 먼저 리턴(순서대로)
    return left,right

짝수 페이지와 홀수 페이지의 경우 가운데 선 부분의 위치가 다르므로 다르게 잘라주어야 한다.

그래서 main에서 페이지 부분만 따로 추출하여 매개변수로 넘겨준 후, int형으로 변경하여 사용하였다.

 

잘린 페이지 범위는 실험적인 값으로 정한 것 , 가로는 775으로 맞추었음 

 

"답"이미지를 기준으로 밑부분으로 잘라내기

ORDER

  • 이미지 흑백화
  • 템플릿 매칭
  • 매칭 기준으로 자르기

전체 내용 

def contour(page_rl):

    #이미지 흑백화 
    imgray = cv2.cvtColor(page_rl, cv2.COLOR_BGR2GRAY) 


    #추출하려는 이미지
    template = cv2.imread('/gdrive/MyDrive/ProjectStudy/answer_temp.png',0)
    w, h = template.shape[::-1]

    #템플릿 매칭 
    res = cv2.matchTemplate(imgray,template,cv2.TM_CCOEFF_NORMED)
    threshold = 0.5
    loc = np.where( res >= threshold)

    #for문 내에서 사용할 변수 초기화 
    y_now = 0 #현재 자를 이미지의 y좌표
    pt_now = 0 #현재 탐지한 "답"이미지의 y좌표 

    #탐지한 "답"이미지의 갯수만큼 반복 
    for pt in zip(*loc[::-1]):

        #탐지한 "답"이미지의 y좌표
        pt_past = pt_now # 이전값
        pt_now = pt[1] #현재값

        # 자를 y축 좌표 계산 (y_past : 시작좌표 / y_now : 끝좌표
        y_past = y_now # 이전에 자른 부분(이전 단계의 끝좌표)으로 시작을 대치
        y_now = pt_now + h + 25 #( "답"이미지의 left,top의 y좌표 + 이미지 height + 여백 )

        #같은 "답"이미지가 여러번 탐지되는 부분을 자르는 것을 방지
        if (pt_now >= pt_past+3):
            img_trim = page_rl[y_past:y_now,:] # 원본 이미지를 계산한 좌표로 자르기 
            include(img_trim,qnum) # 잘린 이미지를 저장 

    # 맨 마지막 부분을 위해 1회 더 수행 
    img_trim = page_rl[y_now:,:]
    include(img_trim,qnum)

 

 

이미지 흑백화 및 템플릿 매칭

    #이미지 흑백화
    imgray = cv2.cvtColor(page_rl, cv2.COLOR_BGR2GRAY) 


    #추출하려는 "답"이미지
    template = cv2.imread('/gdrive/MyDrive/ProjectStudy/answer_temp.png',0)
    w, h = template.shape[::-1]

	#템플릿 매칭
    res = cv2.matchTemplate(imgray,template,cv2.TM_CCOEFF_NORMED)
    threshold = 0.5
    
    #매칭되는 이미지의 좌표(loc = {array[y1, y2,...] and array[x1,x2,...]})
    loc = np.where( res >= threshold)

2021/01/18 - [졸업프로젝트/OpenCV] - [OpenCV] 이미지에서 특정 이미지 찾아내기

 

[OpenCV] 이미지에서 특정 이미지 찾아내기

답지에서 해당 답의 영역만을 추출하기 위해서는 다음과 같은 "답"이라는 이미지를 답지 페이지내에서 찾아낸 후 해당 이미지를 기준으로 그 아래를 자르면 된다 그래서 일단 이번 게시글에서

iagreebut.tistory.com

해당 게시글 참조! 

 

 

매칭 기준으로 자르기

#for문 내에서 사용할 변수 초기화 
    y_now = 0 #현재 자를 이미지의 y좌표
    pt_now = 0 #현재 탐지한 "답"이미지의 y좌표 

    #탐지한 "답"이미지의 갯수만큼 반복 
    for pt in zip(*loc[::-1]):

        #
        pt_past = pt_now
        pt_now = pt[1]

        # 자를 y축 좌표 계산 (y_past : 시작좌표 / y_now : 끝좌표
        y_past = y_now # 이전에 자른 부분(이전 단계의 끝좌표)으로 시작을 대치
        y_now = pt_now + h + 25 #( "답"이미지의 left,top의 y좌표 + 이미지 height + 여백 )

        #같은 "답"이미지가 여러번 탐지되는 부분을 자르는 것을 방지
        if (pt_now >= pt_past+3):
            img_trim = page_rl[y_past:y_now,:] # 원본 이미지를 계산한 좌표로 자르기 
            include(img_trim,qnum) # 잘린 이미지를 저장 

    # 맨 마지막 부분을 위해 1회 더 수행 
    img_trim = page_rl[y_now:,:]
    include(img_trim,qnum)

여기는 삽질을 좀 오래했기 때문에 따로 글을 씀 

2021/01/20 - [졸업프로젝트/OpenCV] - [OpenCV] 답지 문제별로 자르기(2) - 오류

 

 

해당 이미지 (page_rl) 한 페이지의 오른쪽 또는 왼쪽 부분 중 하나 내에서 

모든 "답"이미지를 찾아낸다 

이는 loc상에 저장되고 zip( *loc[::-1] )을 이용하여 해당 "답"이미지의 (x,y)좌표 로 저장되며

하나하나 pt[0] : x좌표 / pt[1] : y좌표 에 크기 순서대로 저장된다. ( 즉, 윗쪽에 존재하는 것 부터) 

 

y_past에는 이전에 잘렸던 부분을 저장하고, y_now에는 새로운 "답"이미지의 y좌표를 저장하여

y_past부분 ~ y_now부분까지 잘라주면 된다 

 

2페이지의 왼쪽 페이지 부분을 예시로 설명하면 다음과 같다 

 

 

 

 

저장하기

#저장할이미지 , 해당 이미지의 고유번호(+1씩증가)
def include(cropped,qnum):
    
    #전역변수 qnum을 전역변수로서 사용하기 위해 
    global qnum

    #파일명 = 저장경로 + 고유번호 + _pa 페이지수 . png
    newfile=save_path+str(qnum)+"_pg"+j+".png"

    #해당 경로에 파일을 저장 
    cv2.imwrite(newfile,cropped)
    
    #저장을 완료했기 때문에 고유번호 1증가 
    qnum=qnum+1

각각 크롭된 문제마다 고유번호를 부여하기로 했다(문제와 답지의 고유번호가 같음 -> db검색시 용이)

고유번호인 qnum은 저장될때 마다 1씩증가하게 하며,

전역변수를 main에 선언해두고 사용한다 (밑에나옴) 

 

더보기

전역변수 (global)

파이썬에서 전역변수를 사용하는 방법은 다음과 같다 

다른 부분에서 해당 변수명으로 변수를 작성해둔 뒤

사용을 원하는 함수 내에서 global이라는 타입으로 변수를 정의한 후 사용한다

#main함수나 다른 곳에서

k = 1

#해당 전역변수를 사용하려는 함수내에서
def function():
	
    # 글로벌로 선언
    global k 
    
    # 해당 변수 조작
    k = k + 3
    
#해당 function이 실행되고나면 k는 3씩 증가 -> 1회 수행시 k=4가되어있음 

 

크롭함수로 모으기

#페이지 url을 입력하면 
def problem_crop(image_url,page_num):

    #이미지url을 open CV에 읽어들이기 
    imgfile = image_url
    image = cv2.imread(imgfile)

    #image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 

    #한 페이지를 반페이지로 나누기 
    left,right = imtrim(image,page_num)
    
    #각 반페이지에 대해 크롭 진행 
    contour(left)
    contour(right)

지금까지 작성한 imtrim / contour함수를 사용해서 이미지 url과 page 넘버를 입력하면,

원하는 경로에 자동으로 크롭된 번호를 저장해주는 함수를 작성한다 

imtrim을 반환하는 순서를 left , right

contour를 진행하는 순서를 left -> right

로 해주어야 왼쪽 페이지부터 결과를 받을 수 있다 

 

 

메인함수

#함수실행 
if __name__ == "__main__":

    #고유번호 (전역변수로 사용할 것)
    qnum=1
    
    #이미지를 불러올 경로, 저장경로 
    load_path='/gdrive/MyDrive/ProjectStudy/answers/raw_data/Suwan_ga/2021/example/'
    save_path='/gdrive/MyDrive/ProjectStudy/yj/ans_test2/'
    os.chdir(load_path)
    
    #해당 경로에서 .png로 끝나는 모든 이미지를 배열로 저장 
    images=glob.glob('./*.png')

    #폴더내 이미지 순서대로 불러오기
    images.sort()
       
    #이미지의 수 만큼 반복 
    for i in range(len(images)):
        #하나의 이미지 경로를 저장 
        filename=images[i]
        #파일 이름에서 페이지 이름을 추출 
        page_num=filename[20:23]
        j=page_num
        #이미지 경로와 파일경로를 이용해서 크롭 
        problem_crop(filename,j)

images.sort를 이용하여 페이지를 순서대로 받아오게한다 (2page->3page)

 

 

 

전체코드)

! apt install tesseract-ocr
! apt install libtesseract-dev
! pip install Pillow
! pip install pytesseract
! sudo apt-get install tesseract-ocr-script-hang tesseract-ocr-script-hang-vert

#라이브러리 임포트
import numpy as np
import cv2 #openCV package
from google.colab.patches import cv2_imshow
import re
import pytesseract
import glob
import os
import time
from PIL import ImageEnhance, ImageFilter, Image
from matplotlib import pyplot as plt


#구글 드라이브와 연동 
from google.colab import drive
drive.mount('/gdrive')



#수능완성 page trim 
def imtrim(page,page_num):

    page_num = int(page_num)

    #짝수
    if (page_num%2 == 0): 
        left = page[150:-130, 100:855]
        right = page[150:-130,880:-137]


    #홀수
    else:
        left = page[150:-130, 145:900]
        right = page[150:-130,905:-112]

    return left,right

#반페이지를 입력받고 크롭하기 
def contour(page_rl):

    imgray = cv2.cvtColor(page_rl, cv2.COLOR_BGR2GRAY) 

    #추출하려는 이미지
    template = cv2.imread('/gdrive/MyDrive/ProjectStudy/answer_temp.png',0)
    w, h = template.shape[::-1]
    res = cv2.matchTemplate(imgray,template,cv2.TM_CCOEFF_NORMED)
    threshold = 0.5
    loc = np.where( res >= threshold)

    y_now = 0
    pt_now = 0


    for pt in zip(*loc[::-1]):

        pt_past = pt_now
        pt_now = pt[1]

        y_past = y_now
        y_now = pt_now + h + 25

        
        if (pt_now >= pt_past+3): #a lot of +1 kind of same pts
            img_trim = page_rl[y_past:y_now,:]
            cv2_imshow(img_trim)
            #include(img_trim,qnum)

    
    img_trim = page_rl[y_now:,:]
    #include(img_trim,qnum)
    cv2_imshow(img_trim)
                



def include(cropped,num):
    
    global qnum

    newfile=save_path+str(num)+"_pg"+j+".png"
    #print(newfile)
    #print(num)
    cv2_imshow(cropped)
    #print("===============================================")
    
    cv2.imwrite(newfile,cropped)
    
    qnum=qnum+1
    

#페이지 url을 입력하면 크롭
def problem_crop(image_url,page_num):

    #이미지url을 open CV에 읽어들이기 
    imgfile = image_url
    image = cv2.imread(imgfile)

    #image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 

    #한 페이지를 반페이지로 나누기 
    left,right = imtrim(image,page_num)
    
    #각 반페이지에 대해 크롭 진행 
    contour(left)
    contour(right)


#함수실행 
if __name__ == "__main__":

   
    qnum=1

    load_path='/gdrive/MyDrive/ProjectStudy/answers/raw_data/Suwan_ga/2021/example/'
    save_path='/gdrive/MyDrive/ProjectStudy/yj/ans_test2/'
    os.chdir(load_path)

    images=glob.glob('./*.png')

    #폴더내 이미지 순서대로 불러오기
    images.sort()
    print(images)
       
    for i in range(len(images)):
        filename=images[i]
        page_num=filename[20:23]
        j=page_num

        problem_crop(filename,j)




 

 

결과) 

input  상단에 2~3페이지

output

2페이지 맨 첫번째 9번(2)  / 파일명 : 1_pg002.png
2페이지 두번째 10번 / 파일명 : 2_pg002.png

 

(중략)

 

2페이지 마지막 15번(1) / 파일명 : 8_pg002.png 
3페이지 맨 처음 15번(2) / 파일명 : 9_pg003.png

 

(중략)

 

3페이지 마지막 전 / 16_pg003.png

 

3페이지 마지막 / 파일명 : 17_pg003.png

 

 

4단계인 페이지 붙이기는 다음게시글에서 .... 

-> 위의 output에 있듯

15번(1) : "답"이미지 없음 과 15번(2) : "답"이미지 있음

두 이미지를 이어붙여 하나로 만드는 과정이다 

 

 

728x90