우선 그림판을 통하여 퀴즈의 초기화면을 만들었다.
위 그림에서 우측에는 모자이크된 사진을 띄울 것이고, 퀴즈가 틀릴 때마다 행맨을 걸 것이다.
먼저 이 초기화면을 띄우는 show_quiz()
함수를 정의했다.
def show_quiz(): # hangman 캔버스를 띄움
img = cv2.imread(CANVAS) # width = 640, height = 425
cv2.namedWindow("Hangman", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Hangman", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
cv2.imshow("Hangman", img)
# img = np.full((640,480,3),(255,255,255),np.uint8)
# cv2.imshow("quiz",img)
# cv2.waitKey(0)
return img
def show_question(img, hangman): # hangman 캔버스에 문제 보여줌
height, width, channel = img.shape
hangman[100:height + 100, 576:576 + width] = img
cv2.imshow("Hangman", hangman)
show_question
함수는 우측에 문제를 띄워주는 함수이다. show_quiz와 show_question함수의 차이라면 show_question함수에서는 cv2.imshow를 통하여 캔버스를 띄워주는 코드가 함수 내에 이미 있어서 따로 imshow를 쓸 필요가 없다. 어찌보면 일관성이 없는 함수들이라고 볼 수 있다,,ㅎㅎ
지금까지 어떠한 사진이 주어지면, 그 사진 속 객체들을 Yolov5를 통하여 탐지해내고, 객체들을 둘러싸는 바운딩 박스를 파란색 선으로 표시하고, 바운딩 박스 내의 객체들을 모두 모자이크 시켜서 사용자에게 보여지는 것까지를 완료 하였다.
단순히 출력된 화면을 보기만 하는 것이 아닌, 사용자가 게임을 하는
프로젝트가 되게 하기 위해서는, 입력을 받아야 한다.
모자이크 처리가 된 여러가지 객체 중에서 사용자가 풀고 싶어하는 객체를 선택
하도록 하려면 어떻게 해야 할까 고민했는데, 마우스로 클릭하는 것이 가장 직관적이고 쉬울 듯 보였으나,,
def on_mouse(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
value = param[y,x,:]
# print("왼쪽 버튼 눌림 \t x : {} y : {} 좌표의 픽셀값은 : {}".format(x,y, value) )
global shrinked_cords
global hangman
for val in shrinked_cords:
if x>=val['xmin']+576 and x<=val['xmax']+576 and y>=val['ymin']+100 and y<=val['ymax']+100:
hangman = cv2.rectangle(hangman, (int(val['xmin']) + 576, int(val['ymin']) + 100),
(int(val['xmax']) + 576, int(val['ymax']) + 100), (255, 255, 255), 2)
print(shrinked_cords.index(val))
cv2.imshow("Hangman",hangman)
return
위처럼 좌클릭으로 이벤트 처리를 하여 함수를 만들어봤으나 생각대로 클릭이 되지 않고 디버깅을 아무리 해봐도 고쳐지지가 않았다. 게다가 어떠한 크기가 큰 객체가 크기가 작은 객체를 완전히 둘러싸고 있는 형태의 사진이라면? 한 마우스 클릭의 구역에 해당되는 객체가 2개 이상인 문제가 생길 수도 있다.
결국 반 합리화,,반 회피 식으로 키보드 좌우 입력을 통하여 원하는 객체를 탐색할 수 있도록 하였다.
def keyboard_chooseobj():
global shrinked_cords
global hangman
index_num = 0
while shrinked_cords:
key = cv2.waitKeyEx(30)
if key == 0x270000:
index_num += 1
if index_num > len(shrinked_cords) - 1:
index_num = 0
elif key == 0x250000:
index_num -= 1
if index_num < 0:
index_num = len(shrinked_cords) - 1
elif key == 13: # enter
for val in shrinked_cords:
if shrinked_cords.index(val) == index_num:
# print(val['name'])
return val
elif key == 0x1B: # esc
break
for val in shrinked_cords:
if shrinked_cords.index(val) == index_num:
hangman = cv2.rectangle(hangman, (int(val['xmin']) + 576, int(val['ymin']) + 100),
(int(val['xmax']) + 576, int(val['ymax']) + 100), (255, 255, 255), 2)
else:
hangman = cv2.rectangle(hangman, (int(val['xmin']) + 576, int(val['ymin']) + 100),
(int(val['xmax']) + 576, int(val['ymax']) + 100), (255, 0, 0), 2)
cv2.imshow("Hangman", hangman)
키보드 ←, →를 통해 입력마다 인덱스를 바꿔가면서 해당 인덱스인 경우에 객체의 박스를 흰색으로 쳐줬으며, enter키를 누른 경우에 현재 흰색 박스가 된 객체의 레이블 값을 반환하도록 하였다.
이제 문제가 나오고, 객체 선택까지 할 수 있다. 다음을 선택된 객체의 문제 풀이를 위한 빈칸을 마련해주는 것이다.
Hangman게임은 기본적으로 문자열의 길이가 얼만큼인지는 알려준다. 따라서 선택된 객체의 길이에 따라 빈칸박스를 만들어주는 함수를 정의했다.
def draw_blank_rects(word, char_rects, img):
for i in range(len(char_rects)):
top_left, bottom_right = char_rects[i]
if not word[i].isalpha() or \
ord(word[i]) < 65 or \
ord(word[i]) > 122 or \
(ord(word[i]) > 90 and \
ord(word[i]) < 97):
cv2.putText(img, word[i], (top_left[0], \
bottom_right[1]), \
cv2.FONT_HERSHEY_SIMPLEX, \
1, (0, 0, 255), 2)
continue
cv2.rectangle(img, top_left, \
bottom_right, \
(0, 0, 255), thickness=2, \
lineType=cv2.LINE_8)
cv2.imshow("Hangman", img)
return img
여기서
ord
함수는 해당 문자의 유니코드 정수를 반환해주는 함수로, 각 문자가 알파벳이 맞는지 확인하는 절차를 추가하는 코드를 작성하였다. 또한
그 문자열마다 박스를 치는 좌표값을 알아야 하는데, 이를 위해get_char_coords
함수를 작성하였다.
def get_char_coords(word):
x_coord = 230
y_coord = 440
char_ws = []
char_hs = []
for i in word:
char_width, char_height = cv2.getTextSize(i, \
cv2.FONT_HERSHEY_SIMPLEX, 1, 2)[0]
char_ws.append(char_width)
char_hs.append(char_height)
max_char_h = max(char_hs)
max_char_w = max(char_ws)
char_rects = []
for i in range(len(char_ws)):
rect_coord = [(x_coord, y_coord - max_char_h), \
(x_coord + max_char_w, y_coord)]
char_rects.append(rect_coord)
x_coord = x_coord + max_char_w
return char_rects
결과적으로 enter을 누를 시 draw_blank_rects를 실행하여 위와 같은 화면을 띄울 수 있다.