本项目运用了基础的 Opencv 图像处理算法 来实现魔方色块的识别并判断颜色。 用户可以通过将魔方举在摄像机的前方来让机器自动将魔方的色块录入数据,然后系统将会依靠用户录入的数据来产生当前魔方的解法,并以动画的形式展现出来。
图形用户界面展示:
使用到的库:
在 Anaconda 环境下安装库:
conda install -c anaconda tk
conda install -c conda-forge numpy
conda install -c conda-forge opencv
配置完环境后:
在已具备所有需要的库之后,到 Rubik-s-Cube-Scanner-Solver 将该repo下的 RubiksCubeScanner 路径克隆到电脑中,然后使用python3 运行 client_gui.py 即可。
当该代码第一次运行时,系统需要创建解魔方需要使用到的表格,一般需要花半个小时到一个小时,请耐心等待,并且会占用额外的80MB左右的硬盘空间。此后执行则不需要创建列表,图形化用户界面会直接显示。
当用户界面显示,用户可以直接用客户端里的色盘来给魔方上色,也可以点击 “Turn on cam" 来打开摄像头,让摄像头来自动识别+读入当前魔方的内容。
在整个魔方已经被填充后,点击 ”Solve“ 系统将会连接到之前已经创建的列表并开始解输入的魔方。输出的解法的步骤数会在20步以内。
在魔方已经被解完后,点击 ”Animate“ 即可让魔方“动起来”,作者提前制作的魔方动画将会开始播放解法。左下角可拖动的块可以用来调节动画播放的速度。
本项目可以大致地分为三个步骤 :
1. 检测并录入魔方的内容
2. 生成已录入魔方的解法
3. 创建可以展示给用户的魔方动画
接下来的文章将会对每一个步骤,尤其是第一个步骤进行详细的分解。
为了减少不必要的运算,在窗体程序的摄像机读取的视屏中间画有一个正方形,所有算法在该正方形中运行。使用 ROI(Region of Interest) 提升运算效率是非常实用的算法优化。
一张普通的彩色图片在每个像素含有一个 (0-255,0-255,0-255) 的数组, 也就是说在一个像素可以有 256x256x256 = 1703936 种不同的颜色变换。然而一张二值化后的图在每个像素仅仅包含 0 或者 1 两种可能。一张彩色图片所包含的信息远远大于它的二值化后的图片,二值图所包含的主要信息仅仅是图片中物体的轮廓。我们识别魔方色块只需要物体的轮廓信息,所以我们的第一个步骤是将读取到的图像二值化。
在Python OpenCV 中, 二值化图片的代码为:
// 首先将彩色图片处理成灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
// img(gray): 待处理的图片
// threshold1(130): 将低于该值的像素归为 0
// threshold2(230): 将高于该值的像素归为 0
// option: 二值化选项
// thresh1: 返回的二值化后的图片
ret,thresh1 = cv2.threshold(gray,130,230,cv2.THRESH_BINARY)
在图片已经被二值化后,我们还需要进行一些图像处理让我们获得的数据更加可靠:
// kenel 是一个算子,它可以是我们自定义的形状,Opencv 将以它的形状进行填充以及腐蚀
// dilate 是opencv的膨胀算法,将它运用在有时模糊的数据中可以让该数据更加稳定
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(2,2))
binary_dilated = cv2.dilate(binary_Image,kernel,iterations = 5)
下面的图片: 左边为膨胀前,右边为膨胀后
将我们的图片膨胀完后,即可开始提取轮廓线,在opencv中算法:
// findContours 会返回两个矩阵
// contours 包含了每一个轮廓线
// hierarchy 包含了对应轮廓线的阶级,也就是被多少其他轮廓线包含着
contours, hierarchy = cv2.findContours(binary_dilated,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
接下来可以在contours 矩阵中寻找为四边形的轮廓线,为此opencv提供了算法:
for cnt in contours:
approx = cv2.approxPolyDP(cnt,0.12*cv2.arcLength(cnt,True),True)
x = approx.ravel()[0]
y = approx.ravel()[1]
if (len(approx) == 4 and 245<x<395 and 105<y<255):
这个循环会把contours中的每一个轮廓线都过一遍,如果该轮廓线不能被估作一个四边形,则舍弃;如果可以,则进入下一步继续判断该四边形是否为正方形:
if approx_is_square(approx) == True
approx_is_square(approx) 是作者自己写的函数,它非常简单。通过比对这个四边形四个边的长度是否在其中任意一边的90%~110%之间,再通过比对这四个边之间的角度是否都在80度到100度;这个算法可以粗略地判断输入的形状是否是正方形。
在识别完正方形后,我们即可数当前图片ROI 中的正方形的数量,如果等于9,就将他们标识出来,最基础的色块识别已经完成!
对于我们而言,一个色块可以是“绿“,”红”,“蓝”,“白“ 等等。 然而对于一个机器而言一个色块的颜色仅仅是一个RGB值。我们为了知道一个色块归属于魔方的哪一面则需要知道当前色块的区域的rgb值代表什么颜色。
第一步是遍历该色块,把所有属于本色块的像素的颜色全部加起来,然后除以加起来的像素的数量,这样获得一个当前色块的平均颜色。
接下来我们可以提前设立很多阀值来判断一个色块的平均颜色到底属于什么颜色:
red_low = [30,30,100] #bgr 格式下红色的最低值
red_high = [150,150,255] #bgr 格式下红色的最高值
如果: red_low < 平均颜色 < red_high
则: 该颜色是红色
好了,这样我们就完成了颜色的判断!我们已经可以把颜色填入GUI中的矩阵中了!