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

经常有朋友向我要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的排序功能。首先我们定义一个ColorItem类,每个实例对应一块魔方的色块:

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度。

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

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



对 “解魔方的机器人攻略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>