电源设计技术信息网站

订阅电子杂志   English   繁體中文   日本語   한국어

下载中心

TECH INFOArduino入门指南

智能机器人手臂–第2部分:编程

这篇文章来源于DevicePlus.com英语网站的翻译稿。

点击此处阅读本文的第1部分>

 

robotic arm

智能机器人手臂–第1部分:机械结构和接线中,我们已经将机械手臂的本体组装在一起。机械手臂搭载了一个网络摄像头和一个麦克风,以进行人脸和物体识别以及语音识别。这款在短短15小时内开发出来的机器人手臂在MakeMIT硬件马拉松项目中排名前10。

该机器人手臂的主要功能是结合语音命令检测彩色物体(例如红球或蓝球)、拾取物体、进行人脸识别,并将物体移交给指定的人物/用户。为了成功实现这些功能,我们集成了多个平台,比如OpenCV、Microsoft Cognitive Services Speech、Speaker Recognition API以及Open Weather API。

 

硬件

根据第1部分

  • Arduino Uno
  • 麦克风
  • 网络摄像头
  • 伺服电机

 

软件

 

第1步:制作App

我们已经设法打造了一个能够处理语音命令的机器人手臂。人们说出要求后,该手臂会找到目标物体并将其交给指定人物(机器人可以区分不同的人)。此外,它还可以播放在线音乐或告诉您当前的天气情况。

该应用程序的软件部分用Visual Studio 2015完成。Visual Studio简单易用,并且提供了很多功能。其中一个功能就是Emgu CV——一个用于调用OpenCv函数的包裹函数库。此功能通过Visual Studio Windows Forms实现,在处理图像时非常重要。

如果想启动项目,您只需在电脑上安装Emgu CV即可。具体步骤如下:

1. 下载EmguCV
您可以点击本页 “构建示例”下方的超链接下载Emgu CV工程:

robotic arm

图1: 如何下载EmguCV

2. 添加文件
由于您需要使用添加库,因此应将它们包含在Visual Studio中。OpenCV基于C/C++语言开发,如果您想在C#中使用,那么需要添加DLL(动态链接库)文件——处理视频时必须包含DLL文件。参考文件位于Visual Studio菜单的Solution Explorer.

robotic arm

图2: 参考文件位置

如果您想添加更多文件,请点击References -> Add references,然后从下载的文件中选择以下内容:

robotic arm

图3: 参考管理器(Reference Manager)

更多信息可以参考以下链接:http://www.emgu.com/wiki/index.php/Setting_up_EMGU_C_Sharp

 

 

3. 示例

在下载的库中,您可以找到更多使用视频处理(运动检测、人脸检测和摄像头捕捉)的程序代码示例.

robotic arm

图4: OpenCV应用示例

4. Arduino程序

Arduino代码具有4个命令:up/down负责控制伺服电机;reset是指在出现问题时重置机器人手臂位置;某个部件没有动作时则使用do nothing。程序具有四个自由度:

  • circle – 手臂可以旋转;
  • base – 调整网络摄像头的倾斜度;
  • elbow– 旨在获取手臂的最佳位置;
  • wrist – 调整抓握力.

 

每个伺服电机都有一个给定命令,并且使用不同引脚进行控制:Elbow使用引脚7;Base使用引脚8;Circle使用引脚9;Wrist使用引脚10。对于伺服电机控制,C#应用程序通过串行传输发送4个字母,每个状态对应于一个具体的电机控制指令:

if(cmd == ‘A’) state = 1; control(1, posCircle);
if(cmd == ‘B’) state = 2; control(2, posBase);
if(cmd == ‘C’) state = 3; control(3, posElbow);
if(cmd == ‘D’) state = 4; control(4, posGH);

根据伺服电机的运动角度,代码分为控制和命令指令。

Case 1中,Circle(旋转)运动的命令为left/right(左/右)。

Case 1:

      switch (cmd)
      {
          case '0':
              Left();
              Break;
          case '1':
               //do nothing
              break;
          case '2':
              Right();
              break;
          case '3':
              posCircle = 0;
              break;
      }
      state = 0;
      break;

Case 2和Case 3中,我们考虑基座和肘部的伺服电机,这意味着他们可以上/下(up/down)引导手臂;遇到错误时,机器人手臂会把自己设置为posBase = 50;posElbow = 50;

Case 2:

      switch (cmd)
      {
          case '0':
              downBase();
              break;
			  
          case '1':
              //do nothing
              break;
			  
          case '2':
              upBase();
              break;
			  
          case '3':
              posBase = 50;
              break;
      }
      state = 0;
      break;

Case 3:

      switch (cmd)
      {
          case '0':
              downElbow();
              break;
			  
          case '1':
              //do nothing
              break;
			  
          case '2':
              upElbow();
              break;
			  
          case '3':
              posElbow = 50;
              break;
			  
          case '4':
              posElbow = 10;
              break;
      }
      state = 0;
      break;

Case 4中,我们需要控制爪钳,这个非常简单——将其初始位置设置为posGH = 50;

 

请将以下代码添加到Arduino UNO中,以进行机器人手臂控制:

// States for the RoboticArm:
//0 Down on the left
//1 Pause
//2 Top on the right
//3 Reset


#include <Servo.h>

Servo Circle; //1
Servo Base; //2
Servo Elbow;  //3
Servo Wrist; //4

int posCircle = 0;
int posBase = 50;
int posElbow = 50;
int posGH = 50;

int state = 0;

void setup() {
  Serial.begin(9600);
  Elbow.attach(7);
  Base.attach(8);
  Circle.attach(9);
  Wrist.attach(10);
  delay(1000);
  control(1, posCircle);
  control(2, posBase);
  control(3, posElbow);
  control(4, posGH);
}

void loop() 
{

if(Serial.available() > 0)
{
char cmd = Serial.read();

switch (state) {
    case 0:
     if(cmd == 'A') state = 1;
     if(cmd == 'B') state = 2;
     if(cmd == 'C') state = 3;
     if(cmd == 'D') state = 4;
    
      break;
      
    case 1:
            
             switch (cmd)
             {
              case '0':
              Left();
              break;
			  
              case '1':
              //do nothing
              break;
			  
              case '2':
              Right();
              break;
			  
              case '3':
              posCircle = 0;
              break;
             }
             state = 0;
      break;
      
      case 2:
            
             switch (cmd)
             {
              case '0':
              downBase();
              break;
			  
              case '1':
              //do nothing
              break;
			  
              case '2':
              upBase();
              break;
			  
              case '3':
              posBase = 50;
              break;
             }
             state = 0;
      break;
      
      case 3:
       
             switch (cmd)
             {
              case '0':
              downElbow();
              break;
			  
              case '1':
              //do nothing
              break;
			  
              case '2':
              upElbow();
              break;
			  
              case '3':
              posElbow = 50;
              break;
			  
              case '4':
              posElbow = 10;
              break;
             }
             state = 0;
      break;
      
      case 4:
          
             switch (cmd)
             {
              case '0':
              break;
			  
              case '1':
              break;
			  
              case '2':
              break;
			  
              case '3':
              posGH = 50;
              break;
             }
             state = 0;
      break;
   
  }
  
}
       
}

void control(int motor, int angle)
{
switch (motor) {
    case 1:
    if(angle >= 0) if(angle <= 140) Circle.write(angle + 10);                         
    break;
    
    case 2:
    if(angle >= 0) if(angle <= 60) Base.write(140 - angle);
    break;

    case 3:
    if(angle >= 0) if(angle <= 70) Elbow.write(80 - angle);
    break;

    case 4:
    if(angle >= 0) if(angle <= 65) Wrist.write(75 - angle);
    break; 
  }  
}

void Left()
{
if(posCircle <= 136)
{
  posCircle = posCircle + 4;
}
else posCircle = 140;
control(1, posCircle);
}

void Right()
{
if(posCircle >= 4)
{
  posCircle = posCircle - 4;
}
else posCircle = 0;
control(1, posCircle);
}

///////////////////////////////////
void downElbow()
{
if(posElbow >= 4)
{
  posElbow = posElbow - 4;
}
else posElbow = 0;
control(3, posElbow);
}

void upElbow()
{
if(posElbow <= 66)
{
  posElbow = posElbow + 4;
}
else posElbow = 70;
control(3, posElbow);
}
//////////////////////////////////////
void downBase()
{
if(posBase >= 4)
{
  posBase = posBase - 4;
}
else posBase = 0;
control(2, posBase);
}

void upBase()
{
if(posBase <= 56)
{
  posBase = posBase + 4;
}
else posBase = 60;
control(2, posBase);
}

第2步:如何制作演示App

为了制作一个类似的应用程序,我们需要知道如何使用C#编写应用程序。由于Windows Forms App很简单,并且拥有在线文档,需要时可以随时获取,因此我们采用了该App。对于这个项目,我们需要Visual Studio和.NET framework(您可以在这里下载这两个程序)。下载并安装文件后,我们就可以开始制作应用程序。

首先,我们需要创建一个新工程。创建新工程的步骤如下:点击FileNewProjectWindows Forms App(.NET Framework)。

robotic arm

图5: 如何在Visual Studio中制作App

此时系统会显示一个带有窗体的窗口——这是应用程序自动生成的格式。右侧(Properties)是表格属性,您可以根据自己的偏好进行调整。左侧(Toolbox)提供应用程序使用的功能,比如按钮、复选框、串口等。

robotic arm

图6: Visual Studio中的App属性

对于测试演示,我们可以制作这样一个应用程序:点击按钮,系统显示一条文本信息。

双击Toolbox(工具箱)中的按钮,程序会在左上角自动添加一个按钮。该按钮会被命名为“button1”,因为.NET Framework会对按钮类实例进行自动编号。在button1属性中,我们可以将Text从button1改为Click here!现在,我们需要编写代码,以便让按钮起作用(双击图形界面上的按钮):

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("App made for DevicePlus");
        }
    }
}

当用户点击该按钮时,上述代码会显示一个文本。具体结果如图7所示。

robotic arm

图7: 演示App

第3步:App属性

凭借.NET、OpenCV和Microsoft API的功能,我们能够制作出完整的应用程序。对于该应用程序,我们需要按钮、串口和文本框。

黑色窗口会显示来自摄像头的图像,窗口中还会添加一个镜头框,框内显示人物名称。

左侧的白色窗口可以添加TTS API(文本转语音)的文本,提供反馈以检查工作,并检查说出的话语是否与显示的字词相符。

robotic arm

图8: 机器人手臂应用程序

第4步:人脸识别

为了获得更好的程序结果,您需要在训练模式下添加多个脸部记录。训练是指通过拍摄多个脸部位置以获得最佳识别(不同拍摄角度、光线条件等)。您需要按下Record 10 Faces按钮,通过应用程序记录脸部画面。

在代码中,实现这个功能的语句为Face = Parent.faceClassifier;这通过EmguCV平台完成。人脸检测通过基于主成分分析(PCA)的复杂算法实现,系统会将检测到的脸部与训练后存储的图像进行多次比较。

robotic arm

图9: 人脸检测

蓝色矩形中的图像是需要存储的图像,因为它包含面部的独特特征。

我们需要在不同的位置和不同的光线下获取更多的面部特征:

robotic arm

图10: 添加更多人脸进行训练

经过一系列面部检测拍摄,如果您认为之前记录的脸部图像不利于算法计算,那么可以点击Restart 1 Face按钮删除已经保存的文件,系统将不再检测该脸部信息。

如果要记录多个人、让机器人手臂可以区分许多个人,那么可以使用Delete Data按钮删除照片。

robotic arm

图11:重启训练模式

录制更多图像后,该算法应该能够检测人脸,并在蓝框上方显示名称。机器人可以精确检测像眼镜这样的特征,因为这些特征属于区别性特征.

robotic arm

图12: 训练之后的结果

第5步:天气API

通过天气API,我们可以根据以下方式收集城市数据:城市名称、城市ID、地理坐标甚至邮政编码。该API的优点是免费且易于使用。但是该API有一个限制:使用次数或者流量不能超过60次/分钟或50k/天,如果违反该规定,您的帐户将被封锁。

在本教程中,我们选择根据城市ID调用API。每个城市都有一个ID,详细信息请参见http://bulk.openweathermap.org/sample/city.list.json.gz。扩展名.json用于在浏览器和服务器之间进行交换数据。只有当数据是文本格式时才能实现通信。

我们已经将数据用作WebClient类——这使得文件下载更加轻松。在我们的例子中,构造函数WebClient用于初始化一个新的实例:

WebClient client = new WebClient();

我们需要将数据编码为UTF8格式,这意味着系统用8位块来表示字符,具体指令如下:client.Encoding = System.Text.Encoding.UTF8

如果互联网连接出现问题,无法建立连接时,程序会自动显示消息“Internet Connection failedthrow new WeatherDataServiceException”。通过throw我们可以创建一个异常。

在C#中,我们可以使用trycatch来解决部分代码无法成功的情况。

结合文本转语音API,如果用户询问天气情况,系统会发出以下声音:“The weather in Boston is(波士顿的天气是)…”。

第6步:语音合成

当您使用文本转语音API时,您需要一个可以快速处理的文件——这就是我们需要使用服务器的原因。对于BING API,我们需要授权令牌,具体信息请参阅https://api.cognitive.microsoft.com/sts/v1.0/issueToken

该API的有效期为10分钟。这是一个非常棒的计时器,可以计数到9,同时每9分钟更新一次连接。

如下图所示,您可以看到语音合成的最终结果是“Testing the app for Device Plus”。

robotic arm

图13: 测试API

总体来说,这是一个有趣的项目。该项目让我懂得硬件和软件之间的良好同步对于实现最佳性能至关重要。使用Visual Studio的很多库使项目有点复杂,但最终结果令人满意。

我认为这种项目可以通过多种方式进行开发和改进,以满足每个用户的需求。

DevicePlus 编辑团队
Tiberia Todeila

Tiberia目前是布加勒斯特理工大学电气工程学院的大四学生。她非常热衷于智能家居设备的设计和开发,旨在让我们的日常生活更加轻松。

分享到社交媒体