解魔方的机器人攻略24 – 识别颜色(下)
由 动力老男孩 发表于 2010/04/11 20:55:50经常有朋友向我要QQ号,很遗憾我属于拖了时代后腿的那种人,暂时还没有QQ号。如果有事的话请直接留言或者给我发邮件,邮箱地址在右侧的下方。还有另外一些朋友是要源代码的,事实上我曾经分享过源代码,但是反馈基本上都是“你这个怎么不能用啊?”
晕倒,电机阻力不同,连杆的倾斜角误差不同,魔方大小不同,魔方润滑程度不同,颜色传感器的读数不同。这么多不同,我在代码里面留了很多参数,就是用来调节和配置的。如果我耐心的在QQ上解释的话,我的老板一定会很耐心地给我写一封热情洋溢的开除通知
所以我会在写攻略的同时逐步公开源代码,一方面可以更好的了解原理,另一方面也可以在攻略中找到想要的答案。有问题的朋友请先仔细看攻略,然后再发邮件提问。下面有一些问题请恕我不回邮件:
1,攻略里已经讲过的。例如:请问解魔方的算法是什么?我很久以前就发过代码了(不过估计这样的同学也看不到这个声明,惆怅啊)。
2,一些特别基础的问题。例如:颜色传感器如何使用?这种问题网上一搜一大堆,请自己google一下,比等邮件更省时间
3,对于参加竞赛的,做毕设的,或者保研需要加分的。非常抱歉,时间紧是您自己的事。我不会帮助投机取巧的行为,况且我其实比你们更忙。
好了,今天会介绍颜色识别剩下的部分。到这一篇结束,所有重要的技术细节就都介绍完了,我相信这些攻略对一个真正的DIY爱好者已经足够了。
下面继续介绍颜色识别的代码实现。
4,设置app.config
上一篇介绍了分辨颜色的六个规则,考虑到不同的颜色传感器可能规则不尽相同,所以把它们放到config文件里,可以随时修改:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="Rank0" value="W:Min" /> <add key="Rank1" value="Y:G" /> <add key="Rank2" value="B:B" /> <add key="Rank3" value="G:-R" /> <add key="Rank4" value="O:R+2*RawG-2*RawB" /> <add key="Rank5" value="R:1" /> </appSettings> </configuration>
5,定义ColorItem类和排序类
接下来是根据排序规则对颜色数组排序,事实上这个跟机器人无关,完全是C#语言的知识。不熟悉的同学请复习一下C#中对List
public class ColorItem { public int R, G, B, RawR, RawG, RawB; public int Max, Min, RawMax, RawMin; public int I, J, K; //省略一些赋值操作 }
然后定义一个对ColorItem进行排序的类:
public class ColorItemCompare : IComparer{ private string CompareExpression; public ColorItemCompare() { } public ColorItemCompare(string exp) { CompareExpression = exp; } public int Compare(ColorItem c1, ColorItem c2) { if (c1 == null || c2 == null) return 0; else { return GetEvalOfColor(c1, CompareExpression) - GetEvalOfColor(c2, CompareExpression); } } private int GetEvalOfColor(ColorItem c, string exp) { string realExp = exp.ToLower(); realExp = realExp.Replace("rawmin", c.RawMin.ToString()); realExp = realExp.Replace("rawmax", c.RawMax.ToString()); realExp = realExp.Replace("min", c.Min.ToString()); realExp = realExp.Replace("max", c.Max.ToString()); realExp = realExp.Replace("rawr", c.RawR.ToString()); realExp = realExp.Replace("rawg", c.RawG.ToString()); realExp = realExp.Replace("rawb", c.RawB.ToString()); realExp = realExp.Replace("r", c.R.ToString()); realExp = realExp.Replace("g", c.G.ToString()); realExp = realExp.Replace("b", c.B.ToString()); return Convert.ToInt32(Evaluator.Eval(realExp)); } }
其中Evaluator是一个自定义的函数,它的功能是对一个字符串格式的表达式求值,例如:Evaluator.Eval(“1+2″)的值是3。
然后通过下面这一段代码,对读到的54个色块进行分辨:
for (int n = 0; n < 6; n++) { string[] rankStr = ConfigurationSettings.AppSettings["Rank" + n].Split(':'); string resultColor = rankStr[0]; string compareExp = rankStr[1]; ColorItems.Sort(new ColorItemCompare(compareExp)); for (int i = 0; i < 9; i++) { ColorItem item = ColorItems[ColorItems.Count - 1]; int ijk = item.I * 100 + item.J * 10 + item.K; ColorSortResult.Add(ijk, resultColor); ColorItems.RemoveAt(ColorItems.Count - 1); } }
通过上面的运算,位置坐标为ijk的色块,颜色值就保存在ColorSortResult字典对象中。
6,生成魔方数组
排序之后我们已经知道ijk对应的色块的颜色,接下来再按照i,j,k的顺序读取一遍,就可以生成颜色数组。
ReadColors函数会返回两个字符串,第一个字符串是 “R,G,B,Y….” 格式的返回值,这个是显示那个三维立体魔方用的。第二个字符串是“3,5,2,6….” 这样的格式,在下一步转换为速魔方算法的表示法。
private string[] ReadColors() { string ColorStr = ""; string RealStr = ""; for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { if (!string.IsNullOrEmpty(ColorStr)) { ColorStr += ","; RealStr += ","; } int c = i * 100 + j * 10 + k; string r = ColorSortResult[c]; ColorStr += ColorValue(r); RealStr += r; } } } return new string[] { ColorStr, RealStr }; } private int ColorValue(string c) { if (c.Contains("Y") || c.Contains("y")) return 1; if (c.Contains("B") || c.Contains("b")) return 2; if (c.Contains("R") || c.Contains("r")) return 3; if (c.Contains("W") || c.Contains("w")) return 4; if (c.Contains("O") || c.Contains("o")) return 5; if (c.Contains("G") || c.Contains("g")) return 6; return 0; }
7,魔方表示法的转换
上面我们得到了6*3*3的魔方数组表示法,为了调用魔方快速算法,必须转换到URDLFB的表示法。这个转换没啥捷径可走,优雅的程序员偶尔也要使用暴力:
//其中s是把6*3*3的数组,用逗号按顺序连接成的字符串 private void SolveReadColors(string s) { string[] ArrColors = s.Split(','); ; string sInput = ""; string ReadQ = "URDLFB"; string[] PosQ = new string[6]; for (int i = 0; i < 6; i++) PosQ[Convert.ToInt32(ArrColors[4 + i * 9]) - 1] = ReadQ[i].ToString(); sInput += PosQ[Convert.ToInt32(ArrColors[7]) - 1] + PosQ[Convert.ToInt32(ArrColors[37]) - 1] + " "; //UF sInput += PosQ[Convert.ToInt32(ArrColors[5]) - 1] + PosQ[Convert.ToInt32(ArrColors[12]) - 1] + " "; //UR sInput += PosQ[Convert.ToInt32(ArrColors[1]) - 1] + PosQ[Convert.ToInt32(ArrColors[52]) - 1] + " "; //UB sInput += PosQ[Convert.ToInt32(ArrColors[3]) - 1] + PosQ[Convert.ToInt32(ArrColors[32]) - 1] + " "; //UL sInput += PosQ[Convert.ToInt32(ArrColors[25]) - 1] + PosQ[Convert.ToInt32(ArrColors[43]) - 1] + " "; //DF sInput += PosQ[Convert.ToInt32(ArrColors[21]) - 1] + PosQ[Convert.ToInt32(ArrColors[14]) - 1] + " "; //DR sInput += PosQ[Convert.ToInt32(ArrColors[19]) - 1] + PosQ[Convert.ToInt32(ArrColors[46]) - 1] + " "; //DB sInput += PosQ[Convert.ToInt32(ArrColors[23]) - 1] + PosQ[Convert.ToInt32(ArrColors[30]) - 1] + " "; //DL sInput += PosQ[Convert.ToInt32(ArrColors[41]) - 1] + PosQ[Convert.ToInt32(ArrColors[16]) - 1] + " "; //FR sInput += PosQ[Convert.ToInt32(ArrColors[39]) - 1] + PosQ[Convert.ToInt32(ArrColors[34]) - 1] + " "; //FL sInput += PosQ[Convert.ToInt32(ArrColors[50]) - 1] + PosQ[Convert.ToInt32(ArrColors[10]) - 1] + " "; //BR sInput += PosQ[Convert.ToInt32(ArrColors[48]) - 1] + PosQ[Convert.ToInt32(ArrColors[28]) - 1] + " "; //BL sInput += PosQ[Convert.ToInt32(ArrColors[8]) - 1] + PosQ[Convert.ToInt32(ArrColors[38]) - 1] + PosQ[Convert.ToInt32(ArrColors[15]) - 1] + " "; //UFR sInput += PosQ[Convert.ToInt32(ArrColors[2]) - 1] + PosQ[Convert.ToInt32(ArrColors[9]) - 1] + PosQ[Convert.ToInt32(ArrColors[53]) - 1] + " "; //URB sInput += PosQ[Convert.ToInt32(ArrColors[0]) - 1] + PosQ[Convert.ToInt32(ArrColors[51]) - 1] + PosQ[Convert.ToInt32(ArrColors[29]) - 1] + " "; //UBL sInput += PosQ[Convert.ToInt32(ArrColors[6]) - 1] + PosQ[Convert.ToInt32(ArrColors[35]) - 1] + PosQ[Convert.ToInt32(ArrColors[36]) - 1] + " "; //ULF sInput += PosQ[Convert.ToInt32(ArrColors[24]) - 1] + PosQ[Convert.ToInt32(ArrColors[17]) - 1] + PosQ[Convert.ToInt32(ArrColors[44]) - 1] + " "; //DRF sInput += PosQ[Convert.ToInt32(ArrColors[26]) - 1] + PosQ[Convert.ToInt32(ArrColors[42]) - 1] + PosQ[Convert.ToInt32(ArrColors[33]) - 1] + " "; //DFL sInput += PosQ[Convert.ToInt32(ArrColors[20]) - 1] + PosQ[Convert.ToInt32(ArrColors[27]) - 1] + PosQ[Convert.ToInt32(ArrColors[45]) - 1] + " "; //DLB sInput += PosQ[Convert.ToInt32(ArrColors[18]) - 1] + PosQ[Convert.ToInt32(ArrColors[47]) - 1] + PosQ[Convert.ToInt32(ArrColors[11]) - 1]; //DBR ResultSteps = RubikSolve.GetResult(sInput); }
这个神奇的SolveReadColors函数,吃进去的是颜色数组,挤出来的是解魔方的步骤。结果保存在ResultSteps变量中,格式为:
F1 U2 F2 D3 L2 D1 F1 U3 L2 D1
其中每两个字符表示一个旋转步骤,第一个字母表示操作的面,第二个字母表示旋转的方向。1是顺时针,3是逆时针,2是旋转180度。
至此萝卜头已经知道了解魔方的方法,在前面的攻略中,我们已经介绍了旋转魔方的分解动作。
接下来的工作就简单了,下一篇会介绍如何通过蓝牙遥控萝卜头动手干活。