2024第一次尝试电赛,记录自己的视觉组经验(E题三子棋游戏装置Open MV/Open ART mini)

        目录

                一.序言

                二.摄像头的选择

                                1.简要介绍一下我对这两个东西的印象

                                2.常用资料

                                3.二者在硬件配置上的差别,我所了解的

                                4.总结下来

                三.本次电赛的思路

                                1.心路历程

                                2.简说机械臂

                                3.视觉的方案选择

                                        (1)矩形识别不太好用

                                        (2)决定用色块识别

                                        (3)色块排序

                                        (4)色块滤波

                                        (5)其他功能

                                        (6)代码

                                        (7)最后的话

一.序言

今年大一下,参与一下电赛涨涨经验。临比赛前一周开始学习视觉,走了一些弯路,也有一些收获。在准备期间看了很多CSDN上往期电赛思路的分享,发现视觉方面的资料相比其他来说少一些,于是打算学着大佬们记录一下这次的经验和体会,也希望我的文章能够给后来学习视觉的同学一些启发和参考。不过很明显本人纯纯菜鸟,所以也不会有很多正经的硬核干货,大佬勿怪。仅希望让小白们少走一点弯路。

二.摄像头的选择

在开始学习之前,选择一个合适的硬件当然是重中之重。不过我没有选择,因为给我的只有Open ART mini。虽然说我手里也有一个从零件堆里翻出来的Open MV,但是因为带我的学长他们采购和使用的都是ART mini,所以我也就只能用这个了。

因为这次我实现的功能较为简单,所以我并没有感到两者很明显的差别。不过价钱有差别,ART mini一个要300多,MV一个要400多。

1.简要介绍一下我对这两个东西的印象

Open MV是国外一家公司生产的,目前国内的代理商是星瞳科技,在星瞳科技的Open MV官网上提供了许多中文资料,包括一个帮助你快速上手的中文手册,一个可以查询函数库的中文网站,下载IDE的网站,论坛,官方录制的视频教程(根据上手手册讲的),还有售卖的MV的购买界面和详细参数。

而Art mini似乎是另一家叫做逐飞科技的公司代理的。我们当时学习的时候上网想寻找一些资料,结果可以说是一无所获。只知道Art mini跟MV使用时的代码什么的基本都是通用的。无论是在浏览器搜索还是在逐飞科技的官网或者资料仓库里寻找都是一无所获。CSDN上的讲解更是几乎没有,甚至于我昨天才刚刚通过AI找到了一篇疑似为Open Art系列产品做宣传的文章,里面也没提及过mini。有人建议我去外网看看,可惜我不会翻墙,有需要的同学可以尝试一下。反正最终我是一直当作MV来用的。然后总学长那里搞到了一些之前他们写的代码,不过太多太乱,我也没细看。

还有一点很重要,其实我们当初选择采购Art mini而不是MV,除了便宜一百块,就是因为Art mini可以跑神经网络,而MV不能。不过其实对于我们这种水平而言,基本也用不上神经网络,而且据学长说,他自己搞了半个月,又叫来另一个大佬学长搞了三天,也没能弄明白怎么搞这个神经网络。所以只能说有余力,有需要的同学再去认真研究一下这个功能了。我其实也不是很了解,神经网络似乎就是类似调教一个大数据模型,举个例子就是可以识别手写数字。如果用普通的识别模板功能,我们只能做到识别打印出来的数字,而且要在摄像头里的储存用摄像头拍摄的多个角度的照片,以防识别不出来。

2.常用的资料

首先是星瞳科技给我们写的资料

搜Open MV跳出来的第一个就是星瞳科技的网站

下图是上手教程,其中不光包括了基础的快速上手的知识,还有最常用的几个功能的具体介绍。最下面还有很多官方例程,几乎覆盖了Open MV的大部分用法了,很值得一看。在电赛里运用到的功能其实经常就这几种,有空可以认真学一下,像我一样来不及的,可以先走马观花有一些印象,具体需要了再现认真研究。因为官方底层封装了很多代码,非常利于小白上手实现一些简单功能。

顺带提一嘴,Open MV和Open Art mini的底层代码都是C语言,但是为了便于上手,编程语言是Python。没学Python的同学也不要担心,我们在这里使用的都是Python中最基础的语法,着急上手的话可以去看官网教学视频,里面大致讲了一些需要用到的Python语法(在下图手册里叫Python背景知识),不过讲的不是很详细,还可以再结合一些速成Python的视频来学习,因为Python跟C语言的思维逻辑差别有点大,所以我当时学的也是稀里糊涂,不过你要是理解能力很强,那这简单的教程就完全够用了。

下图是中文文档中的内容,我主要是用来看各种函数库的(红框已框出)。还可以在搜索栏搜索自己已知的函数或参数等,来详细了解用法。不过你如果想搜需要实现的功能,比如这次比赛用到的色块识别,他就搜索不出来。仅适用于已知函数名,参数名等的情况下使用。由于我也是小白上手,而且没有时间系统学习,所以我都是先问问AI有哪些函数可以实现我想要的功能,再在这里搜索一下该函数学习如何使用(以前没怎么用过AI,现在发现AI是真好用啊)。不过虽然确实这个函数库已经挺方便了,但是其中语言还是有点简介的,对于我这个Python也是现学的人来说,看起来有点困难。实在有看不明白的,我也去问问AI,不过AI给出的信息也不一定准确,只能说起码比自己在这瞎猜强,大家还是需要实际去试试,甄别一下。有空的话可以认真学习一下常用的功能,对于电赛来说其实常用的功能也就那几样,就是在教学手册里单独讲的那些功能,尤其是排在前面的几个。大家如此聪明,看一眼就知道哪些比较常用了 :)。

再有就是在IDE里面官方也给了很多实例,大家有空的时候也可以多看看,里面的注释也是比较详细的。无论是IDE里的示例还是手册里的历程,都会对我们帮助很大,后面讲思路的时候还会再提。

还有那个官方的视频教程在最开始的那几个视频里有讲一些很基础的知识来帮助大家上手,比如IDE的使用啊之类的,视频也不长,初学时用来了解一下Open MV还是很不错的。其他视频也讲解了很多功能,不过似乎都是照着手册讲的,所以我没细看,大家想学习某一功能的时候可以看看,不过也是浅浅吐槽一下,确实不是专门教学的老师,讲授的水平有限,视频也是很简陋,看起来就像是在办公室随便拿手机录的,跟视频聊天似的。不过有总比没有强了,我见识少,能为一个产品的使用做这么详细的资料和教程,我也是头回见,所以其实也是很开心,大大降低了我们的上手难度,这样也更好卖货不是。(在此严肃批评Art mimi,资料为0,看起来像是不想卖货一样,淘宝上也只有逐飞旗舰店有卖,我都好奇他们怎么想到要买这个的)

除了这些官方给的资料,我昨天还发现CSDN上有两个讲的很详细的文章,当然了这只是我偶然搜到的,还有许多讲的很好的,大家可以多搜多看。

这种详细讲解摄像头基础原理的文章还是建议大家有时间认真了解一下,不仅会帮助你在写代码和运用摄像头功能的时候有更多思路,也能少走一些弯路,避免因为一些简单的基础设置影响到功能实现,也便于调整你需要的基础参数。

尤其摄像头在使用的时候其实非常灵活,很多时候都是需要你有一些巧思来研究如何实现想要的功能。在初次看到赛题的时候,我跟学长商量策略,他说用矩形识别这个功能来识别棋盘格,而我这个疏忽大意的甚至都没注意到摄像头有这个功能。不过因为矩形识别的准确率低,条件更苛刻,最后还是选择用色块识别,具体的想法再后面再讲。在这里举这个例子还是希望大家能打好基础,才能更灵活的运用摄像头的功能。其实摄像头功能很强大的,而且官方已经封装好了许多函数,我们基本都是调用就好了,所以我还是很看好摄像头未来在电赛中的前景的,这也算是我写这篇文章的原因之一了。

Open MV图像处理的基本方法:http://t.csdnimg.cn/QhN6R

Open MV4之sensor模块:http://t.csdnimg.cn/pjehP

插一嘴,在使用mini的时候想要脱机运行除了要把写好的代码放在摄像头U盘文件夹里(包括各种自己写的模块和main.py文件),还要有两个文件才能启动,不过我是直接用的学长给的,大家可以在网上自己翻翻,具体这是什么我也不知道,而且学长给的东西我也不好做主擅自开源,抱歉。

3.二者在硬件配置上的差别,我所了解的

(1)MV的最大帧率比mini要高,据说好像是MV最高可以达到120帧,而mini最高也就50左右(我记性不好,只记得数据大概是这样,有认真的读者可以去查一查官方数据,但是mini几乎没有资料),我在使用的时候也很明显的感觉到了这一点,MV确实比mini的画面流畅很多。MV我没试,但是我同时识别三个色块的时候,mini确实很卡。

(2)MV自己有内存的空间,mini必须要插SD卡。但是MV也能插,不过我拿到的那个根本没有SD卡。因为摄像头的的上面其实也是芯片,而且基本都是脱机运行,所以我们在写好代码之后,要把文件都放在摄像头的文件夹中,这个储存空间也需要注意一下。(这两个摄像头都可以用数据线和电脑连接,连接之后显示的是插入了一个U盘,可以把需要运行的文件放在里面)

(3)两者调焦距的时候稍有差别。我的那个MV调焦距的时候是直接跟拧瓶盖一样自己拧出伸出来的长度,我在使用过程中调焦距基本都要拧出来一段有点小长的距离,收起来的时候又怕镜头会掉,就每次都给他拧回去,所以每次重新使用都要手动调一遍焦距,稍显繁琐。

mini虽然也是拧镜头调焦距,但是在镜头上面有一个小小的螺丝,调之前要给他拧下来。看起来更加繁琐,但是初始的mini焦距就已经是调好的了,而且伸出来的很短,还有螺丝保护,基本就不需要自己再手动调整,所以方便一些。

(4)二者貌似都是广角镜头,镜头畸变严重。不过跟学长探讨的时候他有提到过两者镜头有一个是纯广角,另一个稍好一点。但是我记性不好没记住,认真的读者还是请自行查阅。

(5)MV上采用的是STM32芯片,mini我没仔细看过但估计也大差不差吧,主要是mini的资料确实太少了。下图是Open MV里的截图,大家可以参考一下,具体的我也不是很了解。

还有一点提醒初次尝试的同学,这两种摄像头发热都很严重,是正常现象,不过我个人还是有点害怕给烧坏了,毕竟挺贵的,所以建议大家仅在调试时候插在电脑上,改代码时候还是拔了吧(本人也是硬件小白,如果频繁插拔对硬件其实不好的话那大家当我没说)。但是真的热的烫手也不用惊慌,确实是正常。

4.总结下来

MV和Art mini在软件上区别不大,函数库和IDE都可以用。

硬件呢如果你仅仅是搞电赛而且没有很高要求的话,其实也无所谓。两者之间最有价值的区别一个是价钱上的区别,一个是能否运行神经网络上的区别了。我所讲的也仅限于我的一点了解,同学们可以再多看看资料再进行选择。

三.本次电赛的思路

1.心路历程

其实我们一开始是打算做小车的,但是因为前期没有学TI,看到赛题说小车要用TI,而且循迹寻到一半连线都没有了,当时感觉天都塌了。最后还是在老师和学长的分析和帮助下决定去做E题这个机械臂,直接就是从零开始(虽然我觉得磁悬浮那题看起来更简单,但是我毕竟是外行,而且我们选哪个都是现学也差不多)。不过介于我们都是大一的同学,没什么经验,水平也有限,最终只能说是能让手臂动起来,摄像头我虽然写出来了但是也没用上。不过本来也没对这次报很大的期望,主要还是抱着一个参与和学习的心思,所以也没有很灰心,而且也积累了一些经验,我自己能写出这个摄像头的代码,还是觉得已经很不错了的。

2.简说机械臂

说回商讨赛题思路,机械臂我们一开始是打算用履带传动,步进电机驱动做一个看起来像3D打印或者是抓娃娃机那种运动模式的,这种机械手臂最大的好处就是可以很方便的通过坐标来控制他的位置,只需要我们摄像头给出需要走到的坐标,就可以轻松实现下棋。但是后来种种原因把,只能放弃这个想法。最后还是改用两个舵机做那种类似人的胳膊的水平面上的机械手臂。原理也不难,就是靠两个舵机转角度走到地方。但是最大的问题就是舵机的选择和调试上面。而且因为是两个舵机,两段手臂,若是想要实现仅用xy坐标就走到指定位置,显然是需要经过一番计算的。

以上简述一下手臂的思路,其实还是因为机械臂和摄像头需要配合嘛。我水平也有限,只能给出坐标,再详细调试时间来不及,所以最后我们的成品根本用不了摄像头,有点遗憾。但是我自认为我摄像头的思路还是很好很简单的,所以还是想简单讲讲,也不算白写了。

3.视觉的方案选择

(1)矩形识别不太好用

关于视觉的方案,我一开始看到题,我的的第一想法是做一个五颜六色的棋盘,九个格子颜色都不一样,然后用色块识别,这样不光识别起来方便一点,排序的时候也更简单。可惜在问答里有人问过了,老师回答说棋盘只能有一种颜色,我就只能放弃这种方案。因为我确实是刚开始学摄像头,从来没实战过,我有点担心,格子线太细,如果接着用色块识别,会不会只能识别出一个大的棋盘,而不是每个的小格子,所以我还是问了学长的想法,就如前文所说,选择了矩形识别。

但是我和学长们分别尝试搞了一天半甚至更久一点,差不多直到第二天中午,他们用了各种方法滤波,我自己在那瞎调也调了好久,都先不管误识别率高不高,就连九个格子都不能识别全。我最多也就两三个,学长最多也就4-6个,每一次循环储存下来的数据还会更新,所以始终识别不出来九个。而且我的摄像头每一次循环拍出来的照片识别的矩形都不一定一样,每个格子被识别的概率也不一样,有的格子几乎每次都能被识别到,有的只能是偶尔识别到一次,这也跟我没有摄像头支架有关,用手拿着确实是不稳,大家在调试的时候也尽量是要有支架给摄像头固定住,而且最好是能调整高度和前后位置的。我以为是持续拍照,画面不稳的问题,于是我尝试给拍照的函数移出while循环,只用一张照片反复寻找矩形,但是显然没用,仅拍一张照片无论你怎么反复让他识别,也只能识别出来一两个了。

(2)决定用色块识别

我当时实在是抓耳挠腮了,我就想起来,之所以我比赛之前一点都不着急,没怎么认真学,就是因为官网给了很多例程,所以我就回去翻,结果真让我翻到了识别色块的程序,识别出来之后还能把中心坐标记录下来,并且把色块的边框和中心点画出来。我直接粘进IDE,改了一下颜色阈值,效果简直不要太好。当时真是觉得自己走了好多弯路。恰好学长他们终于决定改用色块识别,也是互相分享了一波喜悦又复杂的感受。

因为我觉得排序更难,需要的时间更久,所以我打算等后面在纠结色块面积和颜色的阈值,暂时只要把摄像头调低一点,只能看得见这九个格子就没问题了。

(3)色块排序

所以我开始研究如何排序,简单商讨了之后还是决定用最简单的冒泡排序。先比较y坐标,将九个格子分成三行,再根据x坐标排序,然后我担心xy值在识别时会有误差,一行的格子y值不一定一样,所以我加了一个允许的误差,两个坐标的x或y大小小于参数给的范围就可以视为在同一行或同一列,然后刚刚矩形识别的时候真是被识别不全搞怕了,在排序里面也加上了检测该点是否已经排序的内容。

同样因为担心识别到的格子有重复,或者遗漏,我又想了一个小办法(不过据学长说没必要)。就是每一种颜色(棋盘和黑白棋子)预先初始化四个列表,分别为:保存列表,输入列表,更新列表和点列表。每一次识别到的点都会先放在输入列表里,再跟保存列表进行比较,如果输入列表中存在保存列表中没有的点,那就将其加在保存列表的末尾,每个点同样允许一点误差(只需计算两个点之间的距离,确保在允许的误差范围内即可视为两个点是同一个点)最后对保存列表进行排序,排好之后保存在更新列表里,然后需要传输数据之前再将其存在对应颜色的点列表里便于区分和书写。在每一次识别结束时需要清空输入列表,保存列表仅在while循环外初始化时清空,这就意味着误识别出来的会被一直储存在保存列表里,所以滤波也显得尤为重要,好在视野里其实没有很多东西,所以还不算很难。

(4)色块滤波

关于滤波,因为我技术有限,所以只用了颜色阈值和色块面积阈值,效果也是可以的。不过我们后来遇到一个问题,我们做出来的棋盘直接用彩印打出来了一个红色的棋盘,反光严重,识别时候受环境影响很大,所以要么是现场脱机调颜色阈值,要么就是用一个哑光的材料做棋盘,不过我一开始没遇到这个问题,而且说现场允许打普通灯光,所以我原本打算把亮度阈值调高,直接去现场打光。但是学长很给力,废了好大力气试出来一个哑光的蓝色的布,比较好用,我最后就是坐享其成了。

关于面积这个吧,说来也搞笑,我太小白了,寻找色块函数的参数还特长,我就没看明白,随便挑了两个参数去限制他的大小,结果跟我说语法错误,学长说这种情况肯定是我不会用这个参数,数值不规范,反正最后我也是没办法了,用if套上去,面积符合我if里的要求才能开始处理,虽然粗糙但是也能用。不过后来加上黑白棋子之后不知道为什么黑白棋子用if限制就不太好使了,那个时候我已经预感到可能用不上摄像头了,所以确实是没心思去找原因了,但是应该是可以解决的,我现在这个代码解决没有我也忘了,摄像头交上去了试不了。不过我后来又去翻官方示例的时候我发现他们在限制面积的时候用的是另一个参数,这个我试了一下就好使了,但是在我现在代码里就没改了。(红色框里是一开始不好使的参数,蓝色框是后来示例里找到的那个)

顺便吐槽一下,有这两个参数的示例是在IDE里找到的,IDE里其他选项都可以是中文,就示例名字全是英文的,本来就不会摄像头,英语也不好,对我太不友好了。

以上都属于是函数部分,基本上都是写在while循环外等待被调用的while里的东西我也没有搞得很复杂,就是识别色块,识别到之后按照颜色分别进行处理,处理的内容基本上就是上面那些,相信大家看代码应该能看明白的,而且由于我是在色块识别的示例代码基础上改的,所以注释还是很多的。不过有一点我同样记录一下我走过的弯路,因为现学的Python,对于列表还不是很熟练,我本来想的是识别三次色块,然后识别出来三个色块列表去分别处理,但是我尝试的时候报错了,我担心难道说列表这种东西不能自己命名还是怎样,所以也没去仔细修改研究。但是依然要识别三种色块,有三个颜色阈值,问了一下AI,AI让我给三种阈值写成一个列表一起放进找色块函数,反正效果是差不多。

(5)其他功能

这些都是最基本的东西,只能是给出棋盘格的坐标和棋子的坐标,后面还包括一些问题,例如:怎样区分棋子是在棋盘内还是棋盘外,对棋盘外的棋子还要排序,怎么确定棋子被下在了哪个格子。关于这三个问题,我没有实现,主要是最后发现可能根本做不到用摄像头下棋那一步,所以我有思路但没写了。因为没有实现,所以在此说一下我的想法仅供参考。

区分棋子在盘内或盘外,只需要得到棋盘边缘的坐标或者说框框,然后再对棋子的坐标进行判定就可以了。至于如何获得棋盘边缘,有四种办法可选:识别物体边缘,识别特征点(四个角),识别四个角的格子的点坐标并在x和y上加半个格子的宽度,或者识别矩形。不过我突然想到其实用不了这么复杂,因为棋子是摆在棋盘两侧,所以只需要检测x坐标在不在棋盘范围内了,这很明显更好实现了,大家就可以大胆尝试。

至于对棋盘外的棋子排序,更是简单的冒泡就可以实现,不再赘述。

最后确定已下的棋子位置,我想的是写在机械臂的程序里(一个是我懒不想自己写,怕给我写好的程序搞乱了,一个也是摄像头处理图像已经够吃力了,算力有限,怕他炸了bushi),只需要比较棋子坐标和格子坐标,在一定误差内,就可以确定他是在哪个格子里了。

至于下棋的逻辑,我们没做到那一步所以就没考虑过,大家还是参考一下其他大佬吧,不过我当时闲逛的时候在CSDN看见有个大佬给下棋逻辑写在摄像头里了,属实是给我惊到了,我一直以为这是机械臂的活应该。下棋逻辑这方面我听说是有一些必胜下法在,网上都有大家可以看看,具体的跟人对弈的完整算法我好像也看见有人发了,没仔细研究,大家自己翻翻吧。

(6)代码

最后就是我自己的完整代码了,因为最后没用上,所以好多东西我也懒得改了,大家凑活看个意思吧,有些注释有点乱。我里面虽然注释和列表名写的都是红色,但是最后棋盘的颜色阈值其实是按蓝色写的,棋子大家也可以自己改。IDE的工具里面自带颜色的阈值编辑器,官方视频里有讲,使用起来很方便,不再赘述。

不过后来我跟学长总结的时候他说可以把这种颜色搞得极端一点,或者二值化,灰度什么的因为摄像头彩色无非就是RGB或者LAB嘛,实际上就是几个颜色通道,比如RGB里白色一般都是(255,255,255),就可以大概给颜色一个范围,识别起来对环境要求没有那么高。我对这些颜色的处理知识其实也不是很了解,所以只能是大概转述出这个意思,如果有不对的,还请指正。

下图是最后效果,因为最后没用上摄像头,所以也没有认真拍,看个意思。

# Blob Detection Example
#
# 这个例子展示了如何使用find_blobs函数来查找图像中的颜色色块。这个例子可以用于寻找红色小方格。

import sensor, image, time


# 为了使色彩追踪效果真的很好,你应该在一个非常受控制的照明环境中。
red_threshold   = (25, 100, -49, 127, -66, -24)
white_threshold = (66, 100, -128, 127, -128, 127) # 白色阈值,L值较高
black_threshold = (12, 33, -16, 1, -21, 127)     # 黑色阈值
# 设置红色的阈值,括号里面的数值分别是L A B 的最大值和最小值(minL, maxL, minA,
# maxA, minB, maxB),LAB的值在图像左侧三个坐标图中选取。如果是灰度图,则只需
# 设置(min, max)两个数字即可。

# 你可能需要调整上面的阈值来跟踪红色的东西…
# 在Framebuffer中选择一个区域来复制颜色设置。

sensor.reset() # 初始化sensor

sensor.set_pixformat(sensor.RGB565) # use RGB565.
#设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种

sensor.set_framesize(sensor.QQVGA) # 使用QQVGA的速度。
#设置图像像素大小
sensor.set_brightness(500)
sensor.skip_frames(10) # 让新的设置生效。
sensor.set_auto_whitebal(False) # turn this off.
#关闭白平衡。白平衡是默认开启的,在颜色识别中,需要关闭白平衡。
clock = time.clock() # 跟踪FPS帧率

# 创建颜色阈值列表
thresholds = [red_threshold, black_threshold, white_threshold]

# 定义颜色代码常量
RED_CODE = 1 << 0
BLACK_CODE = 1 << 1
WHITE_CODE = 1 << 2

white_point=[]
red_point=[]

preserve_list=[]    #保存列表(红色)
input_list=[]   #输入列表(红色)
white_preserve_list=[]    #保存列表(白色)
white_input_list=[]  #输入列表(白色)
white_updated_preserve_list=[] #更新列表(白色)
black_preserve_list=[]    #保存列表(黑色)
black_input_list=[]  #输入列表(黑色)
black_updated_preserve_list=[] #更新列表(黑色)


sorted_points_with_index=[]   #排序后的列表
def merge_unique_points(preserve_list, input_list, tolerance=0.5):
    """
    将输入列表中未在保存列表中发现的点坐标数据添加到保存列表的末尾。

    参数:
    preserve_list (list of tuples): 保存列表,包含点坐标的元组。
    input_list (list of tuples): 输入列表,包含需要添加的点坐标的元组。
    tolerance (float): 点坐标数据在一定误差内视为相等的误差范围,默认为0.5。

    返回:
    list of tuples: 更新后的保存列表,包含所有唯一的点坐标。
    """
    updated_preserve_list = preserve_list[:]
    seen_points = {tuple(point) for point in preserve_list}  # 将保存列表中的点坐标转换为集合

    for new_point in input_list:
        # 检查新点是否在保存列表的误差范围内
        is_unique = True
        for existing_point in seen_points:
            # 计算两点之间的距离
            distance = ((new_point[0] - existing_point[0]) ** 2 +
                        (new_point[1] - existing_point[1]) ** 2) ** 0.5
            # 如果距离小于等于误差范围,则点不是唯一的
            if distance <= tolerance:
                is_unique = False
                break

        # 如果点是唯一的,添加到保存列表
        if is_unique:
            updated_preserve_list.append(new_point)
            seen_points.add(tuple(new_point))  # 更新已见点集合

    return updated_preserve_list


def sort_points_3x3(points, tolerance=0):
    """
    对九个点进行排序,使其按照 3x3 方形点阵的顺序排列,同时考虑坐标的误差。

    参数:
    points (list of tuples): 九个点的坐标列表,每个点是一个 (x, y) 元组。
    tolerance (float): 判断两个点 X 值或 Y 值是否相同时允许的最大误差,默认为0。

    返回:
    list of tuples: 排序后的点,每个元组包含点的坐标、它的序号以及是否已分配序号的标志。
    """
    # 首先根据 Y 值排序,如果 Y 值相同,则根据 X 值排序
    sorted_points = sorted(points, key=lambda point: (point[1], point[0]))

    # 初始化一个列表,用于存储排序后带序号的点
    sorted_points_with_index = []

    # 用于标记点是否已分配序号
    assigned = [False] * len(sorted_points)

    # 给排序后的点分配序号
    for i, point in enumerate(sorted_points):
        # 寻找在误差范围内的第一个未分配序号的点
        for j, (x, y, flag) in enumerate(sorted_points_with_index):
            if flag:  # 如果该点已经分配了序号,跳过
                continue
            # 如果当前点与已排序的点在 Y 值上近似且 X 值在误差范围内
            if abs(y - point[1]) <= tolerance and abs(x - point[0]) <= tolerance:
                sorted_points_with_index[j] = (x, y, i + 1)
                assigned[i] = True
                break

    # 如果有未分配序号的点,则按顺序分配
    for i, assigned_flag in enumerate(assigned):
        if not assigned_flag:
            sorted_points_with_index.append((sorted_points[i][0], sorted_points[i][1], i + 1))

    return sorted_points_with_index



while(True):
    white_point=[]
    red_point=[]
    clock.tick() # 追踪两个snapshots()之间经过的毫秒数.
    img = sensor.snapshot() # 拍一张照片并返回图像。

    hist = img.get_histogram()
    Thresholds = hist.get_threshold()
    #print(Thresholds)
    v = Thresholds.value()
    #img.binary([(0,v)])



    blobs = img.find_blobs(thresholds, merge=False)
    #find_blobs(thresholds, invert=False, roi=Auto),thresholds为颜色阈值,
    #是一个元组,需要用括号[ ]括起来。invert=1,反转颜色阈值,invert=False默认
    #不反转。roi设置颜色识别的视野区域,roi是一个元组, roi = (x, y, w, h),代表
    #从左上顶点(x,y)开始的宽为w高为h的矩形区域,roi不设置的话默认为整个图像视野。
    #这个函数返回一个列表,[0]代表识别到的目标颜色区域左上顶点的x坐标,[1]代表
    #左上顶点y坐标,[2]代表目标区域的宽,[3]代表目标区域的高,[4]代表目标
    #区域像素点的个数,[5]代表目标区域的中心点x坐标,[6]代表目标区域中心点y坐标,
    #[7]代表目标颜色区域的旋转角度(是弧度值,浮点型,列表其他元素是整型),
    #[8]代表与此目标区域交叉的目标个数,[9]代表颜色的编号(它可以用来分辨这个
    #区域是用哪个颜色阈值threshold识别出来的)。
    for blob in blobs:
        # 检查色块的颜色代码
        if blob.code() == RED_CODE:
            if 500 < blob[4] < 1000:
                # 用矩形和十字标记出目标颜色区域
                img.draw_rectangle(blob[0:4], color=(255, 0, 0)) # 矩形标记
                img.draw_cross(blob[5], blob[6], color=(0, 255, 0))   # 十字标记

                # 将新检测到的点添加到输入列表
                input_list.append((blob[5], blob[6]))

            # 更新保存列表,只保留在误差范围内未重复的点
            updated_preserve_list = merge_unique_points(preserve_list, input_list, tolerance=0.5)
            # 清空输入列表,为下一帧的检测做准备
            input_list.clear()

            sorted_points_with_index = sort_points_3x3(preserve_list, tolerance=1)#获取排序后列表
            for point in sorted_points_with_index:
                print(f"Point {point[2]}: ({point[0]}, {point[1]})")

            red_point=updated_preserve_list
            #print(red_point)
            # 打印更新后的保存列表和帧率
            #print(updated_preserve_list)
            print(blob[4]) #色块像素大小
            #print(clock.fps())

        elif blob.code() == WHITE_CODE:
             #白色色块处理(跟棋盘差不多)
            if 100 < blob[4] < 500:
                img.draw_rectangle(blob.rect(), color=(255, 255, 255))  # 绘制白色色块边界
                img.draw_cross(blob.cx(), blob.cy(), color=(255, 255, 255))  # 绘制中心点
                 # 打印白色色块的坐标
                #print("White blob at: x={}, y={}".format(blob.x(), blob.y()))
                white_input_list.append((blob[5], blob[6]))
            white_updated_preserve_list = merge_unique_points(white_preserve_list, white_input_list, tolerance=0.5)
            white_input_list.clear()
            white_point= white_updated_preserve_list
            #print(white_point)


        elif blob.code() == BLACK_CODE:
             #黑色色块处理(也跟棋盘差不多)
            if 100 < blob[4] < 500:
                img.draw_rectangle(blob.rect(), color=(0, 0, 255))  # 绘制黑色色块边界
                img.draw_cross(blob.cx(), blob.cy(), color=(0, 0, 255))  # 绘制中心点
                 # 打印黑色色块的坐标
                #print("Black blob at: x={}, y={}".format(blob.x(), blob.y()))
                black_input_list.append((blob[5], blob[6]))
            black_updated_preserve_list = merge_unique_points(black_preserve_list, black_input_list, tolerance=0.5)
            black_input_list.clear()
            black_point= black_updated_preserve_list
            #print(black_point)

下面是官方的原版的色块识别的代码,就在手册里颜色追踪示例的最后一个

# Blob Detection Example
#
# 这个例子展示了如何使用find_blobs函数来查找图像中的颜色色块。这个例子特别寻找深绿色的物体。

import sensor, image, time

# 为了使色彩追踪效果真的很好,你应该在一个非常受控制的照明环境中。
green_threshold   = (   0,   80,  -70,   -10,   -0,   30)
# 设置绿色的阈值,括号里面的数值分别是L A B 的最大值和最小值(minL, maxL, minA,
# maxA, minB, maxB),LAB的值在图像左侧三个坐标图中选取。如果是灰度图,则只需
# 设置(min, max)两个数字即可。

# 你可能需要调整上面的阈值来跟踪绿色的东西…
# 在Framebuffer中选择一个区域来复制颜色设置。

sensor.reset() # 初始化sensor

sensor.set_pixformat(sensor.RGB565) # use RGB565.
#设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种

sensor.set_framesize(sensor.QQVGA) # 使用QQVGA的速度。
#设置图像像素大小

sensor.skip_frames(10) # 让新的设置生效。
sensor.set_auto_whitebal(False) # turn this off.
#关闭白平衡。白平衡是默认开启的,在颜色识别中,需要关闭白平衡。
clock = time.clock() # 跟踪FPS帧率

while(True):
    clock.tick() # 追踪两个snapshots()之间经过的毫秒数.
    img = sensor.snapshot() # 拍一张照片并返回图像。

    blobs = img.find_blobs([green_threshold])
    #find_blobs(thresholds, invert=False, roi=Auto),thresholds为颜色阈值,
    #是一个元组,需要用括号[ ]括起来。invert=1,反转颜色阈值,invert=False默认
    #不反转。roi设置颜色识别的视野区域,roi是一个元组, roi = (x, y, w, h),代表
    #从左上顶点(x,y)开始的宽为w高为h的矩形区域,roi不设置的话默认为整个图像视野。
    #这个函数返回一个列表,[0]代表识别到的目标颜色区域左上顶点的x坐标,[1]代表
    #左上顶点y坐标,[2]代表目标区域的宽,[3]代表目标区域的高,[4]代表目标
    #区域像素点的个数,[5]代表目标区域的中心点x坐标,[6]代表目标区域中心点y坐标,
    #[7]代表目标颜色区域的旋转角度(是弧度值,浮点型,列表其他元素是整型),
    #[8]代表与此目标区域交叉的目标个数,[9]代表颜色的编号(它可以用来分辨这个
    #区域是用哪个颜色阈值threshold识别出来的)。
    if blobs:
    #如果找到了目标颜色
        for b in blobs:
        #迭代找到的目标颜色区域
            # Draw a rect around the blob.
            img.draw_rectangle(b[0:4]) # rect
            #用矩形标记出目标颜色区域
            img.draw_cross(b[5], b[6]) # cx, cy
            #在目标颜色区域的中心画十字形标记
            print(b[5], b[6])
            #输出目标物体中心坐标

        print(clock.fps()) # 注意: 当连接电脑后,OpenMV会变成一半的速度。当不连接电脑,帧率会增加。

(7)最后的话

我在查资料的时候发现这个Open MV貌似也是最近几年才走进大众视野,我个人认为这个东西非常适合视觉的初学者去学习,而且可以实现许多功能,应用在题目里应该很广泛。所以我想以后电赛的题目可能更偏向于加入一些摄像头的功能要求,也算是一种创新了。

或许以后的队伍里会更多的要求有人专门负责视觉,很荣幸能成为其中一份子,我也觉得摄像头是一个很好玩的东西。希望我这一次微薄的一点点经验能帮到一些跟我一样迷茫无措的初学者吧。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值