四年前,就想用智能手机作为小车的控制器,实现视频采集和远程控制。当时还是WinCE时代。小车都做好了,可WinCE 太难上手(应该说我的基础太差),搁浅了……
后来 Android出现,又燃起了希望,但Android开发同样有门槛,对于我这种“拿着JAVA当C看”的菜鸟而言,还是困难重重……
终于遇到了 Processing 的 Android模式,以及第三方分享的库 Ketai,使我可以开始尝试,经过断断续续近一年的努力,总算完成了自己第一个手机控制的 WifiRobot!
注意:不只是用手机控制哦!小车上的控制器也是用的手机!而且所有功能都是我自己编程,是真正的DIY!
本文给大家分享一下编程实现过程的感受。
1)图像输出很方便
视频中的手机操控端所显示的图像,用以下Processing 语句很容易就实现了:
// 显示采集的图像
pushMatrix();
translate(360, 640);
rotate(radians(90));
scale(2.0);
imageMode(CENTER);
image(video, 0, 0, CAM_WIDTH, CAM_HEIGHT);
popMatrix();
stroke(200); // 画出操作区域
line(40,480,680,480);
line(40,800,680,800);
line(250,160,250,1120);
line(470,160,470,1120);
……
想放大、缩小、翻转以及变换位置都很方便。
2)触屏交互操作实现很容易
Processing 的Android模式中,将触屏操作映射为PC上的鼠标操作,有丰富的鼠标函数可以用。如果要用到更高级的触屏操作,可以使用Ketai库中所提供的函数。
这是第一次尝试编写触屏交互,能把标准的鼠标函数用好就不错了。
开始构思的操作方式是:
1)按画面上部(1/3)区域,发送小车前进命令,按下时显示行走距离,随着时间增加而增加,释放后发送,暂时不考虑改变速度。最小行走10mm。
2)按画面下部(1/3)区域,小车后退,其余同上。
3)按画面中部右侧,发送小车右转命令,按下时显示转动角度,随着时间增加而增加,最大角度360度,最小转角5度。
4)按画面中部左侧,发送小车左转命令,其余同上。
5)按画面正中,发送停止命令,终止小车目前执行的操作。
实现后发现不方便,太慢,而且设置的数值不能减小,只能增加。故改为以下方式:
1) 前进:按画面下部中心区域,向上滑动,Y像素从 1000 开始,向上到 200像素,每像素对应 2mm,最长一次行走距离 1600mm
2) 后退:按画面上部中心区域,向下滑动,Y像素从 200 开始,向下到 1000像素,每像素对应 2mm,最长一次行走距离 1600mm
3) 右转:按画面左侧中心区域,向右滑动,X像素从 144 开始,向右到 536 像素,每像素对应 1.2度,最大一次转 360 度
4) 左转:按画面右侧中心区域,向左滑动,X像素从 536 开始,向右到 144 像素,每像素对应 1.2度,最大一次转 360 度
5) 停止:按画面正中,发送停止命令,终止小车目前执行的操作。
很容易就修改完成了,感觉用Processing编写这类的交互还是很方便的。
代码如下:
/* 获取操作 */
int getOperation()
{
int Op = INVALID;
if(mousePressed)
{
if((mouseX> ForwardLeftX)&&(mouseY > ForwardLeftY)&&
(mouseX < ForwardRightX)&&(mouseY < ForwardRightY))
{
Op = FORWARD;
g_sMessageName = "Forward Dis:";
}
else
{
if((mouseX> BackwardLeftX)&&(mouseY > BackwardLeftY)&&
(mouseX < BackwardRightX)&&(mouseY < BackwardRightY))
{
Op = BACKWARD;
g_sMessageName = "Backward Dis:";
}
else
{
if((mouseX > RightLeftX)&&(mouseY > RightLeftY)&&
(mouseX < RightRightX)&&(mouseY < RightRightY))
{
Op = RIGHT_ROTATE;
g_sMessageName = "Right Rotate:";
}
else
{
if((mouseX > LeftLeftX)&&(mouseY > LeftLeftY)&&
(mouseX < LeftRightX)&&(mouseY < LeftRightY))
{
Op = LEFT_ROTATE;
g_sMessageName = "Left Rotate: ";
}
else
{
if((mouseX > StopLeftX)&&(mouseY > StopLeftY)&&
(mouseX < StopRightX)&&(mouseY < StopRightY))
{
Op = STOP;
g_sMessageName = "Stop!";
}
}
}
}
}
}
return(Op);
}
/* --- 根据位置设置参数 ----- */
int getPara1(int Op)
{
int Para1;
switch(Op)
{
case 1: // Forward
{
Para1 = (ForwardStartY - mouseY)*2;
if(Para1<0) Para1 = 0;
g_sMessage = str(Para1) + " mm";
break;
}
case 2: // Backward
{
Para1 = (mouseY - BackwardStartY)*2;
if(Para1<0) Para1 = 0;
g_sMessage = str(Para1) + " mm";
break;
}
case 3: // RIGHT_ROTATE
{
Para1 = (mouseX - RightStartX)*12/10;
if(Para1<0) Para1 = 0;
if(Para1>360) Para1 = 360;
g_sMessage = str(Para1) + " Deg";
break;
}
case 4: // LEFT_ROTATE
{
Para1 = (LeftStartX - mouseX)*12/10;
if(Para1<0) Para1 = 0;
if(Para1>360) Para1 = 360;
g_sMessage = str(Para1) + " Deg";
break;
}
default:
{
Para1 = 0;
g_sMessage = "?";
break;
}
}
return(Para1);
}
通过上面两个函数就可以轻松实现用触屏选择操作,并设置参数,效果可以在视频中看到,详细的实现过程见附件中的完整代码。
3)程序监控及运行信息输出方便
程序中常常需要输出一些信息以监控程序的进程,或者输出一些状态。
在 Processing 中,设置一些字符串变量,将需要输出的信息赋值给字符串,即可方便的显示。上面交互操作函数中的 g_sMessageName 及 g_sMessage 就是用于显示的变量,相应的显示处理程序如下:
……
fill(200,200,0);
textAlign(LEFT, CENTER);
textSize(40);
text(g_sMessageName, 20, 60);
fill(250);
text(g_sMessage, 360, 60);
……
位置、大小和颜色都可以方便的改变。
以上是我在编程过程的一点感受,唯一不足的是:没有类似于VC环境中的调试工具,程序开始调试比较麻烦,需要自己根据设计的流程在合适的地方添加输出语句,以查找问题所在,类似于VC中设置断点,只是这里将你关心的变量通过print()语句输出到控制台。
据说 Linux 下程序都是这样调试的?
不过,换个角度考虑,这样倒是对学习编程有利,你必须了解程序的流程,否则无法查找问题,就不能简单的拷贝一段程序了,需要看懂它,否则程序罢工了,你都无从下手^_^
这是第一步,所用的小车控制方式有些另类,是用舵机控制方向,一个驱动轮驱动。
下一步会做一个最常见两轮差分的小车,这样大家交流就方便一些。
遥控不是目标,等把基本的小车控制做好后,将要尝试用 OpenCV 实现图像识别,用图像控制小车行为,第一步打算先尝试一下走轨迹,一个最古老的小车项目!
欢迎有兴趣者共同探讨!