[ OpenCV ] 가로 직선, 세로 직선 지우기 -> 문제 별로 크롭 && 문제 영역 확보해서 자르기
초기 화면
결과화면
과정
1. 이미지 불러오기
2. 문제집 위의 헤더 (문제 영역이 아닌 부분) 자르기
3. 가운데 세로선 기준으로 왼쪽 오른쪽 나누기
4. 문제영역 contour잡기 (왼쪽-> 오른쪽 순서)
5. 각 contour의 위쪽 좌표를 이용해서 현재 contour~ 다음 contour까지 영역 잡아서 자르기 (이걸 반복)
6. 헤더에서 페이지 수 읽어오기 //아직 성공 못했다. 이건 다 멍청한 pytesseract 탓이다
코드
0. 드라이브 마운트
#일단 드라이브 마운트
from google.colab import drive
drive.mount('/content/drive')
1. 2. 이미지 불러오기 && 윗 부분 자르기
#이미지 윗 부분 자르기
def cropTop(image):
src=cv2.imread(image) #이미지 불러오기
#cv2_imshow(src)
dst = src.copy()
#흑백처리
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
#canny
canny = cv2.Canny(gray, 5000, 1500, apertureSize = 5, L2gradient = True)
#min_theta=0 max_theta=np.pi/2
#허프만 이용해서 가로 직선 찾기
#220으로 되어있는 것이 임계값 : 높을수록 선명한? 직선을 검출
#min_theta :최소각도
#max_theta: 최대 각도
lines = cv2.HoughLines(canny, 0.8, np.pi / 180, 220, srn = 100, stn = 200, min_theta = 89, max_theta = 91)
#직선이 여러개 나오는데 가장 위쪽에 있는 직선을 찾으려고 miny사용
miny=700
for i in lines:
rho, theta = i[0][0], i[0][1]
#print(theta)
a, b = np.cos(theta), np.sin(theta)
x0, y0 = a*rho, b*rho
scale = src.shape[0] + src.shape[1]
x1 = int(x0 + scale * -b)
y1 = int(y0 + scale * a)
x2 = int(x0 - scale * -b)
y2 = int(y0 - scale * a)
#print("y1",y1,"y2",y2)
if (y2 < miny):
miny=y2
cv2.line(dst, (x1, y1), (x2, y2), (0, 0, 255), 2)
#cv2.circle(dst, (x0, y0), 3, (255, 0, 0), 1, cv2.FILLED)
#찾은 가로 직선 기준으로 위쪽은 header, 아래쪽은 body로 넘겨줌
header = src[:miny, :]
body=src[miny+10:,:]
#print(miny)
#cv2_imshow(body)
return header,body
3. 가운데 세로선 기준으로 왼쪽 오른쪽 나누기
<모의고사 , 평가원>
이 문제집들은 정가운데에 세로 줄이 있어서 이미지의 가로길이의 절반을 이용했다.
def cropCenter(img):
src=img
dst=src.copy
#가로길이
w=int (img.shape[1]/2)
#왼쪽
left=src[:,:w-5]
#오른쪽
right=src[:,w+5:]
#cv2_imshow(left)
#cv2_imshow(right)
contour(left)
contour(right)
<수능완성>
수능완성은 오른쪽 왼쪽 페이지가 있어서 세로선이 정 가운데에 있지 않다.
def cropCenter(img):
src=img
dst = src.copy()
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray, 5000, 1500, apertureSize = 5, L2gradient = True)
lines = cv2.HoughLines(canny, 0.8, np.pi / 180, 200, srn = 100, stn = 200, min_theta = 0, max_theta = np.pi/8)
row=0
col=0
for i in lines:
rho, theta = i[0][0], i[0][1]
a, b = np.cos(theta), np.sin(theta)
x0, y0 = a*rho, b*rho
print("x0",x0,"y0",y0)
scale = src.shape[0] + src.shape[1]
x1 = int(x0 + scale * -b)
y1 = int(y0 + scale * a)
x2 = int(x0 - scale * -b)
y2 = int(y0 - scale * a)
row=x2
col=y2
cv2.line(dst, (x1, y1), (x2, y2), (0, 0, 255), 2)
#cv2.circle(dst, (x0, y0), 3, (255, 0, 0), 1, cv2.FILLED)
left = src[:, :row-15]
right= src[:,row+15:]
cv2_imshow(dst)
#contour(left)
#contour(right)
위의 cropTop에서 min_theta, max_theta를 수정한것이다.
이 방법보다는 가능하면 정가운데에 직선이 있는 애들이 좋다. 왜냐면 문제속에 박스나 표가 있으면 걔네도 임계값을 넘어서는 세로 직선으로 인식되었다.
임계값을 잘 조정하면 될 것 같은데 220은 작동 잘하고 250이면 아예 아무 직선도 통과를 못해서.. 필요할때 다시 조정해봐야겠다.
4. 문제 영역잡기 && 5. 문제영역 자르기
#반페이지를 입력받고 크롭하기
def contour(page_rl):
print("contour")
#이미지 흑백화
imgray = cv2.cvtColor(page_rl, cv2.COLOR_BGR2GRAY)
img2=imgray.copy()
#이미지 이진화 (스캔본 처럼)
blur = cv2.GaussianBlur(imgray, (3,3), 0)
thresh = cv2.threshold(blur, 70, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# Morph operations
edge = cv2.Canny(imgray, 100, 200)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(1000,200))
closed = cv2.morphologyEx(edge, cv2.MORPH_CLOSE, kernel)
#문제영역 윤곽 잡기
#contours가 찾은 경계의 배열
contours, hierarchy = cv2.findContours(closed.copy(),cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours_xy = np.array(contours , dtype=object)
contours_xy.shape
#한페이지 내에서 문제 순서대로 불러오기
contours=reversed(contours)
#한페이지 내의 모든 폐곡선 범위에 대해 실행
top=[] #폐곡선의 맨 위 x값을 담아놓는 배열
for c in contours:
#폐곡선 바운더리
x,y,w,h = cv2.boundingRect(c)
top.append(y)
total=len(top)-1
for i in range(total):
#여백이 너무 좁아서 맨위에 있는 문제 제외하고 위쪽 여백을 추가했음
#어차피_여백이 넉넉해서 괜찮
if (i==0):
img_trim=page_rl[top[i]:top[i+1]-5,:]
else:
img_trim=page_rl[top[i]-10:top[i+1]-5,:]
#cv2.imwrite('/content/MyDrive/GRADIING_Study/kh/trim/'+str(qnum)+'.png',img_trim)
#print(qnum)
cv2_imshow(img_trim)
6. 헤더에서 페이지 수 읽어오기
pytesseract를 사용해서
이 header의 페이지 수를 추출하려고 했으나 몽총이 pystesseract가 읽어 내지 못하고 참고로 google cloud vision은 잘 읽어냄.
욜로로 잘 하면 페이지를 읽을 필요가 없어서 일단은 이렇게 마무리 하도록 했다.