目录
1,背景和软件的用途
在玩四国军棋时,除了要四个玩家之外,还需要第五个人做裁判。往往很难凑齐。又想玩又确认的情况下萌生了做一个人工智能裁判的想法。最初在淘宝搜了一下,只有能识别棋子内置特殊标记的裁判器,价格不菲。于是想到用python自制一个。
本人由于不会python,本程序是第一个python程序。第一步自然是搜索,找到了本站内littlezhuhui发表的《python开发的军棋自动裁判软件》参考。该文采用cv2库对图像进行处理,采用pytesseract识别文字。验证后发现图像处理步骤繁杂,文字识别效果不好。
本文采用easyocr库,对摄像头拍摄的照片直接进行文字识别。光线充足的情况下识别效果良好。
2,环境介绍
2.1 硬件环境
1,USB摄像头。用来采集图像。因为棋子面要朝下,光照不好,最好带补光效果的摄像头。
这个没有补光效果,识别笔画多的字时,需要用手机手电筒补光,比如”地雷”。
2,支架
其实只有摄像头是必须的硬件,支架可有可无。比如我在隔离宾馆没有条件,用下图所示的简答搭建也可以用。
有条件可以按图示做一个漂亮一点的支架
2.2 软件环境
1,Pycharm, 程序编写软件;网上下载即可。
2,python安装,参考网络教程安装即可
3,其他支持包通过pip安装
pip install pillow # 图片处理
pip install opencv_python #图片处理
pip install easyocr #OCR文字识别
pip install pygame #界面
pip install playsound #播放音效
2.3 素材准备
1,准备了一些图片和一些音效素材。
录制声音的网站有很多,但大多不方便下载,这里推荐httpshiwp.comtext-to-sound.html,简洁方便。声音素材的文件名必须为英文。
分别对应以下音效:
地雷和军棋不能移动
红方不能识别,请放正重试
红方胜
红方胜,蓝方亮旗
红方胜,蓝方游戏结束
蓝方不能识别,请放正重试’
蓝方胜
蓝方胜,红方亮旗’
蓝方胜,红方游戏结束
同归于尽
同归于尽,红方亮旗
同归于尽,红方游戏结束
同归于尽,蓝方亮旗
同归于尽,蓝方游戏结束
同归于尽,同时亮旗
只能比较两个棋子哦
3,软件功能介绍
1,调试和裁判模式切换。 调试模式显示棋子图像,裁判模式隐藏棋子图像,避免泄露。
2,裁判记录自动保存。裁判结果记录为TXT文档,保存在根目录下的cache文件夹,文件按时间命名,程序启动一次的裁判记录存档在一个文件。
3,裁判结果语音播报。萝莉音嗲嗲的,增加游戏乐趣。
4,裁判结果显示。裁判结果会文字显示在软件上方,方便未听清声音时查看。 调试模式会显示棋子名字,裁判模式不显示棋子名字。
演示视频:
四国军棋AI智能裁判
4,代码
1,Config.py:用来方便管理一些参数。
配置数据
class Config:
def __init__(self):
pass
src = "pic/3.jpg"
checkname = [] # ORC识别的棋子名字
result = '' # 裁判结果
screen = None
frame = None
judgemode = False
filename= '' # 文本文件名,存放对战记录的文本文件
2,bf_button.py 按钮对象
-*- coding=utf-8 -*-
import threading
import pygame
from pygame.locals import MOUSEBUTTONDOWN
class BFControlId(object):
_instance_lock = threading.Lock()
def __init__(self):
self.id = 1
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(BFControlId, "_instance"):
BFControlId._instance = BFControlId(*args, **kwargs)
return BFControlId._instance
def get_new_id(self):
self.id += 1
return self.id
CLICK_EFFECT_TIME = 100
class BFButton(object):
def __init__(self, parent, rect, text='Button', click=None):
self.x,self.y,self.width,self.height = rect
self.bg_color = (225,225,225)
self.parent = parent
self.surface = parent.subsurface(rect)
self.is_hover = False
self.in_click = False
self.click_loss_time = 0
self.click_event_id = -1
self.ctl_id = BFControlId().instance().get_new_id()
self._text = text
self._click = click
self._visible = True
self.init_font()
def init_font(self):
#font = pygame.font.Font(None, 28)
font = pygame.font.Font("C:\Windows\Fonts\STSONG.TTF", 20)
white = 100, 100, 100
self.textImage = font.render(self._text, True, white)
w, h = self.textImage.get_size()
self._tx = (self.width - w) / 2
self._ty = (self.height - h) / 2
@property
def text(self):
return self._text
@text.setter
def text(self, value):
self._text = value
self.init_font()
@property
def click(self):
return self._click
@click.setter
def click(self, value):
self._click = value
@property
def visible(self):
return self._visible
@visible.setter
def visible(self, value):
self._visible = value
def update(self, event):
if self.in_click and event.type == self.click_event_id:
if self._click: self._click(self)
self.click_event_id = -1
return
x, y = pygame.mouse.get_pos()
if x > self.x and x < self.x + self.width and y > self.y and y < self.y + self.height:
self.is_hover = True
if event.type == MOUSEBUTTONDOWN:
pressed_array = pygame.mouse.get_pressed()
if pressed_array[0]:
self.in_click = True
self.click_loss_time = pygame.time.get_ticks() + CLICK_EFFECT_TIME
self.click_event_id = pygame.USEREVENT+self.ctl_id
pygame.time.set_timer(self.click_event_id,CLICK_EFFECT_TIME-10)
else:
self.is_hover = False
def draw(self):
if self.in_click:
if self.click_loss_time < pygame.time.get_ticks():
self.in_click = False
if not self._visible:
return
if self.in_click:
r,g,b = self.bg_color
k = 0.95
self.surface.fill((r*k, g*k, b*k))
else:
self.surface.fill(self.bg_color)
if self.is_hover:
pygame.draw.rect(self.surface, (0,0,0), (0,0,self.width,self.height), 1)
pygame.draw.rect(self.surface, (100,100,100), (0,0,self.width-1,self.height-1), 1)
layers = 5
r_step = (210-170)/layers
g_step = (225-205)/layers
for i in range(layers):
pygame.draw.rect(self.surface, (170+r_step*i, 205+g_step*i, 255), (i, i, self.width - 2 - i*2, self.height - 2 - i*2), 1)
else:
self.surface.fill(self.bg_color)
pygame.draw.rect(self.surface, (0,0,0), (0,0,self.width,self.height), 1)
pygame.draw.rect(self.surface, (100,100,100), (0,0,self.width-1,self.height-1), 1)
pygame.draw.rect(self.surface, self.bg_color, (0,0,self.width-2,self.height-2), 1)
self.surface.blit(self.textImage, (self._tx, self._ty))
3,Judge.py 比较棋子大小
#coding:utf-8
#军棋自动裁判 判断类
from config import *
piecePower={
'工兵':1,
'排长':2,
'连长':3,
'营长':4,
'团长':5,
'旅长':6,
'师长':7,
'军长':8,
'司令':9,
'地雷':-1,
'炸弹':-2,
'军旗':200,
}
#裁判类
class Judger:
def __init__(self):
pass
def judge(self):
if type(Config.checkname)==type('正在处理'):
return Config.checkname
return self.judgeOcrResut()
#识别ocr识别的结果, ocr识别的结果是一个列表,存放于 Config.checkname中
def judgeOcrResut(self):
red=''
black=''
if len(Config.checkname)!= 2 :
return "只能比较两个棋子哦"
else :
red = Config.checkname[0]
black = Config.checkname[1]
#print(red,black)
if red not in piecePower.keys():
return '红方不能识别,请放正重试'
if black not in piecePower.keys():
return '蓝方不能识别,请放正重试'
return self.compare(red,black)
#比较两个棋子棋力的大小
def compare(self,red,black):
if piecePower[red] ==piecePower[black] : #如果相同
tmp="同归于尽"
if piecePower[red] == 9 :
tmp=tmp+",同时亮旗"
elif piecePower[red]==200 :
tmp="地雷和军棋不能移动"
else:
if piecePower[red] ==-2 or piecePower[black] ==-2 : # 有炸弹
tmp='同归于尽'
if piecePower[red] == 9 or piecePower[black] == 9: # 炸到司令
if piecePower[red]>piecePower[black] :
tmp=tmp + ",红方亮旗"
else :
tmp=tmp + ",蓝方亮旗"
if piecePower[red] == 200 or piecePower[black] == 200: # 炸到军旗
if piecePower[red]>piecePower[black] :
tmp=tmp + ",红方游戏结束"
else :
tmp=tmp + ",蓝方游戏结束"
elif piecePower[red] * piecePower[black]==-200 : # 地雷遇到军旗
tmp = "地雷和军棋不能移动"
elif piecePower[red] ==200 or piecePower[black] ==200 : # 有军旗
if piecePower[red]>piecePower[black] :
tmp= "蓝方胜,红方"
else :
tmp= "红方胜,蓝方"
tmp=tmp + '游戏结束'
elif piecePower[red] ==-1 or piecePower[black] ==-1 : # 有地雷
if piecePower[red]>piecePower[black] :
tmp="蓝方胜"
else :
tmp="红方胜"
if piecePower[red] == 1 : # 遇到工兵要反转
tmp='红方胜'
if piecePower[black] == 1 : # 遇到工兵要反转
tmp='蓝方胜'
if piecePower[red] == 9 : # 司令撞雷
tmp=tmp + ',红方亮旗'
if piecePower[black] == 9: # 司令撞雷
tmp=tmp + ',蓝方亮旗'
else :
if piecePower[red]>piecePower[black] :
tmp="红方胜"
else :
tmp="蓝方胜"
return tmp
4,main主函数
coding:utf-8
将两个棋子的内容从棋子图像采集器中提取出来,调用easyocr识别出文字后判断两个棋子的棋力大小
图像预处理所需的模块
from config import *
from Judge import Judger
import cv2
import time
import numpy as np
import easyocr
import pygame
from playsound import playsound
from bf_button import BFButton
def soundfile(str):
if str== '红方胜':
return 'hfs'
if str== '红方胜,蓝方亮旗':
return 'hfslflq'
if str== '红方胜,蓝方游戏结束':
return 'hfslfyxjs'
if str== '红方游戏结束':
return 'hfyxjs'
if str== '蓝方胜':
return 'lfs'
if str== '蓝方胜,红方亮旗':
return 'lfshflq'
if str== '蓝方胜,红方游戏结束':
return 'lfshfyxjs'
if str== '蓝方游戏结束':
return 'lfyxjs'
if str== '同归于尽':
return 'tgyj'
if str== '同归于尽,红方亮旗':
return 'tgyjhflq'
if str== '同归于尽,蓝方亮旗':
return 'tgyjlflq'
if str== '同归于尽,红方游戏结束':
return 'tgyjhfyxjs'
if str== '同归于尽,蓝方游戏结束':
return 'tgyjlfyxjs'
if str== '同归于尽,同时亮旗':
return 'tslq'
if str== '只能比较两个棋子哦':
return 'znbj2g'
if str== '红方不能识别,请放正重试':
return 'hfbnsb'
if str== '蓝方不能识别,请放正重试':
return 'lfbnsb'
if str== '地雷和军棋不能移动':
return 'bnyd'
else:
return '无'
提取目标区域,并对提取的图像进行文字识别
def pickOut(srcImg=None):
Config.checkname = []
if srcImg is None:
# 开始图像处理,读取图片文件
image = cv2.imread(Config.src)
else:
image = srcImg
# 设置识别中英文两种语言 更换OCR源
# reader = easyocr.Reader(['ch_sim', 'en'], gpu=False) # need to run only once to load model into memory #放在主程序允许一次就可以了
Config.checkname = reader.readtext(image, detail=0)
# 判断结果
tmp = theJudger.judge()
# 播放音效
# tmp ='sound/' + soundfile(tmp) + '.mp3' # 对应成声音文件名,只能英文
if soundfile(tmp) != '无':
playsound('sound/' + soundfile(tmp) + '.mp3')
# 输出文字结论
Config.result = ""
if not Config.judgemode :
for code in Config.checkname:
Config.result += code + ","
Config.result = Config.result + tmp
print(Config.result)
with open(Config.filename, "a") as f: # 结果存入文本文档,追加型式
f.write(Config.result + '\n') # 自带文件关闭功能,不需要再写f.close()
在pygame上显示图像
def showImgOnScreen(img, pos, isBGR=True):
imgFrame = np.rot90(img, k=1, axes=(1, 0)) # 根据摄像头,图片的方向可以通过数组(1,0)来控制
imgFrame = cv2.flip(imgFrame, 1, dst=None) # 水平镜像
if isBGR:
# cv2用的是BGR颜色空间,pygame用的是RGB颜色空间,需要做一个转换
imgFrame = cv2.cvtColor(imgFrame, cv2.COLOR_BGR2RGB)
# pygame不能直接显示numpy二进制数组数据,需要转换成surface才能正常显示
imgSurf = pygame.surfarray.make_surface(imgFrame)
Config.screen.blit(imgSurf, pos)
按扭事件处理
def do_click1(btn):
if Config.frame is None:
print('Config.frame is None')
else:
pickOut(Config.frame)
按扭事件处理
def do_click2(btn):
if Config.frame is None:
print('Config.frame is None')
else:
Config.judgemode = not Config.judgemode
if Config.judgemode :
button2.text = '■裁判模式'
else:
button2.text = '□调试模式'
-----------------------------------------------------------------------------------------------
pygame.init()
screen = pygame.display.set_mode([600, 600]) # 设置图形窗口大小为600*600
Config.screen = screen
pygame.display.set_caption("军棋智能裁判") # 设置图形窗口标题
BLACK = (0, 0, 0) # 用RGB值定义黑色
WHITE = (255, 255, 255) # 用RGB值定义白色
BROWN = (166, 134, 95) # 用RGB值定义棕色
格式化成20220507 114539形式 初始化文件名 用文本文件来保存对战记录
Config.filename = 'Cache/' + time.strftime("%Y%m%d %H%M%S", time.localtime()) + '对战记录.txt'
生产勾选按钮
button2 = BFButton(screen, (0, 550, 110, 30))
button2.text = '□调试模式'
button2.bg_color = BLACK
button2.click = do_click2
生成按钮对象
button1 = BFButton(screen, (260, 545, 200, 40))
button1.text = '判断(快捷键J或回车)'
button1.click = do_click1
准备捕捉摄像头内容
camera = cv2.VideoCapture(0)
设置显示中文所用的字体
font = pygame.font.Font("C:\Windows\Fonts\STSONG.TTF", 36)
窗口背景
screen.fill(BLACK)
生成一个定时器对象
timer = pygame.time.Clock()
#生成一个裁判员对象
theJudger = Judger()
设置easyocr,设置识别中英文两种语言
reader = easyocr.Reader(['ch_sim', 'en'], gpu=False) # need to run only once to load model into memory
reader = easyocr.Reader(['ch_sim'], gpu=False) # need to run only once to load model into memory #只要中文即可
keepGoing = True
while keepGoing: # 事件处理循环
# 显示摄像头内容
success, frame = camera.read()
Config.frame = frame
# cv2直接显示捕获的图像没有问题,但要在pygame中正常显示,还要做一些处理
# cv2.imshow('MyCamera',frame)
if not Config.judgemode :
showImgOnScreen(frame, (0, 50), True)
else:
showImgOnScreen(cv2.imread(Config.src), (0, 50), False)
# screen.fill(BROWN, rect=(0, 50, 600, 480)) # 不显示图片 用背景色代替
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
if event.type == pygame.KEYDOWN: # 如果按下了键盘上的键
if event.key == pygame.K_j: # 如果按下'j'
pickOut(frame)
elif event.key == pygame.K_RETURN: # 如果按下了回车键
pickOut(frame)
elif event.key == pygame.K_ESCAPE: # 如果按下了ESC键则退出
keepGoing = False
button1.update(event)
button2.update(event)
# pickOut(frame) # 不停检测 比较耗资源 且无法记录历史
# 输出提示信息
text = font.render(Config.result, True, WHITE)
text_rect = text.get_rect()
# text_rect.centerx = screen.get_rect().centerx
text_rect.x = 10
text_rect.y = 0
screen.fill(BROWN, rect=(0, 0, 600, 50)) # 写过字的区域要刷一遍背景色才能重新写字
screen.blit(text, text_rect)
pygame.display.update() # 刷新窗口
button1.draw()
button2.draw()
timer.tick(30) # 设置帧率不超过30
pygame.quit() # 退出
5,源程序打包下载
下载地址:基于Python的四国军棋AI智能裁判-Python文档类资源-CSDN下载
Original: https://blog.csdn.net/lietuzhou/article/details/124642954
Author: lietuzhou
Title: Python开发的四国军棋AI智能裁判
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/704496/
转载文章受原作者版权保护。转载请注明原作者出处!