每次轮至你啊就好选走你的棋子或者放置一块木板到棋盘中。树梅派之所以OpenCV处理摄像头。

— 关于这桌游 —

起前先行介绍一下假设开发的是戏,它深受Quoridor。如果您是一个纵深桌游爱好者,那你或许会见懂她的华语版本——步步为营/围追堵截。

先期上转它们美美的剧照。

Quoridor

Quoridor规则非常简单,一个总人口发出一个棋,十片木板。轮流下棋,每次轮至您呢就是可择走你的棋类或者放置一块木板到棋盘中。
棋子的动上下左右,而且不克过了木板阻隔的职务。当起敌手在公身边的时节,你可以借助他为此走多同步。
木板可以轻易停放,但是,必须叫对手至少留一长长的活。

唯独,越简单,越不略。反正,从第一蹩脚打这戏,我就算彻底迷上了。那时候,我还非会见iOS开发为,就对好说,我事后,要拿当时戏,实现到手机上。

微信跳一超越好像火了老大老了,我才起接触,作为一个手残党,
玩了几赖未交20分,打算直接放弃了。但作为一个技术宅,下一反应肯定是“上脚本”。最初想法是用arduino(不见面好学啊)+树梅派,树梅派之所以OpenCV处理摄像头,读取手机屏幕,判断跳跃距离,发命令给arduino控制机械手臂(触控笔),任务就。但问题来了

— 关于这戏 —

地方先奉上。Github-Quoridor
自然,我并无是第一单将这个戏实现的人数。甚至,我非是率先只以iOS平台及落实这游乐人。就我所搜索到之,这个玩有Java版本,C++版本,Javascript版本。
但是,倒霉的事体是,我未曾能够给自身下充斥至之另外一个版成功运行起来。(T-T)天啦撸,这是怎么的伤悲。加之自己本着以上三栽语言都是高居勉强能够翻阅之品位,对于一切程序,Hold不停止呀。(本来找到C++版本好开心之,然而其中所以了很多自己连摸索还摸未下是啊意思的讲话,就……跪了。)
为此,只好全盘自己写了。

理所当然在始发前,我要事先验证,我吧不得不算是一个有些白。所以要您道我代码写得一样塌糊涂。我挺狂的接您对我鄙视然后写一个牛逼的本被自身拜读,我将感激不尽。

  • 勿见面arduino,也从来不arduino和触控笔。
  • 尚无树梅派用的摄像头。
  • 不会OpenCV

— 游戏UI —

为了能够吃自己后开发起来有的放矢,知道好当写什么,所以自己习惯先打个UI出来。
自己之所以Sketch,画了一个充分粗略的界面。

屏幕快照 2016-01-09 下午11.46.07.png

要是大家所见,就是当中棋盘,两止一个悔棋,一个再度开,还有木板数量,到达步数。简简单单的游戏界面就这么出来了。
自身认同这并无为难,我耶确认当时设计有些屎,但是!我会竭尽全力的。欢迎提建议。

arduino控制触控笔,估计很辛苦,但还要必将非常有趣。就到底入手arduino,学会控制机械手臂,难度好像是普程序中最为难之。然后退一步,用adb给屏幕发送模拟触屏命令,暂时缓解第一有的。树梅派的摄像头为要是请,暂时用笔记本摄像头代替(把手机举在摄像头前面确实挺二),再跌一步,用adb截屏读取图像,处理图像比视频流要简单些。然后开发,哎,我必然不是首先单想到这些的,去搜搜,果然就是找到了此地[3](https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fwangshub%2Fwechat_jump_game)。具体做法是:

— 游戏结构 —

事先丢上游戏的程序结构。

屏幕快照 2016-01-09 下午11.53.58.png

是的,这是一个藏的MVC设计模式。
毕竟游戏结构非常简短,我们没有必要把其复杂化。

  1. 据此adb读取游戏截屏
  2. 根据颜色各异判断棋子和生一致跳棋盘的职位。计算两沾之间的离
  3. 找到适当的参数,得到屏幕仍压时间,用adb把命发过去。

— 开始构建游戏 —

我爱自UI开始构建一个用到,而且自爱不释手用Storyboard。(曾几乎哪里时,我一直以为纯代码构建整个应用才是绝可怜的,但是当我本着一个采用之UI进行了几乎糟变动后,我虽体会至这种酷是有代价的。)
所以,上UI截图。

屏幕快照 2016-01-10 上午12.09.40.png

纳尼!一片空白,啪!回水!
萧条冷静,注意看,其实这UI界面还是发生成千上万视图的。只不过我都拿她们之颜料为选择clearColor了。原因呢大粗略,我要圆角,而最好简便易行的贯彻圆角的点子尽管是为此代码修改UIView的Layer。而且这样做的功利也坏多,比如,我得以非常爱的就是开展主题颜色之切换。

自己先放大个非常图然后后面摆我之思绪。

屏幕快照 2016-01-10 上午12.23.04.png

0.先是玩由三单部分构成。
棋盘;
Top控制面板;
Down控制面板;

每个控制面板中四独按钮。
于是Center、Top、Down Background确定。

它们都出于一个Background类,来绘制一个边框和一个圆角。

1.出于玩耍一经出示当前轮至哪个了,我的做法比较粗暴,就是轮子至您,才显得你的控制面板。否则就管它们坐起。
于是以多矣片只视图,Top、Down Screen。它们和Top、Down
Background完全对联合。
打算就是是轮到你时不时,就暗藏起来,让你得看到好的控制面板,否则即展示出来,当个马赛克。

她都出于Screen类控制,以显示对承诺内容还有形没有等控制时之卡通片。

2.游戏棋盘是Chess Board。
出于ChessBoard控制,其实也甚简单,就是画9*9个小格子。
它们和Center Background对同步,但是比其有些10只像素。
有关怎么非把其放在Center Background里面也,因为……我不喜欢。

3.Chess Wall层的做事是绘制当前之墙。

4.Player Prompt层是为此来在你提起棋子时显得当前可倒的职位的图层。

5.Chess
Player层就是绘制棋子的图层啦,里面有个别单Layer,各自表示同样方棋子。由于各种缘由,这个游戏只能两独人口打。

6.Wall
Prompt,顾名思义,就是若停墙壁时用来提醒墙壁位置的。它见面以墙及不足放置时成为红色。

7.Touch
View,这即是因此来收玩家触摸位置信息之图层。上面说及的富有的类都是属Views当中的好像。唯有TouchView我是把它坐落Controllers当中的。因为在我看来,它的角色就是是一个控制器。实际上,它呢未亮其他东西,只是独自的收触摸位置,然后进行简短的辨析之后,通过Delegate设计模式将触摸事件反馈让GameController。
自然,你如说立刻是一个View也是对之。

8.EndScreen。这是用来显示游戏了动画的图层。

好哪。至此,整个娱乐之UI我们不怕构建好了。请见谅自己只是介绍了瞬间每个图层的意图。因为对应的代码你都得以找到。而且我以中间也闹对应的注释。并且每个接近为都为自家之所以各种MARK把一个个效应分开来。如果您有无懂得的地方,也欢迎来吐槽一下。

此间太要害的地方是判定棋子和生一致跳棋盘的职务,因为早期就是设因此上OpenCV,他人处理颜色的代码看了少于双眼觉得最好难为就直接放弃了。所以马上首日记的顶要紧的地方就是怎用OpenCV找到这简单个点。

— 构建游戏模型 —

屏幕快照 2016-01-10 上午12.52.36.png

戏的Models一共有五个文本,但是事实上只有出3单近乎。
自身弗思量吃GameModel看起最为过臃肿,就因故了扩大把它分成几只文本,希望能叫他拘留起越分明一点。

自从不过基础之局部说吧。

— DataModel —

顾名思义,这虽是周游戏最基础的数据结构。
故而外可来表示棋子,木板两栽素。
重大内容极简单,就是:

// MARK: - Data
    /* 数据具体坐标和类型 */

    /** x轴坐标 */
    var x: Int { didSet { updateId() } }
    /** y轴坐标 */
    var y: Int { didSet { updateId() } }
    /** Horizontal: 如果是木板,则表示横向horizontal与竖向vertical。如果是棋子,则表示顶方Top与下方Down。 */
    var h: Bool {
        didSet {
            if t {
                updateWallIds()
            }
        }
    }
    /** Type: 类型,True表示木板,False表示棋子。 */
    var t: Bool

自身还他形容了另外两个特性,用来方便计算游戏逻辑与Ai的时段下。你可小心到,这里自己尚未利用计算属性,而是采取性监视器在XY坐标改变时更新她。因为,我认为,比从变更坐标。调用辅助属性的次数会又多,如果老是调用都进行计算,无疑是会见比消耗CPU的。加上,这为淘不了聊内存,所以便这样做了。
包涵我本着CPU占用如此敏感,因为我首先个Ai计算同一步棋需要四分钟,所以……我……

    // MARK: Count Data
    /* 不进行存储的辅助数据 */

    /** 棋子坐标 */
    var id: Int = 0

    /** 墙壁坐标 */
    var wallIds: [Int] = [Int]()

除此以外,我还他形容了有些Copy方法,还有转换方法,都是为了调用方便。有趣味而可省。

— GameModel —

立马有的就是是真正的重头了。
这个看似第一组成部分凡Interface。这是自个儿以方便温馨进行调用而写的函数。我道这么做实在十分违背Swift的初衷的,毕竟她将接口文件都取消了,我还协调写了一个接口部分。但是本人以为这样做,能叫自身以逐一对象期间调用的早晚思路更加清晰。
各级一个类似都放在心上做团结的工作,就类似一个口一致,并且发生一定的及其余类似进行互的情。这样我道好好。至少自己是这样理解面向对象开发的——程序的本色是各种对象协作方法的体现。(自言自语,如有雷同,我发荣幸。)

及时是一个单例类,游戏模型嘛,你知道的。

    // MARK: - Singleton Class
    /* 创建游戏模型的单例类,并禁止其他类初始化该方法 */

    /** 游戏模型的单例 */
    static var shared = GameModel()

    /** 私有化初始化方法 */
    private override init() {
        super.init()
        initModelData()
    }

各个接口的含义我都出注释,所以大家看看就算好。
主要的情节包括了如此把部分:

0.操打主题颜色之变量

// MARK: 色彩方案
    /** Color Model */
    var color: Bool = true

1.意味着时玩耍的玩家数量信息

    // MARK: 玩家数据
    /** Top Player Data */
    var topPlayer: DataModel  = DataModel()
    /** Down Player Data */
    var downPlayer: DataModel = DataModel()

    /** Top Walls Data */
    var topWalls: [DataModel]  = [DataModel]()
    /** Down Walls Data */
    var downWalls: [DataModel] = [DataModel]()

    /** All walls data */
    var allWalls: [DataModel] { return topWalls + downWalls }

    /** Ai is open */
    var gameAi: Bool = true

2.棋子谱数据,悔棋的上会因此到。也堪保存游戏数量,但是出于一盘棋很紧缺,所以自己少没开保留的效能。感觉需求不老。

    // MARK: 棋谱
    /** Chess manual Stack */
    var gameStack: [DataModel] = [DataModel]()

3.游戏数目。
连目前轮到谁玩家。游戏之状态,是何许人也胜了。还是仍然在进展中。实际上这娱乐是出平局的。但是时还尚无体现。计算方法要命常见,稍微看看代码就可以清楚的。

    // MARK: 游戏数据
    /** The current player */
    var player: Bool {...}

    /** The game state */
    var status: GameStatus {...}

再有老要紧之星星只数据。
棋盘通道记录,我把各级一个棋盘的微方块用0-80协办81只数子来表示,然后记录每一个沾好走路之产一致步之限。
墙数据。我管棋盘从9×9.长墙壁的槽,变成了17×17的深棋盘。每一个墙壁都见面占据三只点。这样即便可防止出现墙壁交叉的场面。
即片单数据的是依然是为因空间更换时间。我于Ai部分来深深的恐惧感,为了能还快的计算出合适的棋步。只好这样了。

    /** 棋盘通道记录 */
    var gameNears: [[Int]] = [[Int]]()

    /** 墙壁数据记录 */
    var gameWalls: [Bool] = [Bool]()

老样子,看图说话:

屏幕快照 2016-01-10 上午10.31.05.png

屏幕快照 2016-01-10 上午10.31.31.png

GameModel+Action
文件基本上是针对娱乐接口函数的落实。假如在后头来什么游戏操作行为的函数,都见面停放这中间。
GameModel+Logic
是一个盘算时棋子的不过走距离的函数。以后发生需要计算的逻辑类型的情节为都见面当及时个中。

— GameAi —

人情。这个,故称为思议,游戏之Ai是这个文件计算出来的。……我放最后面才说戏Ai部分,因为它是自我最后实现的。同时,它呢是至今天犹直接不绝于耳在改善的地方。

从最开始勾画这本子到起来勾画个记录,不断追寻到新的艺术来搜寻这有限个点。最近之发现[2](https://link.jianshu.com?t=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F32636329),
这里用了Tensorflow,对斯代码我还了解不能够。我打算放弃写这首记录,认真看TF。
转念一相思,我之法门则尚无其他人的赫赫上,也从不什么效率,但终是一律种缓解措施,所以还是记下来吧。

— Controller —

屏幕快照 2016-01-10 上午10.33.46.png

合计就三只公文。

FrameCalculator文件是一个坐标计算器,是为为View他们以的时光考虑部分偏移之类的动静。我联合将及时一部分底测算函数放在一个新的类里。由于,这有些自当是Controller的物,所以在这目录之下。
(到此地,可能而有人说,我都将MVC设计模式丢到有不红的繁星上了。确实我承认当时同片自开得无是杀小心,但是,我觉得MVC设计模式的精粹是为我们分别好合程序的意义架构。分成三单有,一部分管制数据,一部分管制视图,一部分协调数据及视图之间的关联。所以,我认为,只要会叫别人一眼看出来,我怎样文件是承担啦一样片功能,并且文件中的干是什么样的,那就算足足了。毕竟,后来新起来的各种设计模式,也都是于MVC的基础及发展使来的,无非,就是拿各个模块进行分割罢了。目的,也尽管是一个,清晰思路。)

TouchView就是一个接收器,前面提到了了。

GameController是一个正规的控制器。打开它的目录,你得观看,尽管自早就将它们的很多力量还绝对下了。但是依然非常丰富生丰富。

屏幕快照 2016-01-10 上午10.42.09.png

咱们一点点来。

— Controller Life Cycle —

斯非常好理解,就是生命周期的决定。而这边要就是局部初始化配置。你可望,这里我便是调用一个个函数,而休是一直进行逐项控件之间的安装。
这个是自己的一个习惯,不明了竟好或算坏。

自身欢喜用MARK吧各个功能模块划分出来。
按部就班下一个GameLogic模块,就是控制了一日游逻辑的代码。
再度下一个Buttons。这个模块中好找到有的按钮初始化函数,按钮事件反映。
每个模块我还以它算一个独自小目标来对待。这样的分开为自身可以好直观的即时查询及自身所思只要之内容。而且,极大的减少了本人转某一个功能模块的代码时,对其他模块的影响。

// MARK: Init Game

    /** 初始化游戏启动配置。 */
    private func gameLaunch() {
        initDemonstrationView()
        initSound()
    }

    /** 更新游戏显示内容 */
    private func gameAppear() {
        alignmentButtons()
        updateButtonsTitle()
        updateGameColor()
        initTouchView()
        updateScreen()
        chessPlayer.update(true)
        chessPlayer.update(false)
    }

那,按照我之这种逻辑。
周控制器其实就算充分简单了。
它们的功用实现重点就通过实现TouchViewDelegate的函数,接收至触摸事件后,将触摸点分发给相应的视图,让他俩做出反馈。
当触摸结束时,判断是否相同糟合理之操作。不是就是一直收操作。
是的口舌就创新GameModel数据。
然后调用Game Logic当中的changePlayer函数。
changPlayer函数负责更新视图,并且判断游戏是匪是终结了。
是无是下同样步而计算机来下棋了。

OpenCV的齐全是Open
Source Computer Vision
Library,是一个跨平台的电脑视觉库,可用来支付实时的图像处理、计算机视觉及模式识别程序[1](https://link.jianshu.com?t=https%3A%2F%2Fzh.wikipedia.org%2Fwiki%2FOpenCV)。听介绍就知晓用当这边非常得体。根据OpenCV的合法教程[4](https://link.jianshu.com?t=https%3A%2F%2Fdocs.opencv.org%2F3.1.0%2Fd2%2Fd96%2Ftutorial_py_table_of_contents_imgproc.html)
初步确定寻找所待的少独点之法:

— 说回GameAi —

哼了。终于,回到这话题了。其实自己勾勒了如此丰富的字数,我不怕想以及大家拉家常这个。
坐……臣妾真的召开不交!!!

— 0.0版 —

棋盘类游戏之Ai,相信广大会面即时想到,做一个博弈树,然后根据阿法贝塔剪枝技术拓展优化。所以,很简短啊。
本身同开始吧是这样的认为的,于是我为广度优先算法计算出最好缺乏路径,然后坐最好缺路径和木板数量作为评估办法。
Ai开始算计的早晚,直接列支出富有可能的落子范围,对各级一个触及进展评估,计算产生无限特别价值,然后针对极度充分价值进行下一致层评估,获取最小价。(极大极小算法)
中规中矩但是充分实用。一开始自己仅吃他盘算两重合,即现已方下一步,对方下同样步。而且,由于自明白这戏不克大概的庞然大物极小,最缺乏路径很可能是对方让您留给的坑,所以自己放弃了剪枝。
乃,我高兴的启玩了。
然后,等了4分钟,它算动了。好极了,Xcode上出示Cpu占用100%4分钟,如果自身为此底是手机,那立时刻鸡蛋都熟了。

乃我仔细翻看了转层序,发现,逻辑没有其他不当。错就蹭在,每一样步棋,需要计算的或者清除是132个,计算两步就是是132单132次方。天呐撸……而且由于每次计算都设考虑是不是封死对方的路,就是待开展有限浅广度优先计算。而且在即时棋子可能清除,墙壁可能清除这些自还都是一直每次都进展演算得出的,没有就此空间更换时间。所以……

虽说剪枝后会见好点,但是说实话,剪枝后大多和瞎下也从没最好死差距……

怎处置呢?
蒙特卡罗算法?
遗传算法?
蚁群算法?
大数据?

— 1.0版 —

自身仔细的考虑之后,感觉,问题应当出以圈上。
以过剩岗位的墙壁是可无欲考虑的。
又,不管是怎样的算法,如果自身从未主意吃来一个高速而富有实际意义的框框评分,那都没法儿算有中的棋路。
于是乎,我马上无异于赖Ai的改良集中在棋局评估上。

接下来自己想开,为什么未直接挡对方的卓绝缺路径也?

据此,这同样涂鸦Ai总算靠谱点了,只针对对方的最为缺乏路径进行运转。查找能够太受对方恶心却同时无影响好的顶差路径的一个岗位放木板。(也便是,计算木板对对方最好短缺路径的熏陶。取影响最充分的那么同样块。)玩起来已经来当博弈的痛感了。

下一场自己开了一个开场函数,让其前面几乎步都是自由放置的,而至了中就根据前的任意木板进行演算,从而达成不会见说一直还一个下法的力量。

长我改进数据结构,空间更换时间,空间更换时间,空间更换时间。好极了。快到转之作业。

而是,这家伙只会恶心对方,却全无可能胜利呀。因为它见面傻傻的把有木板一次性放置进去。但是就算自身本着立即游戏的了解,木板就仿佛核武器,你不怕留着同样片对对方还是一个胁迫。在开场被对方堵到吐血,等对方故仅木板后将它们当猪一样大是非常简单的工作。
唯独,这个本子的Ai就是那头猪。

— 2.0版 —

自身道我当考虑一下出现多路的气象。
即使当电脑知道好现在产生半点条总长,一条可以五六步到终点,另一样长达是十基本上步到达的早晚,它应有有意识地失去断自己之长路,从而进行防卫。
当即是每个正常玩家都得以想到的工作。

这就是说,如何计算出来多路也?

翻遍了算法书,我吧从没找到可以现成使用的算法……当然,也或是自翻译的书无敷多,如果自身翻至了,我回来自己扇自己面子。

设理解,我一旦算多途径,是计算各国一个恐清除的无比缺路径。中间不克在棋盘上乱绕。所以什么吃他于遇到分叉路口的当儿会自动发出分支为?

— 2.1版 —

自家于看到众多拓扑排序的使用后,忽然觉得来个灵感……(看完别问我马上和拓扑排序有什么关联,我为非理解,但是本人不怕是如此来灵感的。)

自己可以于广度优先算法的盘算达,每次进行扩展时,对扩大出来的触发展开解析,如果他们是相邻的,则位于一个汇中。如果她们不相邻,这来多独集。
下一场,每个新的聚合都见面化为一个初的旁,然后进行迭代处理就可查及到具有的路了。

示意图如下,以46哀号点当棋子的职。
第一不良扩大会扩张出来37,45,55号点得位置。他们还是邻之。
老二蹩脚则是56,64,54 以及
36,28五单点。我们可显著的看到她们并无相邻。于是,进行分。如表格1-1同1-1-1.
中1-1.以展开了并且平等差扩大后,找到了首要岗位,于是返回最缺少路径。
1-1-1.虽说持续运行,并且以上边会以产生新的支行。

屏幕快照 2016-01-10 上午11.24.23.png

末段有如此的门道图谱。

屏幕快照 2016-01-10 上午11.27.52.png

自身一样看乐了。多路有矣,连路径宽度都产生了。当时产生种植运动及人生巅峰的感到。
遂,我抢贯彻之算法。

不过……实际测试着,这样出现的问题尚真的不略……我每次进行分都见面连续上一个分层所查询过的触及,而由这点是歪方向扩张的,很可能会见把有回路给挡住掉。导致本有差不多独途径,结果计算出来只发1独路子。

完到即,我还以向阳这主旋律努力受到。其实写了及时首稿子要的目的来三三两两只,一个是料理清自己自己之思路。另一个凡是期待能够发重复多对之戏感兴趣之爱人共同来改善其。我深信不疑有再多的总人口开展思想碰撞后,肯定可以取得更多又强的想法。

先是个目的我上了,因为写到此处,我早就大半出来了不少想方设法,包括什么样改进多途径查询。然后下这样的层面评分做到准确的判定。以及如何运用棋局记录来做一个学习型的Ai。

次个目的就看你的了。棋盘摆好了,我们来下一局?

对了,我实在不是标题党,这个用我早就提请上架了。审核中~当然,这是免费之游乐。因为当一如既往款款打,它缺少缺趣味性,欠缺美感,欠缺各种各种……但是,作为一个用到,它给自身学到了不少物。

迎接各位吐槽,同时,也可望大家能够来打一下。我还以攻,也以攻怎样学习。希望大家能够指导一二。

  1. 用图像的情调空间由RGB转换HSV
  2. 规定图形边缘与轮廓
  3. 若果其中一个概略的着力就所急需的接触。

图片 1

找到所需点的步骤图

为什么要把色彩空间转化为HSV,而休是以原来的RGB或者灰度?
据说HSV比RGB能重好之处理颜色,处理一个值H比三单RGB要简单,饱和度S和明度V能帮助处理光照和阴影。下图可以见到,只有转成HSV色彩空间的以后面的处理面临得以是的检测到方框的顶面边缘,同样的阈值条件下,RGB模式对顶面和右手侧面的边缘检测能力较弱,灰度模式下顶面和片单侧面都分不清楚。而降噪处理在当时张图无什么明显的变型,但对此小图像–比如有木纹理的几–还是起辅助的,所以要保留,高斯模糊应该就够了,Bilateral
Filtering is highly effective in noise removal while keeping edges
sharp. But the operation is slower compared to other filters,
所以没必要。

图片 2

今非昔比色彩空间对边缘检测处理的震慑比

根据上面的希冀,即便有矣边缘点,得到充分多单大概的骨干,仍然无法确定谁中心点是棋子。根据涉,发现棋子总是某种黑紫色,而不论是是产一致超的棋盘或者背景都无见面和这种颜色相近,所以据悉颜色特征找到棋子更简短。所以接下去便找到这个颜色值的上下限,面向Google编程,我找到了别人的一个艺术,自己修改后加大gist。

def get_start_point(image):
    chess = image.copy()
    blurred = cv2.GaussianBlur(chess, (3, 3), 1)
    mask = cv2.inRange(blurred, lower_purple, upper_purple)
    mask_1 = cv2.inRange(blurred, lower_purple_1, upper_purple_1)
    masked = cv2.bitwise_and(blurred, blurred, mask=mask+mask_1)
    chess_edges = cv2.Canny(masked, 100, 200)
    thresh = cv2.adaptiveThreshold(
        chess_edges,
        255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY,
        7,
        1)
    _, cnts, _ = cv2.findContours(
        thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    cs = list()
    for cnt in cnts:
        (x, y), r = cv2.minEnclosingCircle(cnt)
        r = int(r)
        # print(r)
        if 25 < r < 40:
            cs.append((int(x), int(y)))
    mx = min(cs, key=operator.itemgetter(1))
    return mx

边缘检测函数就是者的Canny(),该办法的参数有三独,第一个是如果处理的图像,后少单凡是阈值的上下限。Canny前面的凡正统的挑三拣四颜色方法。有矣边缘值再就此adaptiveThreshold函数进行二值化处理,然后用findContours搜寻棋子的轮廓,
轮廓(Contour)在此其实就算是一样多级边缘点的列表,根据这些点即得算轮廓的成千上万特性,比如形状,面积相当于,OpenCV有成千上万函数帮助处理这些特点,
在此处可以据此boundingRect(包括了所有轮廓点的长方形),minEnclosingCircle(包括了具有轮廓点的周到)或
moments(轮廓的几乎哪里重心),。因为棋盘顶了个球,并且minEnclosingCircle同时返回圆心坐标和半径,在此太恰当不过了。由于findContours凡回的富有轮廓,很是乱套,如图右下里之红圈,所以如果过滤下,根据半径过滤是无与伦比直白的想法,因为球的半径可测量出来,在1080p的屏幕及大致也30只像素。这样便会博得球心坐标了,如同左下的绿圈,注意:1.
虽过滤后呢或发生多单坐标,这里获得最好上面的,就是mx = min(cs, key=operator.itemgetter(1))。2.这个坐标不是起始点的坐标,起始点应该为棋子底部圆心,y坐标应丰富120px(图的左上为坐标原点)。

图片 3

寻棋子.png

出矣棋子的位置坐标,接下就是找到下同样跳的棋盘的顶部中心坐标了,方法大概是如此的:如果面前同跳恰恰落于了当下棋盘的中坚,下一样超越棋盘的骨干会发生个白点(这样各个一样超过的分数为会见递增,朋友围排行上数k的分数都是他挂刷来的),只要找到这白点就ok了。寻找这个白点与找棋子一样也是透过颜色特征white_dot = np.array([0, 0, 245])。但绝非这白点之前还是设为此检测边缘之艺术,然后计算棋盘的顶面中心,如果没有噪音,这个艺术与直找到白点坐标应该是同样的,如果没有噪音。处理图像的步骤还是一如既往,有个细节可以减掉计算量:棋子和下一样跳的棋盘总是以屏幕中心的两侧,利用就点可以把棋盘从图及打出来

def get_end_point(image, start_point):
    global half_width
    global cutted

    cutted = image.copy()
    if start_point[0] < half_width:
        #棋子在屏幕左侧,取棋子坐标右侧的图
        cutted = cutted[0:start_point[1] +
                        chess_height, start_point[0]+chess_width:]
    else:
        cutted = cutted[0:start_point[1] +
                        chess_height, :start_point[0]-chess_width]

    w_dot = _get_end_dot(cutted)

    if w_dot is None:
        print("get by edges")
        w_dot = _get_end_by_edges(cutted)

    x, y = w_dot
    ### 最后得到坐标要还原回去
    if start_point[0] < half_width:
        x += start_point[0]+chess_width

    return (x, y)

上面_get_end_dot()get_start_point类似,_get_end_by_edges大凡当无找到白点的状态下之笨方法,_get_ul方法来这里。findContours主意呢同前面不绝雷同,返回轮廓的又还回去了轮廓的层次结构,因为只有待内侧的概况,用ch[2] < 0过滤出来。关于这个的
解释。
最后还要处理局部噪声造成的极端小的轮廓圆及全体棋盘+阴影造成的卓绝特别之完善。

def _get_ul(c, sigma=0.15):
    l = int(max(0, (1.0 - sigma) * c))
    u = int(min(255, (1.0 + sigma) * c))
    return (l, u)


def _get_end_by_edges(image):
    image = cv2.GaussianBlur(image, (1, 1), 0)
    bg = image[:20, :]
    mid = np.median(bg)
    l, u = _get_ul(mid)
    end_edges = cv2.Canny(image, l, u)
    end_thresh = cv2.adaptiveThreshold(
        end_edges,
        255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY,
        5,
        1)
    _, cnts, hierarchy = cv2.findContours(
        end_thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    hierarchy = hierarchy[0]
    ms = list()
    for cpnt in zip(cnts, hierarchy):
        cnt = cpnt[0]
        ch = cpnt[1]
        if ch[2] < 0:
            (x, y), r = cv2.minEnclosingCircle(cnt)
            r = int(r)
            # print(r)
            if 25 < r < 230:
                ms.append((int(x), int(y)))
    mx = min(ms, key=operator.itemgetter(1))
    return mx

以检测边缘之方寻找中心点的功效如下:单色且形状规则来的可找到正中心或不是不深。奇形怪状的连能不克超越到上面还不包。

图片 4

Figure_1-5.png

图片 5

Figure_1-7.png

主要方法就是这些了,整个代码可以去自己之gist查。整个代码还是会微微问题,有些能改进的地方,但是作为一个演示程序应该足够了。我测试了几乎不好,都无到1k,汗。但是无论是走多少分,都见面碰到下图(当然我之史最高分吧是下论走了,只是这尚没有今天底外挂检测),反正除了测试改进代码,我是未会见用这跑分了。

图片 6

sa.png

一旦生趣味,我恐怕会见增加点关于python的环境布置。

相关文章