智能机器人手臂 - 第1部分:机械结构和接线

这篇文章来源于DevicePlus.com英语网站的翻译稿。
image001-2.jpg
您有没有想过拥有一款可以帮您满足日常需求的设备?而且该设备能够区分您(主人)和其他非授权用户,这并非简单的普通工具。在本教程中,我们将向您介绍一款能够识别并理解您语音命令的智能机器人手臂。此机器手臂通过OpenCV软件实现了人脸识别系统。(为了测试目的,我们开发了算法来检测小球颜色。)

作为MakeMIT硬件马拉松项目的一部分,我与同事Isac Andrei和VladNiculescu合作研发了该智能机器人手臂。由于其创新程度和创造性,该项目在硬件马拉松中排名前10。

硬件

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

软件

  • Arduino IDE
  • GitHub (https://github.com/DevicePlus/SmartRoboticArm)

工具

  • 胶枪
  • 丙烯酸树脂
  • 螺丝
  • 胶带
  • 电锯

第一步:结构与力学
处理机器手臂的手腕时,必须非常小心。如果不考虑伺服电机角度就开始组装手腕部件,那么可能会导致手臂发生故障——机器人手臂可能发生您无法控制的混沌行为。在步骤1中,您需要做的就是将5台伺服电机连接到Arduino开发板上,并找到每个伺服机构的正确位置。正确的顺序是自下而上进行校准。

I.底座
底座设计为可向左移动90度,也可向右移动90度。将它放置在金属升降机上进而使基座可以旋转。这是一个关键部分,因为当发出语音命令的人移动时,手臂必须跟随并追踪人物。而且这种跟随必须准确,以便摄像头总能发现人脸。
image003-copy.jpg
图1:底座顶视图

底座随动系统安装在一个40 x 30厘米的大亚克力板上,以保持稳定。您也可以用更强大的材料替代亚克力板,从而支撑能够拾起重物的大机器人手臂。
伺服电机安装在一块12 x 12厘米的较小亚克力板上。我们在小板中间钻取了一个与伺服电机尺寸相同的孔。然后,将伺服电机用螺丝拧到孔上(图2)。
image005-1.jpg
图2:底座(前视图)

II.肩部
肩部由两台伺服电机组成,它们相互配合以拾取物体并放置在正确的位置。该项目最重要的校准就在肩部。这两个伺服系统必须完美结合,从Arduino开发板上获得完全相同的命令,并且必须反相同步。为了获得良好的同步性,将两台电机放置在机器人手臂上之前,必须将其面对面放置并通过编程摆动到同一侧。
image007-1.jpg
图3:伺服电机同步性

如果它们的行为不一致,那么处于上游位置的伺服电机会强制第二个电机移动,从而产生短路,最终导致第二个伺服电机烧掉。
如果伺服系统的分辨率不一样,您将无法使它们同步。在这种情况下,最好的办法则是仅使用一种伺服电机。如果它们质量很好,那么实际扭矩会与数据表相同,并且具有提升重物的能力。

另外,为了安全起见,在肩部的基座上装一个旋钮非常好。您也可以使用金属支架,将其放置到所需角度,以防止机械臂坠落。
image009-copy.jpg
图4:肩部组件

图4显示了我们之前构建的安装在底座上的肩部。伺服机构固定的部位采用旋转结构。肩部必须牢固地固定在基座上,但也必须能够自由旋转。平衡旋转部分的每一边也很重要,因为我们的装置含有一些较重的支撑金属部件。中心重量必须相同,这样机器手臂旋转时才不会掉落。对于这一部分,我们还放置了一个金属筋(即金属弯头),以便伺服机构没有插入时维持机器臂重量。

III.肘部
现在,我们来看一下肘部。肘部伺服部分通过手臂高度控制。由于手臂必须返回到原来位置,所以肘部延伸部分与肩部之间的最大角度不得超过100度。我们对该机器人手臂原型的肘部伺服系统进行编程,让手臂捡起一个球——系统会协调捕捉器和肩膀,两者相互配合,最终将球拾起。
肘部的工作原理如下:

  • 当肘部延伸部分与肩部之间的角度较小时,肩部的角度将增大;
  • 当肘部和肩部之间的角度较大时,肩部的角度将会减小。
image011-1.jpg
图5:肘部

肘部使用SketchUp STL设计,并用3D打印机打印。(STL扩展可以将图形转换为能够打印的3D模型)。根据设计,肘部在允许弯曲的角度连接2个延伸部分。
image013-1.jpg
图6:肘部的3D模型

当然,肘部的延伸部件需要能够很容易地抬起,所以除了肘部,延伸部分不应连接其他任何东西。每个丙烯酸树脂延伸部件尺寸为20 x 7厘米。延伸部分的另一端用小丙烯酸片粘合起来,以便将两部分固定在一起。您可以改变尺寸,但是由于伺服机构的限制,机械臂的尺寸不应太大
如果您决定更改尺寸(比如制作一个较小的手臂),那么请确保计算出零件的正确尺寸。否则,手臂将发生故障,无法拾起重物。网络摄像机安装在延伸部件上。
image015-1.jpg
图7:安装在机器人手臂上的网络摄像头

VI.手腕
手腕由能够抓取某些小物体的爪钳/捕捉器构成。在本文中,我们以抓取小球为例。当然,手臂还可以抓取并提起适合爪钳的其他物体。您还可以根据您的具体偏好进行设计——必要时请使用SG90 SketchUp文件进行必要修改。您需要做的就根据所抓物体的形状来改变爪钳形状。
image017-1-e1497642998317.jpg
图8:手腕

手腕部分也使用SketchUp中的3D模型构建。如果我们想要拿起较重的物体,可以用爪钳抓住它们。但是,爪钳抓取物体时比较有力,可能会对其造成损坏。
image019-1.jpg
图9:手腕的3D模型

步骤2:连接伺服电机

底座

image021-1.jpg
图10:底座伺服系统的接线图

底座伺服(图2)被设置为从0度开始,这意味着它将从左侧开始搜索用户。其旋转角度为140度——机器人手臂可追踪人员递送物体的范围。我们在0度进行校准——这是伺服的中心,叶片面向上方垂直放置。
在控制功能中,基座的分辨率为4度。这是因为在这种情况下,精度不需要太完美。较高的精度反而会导致处理变得缓慢。
每次我们都都需要检查伺服电机的位置,因为相互作用实时进行。为此,我们开发了两个功能:左方和右方。这两个功能可以从35个不同位置追踪人员。

肩部和肘部
肩部与肘部一起能够完成一项重要功能。他们必须弯曲机器人手臂。我们应当指出以下限制,这很重要:
由于前臂的长度,肩部的操作角度不能小于45度,我们有2个例子:
当肩部处于最低位置时,机器人手臂可以拾起最远的物体;
当肩部处于最高位置时,机器人手臂可以拾起最近的物体;
我们选定的角度对于肩部来说已经足够,因为它足以让手臂拾起物体并将其交给正确的人员。
Screen-Shot-2017-06-16-at-4.04.00-PM-copy.jpg
图11:肩部范围

肘部的最大位置不能超过140度,因为该项目的目的是在平面上构建机器人手臂,并且将摄像头与用户处保持在同一高度。我们认为只用70度即可,因为这足以弯曲机器人手臂。
肩部角度和肘部角度之间的组合使手臂具有很大的灵活性,从而形成完整的机器手臂,最终实现拾取物体的功能。
Screen-Shot-2017-06-16-at-4.04.45-PM-copy.jpg
图12:肘部范围

Pic-01.jpg
图13:肩部伺服接线图

Pic-02.jpg
图14:肘部伺服接线图

本智能机器人手臂教程的第1部分描述了手臂的一般机械结构,以及伺服电机如何与手臂不同连接处进行连接的方法。本教程的第2部分将会介绍几款用C#编写的机器人手臂控制程序。敬请关注!


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

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

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

硬件
根据第1部分:

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

软件

  • Arduino IDE
  • Visual Studio
  • JARVIS [https://github.com/isacandrei/Jarvis-Rocket]

第1步:制作App

我们已经设法打造了一个能够处理语音命令的机器人手臂。人们说出要求后,该手臂会找到目标物体并将其交给指定人物(机器人可以区分不同的人)。此外,它还可以播放在线音乐或告诉您当前的天气情况。
该应用程序的软件部分用Visual Studio 2015完成。Visual Studio简单易用,并且提供了很多功能。其中一个功能就是Emgu CV——一个用于调用OpenCv函数的包裹函数库。此功能通过Visual Studio Windows Forms实现,在处理图像时非常重要。
如果想启动项目,您只需在电脑上安装Emgu CV即可。具体步骤如下:

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

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

如果您想添加更多文件,请点击References -> Add references,然后从下载的文件中选择以下内容:
image009-copy-1.jpg
图3: 参考管理器(Reference Manager)

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

3. 示例
在下载的库中,您可以找到更多使用视频处理(运动检测、人脸检测和摄像头捕捉)的程序代码示例.
image011-copy.jpg
图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:
  1.       switch (cmd)
  2.       {
  3.           case '0':
  4.               Left();
  5.               Break;
  6.           case '1':
  7.                //do nothing
  8.               break;
  9.           case '2':
  10.               Right();
  11.               break;
  12.           case '3':
  13.               posCircle = 0;
  14.               break;
  15.       }
  16.       state = 0;
  17.       break;
Case 2和Case 3中,我们考虑基座和肘部的伺服电机,这意味着他们可以上/下(up/down)引导手臂;遇到错误时,机器人手臂会把自己设置为posBase = 50;posElbow = 50;

Case 2:
  1.       switch (cmd)
  2.       {
  3.           case '0':
  4.               downBase();
  5.               break;
  6.                           
  7.           case '1':
  8.               //do nothing
  9.               break;
  10.                           
  11.           case '2':
  12.               upBase();
  13.               break;
  14.                           
  15.           case '3':
  16.               posBase = 50;
  17.               break;
  18.       }
  19.       state = 0;
  20.       break;
Case 3:
  1.       switch (cmd)
  2.       {
  3.           case '0':
  4.               downElbow();
  5.               break;
  6.                           
  7.           case '1':
  8.               //do nothing
  9.               break;
  10.                           
  11.           case '2':
  12.               upElbow();
  13.               break;
  14.                           
  15.           case '3':
  16.               posElbow = 50;
  17.               break;
  18.                           
  19.           case '4':
  20.               posElbow = 10;
  21.               break;
  22.       }
  23.       state = 0;
  24.       break;
Case 4中,我们需要控制爪钳,这个非常简单——将其初始位置设置为posGH = 50;

请将以下代码添加到Arduino UNO中,以进行机器人手臂控制:
  1. // States for the RoboticArm:
  2. //0 Down on the left
  3. //1 Pause
  4. //2 Top on the right
  5. //3 Reset
  6. #include <Servo.h>
  7. Servo Circle; //1
  8. Servo Base; //2
  9. Servo Elbow;  //3
  10. Servo Wrist; //4
  11. int posCircle = 0;
  12. int posBase = 50;
  13. int posElbow = 50;
  14. int posGH = 50;
  15. int state = 0;
  16. void setup() {
  17.   Serial.begin(9600);
  18.   Elbow.attach(7);
  19.   Base.attach(8);
  20.   Circle.attach(9);
  21.   Wrist.attach(10);
  22.   delay(1000);
  23.   control(1, posCircle);
  24.   control(2, posBase);
  25.   control(3, posElbow);
  26.   control(4, posGH);
  27. }
  28. void loop()
  29. {
  30. if(Serial.available() > 0)
  31. {
  32. char cmd = Serial.read();
  33. switch (state) {
  34.     case 0:
  35.      if(cmd == 'A') state = 1;
  36.      if(cmd == 'B') state = 2;
  37.      if(cmd == 'C') state = 3;
  38.      if(cmd == 'D') state = 4;
  39.    
  40.       break;
  41.       
  42.     case 1:
  43.             
  44.              switch (cmd)
  45.              {
  46.               case '0':
  47.               Left();
  48.               break;
  49.                           
  50.               case '1':
  51.               //do nothing
  52.               break;
  53.                           
  54.               case '2':
  55.               Right();
  56.               break;
  57.                           
  58.               case '3':
  59.               posCircle = 0;
  60.               break;
  61.              }
  62.              state = 0;
  63.       break;
  64.       
  65.       case 2:
  66.             
  67.              switch (cmd)
  68.              {
  69.               case '0':
  70.               downBase();
  71.               break;
  72.                           
  73.               case '1':
  74.               //do nothing
  75.               break;
  76.                           
  77.               case '2':
  78.               upBase();
  79.               break;
  80.                           
  81.               case '3':
  82.               posBase = 50;
  83.               break;
  84.              }
  85.              state = 0;
  86.       break;
  87.       
  88.       case 3:
  89.       
  90.              switch (cmd)
  91.              {
  92.               case '0':
  93.               downElbow();
  94.               break;
  95.                           
  96.               case '1':
  97.               //do nothing
  98.               break;
  99.                           
  100.               case '2':
  101.               upElbow();
  102.               break;
  103.                           
  104.               case '3':
  105.               posElbow = 50;
  106.               break;
  107.                           
  108.               case '4':
  109.               posElbow = 10;
  110.               break;
  111.              }
  112.              state = 0;
  113.       break;
  114.       
  115.       case 4:
  116.          
  117.              switch (cmd)
  118.              {
  119.               case '0':
  120.               break;
  121.                           
  122.               case '1':
  123.               break;
  124.                           
  125.               case '2':
  126.               break;
  127.                           
  128.               case '3':
  129.               posGH = 50;
  130.               break;
  131.              }
  132.              state = 0;
  133.       break;
  134.    
  135.   }
  136.   
  137. }
  138.       
  139. }
  140. void control(int motor, int angle)
  141. {
  142. switch (motor) {
  143.     case 1:
  144.     if(angle >= 0) if(angle <= 140) Circle.write(angle + 10);                        
  145.     break;
  146.    
  147.     case 2:
  148.     if(angle >= 0) if(angle <= 60) Base.write(140 - angle);
  149.     break;
  150.     case 3:
  151.     if(angle >= 0) if(angle <= 70) Elbow.write(80 - angle);
  152.     break;
  153.     case 4:
  154.     if(angle >= 0) if(angle <= 65) Wrist.write(75 - angle);
  155.     break;
  156.   }  
  157. }
  158. void Left()
  159. {
  160. if(posCircle <= 136)
  161. {
  162.   posCircle = posCircle + 4;
  163. }
  164. else posCircle = 140;
  165. control(1, posCircle);
  166. }
  167. void Right()
  168. {
  169. if(posCircle >= 4)
  170. {
  171.   posCircle = posCircle - 4;
  172. }
  173. else posCircle = 0;
  174. control(1, posCircle);
  175. }
  176. ///////////////////////////////////
  177. void downElbow()
  178. {
  179. if(posElbow >= 4)
  180. {
  181.   posElbow = posElbow - 4;
  182. }
  183. else posElbow = 0;
  184. control(3, posElbow);
  185. }
  186. void upElbow()
  187. {
  188. if(posElbow <= 66)
  189. {
  190.   posElbow = posElbow + 4;
  191. }
  192. else posElbow = 70;
  193. control(3, posElbow);
  194. }
  195. //////////////////////////////////////
  196. void downBase()
  197. {
  198. if(posBase >= 4)
  199. {
  200.   posBase = posBase - 4;
  201. }
  202. else posBase = 0;
  203. control(2, posBase);
  204. }
  205. void upBase()
  206. {
  207. if(posBase <= 56)
  208. {
  209.   posBase = posBase + 4;
  210. }
  211. else posBase = 60;
  212. control(2, posBase);
  213. }
第2步:如何制作演示App
为了制作一个类似的应用程序,我们需要知道如何使用C#编写应用程序。由于Windows Forms App很简单,并且拥有在线文档,需要时可以随时获取,因此我们采用了该App。对于这个项目,我们需要Visual Studio和.NET framework(您可以在这里下载这两个程序)。下载并安装文件后,我们就可以开始制作应用程序。
首先,我们需要创建一个新工程。创建新工程的步骤如下:点击FileNewProjectWindows Forms App(.NET Framework)。
image019-copy.jpg
图5: 如何在Visual Studio中制作App

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

对于测试演示,我们可以制作这样一个应用程序:点击按钮,系统显示一条文本信息。
双击Toolbox(工具箱)中的按钮,程序会在左上角自动添加一个按钮。该按钮会被命名为“button1”,因为.NET Framework会对按钮类实例进行自动编号。在button1属性中,我们可以将Text从button1改为Click here!现在,我们需要编写代码,以便让按钮起作用(双击图形界面上的按钮):
  1. namespace WindowsFormsApp3
  2. {
  3.     public partial class Form1 : Form
  4.     {
  5.         public Form1()
  6.         {
  7.             InitializeComponent();
  8.         }
  9.         private void button1_Click(object sender, EventArgs e)
  10.         {
  11.             MessageBox.Show("App made for DevicePlus");
  12.         }
  13.     }
  14. }
当用户点击该按钮时,上述代码会显示一个文本。具体结果如图7所示。
image023-copy.jpg
图7: 演示App

第3步:App属性
凭借.NET、OpenCV和Microsoft API的功能,我们能够制作出完整的应用程序。对于该应用程序,我们需要按钮、串口和文本框。
黑色窗口会显示来自摄像头的图像,窗口中还会添加一个镜头框,框内显示人物名称。
左侧的白色窗口可以添加TTS API(文本转语音)的文本,提供反馈以检查工作,并检查说出的话语是否与显示的字词相符。
image025-copy.jpg
图8: 机器人手臂应用程序

第4步:人脸识别
为了获得更好的程序结果,您需要在训练模式下添加多个脸部记录。训练是指通过拍摄多个脸部位置以获得最佳识别(不同拍摄角度、光线条件等)。您需要按下Record 10 Faces按钮,通过应用程序记录脸部画面。
在代码中,实现这个功能的语句为Face = Parent.faceClassifier;这通过EmguCV平台完成。人脸检测通过基于主成分分析(PCA)的复杂算法实现,系统会将检测到的脸部与训练后存储的图像进行多次比较。
image027.jpg
图9: 人脸检测

蓝色矩形中的图像是需要存储的图像,因为它包含面部的独特特征。
我们需要在不同的位置和不同的光线下获取更多的面部特征:
image029.jpg
图10: 添加更多人脸进行训练

经过一系列面部检测拍摄,如果您认为之前记录的脸部图像不利于算法计算,那么可以点击Restart 1 Face按钮删除已经保存的文件,系统将不再检测该脸部信息。
如果要记录多个人、让机器人手臂可以区分许多个人,那么可以使用Delete Data按钮删除照片。
image031.jpg
图11:重启训练模式

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

第5步:天气API
通过天气API,我们可以根据以下方式收集城市数据:城市名称、城市ID、地理坐标甚至邮政编码。该API的优点是免费且易于使用。但是该API有一个限制:使用次数或者流量不能超过60次/分钟或50k/天,如果违反该规定,您的帐户将被封锁。
在本教程中,我们选择根据城市ID调用API。每个城市都有一个ID,详细信息请参见https://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”。
image035-copy.jpg
图13: 测试API

总体来说,这是一个有趣的项目。该项目让我懂得硬件和软件之间的良好同步对于实现最佳性能至关重要。使用Visual Studio的很多库使项目有点复杂,但最终结果令人满意。
我认为这种项目可以通过多种方式进行开发和改进,以满足每个用户的需求。

来源:techclass.rohm