使用opencv做一个猜字谜游戏

 今天,使用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/ 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值