今天,使用opencv的基础知识做1个猜字谜游戏,因为要用到xwindow相关功能,所以不能在谷歌的colab做实验,只能在本地Ubuntu上实验了。不了解猜字谜游戏的请看:https://baike.baidu.com/item/Hangman/9308312?fr=aladdin 本实验来源于墙外大神,要看原版英文的请科学上网解决,本文做了实验,并做了分析,帮助初学者理解。
用到的图片:blank-canvas.png
用到的字谜数据来源:https://download.csdn.net/download/u013679159/12170794,为.csv数据文件
假如将如下代码存为main.py 文件,在命令行输入:python3 main.py即可,注意,运行命令前要把上面提到的图片和.csv文件放在同一目录下。
import cv2
import numpy as np
import sys
import os
def read_from_csv(csv_f):
with open(csv_f,'r') as f:
movie_data = {}
for line in f.readlines():
line_split = line.strip().split(",")
year = line_split[-1].split("|")
keywords = line_split[-2].split("|")
tagline = line_split[-3].split("|")
director = line_split[-4].split("|")
cast = line_split[-5].split("|")
movie = line_split[0].upper()
movie_data[movie] = [year,keywords,tagline,director,cast]
return movie_data
def get_movie_info(movies_data):
movies_list = list(movies_data.keys())
movie = np.random.choice(movies_list,1)[0].upper()
movie_info = movies_data[movie]
return movie,movie_info
def select_hints(movie_info):
# We will randomly select 3 types of
# hints to display
hints_index = list(np.random.choice(5,3,replace=False))
hints = []
hints_labels = ["Release Year","Keyword","Tagline","Director","Cast"]
labels = []
for hint_index in hints_index:
hint = np.random.choice(movie_info[hint_index],1)[0].upper()
hints.append(hint)
labels.append(hints_labels[hint_index].upper())
return hints,labels
def get_canvas(canvas_file):
img = cv2.imread(canvas_file,1)
return img
def draw_wrong(img,incorrect_attempts):
cv2.putText(img,"WRONG {}/6".format(incorrect_attempts+1),(380,40),\
cv2.FONT_HERSHEY_SIMPLEX,1,\
(0,0,255),2)
return img
def draw_hint(img,hints,labels,incorrect_attempts):
x,y = 20,30
if incorrect_attempts == 0:
return img
elif incorrect_attempts <= 1:
index = 0
elif incorrect_attempts <= 3:
index = 1
elif incorrect_attempts <= 6:
index = 2
cv2.putText(img,"HINT: {}".format(labels[index]),(x,y),\
cv2.FONT_HERSHEY_SIMPLEX,0.6,\
(255,0,255),1)
cv2.putText(img,"{}".format(hints[index]),(x,y+30),\
cv2.FONT_HERSHEY_SIMPLEX,0.6,\
(255,0,255),1)
return img
def draw_right(img):
cv2.putText(img,"RIGHT",(380,40),\
cv2.FONT_HERSHEY_SIMPLEX,0.7,\
(0,255,0),2)
return img
def draw_lost(img):
cv2.putText(img,"YOU LOST",(380,40),\
cv2.FONT_HERSHEY_SIMPLEX,0.7,\
(0,0,255),2)
return img
def draw_won(img):
cv2.putText(img,"YOU WON",(380,40),\
cv2.FONT_HERSHEY_SIMPLEX,0.7,\
(0,255,0),2)
return img
def draw_invalid(img):
cv2.putText(img,"INVALID INPUT",(300,40),\
cv2.FONT_HERSHEY_SIMPLEX,0.7,\
(0,0,255),2)
return img
def draw_reuse(img):
cv2.putText(img,"ALREADY USED",(300,40),\
cv2.FONT_HERSHEY_SIMPLEX,0.7,\
(0,0,255),2)
return img
def draw_used_chars(img,chars_entered,letter):
cv2.putText(img,"Letters used:",(300,80),\
cv2.FONT_HERSHEY_SIMPLEX,0.5,\
(0,0,0),1)
y = 120
x = 350
count = 0
for i in chars_entered:
if count == 10:
x += 50
y = 120
if i==letter:
cv2.putText(img,i,(x,y),\
cv2.FONT_HERSHEY_SIMPLEX,0.5,\
(0,0,255),1)
else:
cv2.putText(img,i,(x,y),\
cv2.FONT_HERSHEY_SIMPLEX,0.5,\
(0,0,0),1)
y += 20
count += 1
return img
def get_char_coords(movie):
x_coord = 100
y_coord = 400
char_ws = []
char_hs = []
for i in movie:
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
def draw_blank_rects(movie,char_rects,img):
for i in range(len(char_rects)):
top_left, bottom_right = char_rects[i]
if not movie[i].isalpha() or \
ord(movie[i]) < 65 or \
ord(movie[i]) > 122 or \
(ord(movie[i]) > 90 and \
ord(movie[i]) < 97):
cv2.putText(img,movie[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=1,\
lineType = cv2.LINE_8)
return img
def check_all_chars_found(movie, chars_entered):
chars_to_be_checked = [i for i in movie if i.isalpha()]
for i in chars_to_be_checked:
if i not in chars_entered:
return False
return True
def draw_circle(img):
cv2.circle(img,(190,160),40,(0,0,0),thickness=2,\
lineType=cv2.LINE_AA)
return img
def draw_back(img):
cv2.line(img,(190,200),(190,320),\
(0,0,0),thickness=2,\
lineType=cv2.LINE_AA)
return img
def draw_left_hand(img):
cv2.line(img,(190,240),(130,200),\
(0,0,0),thickness=2,\
lineType=cv2.LINE_AA)
return img
def draw_right_hand(img):
cv2.line(img,(190,240),(250,200),\
(0,0,0),thickness=2,\
lineType=cv2.LINE_AA)
return img
def draw_left_leg(img):
cv2.line(img,(190,320),(130,360),\
(0,0,0),thickness=2,\
lineType=cv2.LINE_AA)
return img
def draw_right_leg(img):
cv2.line(img,(190,320),(250,360),\
(0,0,0),thickness=2,\
lineType=cv2.LINE_AA)
return img
def draw_hangman(img,num_tries):
if num_tries==1:
return draw_circle(img)
elif num_tries==2:
return draw_back(img)
elif num_tries==3:
return draw_left_hand(img)
elif num_tries==4:
return draw_right_hand(img)
elif num_tries==5:
return draw_left_leg(img)
elif num_tries==6:
return draw_right_leg(img)
else:
return img
def revealMovie(movie,img,char_rects):
#img = cv2.imread(canvas_file,1)
for i in range(len(movie)):
top_left, bottom_right = char_rects[i]
cv2.putText(img,movie[i],(top_left[0],bottom_right[1]),\
cv2.FONT_HERSHEY_SIMPLEX,\
1,(0,255,0),2)
return img
def displayLetter(img,letter,movie,char_rects):
for i in range(len(movie)):
if movie[i]==letter:
top_left, bottom_right = char_rects[i]
cv2.putText(img, movie[i],\
(top_left[0],bottom_right[1]),\
cv2.FONT_HERSHEY_SIMPLEX,\
1,(255,0,0),2)
return img
movie_csv = "movies-list-short.csv"
canvas = "blank-canvas.png"
movies_data = read_from_csv(movie_csv)
movie, movie_info = get_movie_info(movies_data)
print(movie)
hints,labels = select_hints(movie_info)
img = get_canvas(canvas)
char_rects = get_char_coords(movie)
img = draw_blank_rects(movie,char_rects,img)
cv2.namedWindow("Hangman", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Hangman",cv2.WND_PROP_FULLSCREEN,cv2.WINDOW_FULLSCREEN)
cv2.imshow("Hangman",img)
chars_entered = []
incorrect_attempts = 0
img_copy = img.copy()
while 1:
img = img_copy.copy()
img = draw_hint(img,hints,labels,incorrect_attempts)
if incorrect_attempts >= 6:
img = draw_lost(img)
break
elif check_all_chars_found(movie, chars_entered):
img = draw_won(img)
break
else:
letter = cv2.waitKey(0) & 0xFF
if letter < 65 or letter > 122 or (letter > 90 and letter < 97):
img = draw_invalid(img)
cv2.imshow("Hangman",img)
continue
else:
letter = chr(letter).upper()
if letter in chars_entered:
img = draw_reuse(img)
img = draw_used_chars(img,chars_entered,letter)
cv2.imshow("Hangman",img)
continue
else:
chars_entered.append(letter)
if letter in movie:
img = draw_right(img)
img = displayLetter(img,letter,movie,char_rects)
img_copy = displayLetter(img_copy,letter,movie,\
char_rects)
else:
img = draw_wrong(img,incorrect_attempts)
incorrect_attempts += 1
img = draw_used_chars(img,chars_entered,letter)
img = draw_hangman(img,incorrect_attempts)
img_copy = draw_used_chars(img_copy,chars_entered,letter)
img_copy = draw_hangman(img_copy,incorrect_attempts)
cv2.imshow("Hangman",img)
#cv2.waitKey(0)
img = revealMovie(movie,img,char_rects)
cv2.imshow("Hangman",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
movie_csv = "movies-list-short.csv"
canvas = "blank-canvas.png"
movies_data = read_from_csv(movie_csv)
这个游戏导入必要的库之后,紧接着是一些函数定义,而后开始运行代码,代码从上面这段开始真正运行,类似于c语言中的main函数是这个应用程序入口一样。其实任何语言,理解其运行时的执行顺序都是基础的、必须的。
先定义两个变量 movie_csv 和 canvas, 接下来调用read_from_csv获取.csv文件中的电影信息记录。接下来看read_from_csv是怎么做的:
def read_from_csv(csv_f):
with open(csv_f,'r') as f:
movie_data = {}
for line in f.readlines():
line_split = line.strip().split(",")
year = line_split[-1].split("|")
keywords = line_split[-2].split("|")
tagline = line_split[-3].split("|")
director = line_split[-4].split("|")
cast = line_split[-5].split("|")
movie = line_split[0].upper()
movie_data[movie] = [year,keywords,tagline,director,cast]
return movie_data
with语法:https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/index.html
csv文件中的第一行 Jurassic World,Chris Pratt|Bryce Dallas Howard|Irrfan Khan|Vincent D'Onofrio|Nick Robinson,Colin Trevorrow,The park is open.,monster|dna|tyrannosaurus rex|velociraptor|island,2015
所以line_split变为["Jurassic World", "Chris Pratt|Bryce Dallas Howard|Irrfan Khan|Vincent D'Onofrio|Nick Robinson", "Colin Trevorrow", "The park is open.", "monster|dna|tyrannosaurus rex|velociraptor|island", "2015"]
从倒数第一项开始依次取出 year keywords tagline director cast, 然后取出正数第一项movie(电影名),最后给movie_data这个字典增加一条记录,movie为key,value是一个列表,该列表元素为year keywords tagline director cast,最后movie_data(这里只列举前3项,因为有1000多项,完全列出太长)为:
{'JURASSIC WORLD': [['2015'], ['monster', 'dna', 'tyrannosaurus rex', 'velociraptor', 'island'], ['The park is open.'], ['Colin Trevorrow'], ['Chris Pratt', 'Bryce Dallas Howard', 'Irrfan Khan', "Vincent D'Onofrio", 'Nick Robinson']],
'MAD MAX: FURY ROAD': [['2015'], ['future', 'chase', 'post-apocalyptic', 'dystopia', 'australia'], ['What a Lovely Day.'], ['George Miller'], ['Tom Hardy', 'Charlize Theron', 'Hugh Keays-Byrne', 'Nicholas Hoult', 'Josh Helman']],
'INSURGENT': [['2015'], ['based on novel', 'revolution', 'dystopia', 'sequel', 'dystopic future'], ['One Choice Can Destroy You'], ['Robert Schwentke'], ['Shailene Woodley', 'Theo James', 'Kate Winslet', 'Ansel Elgort', 'Miles Teller']]}
接下来运行到:
movie, movie_info = get_movie_info(movies_data)
看get_movie_info如何实现:
def get_movie_info(movies_data):
movies_list = list(movies_data.keys())
movie = np.random.choice(movies_list,1)[0].upper()
movie_info = movies_data[movie]
return movie,movie_info
该部分代码从movies_data这个字典中随机取出1条数据,看movie和movie_info的结果为:
HOUSEBOUND
[['2014'], ['haunted house', 'superstition', 'horror movie', 'home detention'], ['Terror Gets Domesticated'], ['Gerard Johnstone'], ["Morgana O'Reilly", 'Rima Te Wiata', 'Glen-Paul Waru', 'Cameron Rhodes', 'Millen Baird']]
接下来是
hints,labels = select_hints(movie_info)
分析select_hints是怎么做的:
def select_hints(movie_info):
# We will randomly select 3 types of
# hints to display
hints_index = list(np.random.choice(5,3,replace=False))
hints = []
hints_labels = ["Release Year","Keyword","Tagline","Director","Cast"]
labels = []
for hint_index in hints_index:
hint = np.random.choice(movie_info[hint_index],1)[0].upper()
hints.append(hint)
labels.append(hints_labels[hint_index].upper())
return hints,labels
hints_index = list(np.random.choice(5,3,replace=False)) 用于随机选择3个下标,比如[1,3,4]
hint选择movie_info的第1、3、4条信息的任意一个元素,labels选择第1、3、4条标签,即“Keyword”、“Director”、“Cast”。
接下来:
img = get_canvas(canvas)
char_rects = get_char_coords(movie)
img = draw_blank_rects(movie,char_rects,img)
img = get_canvas(canvas)读取图片文件
char_rects = get_char_coords(movie) 获取由电影名称的字母个数决定的方框坐标列表。
def get_char_coords(movie):
x_coord = 100
y_coord = 400
char_ws = []
char_hs = []
for i in movie:
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
结果如下:
[[(100, 378), (124, 400)], [(124, 378), (148, 400)], [(148, 378), (172, 400)], [(172, 378), (196, 400)], [(196, 378), (220, 400)], [(220, 378), (244, 400)], [(244, 378), (268, 400)], [(268, 378), (292, 400)], [(292, 378), (316, 400)], [(316, 378), (340, 400)]]
接下来显示被吊的小人和所有字符的空框
img = draw_blank_rects(movie,char_rects,img)
cv2.namedWindow("Hangman", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Hangman",cv2.WND_PROP_FULLSCREEN,cv2.WINDOW_FULLSCREEN)
cv2.imshow("Hangman",img)
看看是如何实现的:
def draw_blank_rects(movie,char_rects,img):
for i in range(len(char_rects)):
top_left, bottom_right = char_rects[i]
if not movie[i].isalpha() or \
ord(movie[i]) < 65 or \
ord(movie[i]) > 122 or \
(ord(movie[i]) > 90 and \
ord(movie[i]) < 97):
cv2.putText(img,movie[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=1,\
lineType = cv2.LINE_8)
return img
说明:如果movie里的字符不是a~z和A~Z,那么直接显示出来,否则显示为空框框
接下来,进入该程序的主循环,输入字符,显示结果。接下来重点介绍几个函数实现:
① 提示语绘制,根据错误次数进行显示不同的提示语
def draw_hint(img,hints,labels,incorrect_attempts):
x,y = 20,30
if incorrect_attempts == 0:
return img
elif incorrect_attempts <= 1:
index = 0
elif incorrect_attempts <= 3:
index = 1
elif incorrect_attempts <= 6:
index = 2
cv2.putText(img,"HINT: {}".format(labels[index]),(x,y),\
cv2.FONT_HERSHEY_SIMPLEX,0.6,\
(255,0,255),1)
cv2.putText(img,"{}".format(hints[index]),(x,y+30),\
cv2.FONT_HERSHEY_SIMPLEX,0.6,\
(255,0,255),1)
return img
②绘制已经输入过的字符:如果当前输入的字符已经在之前被输入过,那么之前输入过的字符会是红色显示。
def draw_used_chars(img,chars_entered,letter):
cv2.putText(img,"Letters used:",(300,80),\
cv2.FONT_HERSHEY_SIMPLEX,0.5,\
(0,0,0),1)
y = 120
x = 350
count = 0
for i in chars_entered:
if count == 10:
x += 50
y = 120
if i==letter:
cv2.putText(img,i,(x,y),\
cv2.FONT_HERSHEY_SIMPLEX,0.5,\
(0,0,255),1)
else:
cv2.putText(img,i,(x,y),\
cv2.FONT_HERSHEY_SIMPLEX,0.5,\
(0,0,0),1)
y += 20
count += 1
return img
③如果当前输入字符是新字符,且在movie里有出现,则调用如下函数,也就是凡是有该字符出现的位置,就在对应的框框上填上该字符。
def displayLetter(img,letter,movie,char_rects):
for i in range(len(movie)):
if movie[i]==letter:
top_left, bottom_right = char_rects[i]
cv2.putText(img, movie[i],\
(top_left[0],bottom_right[1]),\
cv2.FONT_HERSHEY_SIMPLEX,\
1,(255,0,0),2)
return img
④注意img = img_copy.copy()这句代码,每次绘制都要从一个副本重新开始,这样就避免提示词、对错信息等一次次叠加在图像上。
总体来说,这个小游戏还有点意思,只是我不怎么看电影,所以还少猜对,那个小人儿总是被吊死。~-~
参考链接:https://www.learnopencv.com/hangman-creating-games-in-opencv/