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

今天看到架子上的萝卜头,已经落了很多灰尘。想起萝卜头的攻略还剩几篇迟迟没有写完。前一段时间一直在试验小爱的手机遥控器功能,从今天开始准备陆续把萝卜头的攻略补完,给博客也打扫打扫灰尘。

说起来真是很惭愧,颜色识别在萝卜头制作过程中是花费时间最多的部分。其中还有一段小插曲:
我在淘宝上买的颜色传感器,在NXT上测试时,发现只有用强光照射在魔方表面的时候,传感器才有读数。那时候在网上很难找到相关的资料,不知道是我买了次品,还是设置不当。后来我猜想传感器中心的那个透明小灯泡是光源,就擅自去电子市场买了一个LED小灯,然后把这个500块钱的传感器敲开换上。一通电,嘿,灯居然亮了,然后我就把拆下来的小灯扔到垃圾桶继续测试。结果…..这次传感器彻底废了。接下来是从垃圾桶里翻那个透明的小灯泡,非常悲剧的是那天正好吃了虾和鱼,我把整垃圾桶的虾皮鱼骨摸了两遍,才找到那个透明的小灯泡,把它洗洗干净又换上了。后来才知道,这个灯泡其实是用来读取颜色的,而不是照明的。而我买的那个颜色传感器确实是个次品,必须用灯光照射才能勉强读数。所以你们看到第一版的萝卜头,在悬臂上是带有一个照明灯的。

在此提醒一下朋友们:颜色传感器在普通的光照环境下,应该是有读数的,而且很敏感,读数会不断小幅跳动。如果你买的传感器读数一直是0或者跳动非常大,那么请尽快找奸商退换。另外,在这里感谢一下北京西觅亚公司,他们给我提供了几个测试用的颜色传感器,并且给我换了一个新的。我也因此了解了一些颜色传感器的特性。

好了,进入正题。上一篇介绍了如何在电脑和NXT之间使用蓝牙进行通讯。有了蓝牙,我们就可以把颜色传感器的读数发送给电脑,然后用电脑识别颜色后调用解魔方的算法。

1,从NXT发送颜色数据到电脑
之前的一篇博客里,我介绍了三个函数:ReadAllSide,ReadOneSide和SendColorToPC。现在蓝牙已经调通,可以改写SendColorToPC函数用来发送数据。其中getRed()和getRawRed()等函数的说明,请参考颜色传感器的API文档

//Send colors to PC
public static void SendColorToPC(int center, int n) throws Exception
{
	//get the x,y of n
	int y = n % 3;
	int x = (n - y) / 3;

	//send to PC by bluetooth
	byte[] data = new byte[9];
	data[0] = (byte)center;    //center表示是魔方的某一面
	data[1] = (byte)x;         //x 表示魔方这一面3*3的色块中,第x行的色块
	data[2] = (byte)y;         //y 表示魔方这一面3*3的色块中,第y列的色块
	data[3] = (byte)color.getRed();
	data[4] = (byte)color.getGreen();
	data[5] = (byte)color.getBlue();
	data[6] = (byte)(color.getRawRed() / 3);
	data[7] = (byte)(color.getRawGreen() / 3);
	data[8] = (byte)(color.getRawBlue() / 3);
	BlueTooth.WriteBytes(data);
}

2,在PC端接受颜色数据
PC程序中的BlueToothDataReceived函数,用来响应接受到蓝牙数据的事件。我们加上下面这段函数:

else if (length == 9)
{
    int i = data[0];
    int j = data[1];
    int k = data[2];
    int r = data[3];
    int g = data[4];
    int b = data[5];
    int rawR = data[6];
    int rawG = data[7];
    int rawB = data[8];
    ColorItem newItem = new ColorItem(i, j, k, r, g, b, rawR, rawG, rawB);
    colorDistinguish.ColorItems.Add(newItem);
    DisplayMessage += newItem.ToString() + "\r\n";
    Status = "成功获取数据:" + i + "," + j + "," + k;
}

其中用到了两个类 ColorItem 和 ColorItemDistinguish。这两个类的作用后面再说,总之这里把所有的颜色数据都先保存到一个阵列(Array)里,最后统一识别颜色。

3,解析颜色的方案
细心的朋友可能在API中看到了getColor()函数,我们何必要全部保存颜色后再统一分辨呢,直接读一个分辨一个不是更好?事实证明这个函数基本没什么用,红色和橙色都会解析成红色,而且环境光线变化时影响很大。还有一些朋友建议用HSV颜色模型,这种方案我也试过了,基本上也很难分辨。为什么呢?请看下面一组读数:

Red
[0,1,2]=>RGB=(23,0,0),RawRGB={45,1,8}
[0,2,2]=>RGB=(30,0,0),RawRGB={60,1,5}
[0,2,1]=>RGB=(25,0,0),RawRGB={49,3,12}
[0,2,0]=>RGB=(32,0,0),RawRGB={63,2,6}
[0,1,0]=>RGB=(22,0,0),RawRGB={43,2,11}
[0,0,0]=>RGB=(25,0,0),RawRGB={59,3,3}
[0,0,1]=>RGB=(30,0,0),RawRGB={58,5,17}
[0,0,2]=>RGB=(31,0,0),RawRGB={61,8,17}
[0,1,1]=>RGB=(31,0,0),RawRGB={62,15,22}

Orange
[2,1,2]=>RGB=(28,0,0),RawRGB={55,12,8}
[2,2,1]=>RGB=(30,0,0),RawRGB={57,14,14}
[2,0,1]=>RGB=(32,0,0),RawRGB={62,15,13}
[2,1,0]=>RGB=(32,0,0),RawRGB={63,16,12}
[2,2,2]=>RGB=(42,0,0),RawRGB={83,24,10}
[2,2,0]=>RGB=(41,0,0),RawRGB={82,24,13}
[2,0,0]=>RGB=(41,0,0),RawRGB={80,23,10}
[2,0,2]=>RGB=(39,0,0),RawRGB={76,22,13}
[2,1,1]=>RGB=(41,5,0),RawRGB={81,30,21}

这是在自然光条件下,对红色和橙色的9个色块分别读数的结果。可以看到,它们的Green和Blue分量全部是0,只有红色分量有差别。但是红色的red分量从23~32,橙色的red分量从28~42,它们中间是有重叠的。对于这些读数,HSV完全没用。
有一段时期我几乎已经绝望了,不过终于在最后让我找到了一点区别:红色的RawBlue分量基本上比RawGreen分量稍大,而橙色恰好相反。另外请对比一下[0,0,0]和[2,2,1],它们的RawBlue分量和RawGreen分量是相同的,但是仍然可以找到区别:按公式R+2*RawG-2*RawB计算,橙色的永远比红色大!

也就是说,我们单独取到一组颜色数值时,很难直接知道它是什么颜色,只有对一组数进行排序后,才能区分出不同的颜色。就像刚才这18个数,我们按照R+2*RawG-2*RawB从大到小排序,最终结果的前9个是橙色,后9个就是红色。类似的,我们还可以定义出分辨颜色的判断规则:
1,假设RGB三个值的最小值为Min,按Min从大到小排序,前9个是白色
2,剩下的颜色,按照G分量从大到小排序,前9个是黄色(有意思吧,绿色分量最大的是黄色)
3,剩下的颜色,按照B分量从大到小排序,前9个是蓝色(这个还算靠谱)
4,剩下的颜色,按照R分量从小到大排序,前9个是绿色
5,剩下的颜色,按照R+2*RawG-2*RawB从大到小排序,前9个是橙色
6,剩下的颜色全是红色

当光线从弱到强变化时,这些值基本会成比例的变大,所以这些规则依然有效。
有兴趣的朋友可以查看一组完整的颜色读数,来验证以上这些规则:http://www.diy-robots.com/rubiksolver/readcolors.txt
下一篇继续介绍这种分辨方式的具体代码实现。



对 “解魔方的机器人攻略23 – 识别颜色(上)” 的 13 条 评论

  1. dead_lee 说:

    没想到颜色识别这么麻烦, 是因为传感器不好导致的么

  2. 老四 说:

    嗨,老男孩,盒子博客的地址已经更改,请将链接名改为:老四,链接为http://bulog.org

  3. seehao 说:

    color sensor这东西太不好买了。。。

  4. Xin 说:

    ^&%#^%@^@%$#
    我在用USB摄像头做,读数更夸张… = =

  5. aaaxiatian 说:

    各部分零件有没有卖

  6. 橙C 说:

    楼主,好久不见!
    我是那杯纠结的橙C,今天又看了一遍你的视频,又在想你的颜色识别算法其实是一个对54个小块的排序算法,那么如果在读六个面的时候外界光强发生变化,比如第三个面,会不会导致这个面上的9个小块在54块的排序中会相对排序更靠前(比如读第三个面的时候外界光线很强,会不会第三个面上的黄色被识别为白色)?

  7. xmxoxo 说:

    —第1点:—
    Color Value=7586023
    Hue=39.82759,Brightness=91
    ColorValue=7586023
    Hue=39.82759,Brightness=91
    R=231,G=192,B=115
    Value=385
    sRGB=1,3,0
    橙=4
    —第2点:—
    Color Value=9539616
    Hue=180.531,Brightness=57
    ColorValue=9539616
    Hue=180.531,Brightness=57
    R=32,G=144,B=145
    Value=30
    sRGB=0,0,20
    绿=3
    —第3点:—
    Color Value=5672390
    Hue=29.46429,Brightness=78
    ColorValue=5672390
    Hue=29.46429,Brightness=78
    R=198,G=141,B=86
    Value=308
    sRGB=1,4,0
    橙=4
    —第4点:—
    Color Value=15497277
    Hue=219.7714,Brightness=93
    ColorValue=15497277
    Hue=219.7714,Brightness=93
    R=61,G=120,B=236
    Value=-171
    sRGB=1,0,8
    蓝=1
    —第5点:—
    Color Value=7035326
    Hue=349.3069,Brightness=75
    ColorValue=7035326
    Hue=349.3069,Brightness=75
    R=190,G=89,B=107
    Value=154
    sRGB=3,1,0
    红=2
    —第6点:—
    Color Value=16777200
    Hue=180,Brightness=100
    ColorValue=16777200
    Hue=180,Brightness=100
    R=240,G=255,B=255
    Value=240
    sRGB=1,1,1
    白=0
    —第7点:—
    Color Value=10157780
    Hue=85.2,Brightness=100
    ColorValue=10157780
    Hue=85.2,Brightness=100
    R=212,G=254,B=154
    Value=412
    sRGB=1,2,1
    黄=5
    —第8点:—
    Color Value=8177131
    Hue=39.45946,Brightness=92
    ColorValue=8177131
    Hue=39.45946,Brightness=92
    R=235,G=197,B=124
    Value=381
    sRGB=1,3,0
    橙=4
    —第9点:—
    Color Value=6575275
    Hue=348.9655,Brightness=67
    ColorValue=6575275
    Hue=348.9655,Brightness=67
    R=171,G=84,B=100
    Value=139
    sRGB=2,1,0
    红=2

  8. 风雪冲 说:

    color.getRed()与 color.getRawRed() 了两个函数有啥区别啊?
    color.getRawRed()/3,除以3理解不了?

发表评论

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