문제를 띄워주는 것이 완료됐으니, 본격적으로 main코드에서 직접 사용자가 알파벳 값들을 입력해서 문제를 풀어보게 할 것이다.
import copy
import os, random
from picture import *
from gameplay import *
CANVAS = 'hangman/canvas.png'
MOSAIC_RATE = 20
points = 0
for i in range(5):
all_right = True
IMAGE = "./photos/" + random.choice(os.listdir("./photos"))
try:
erase_mat()
except:
pass
object_cords1 = object_cords(IMAGE) # 원래 이미지의 좌표값과 레이블을 구한다
removing_list = []
for x in object_cords1:
if x['confidence'] < 0.5:
removing_list.append(x)
for val in removing_list:
object_cords1.remove(val)
while object_cords1:
new_object_cords = copy.deepcopy(object_cords1)
dst, shrinked_cords = picture_yolo_mosaic(IMAGE, MOSAIC_RATE,
new_object_cords) # 이미지를 모자이크 하고, 사진을 축소하는 과정에서 객체들의 캔버스의 절대 좌표값도 구한다
hangman = show_quiz() # 행맨 캔버스를 띄운다
cv2.putText(hangman, "Letters used:", (80, 540), \
cv2.FONT_HERSHEY_SIMPLEX, 1, \
(0, 0, 0), 2)
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
show_question(dst, hangman) # 문제를 보여주고 다시 띄운다
chosen_value = keyboard_chooseobj()
if not chosen_value:
break
chosen_answer = chosen_value['name']
object_cords_index = shrinked_cords.index(chosen_value)
chosen_answer_lower = chosen_answer.lower()
char_rects = get_char_coords(chosen_answer)
draw_blank_rects(chosen_answer, char_rects, hangman)
chars_entered = []
letter_x = 300
letter_y = 540
index = 0
while True:
letter_count = 0
for letters in chosen_answer.strip():
if letters in chars_entered:
letter_count += 1
if letter_count == len(chosen_answer.replace(' ', '')):
points += 10
erase_area(hangman)
cv2.imshow("Hangman", hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
draw_won(hangman)
cv2.waitKey()
letter_x = 300
letter_y = 540
chars_entered = []
del object_cords1[object_cords_index]
# shrinked_cords.remove(chosen_value)
break
key = cv2.waitKey()
if 65 <= key <= 90 or 97 <= key <= 122:
key = chr(key).lower()
if key not in chars_entered:
chars_entered.append(key)
cv2.putText(hangman, key, (letter_x, letter_y), \
cv2.FONT_HERSHEY_SIMPLEX, 1, \
(255, 0, 0), 2)
letter_x += 30
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
if key in chosen_answer_lower:
displayLetter(hangman, key, chosen_answer_lower, char_rects)
else:
draw_hangman(index, hangman)
index += 1
if index == 4:
hangman1 = copy.deepcopy(hangman)
hangman1 = draw_hint(hangman1)
cv2.imshow("Hangman", hangman1)
cv2.waitKey(1000)
object_cords2 = copy.deepcopy(object_cords1)
delete_val = object_cords2[object_cords_index]
del object_cords2[object_cords_index]
delete_val_list = []
delete_val_list.append(delete_val)
dst, a = picture_yolo_mosaic(IMAGE, MOSAIC_RATE, object_cords2)
show_question(dst, hangman)
delete_val_list = shrink_object_cords(IMAGE, delete_val_list)
box_on_hangman(delete_val_list, hangman, (255, 255, 255))
points -= 5
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
cv2.waitKey(1000)
if index == 6:
del object_cords1[object_cords_index]
hangman2 = copy.deepcopy(hangman)
hangman2 = draw_wrong(hangman2)
points -= 10
all_right = False
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman2)
cv2.waitKey()
break
elif key == 27:
break
# cv2.waitKey(0)
# if chosen_answer:
# reveal_answer(chosen_answer, hangman, char_rects)
if all_right:
points+=30
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
show_answer(hangman)
end_key = cv2.waitKeyEx()
if end_key == 0x1B:
break
erase_all_area(hangman)
draw_final_points(hangman, points)
cv2.imshow("Hangman", hangman)
cv2.waitKey()
cv2.destroyAllWindows()
erase_mat()
import copy
import os, random
from picture import *
from gameplay import *
CANVAS = 'hangman/canvas.png'
MOSAIC_RATE = 20
points = 0
answer_label = ""
앞서 설명한 함수들의 정의를 picture.py와 gameplay.py에 정의하였기 때문에 이들을 가져왔으며, 그림판으로 그린 canvas파일을 CANVAS로 정의하였고, 모자이크 율과 초기 점수를 선언하였다.
import copy
import os, random
from picture import *
from gameplay import *
CANVAS = 'hangman/canvas.png'
MOSAIC_RATE = 20
points = 0
깊은 복사를 통해 캔버스를 수정할 일이 있기 때문에 copy
를 import하고, 디렉토리 내의 파일을 삭제 하는 등 작업을 위해 os
를, 사진을 무작위로 선택하기 위해 random
을 import하였다.
for i in range(5):
all_right = True
IMAGE = "./photos/" + random.choice(os.listdir("./photos"))
try:
erase_mat()
except:
pass
for문을 통해 5번의 문제를 주어지는 것으로 하였고, 한 문제에서 모든 객체 값을 맞춘다면 추가 점수를 주기 위해 all_right 변수를 추가하였다.
IMAGE = "./photos/" + random.choice(os.listdir("./photos"))
는 photos디렉토리 내의 무작위 파일을 이미지 파일로 받아온다. 파이토치에서 받아온 yolo모델은 탐지한 사진들의 정답 사진
을 runs/detect 디렉토리 밑에 저장한다. 따라서 저장이 큰 의미가 없다고 생각하여 erase_mat
함수를 추가적으로 정의하여, 지울 사진이 있는 경우에 한해 erase_mat()을 실행하였다.
def erase_mat(): shutil.rmtree('runs/detect//')
object_cords1 = object_cords(IMAGE) # 원래 이미지의 좌표값과 레이블을 구한다
removing_list = []
for x in object_cords1:
if x['confidence'] < 0.5:
removing_list.append(x)
for val in removing_list:
object_cords1.remove(val)
다음으로 무작위로 받은 사진의 좌표값 리스트 중에서, 탐지의 신뢰도가 0.5보다 적은 경우에는 해당 객체의 좌표값, 레이블 등을 삭제하여 박스쳐질 일이 없도록 하였다.
while object_cords1:
new_object_cords = copy.deepcopy(object_cords1)
dst, shrinked_cords = picture_yolo_mosaic(IMAGE, MOSAIC_RATE,
new_object_cords) # 이미지를 모자이크 하고, 사진을 축소하는 과정에서 객체들의 캔버스의 절대 좌표값도 구한다
hangman = show_quiz() # 행맨 캔버스를 띄운다
cv2.putText(hangman, "Letters used:", (80, 540), \
cv2.FONT_HERSHEY_SIMPLEX, 1, \
(0, 0, 0), 2)
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
show_question(dst, hangman) # 문제를 보여주고 다시 띄운다
앞선 포스트에서 설명한 문제를 띄워주는 코드를 여기서 실행한다.
erase_area(hangman)
함수는
def erase_area(img):
cv2.rectangle(img, (800, 40), (1200, 80), (255, 255, 255), -1)
"You're Right!" , "You're Wrong!", "Hint!" 등등 설명을 위해 캔버스에 글씨를 쓰는 과정에서 글씨가 겹쳐 보이지 않도록 rectangle
함수로 덮어주는 역할을 한다.(쉽게 말해 글씨위에 덮고 글씨를 다시 쓰는 것,,, 더 효율적인 방법이 분명 있을텐데^^ㅎㅎ)
그리고 draw_points
함수는 points를 매개변수로 가져와 캔버스에 점수를 보여주는 역할을 하는 함수이다.
def draw_points(img, points):
cv2.putText(img, "POINTS : " + str(points), (800, 60), \
cv2.FONT_HERSHEY_SIMPLEX, 0.7, \
(0, 255, 0), 2)
return img
chosen_value = keyboard_chooseobj()
if not chosen_value:
break
chosen_answer = chosen_value['name']
object_cords_index = shrinked_cords.index(chosen_value)
chosen_answer_lower = chosen_answer.lower()
char_rects = get_char_coords(chosen_answer)
draw_blank_rects(chosen_answer, char_rects, hangman)
chars_entered = []
letter_x = 300
letter_y = 540
먼저 keyboard_chooseobj()
로 받은 객체의 값의 정보들을 저장한다.
chosen_answer
은 레이블 값, object_cords_index
는 그 객체의 인덱스 값을 받아주고, char_rects
는 레이블 문자열의 박스 좌표를 저장한다.
이를 통해 draw_blank_rects
로 문제 박스를 그려준다. letter_x와 letter_y는 입력 받는 알파벳 값들을 넣어주는 x,y좌표를 뜻한다.
index = 0
while True:
letter_count = 0
for letters in chosen_answer.strip():
if letters in chars_entered:
letter_count += 1
if letter_count == len(chosen_answer.replace(' ', '')):
points += 10
erase_area(hangman)
cv2.imshow("Hangman", hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
draw_won(hangman)
cv2.waitKey()
letter_x = 300
letter_y = 540
chars_entered = []
del object_cords1[object_cords_index]
# shrinked_cords.remove(chosen_value)
break
우선 문자열의 공백을 없애주고, 알파벳이 맞을 때마다 count를 더해준다. 객체를 모두 맞혔을 경우 points에 10을 더하고 draw_won
함수
def draw_won(img):
cv2.putText(img, "YOU'RE RIGHT! PRESS ENTER TO MOVE ON", (120, 60), \
cv2.FONT_HERSHEY_SIMPLEX, 0.7, \
(0, 255, 0), 2)
cv2.imshow("Hangman", img)
를 통해 맞혔음을 사용자에게 보여준다.
마지막으로 letter_x,letter_y,chars_entered를 모두 초기화해주고 object_cords1에 있는 이미 맞춘 객체를 삭제해준다.
key = cv2.waitKey()
if 65 <= key <= 90 or 97 <= key <= 122:
key = chr(key).lower()
if key not in chars_entered:
chars_entered.append(key)
cv2.putText(hangman, key, (letter_x, letter_y), \
cv2.FONT_HERSHEY_SIMPLEX, 1, \
(255, 0, 0), 2)
letter_x += 30
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
정답이 아직 모두 입력되지 않은 경우에는 계속 키보드를 입력받고 아직 입력 받지 않은 key값들을 chars_entered
에 저장하여 일정 간격으로 사용자에게 이미 입력을 한 알파벳들을 보여준다.
if key in chosen_answer_lower:
displayLetter(hangman, key, chosen_answer_lower, char_rects)
else:
draw_hangman(index, hangman)
index += 1
내가 입력한 알파벳이 문자열에 있다면 displayLetter
함수를 통해 박스 안에 알파벳을 그려준다.
def displayLetter(img, letter, word, char_rects): for i in range(len(word)): if word[i] == letter: top_left, bottom_right = char_rects[i] cv2.putText(img, word[i], \ (top_left[0], bottom_right[1]), \ cv2.FONT_HERSHEY_SIMPLEX, \ 1, (0, 0, 0), 2) cv2.imshow("Hangman", img) return img
그리고 틀렸다면,
draw_hangman
을 통해 틀린 개수에 따른 행맨의 모양을 그려주고 몇 번 틀렸는지를 저장하는index
의 값을 올려준다.
def draw_hangman(index, img): if index == 0: cv2.circle(img, (245, 190), 40, (0, 0, 0), thickness=2, \ lineType=cv2.LINE_AA) cv2.imshow("Hangman", img) if index == 1: cv2.line(img, (245, 230), (245, 320), \ (0, 0, 0), thickness=2, \ lineType=cv2.LINE_AA) cv2.imshow("Hangman", img) if index == 2: cv2.line(img, (245, 270), (185, 200), \ (0, 0, 0), thickness=2, \ lineType=cv2.LINE_AA) cv2.imshow("Hangman", img) if index == 3: cv2.line(img, (245, 270), (305, 200), \ (0, 0, 0), thickness=2, \ lineType=cv2.LINE_AA) cv2.imshow("Hangman", img) if index == 4: cv2.line(img, (245, 320), (185, 360), \ (0, 0, 0), thickness=2, \ lineType=cv2.LINE_AA) cv2.imshow("Hangman", img) if index == 5: cv2.circle(img, (245, 190), 40, (0, 0, 255), thickness=2, \ lineType=cv2.LINE_AA) cv2.line(img, (245, 230), (245, 320), \ (0, 0, 255), thickness=2, \ lineType=cv2.LINE_AA) cv2.line(img, (245, 270), (185, 200), \ (0, 0, 255), thickness=2, \ lineType=cv2.LINE_AA) cv2.line(img, (245, 270), (305, 200), \ (0, 0, 255), thickness=2, \ lineType=cv2.LINE_AA) cv2.line(img, (245, 320), (185, 360), \ (0, 0, 255), thickness=2, \ lineType=cv2.LINE_AA) cv2.line(img, (245, 320), (305, 360), \ (0, 0, 255), thickness=2, \ lineType=cv2.LINE_AA) reveal_answer(chosen_answer, hangman, char_rects) cv2.imshow("Hangman", img)
index에 따라 hangman을 머리, 몸통, 양 팔, 양 다리 순으로 순차적으로 그려주고, 6번을 틀리게 되면은(첫번째로 틀렸을 때를 index를 0으로 했으므로, 6번을 틀리면 index가 5이다) hangman 색을 빨강으로 하여 캔버스에 그린다. 여기서
reveal_answer
은 정답을 빈칸에 표시해주는 것이다.
def reveal_answer(word, img, char_rects): for i in range(len(word)): top_left, bottom_right = char_rects[i] cv2.putText(img, word[i], (top_left[0], bottom_right[1]), \ cv2.FONT_HERSHEY_SIMPLEX, \ 1, (255, 0, 0), 2) cv2.imshow("Hangman", img) return img
if index == 4:
hangman1 = copy.deepcopy(hangman)
hangman1 = draw_hint(hangman1)
cv2.imshow("Hangman", hangman1)
cv2.waitKey(1000)
object_cords2 = copy.deepcopy(object_cords1)
delete_val = object_cords2[object_cords_index]
del object_cords2[object_cords_index]
delete_val_list = []
delete_val_list.append(delete_val)
dst, a = picture_yolo_mosaic(IMAGE, MOSAIC_RATE, object_cords2)
show_question(dst, hangman)
delete_val_list = shrink_object_cords(IMAGE, delete_val_list)
box_on_hangman(delete_val_list, hangman, (255, 255, 255))
points -= 5
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
cv2.waitKey(1000)
사용자가 해당 객체에서 알파벳을 4번을 틀린다면, 그 객체의 모자이크 부분을 풀어준다. 먼저 기존에 모든 모자이크가 된 사진에 영향이 가지 않기 위해 hangman1 = copy.deepcopy(hangman)
으로 다른 hangman mat에 모자이크를 풀었고, object_cords 역시 모자이크를 풀 객체를 리스트에서 제외하기 위하여 깊은 복사를 하였다.
그 다음 draw_hint(hangman1)
함수로 힌트를 준다는 표시를 캔버스에 하였다.
def draw_hint(img): cv2.putText(img, "HINT!, MOSAIC CLEARED", (120, 60), \ cv2.FONT_HERSHEY_SIMPLEX, 1, \ (0, 255, 0), 2) return img
새로 좌표값들을 저장한 object_cords2를 이용해 새로 모자이크를 하여 show_question(dst,hangman)
으로 문제를 다시 보여줬고, 제외된 모자이크가 풀린 객체는 선택된 객체가 원래 하얀 박스가 쳐져 있으므로 이는 그대로 유지하기 위하여 box_on_hangman
함수를 만들어 해당 좌표에만 하얀 박스가 쳐지도록 하였다.
def box_on_hangman(shrinked_cords, hangman, color): for val in shrinked_cords: hangman = cv2.rectangle(hangman, (int(val['xmin']) + 576, int(val['ymin']) + 100), (int(val['xmax']) + 576, int(val['ymax']) + 100), color, 2) cv2.imshow("Hangman", hangman)
또한 힌트가 보여졌을 경우에는 5점 감점을 하였고 (points-=5
), erase_area(hangman)
로 기존의 point가 표시된 부분을 덮은 후 draw_points(hangman, points)
로 새로운 점수를 출력하였다.
if index == 6:
del object_cords1[object_cords_index]
hangman2 = copy.deepcopy(hangman)
hangman2 = draw_wrong(hangman2)
points -= 10
all_right = False
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman2)
cv2.waitKey()
break
6번의 기회를 모두 소진하면, 최종적으로 이 객체는 '틀린' 것이 된다. 그러면 object_cords1에서 해당 객체를 제외하고, 새로 깊은 복사를 한 hangman2(글씨 위에 글씨가 안써지도록 깊은복사) 위에 draw_wrong
함수로 틀렸다는 것을 그려 사용자에게 표시해준다.
def draw_wrong(img): cv2.putText(img, "WRONG! CHECK ANSWER AND PRESS ENTER TO MOVE ON", (120, 60), \ cv2.FONT_HERSHEY_SIMPLEX, 0.7, \ (0, 255, 0), 2) return img
points-=10
으로 점수는 10점이 감점되고, 한 문제에서 틀린 객체가 있으므로 all_right = False
로 변경해준다. 마지막으로 바뀐 점수를 캔버스에 그려준다.
cv2.waitKey()
에서 기보드 입력를 받아 다음 문제로 넘어간다
if all_right:
points+=30
erase_area(hangman)
draw_points(hangman, points)
cv2.imshow("Hangman", hangman)
show_answer(hangman)
else:
show_answer(hangman)
end_key = cv2.waitKeyEx()
if end_key == 0x1B:
break
문제의 모든 객체를 틀리지 않을 경우 all_right
가 그대로 True로 저장되어 있다. 이 경우에는 30점을 추가로 준다.
또한 문제 하나가 모두 끝나면 show_answer
함수를 통해
신뢰도와 바운딩 박스가 있는 정답화면을 한 번 더 보여주고 다음문제로 넘어간다.
def show_answer(hangman): answer_img = resize_picture('runs/detect/exp/image0.jpg') height, width, channel = answer_img.shape hangman[100:height + 100, 576:576 + width] = answer_img cv2.imshow("Hangman", hangman)
Yolov5모델이 자동으로 객체탐지를 시행한 사진을 runs/detect/exp/image0.jpg
으로 저장하기 때문에 위 사진을 크기를 줄여서(picture_yolo_mosaic
에서 줄인 만큼) 캔버스에 그려준다.
def show_answer(hangman): answer_img = resize_picture('runs/detect/exp/image0.jpg') height, width, channel = answer_img.shape hangman[100:height + 100, 576:576 + width] = answer_img cv2.imshow("Hangman", hangman)
이렇게 한 문제가 끝난 후 다시 윗줄의 for i in range(5)
반복문으로 돌아가 코드를 반복한다.
erase_all_area(hangman)
draw_final_points(hangman, points)
cv2.imshow("Hangman", hangman)
cv2.waitKey()
cv2.destroyAllWindows()
erase_mat()
erase_all_area(hangman)
으로
def erase_all_area(img): cv2.rectangle(img, (0, 0), (1200, 648), (255, 255, 255), -1)
모든 캔버스 구역을 하얗게 덮어주고 그 위에 draw_final_points
로 마지막 최종 점수를 적어준다.
사진으로 하는 Hangman 코드들의 설명을 이것으로 마치겠다. 여기서 아이디어 발표에서 했던 추가 기능들 (힌트를 edge detection을 통해 제공하는 것 등)을 모두 구현하지는 못했지만, UI 사용에 큰 불편함 없이 깔끔하게 5문제를 풀 수 있는 단순하지만 단순하지만은 않은 게임을 완성했다.
소스는 모두 https://github.com/leeyjwinter/opencv_yolov5-hangman 에서 받을 수 있으며, 파이토치 모듈 사용을 위한 환경을 따로 설정해줘야 한다.