解魔方的机器人攻略24 – 识别颜色(下)

经常有朋友向我要QQ号,很遗憾我属于拖了时代后腿的那种人,暂时还没有QQ号。如果有事的话请直接留言或者给我发邮件,邮箱地址在右侧的下方。还有另外一些朋友是要源代码的,事实上我曾经分享过源代码,但是反馈基本上都是“你这个怎么不能用啊?”

晕倒,电机阻力不同,连杆的倾斜角误差不同,魔方大小不同,魔方润滑程度不同,颜色传感器的读数不同。这么多不同,我在代码里面留了很多参数,就是用来调节和配置的。如果我耐心的在QQ上解释的话,我的老板一定会很耐心地给我写一封热情洋溢的开除通知 :)

所以我会在写攻略的同时逐步公开源代码,一方面可以更好的了解原理,另一方面也可以在攻略中找到想要的答案。有问题的朋友请先仔细看攻略,然后再发邮件提问。下面有一些问题请恕我不回邮件:
1,攻略里已经讲过的。例如:请问解魔方的算法是什么?我很久以前就发过代码了(不过估计这样的同学也看不到这个声明,惆怅啊)。
2,一些特别基础的问题。例如:颜色传感器如何使用?这种问题网上一搜一大堆,请自己google一下,比等邮件更省时间
3,对于参加竞赛的,做毕设的,或者保研需要加分的。非常抱歉,时间紧是您自己的事。我不会帮助投机取巧的行为,况且我其实比你们更忙。

好了,今天会介绍颜色识别剩下的部分。到这一篇结束,所有重要的技术细节就都介绍完了,我相信这些攻略对一个真正的DIY爱好者已经足够了。

下面继续介绍颜色识别的代码实现。

4,设置app.config

上一篇介绍了分辨颜色的六个规则,考虑到不同的颜色传感器可能规则不尽相同,所以把它们放到config文件里,可以随时修改:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <appSettings>  
  4.     <add key="Rank0" value="W:Min" />  
  5.     <add key="Rank1" value="Y:G" />  
  6.     <add key="Rank2" value="B:B" />  
  7.     <add key="Rank3" value="G:-R" />  
  8.     <add key="Rank4" value="O:R+2*RawG-2*RawB" />  
  9.     <add key="Rank5" value="R:1" />  
  10.   </appSettings>  
  11. </configuration>  

5,定义ColorItem类和排序类

接下来是根据排序规则对颜色数组排序,事实上这个跟机器人无关,完全是C#语言的知识。不熟悉的同学请复习一下C#中对List的排序功能。首先我们定义一个ColorItem类,每个实例对应一块魔方的色块:

  1. public class ColorItem  
  2. {  
  3.     public int R, G, B, RawR, RawG, RawB;  
  4.     public int Max, Min, RawMax, RawMin;  
  5.     public int I, J, K;  
  6.     //省略一些赋值操作  
  7. }  

然后定义一个对ColorItem进行排序的类:

  1. public class ColorItemCompare : IComparer<coloritem>  
  2. {  
  3.     private string CompareExpression;  
  4.   
  5.     public ColorItemCompare() { }  
  6.     public ColorItemCompare(string exp)  
  7.     {  
  8.         CompareExpression = exp;  
  9.     }  
  10.   
  11.     public int Compare(ColorItem c1, ColorItem c2)  
  12.     {  
  13.         if (c1 == null || c2 == nullreturn 0;  
  14.         else  
  15.         {  
  16.             return GetEvalOfColor(c1, CompareExpression) - GetEvalOfColor(c2, CompareExpression);  
  17.         }  
  18.     }  
  19.   
  20.     private int GetEvalOfColor(ColorItem c, string exp)  
  21.     {  
  22.         string realExp = exp.ToLower();  
  23.         realExp = realExp.Replace("rawmin", c.RawMin.ToString());  
  24.         realExp = realExp.Replace("rawmax", c.RawMax.ToString());  
  25.         realExp = realExp.Replace("min", c.Min.ToString());  
  26.         realExp = realExp.Replace("max", c.Max.ToString());  
  27.         realExp = realExp.Replace("rawr", c.RawR.ToString());  
  28.         realExp = realExp.Replace("rawg", c.RawG.ToString());  
  29.         realExp = realExp.Replace("rawb", c.RawB.ToString());  
  30.         realExp = realExp.Replace("r", c.R.ToString());  
  31.         realExp = realExp.Replace("g", c.G.ToString());  
  32.         realExp = realExp.Replace("b", c.B.ToString());  
  33.         return Convert.ToInt32(Evaluator.Eval(realExp));  
  34.     }  
  35. }  
  36. </coloritem>  

其中Evaluator是一个自定义的函数,它的功能是对一个字符串格式的表达式求值,例如:Evaluator.Eval(“1+2″)的值是3。

然后通过下面这一段代码,对读到的54个色块进行分辨:

  1. for (int n = 0; n < 6; n++)  
  2. {  
  3.     string[] rankStr = ConfigurationSettings.AppSettings["Rank" + n].Split(':');  
  4.     string resultColor = rankStr[0];  
  5.     string compareExp = rankStr[1];  
  6.   
  7.     ColorItems.Sort(new ColorItemCompare(compareExp));  
  8.     for (int i = 0; i < 9; i++)  
  9.     {  
  10.         ColorItem item = ColorItems[ColorItems.Count - 1];  
  11.         int ijk = item.I * 100 + item.J * 10 + item.K;  
  12.         ColorSortResult.Add(ijk, resultColor);  
  13.         ColorItems.RemoveAt(ColorItems.Count - 1);  
  14.     }  
  15. }  

通过上面的运算,位置坐标为ijk的色块,颜色值就保存在ColorSortResult字典对象中。

6,生成魔方数组

排序之后我们已经知道ijk对应的色块的颜色,接下来再按照i,j,k的顺序读取一遍,就可以生成颜色数组。
ReadColors函数会返回两个字符串,第一个字符串是 “R,G,B,Y….” 格式的返回值,这个是显示那个三维立体魔方用的。第二个字符串是“3,5,2,6….” 这样的格式,在下一步转换为速魔方算法的表示法。

  1. private string[] ReadColors()  
  2. {  
  3.     string ColorStr = "";  
  4.     string RealStr = "";  
  5.     for (int i = 0; i < 6; i++)  
  6.     {  
  7.         for (int j = 0; j < 3; j++)  
  8.         {  
  9.             for (int k = 0; k < 3; k++)  
  10.             {  
  11.                 if (!string.IsNullOrEmpty(ColorStr))  
  12.                 {  
  13.                     ColorStr += ",";  
  14.                     RealStr += ",";  
  15.                 }  
  16.                 int c = i * 100 + j * 10 + k;  
  17.                 string r = ColorSortResult[c];  
  18.                 ColorStr += ColorValue(r);  
  19.                 RealStr += r;  
  20.             }  
  21.         }  
  22.     }  
  23.     return new string[] { ColorStr, RealStr };  
  24. }  
  25.   
  26. private int ColorValue(string c)  
  27. {  
  28.     if (c.Contains("Y") || c.Contains("y")) return 1;  
  29.     if (c.Contains("B") || c.Contains("b")) return 2;  
  30.     if (c.Contains("R") || c.Contains("r")) return 3;  
  31.     if (c.Contains("W") || c.Contains("w")) return 4;  
  32.     if (c.Contains("O") || c.Contains("o")) return 5;  
  33.     if (c.Contains("G") || c.Contains("g")) return 6;  
  34.     return 0;  
  35. }  

7,魔方表示法的转换

上面我们得到了6*3*3的魔方数组表示法,为了调用魔方快速算法,必须转换到URDLFB的表示法。这个转换没啥捷径可走,优雅的程序员偶尔也要使用暴力:

  1. //其中s是把6*3*3的数组,用逗号按顺序连接成的字符串  
  2. private void SolveReadColors(string s)  
  3. {  
  4.     string[] ArrColors = s.Split(','); ;  
  5.     string sInput = "";  
  6.     string ReadQ = "URDLFB";  
  7.     string[] PosQ = new string[6];  
  8.     for (int i = 0; i < 6; i++) PosQ[Convert.ToInt32(ArrColors[4 + i * 9]) - 1] = ReadQ[i].ToString();  
  9.   
  10.     sInput += PosQ[Convert.ToInt32(ArrColors[7]) - 1] + PosQ[Convert.ToInt32(ArrColors[37]) - 1] + " ";  //UF  
  11.     sInput += PosQ[Convert.ToInt32(ArrColors[5]) - 1] + PosQ[Convert.ToInt32(ArrColors[12]) - 1] + " ";  //UR  
  12.     sInput += PosQ[Convert.ToInt32(ArrColors[1]) - 1] + PosQ[Convert.ToInt32(ArrColors[52]) - 1] + " ";  //UB  
  13.     sInput += PosQ[Convert.ToInt32(ArrColors[3]) - 1] + PosQ[Convert.ToInt32(ArrColors[32]) - 1] + " ";  //UL  
  14.     sInput += PosQ[Convert.ToInt32(ArrColors[25]) - 1] + PosQ[Convert.ToInt32(ArrColors[43]) - 1] + " ";  //DF  
  15.     sInput += PosQ[Convert.ToInt32(ArrColors[21]) - 1] + PosQ[Convert.ToInt32(ArrColors[14]) - 1] + " ";  //DR  
  16.     sInput += PosQ[Convert.ToInt32(ArrColors[19]) - 1] + PosQ[Convert.ToInt32(ArrColors[46]) - 1] + " ";  //DB  
  17.     sInput += PosQ[Convert.ToInt32(ArrColors[23]) - 1] + PosQ[Convert.ToInt32(ArrColors[30]) - 1] + " ";  //DL  
  18.     sInput += PosQ[Convert.ToInt32(ArrColors[41]) - 1] + PosQ[Convert.ToInt32(ArrColors[16]) - 1] + " ";  //FR  
  19.     sInput += PosQ[Convert.ToInt32(ArrColors[39]) - 1] + PosQ[Convert.ToInt32(ArrColors[34]) - 1] + " ";  //FL  
  20.     sInput += PosQ[Convert.ToInt32(ArrColors[50]) - 1] + PosQ[Convert.ToInt32(ArrColors[10]) - 1] + " ";  //BR  
  21.     sInput += PosQ[Convert.ToInt32(ArrColors[48]) - 1] + PosQ[Convert.ToInt32(ArrColors[28]) - 1] + " ";  //BL  
  22.   
  23.     sInput += PosQ[Convert.ToInt32(ArrColors[8]) - 1] + PosQ[Convert.ToInt32(ArrColors[38]) - 1] + PosQ[Convert.ToInt32(ArrColors[15]) - 1] + " ";  //UFR  
  24.     sInput += PosQ[Convert.ToInt32(ArrColors[2]) - 1] + PosQ[Convert.ToInt32(ArrColors[9]) - 1] + PosQ[Convert.ToInt32(ArrColors[53]) - 1] + " ";  //URB  
  25.     sInput += PosQ[Convert.ToInt32(ArrColors[0]) - 1] + PosQ[Convert.ToInt32(ArrColors[51]) - 1] + PosQ[Convert.ToInt32(ArrColors[29]) - 1] + " ";  //UBL  
  26.     sInput += PosQ[Convert.ToInt32(ArrColors[6]) - 1] + PosQ[Convert.ToInt32(ArrColors[35]) - 1] + PosQ[Convert.ToInt32(ArrColors[36]) - 1] + " ";  //ULF  
  27.   
  28.     sInput += PosQ[Convert.ToInt32(ArrColors[24]) - 1] + PosQ[Convert.ToInt32(ArrColors[17]) - 1] + PosQ[Convert.ToInt32(ArrColors[44]) - 1] + " ";  //DRF  
  29.     sInput += PosQ[Convert.ToInt32(ArrColors[26]) - 1] + PosQ[Convert.ToInt32(ArrColors[42]) - 1] + PosQ[Convert.ToInt32(ArrColors[33]) - 1] + " ";  //DFL  
  30.     sInput += PosQ[Convert.ToInt32(ArrColors[20]) - 1] + PosQ[Convert.ToInt32(ArrColors[27]) - 1] + PosQ[Convert.ToInt32(ArrColors[45]) - 1] + " ";  //DLB  
  31.     sInput += PosQ[Convert.ToInt32(ArrColors[18]) - 1] + PosQ[Convert.ToInt32(ArrColors[47]) - 1] + PosQ[Convert.ToInt32(ArrColors[11]) - 1];  //DBR  
  32.   
  33.     ResultSteps = RubikSolve.GetResult(sInput);  
  34. }  

这个神奇的SolveReadColors函数,吃进去的是颜色数组,挤出来的是解魔方的步骤。结果保存在ResultSteps变量中,格式为:
F1 U2 F2 D3 L2 D1 F1 U3 L2 D1
其中每两个字符表示一个旋转步骤,第一个字母表示操作的面,第二个字母表示旋转的方向。1是顺时针,3是逆时针,2是旋转180度。

至此萝卜头已经知道了解魔方的方法,在前面的攻略中,我们已经介绍了旋转魔方的分解动作

接下来的工作就简单了,下一篇会介绍如何通过蓝牙遥控萝卜头动手干活。



对 “解魔方的机器人攻略24 – 识别颜色(下)” 的 10 条 评论

  1. iam3i 说:

    很佩服你,你太了不起了,向你学习致敬!

  2. bigapple 说:

    坚决鄙视要代码的…
    虽然偶还没动手,不过魔方机器人肯定不弄了。

    偶一直不知道弄个啥好…诶,为啥每年春天这么忙…

  3. dead_lee 说:

    呵呵, 要代碼無所謂, 要了而不看, 不想才是扯淡.

  4. 胡泊 说:

    俺实在忍受不了的说一句:很多要代码的人都是扯淡,以为代码在手里就什么都懂了。代码不过是嵌入式开发的一半工作而已。最受不了的是留个邮箱:“你把资料和代码发我邮箱里吧。”真不知道这些人脑子里是怎么想的。

  5. 博客收藏 说:

    博主你好!贵博已经被【博客收藏】收藏起来了!http://eesc.info/

  6. 动力小老头 说:

    博主,期待你的下一篇文章,什么时候有空发?

  7. 笑傲青春豆 说:

    偶C#和JAVA基础为0,上学时学过C,基本功不扎实,也忘差不多了,幸亏lejos简单点,对照您的程序,勉强能改改错误。但是C#实在是不敢进行啊,卡在这儿,进行不下去了~~
    用的8547,单独买了个大齿轮转盘,用的触摸传感器替代亮度传感器,爪子翻转魔方的时候,魔方偶尔会掉下来。最主要的问题是,用的总结里面的上位机程序,读完6个面的颜色后,上位机提示“出现了一个问题,导致程序停止正常工作……”估计可能问题是读取的颜色数据上位机处理不了~

    • 嗯,我的博客里提过,分辨颜色是最麻烦的步骤。主要是传感器度颜色不给力,我要是再做一次,一定改用手机摄像头来读

      • 笑傲青春豆 说:

        颜色读取先放一放吧,换换脑子,研究一下结构部分。重新看了“零基础攻略(序)”里面萝卜头的视频,发现爪子与电机连接的多边形机构不一样,萝卜头最顶端的挡了一下,只能向后,不能向前,这样爪子回位时就不会因碰到魔方而卡住,太巧妙了。
        每碰到一个问题,进行不下去的时候,都挺麻烦的。偶抄作业都这么费劲,可以想象您当初弄萝卜头的时候是多么的困难。原创不容易。

发表评论

可以使用下列 XHTML 标签:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>