开源项目:2023 · 中国工程机器人大赛暨国际公开赛 · 芜湖
摘要
本篇博客参考清华大学出版社出版的《NAO机器人程序设计》一书,对其中的部分内容进行总结,以便加深理解和记忆。

1.介绍
产品公司官网:SoftBank Robotics Group Corp
产品参考网站:NAO 6 | Aldebaran
1)仿人机器人:综合运用机械、传感器、驱动器、计算机等技术设计的一种能模仿人的形态和行为的机械电子设备,是在电子、机械及信息技术的基础上发展而来的。仿人机器人的四肢和头部能够自主完成人类所赋予的任务与命令。
2)NAO机器人介绍
①NAO是一款Aldebaran Robotics公司研制的高端仿人机器人,具有一定水平的人工智能,是世界范围、学术领域内应用最广泛的仿人机器人,是机器人世界杯RoboCup组委会指定机器人,并开放到高等教育。
②NAO本质上是一台安装了Gentoo Linux的计算机,该计算机安装了机器人专用的软硬件。主要硬件包括:2个CPU、主板、扬声器、话筒、红外线(多机器人通信控制)、2个相机、声呐(障碍物测距)、传感器(接触传感器、惯性传感器、位置传感器、压力传感器)、执行器(电机、齿轮)、语音合成器、LED灯等,运行自研的系统框架NAOqi,支持网线和wifi。


3)NAO使用方法:可在Linux、Windows、MacOS系统下用Python、C++、.NET、Java等调用NAOqi API或使用图形化的指令盒编程工具Choregraphe进行编程
4)NAO开发环境配置
5)NAO系统恢复与更新
6)项目参考文献 | 博客
地址 | 说明 |
---|---|
NAO 6Aldebaran | Nao6产品首页:包含Nao6产品介绍、拆箱、使用指南、初始化方案、相关软件下载等 |
Developer Center (aldebaran.com) | 开发者中心:包含博客、课程、文档和资源 |
NAO - Aldebaran 2.8.7.4 documentation | NAO文档,包含用户指南和开发指南 |
nao 6代培训手册 (book118.com) | 包括,初始使用指南和可视化开发 |
NAO V6 开发环境配置-CSDN博客 | 包括环境配置和python开发脚本植入 |
2021软银机器人杯NAO机器人比赛培训-竞走 | B站Nao竞走培训视频 |
yolov4目标检测并进行跟踪 | 基于nao机器人实现yolov4目标检测并进行跟踪 |
2.连接NAO
1)配置NAO的无线网络
-
通过以太网将NAO与计算机或者交换机连接
-
开机,听语音播报的IP地址,将本机IP设置与NAO为同一网段
-
打开浏览器,输入NAO的IP地址,用户密码均输入nao,然后进行wifi配置
-
将电脑连接至NAO所连的wifi
2)远程连接NAO:通过SSH远程登录,账号密码均为nao
3)NAOqi
NAOqi框架使用一致的数据模型表示信息,不同模块使用相同的编程模式,各个模块与ALMemory
共享使用相同的结构
机器人启动过程中,NAOqi会自动启动,是NAO的核心依赖,脚本/etc/init.d/naoqi管理NAOqi的启动过程
# 启动NAOqi
nao start
# 停止Naoqi
nao stop
# 重启
nao restart
# 查看运行状态
nao status
3.NAOqi编程
1)NAOqi依赖
/etc/naoqi/autoload.ini
(该文件会在NAOqi启动时被代理程序加载)指定了NAOqi所需的库,这些库位于/usr/lib/naoqi
下

2)NAOqi代理程序
在NAO机器人上执行NAOqi指令是通过一个代理程序Broker完成,使用NAOqi模块时不同于传统Python程序通过import
导入模块,而是通过Broker查找对应模块并调用对应方法。
同时Broker还是一个服务器,这意味着它也可以通过网络(ip+port)而不仅是本地调用模块相应的命令

3)编程基础
①设置naoqi SDK依赖路径,后续代码都需要加入该步骤,故省略
# 注意此处应将naoqi框架的依赖路径正确添加,也可以使用其他方法导入依赖
sys.path.append(r'E:\code\Nao6\choregraphe-suite-2.8.6.23-win64-vs2015\bin')
sys.path.append(r'E:\code\Nao6\choregraphe-suite-2.8.6.23-win64-vs2015\lib')
②依赖导入和通过代理类创建模块实例
# 导入代理类,由于使用添加系统路径的方式导包,因而会报找不到的错
from naoqi import ALProxy
if __name__ == '__main__':
# 通过代理类导入ALTextToSpeech模块,参数1:模块名称;参数2:IP,参数3:端口号
tts = ALProxy("ALTextToSpeech", "192.168.0.47", 9559)
# 文字转语音
tts.say("""你好,小琳猫!""")
③阻塞调用与非阻塞调用
from naoqi import ALProxy
import time
if __name__ == '__main__':
"""非阻塞调用,边走,边说话"""
motion = ALProxy("ALMotion", "192.168.0.47", 9559)
tts = ALProxy("ALTextToSpeech", "192.168.0.47", 9559)
# 设置关节的刚度(电机的转矩限制),刚度为0时关节做不了任何运动,属于非阻塞调用
motion.setStiffnesses("Body", 1.0)
time.sleep(1)
# 运动进程的初始化,检测机器人的当前状态,并选择一个正确的正确的姿势,阻塞调用
motion.moveInit()
# 移动到指定坐标,阻塞调用。但通过post对象进行非阻塞调用
motion.post.moveTo(1, 0.5, 0)
# 文字转语音,阻塞调用
tts.say("""你好,我叫李一帆""")
③脚本导入执行
可以将脚本导入到机器人的/home/nao
目录下运行
4)内存模型
NAOqi的各个模块间通过内存交换数据,内存管理的模块是ALMemory
。ALMemory
以无序映射方式存储数据,数据结构为键值对,可以通过getData("{键名}")
来读取对应的值。
from naoqi import ALProxy
import time
memory = ALProxy("ALMemory","192.168.1.170",9559)
for i in range(10):
print(memory.getData("Device/SubDeviceList/InertialSensor/AccelerometerX/Sensor/Value"))
NAOqi中的数据主要分为两类:
①NAO的状态数据:包括执行器和传感器的数据。NAOqi周期性地调用各种传感器驱动程序接口,将传感器值写入内存中
②订阅事件/微型事件数据:如人脸识别模块等处理时的运算量很大,NAOqi只有在订阅这些功能时才会向内存中写数据,订阅的模块在完成后以事件的方式通知NAOqi
5)NAOqi的数据类型
类型 | Python | C++ |
---|---|---|
整型 | int | int |
布尔型 | bool | bool |
浮点型 | float | float |
列表 | vector | [ ] |
字符串 | str | std::string |
二进制数据 | str | AL Value |
6)NAO机器人状态
状态 | 解释 |
---|---|
互动(Interactive) | 称为自主生活ALAutonomousLife,自主生活状态下可以执行功能,同一时间只能进行一个功能,默认开启。可以通过双击机器人胸部,或在Nao网页中设置 |
孤立(Solitary) | |
保护(Safeguard) | |
禁用(Disabled) |
4.运动控制
1)运动模型
NAO相连部件之间可以在两个甚至三个方向上做相对运动,每个方向上的运动都是通过电机驱动机械结构完成的。
①坐标系与方向:x指向身体前方,y水平从右到左,z从下向上。
NAO运动时,远离躯干的关节运动时绕轴转动,单位为弧度(注意要用角度计算时,需要进行单位转换)

②运动
机器人能够独立运动的关节数称为机器人的运动自由度(NAO为26)
腿部需要支撑NAO全身的重量


运动名称 | 转动幅度 |
---|---|
HeadPitch | 仰头[-38.5,29.5]抬头 |
HeadYaw | 左转[-119.5,119.5]右转 |
ShoulderRoll | L[-18,76]R |
ShoulderPitch | L[-119.5,119.5]R |
ElbowYaw | L[-119.5,119.5]R |
WristYaw | L[-104.5,104.5]R |
HipYawPitch(髋关节,连接腿和躯干,控制腿内外转) | 外[-65.62,42.44]内 |
HipPitch(髋关节,控制腿的前摆、后摆) | [-88,27.73] |
HipRoll(髋关节,控制腿的横向移动) | [-21.74,45.29] |
KneePitch(屈膝) | [-5.29,121.04] |
AnklePitch(脚前后转动) | [-67.97,53.40] |
AnkleRoll(脚左右转动) | [-22.79,44.06]/[-44.06,22.79] |
2)ALRobotPosture
ALRobotPosture
模块可以让机器人转到不同的预定义姿势
from naoqi import ALProxy
postureProxy = ALProxy("ALRobotPosture","192.168.1.170",9559)
# 返回当前姿势名称,若当前姿势不是预定义姿势,返回unknow,阻塞调用
postureProxy.getPosture()
# 返回预定义姿势列表,阻塞调用
postureProxy.getPostureList()
# 转到预定义姿势,阻塞调用,参数1:姿势名,参数2:速度
postureProxy.goToPosture("StandInit",1.0)
postureProxy.goToPosture("SitRelax",1.0)
postureProxy.goToPosture("StandZero",1.0)
postureProxy.goToPosture("LyingBelly",1.0)
postureProxy.goToPosture("LyingBack",1.0)
postureProxy.goToPosture("Stand",1.0)
postureProxy.goToPosture("Sit",1.0)
postureProxy.goToPosture("Crouch",1.0)
# print(postureProxy.getPostureFamily)
# 转到预定义姿势,没有goToPosture的中间动作,阻塞调用
postureProxy.applyPosture("{预设动作名}",{速度})
# 停止当前动作
stopMove()
3)ALMotion
ALMotion
模块包括与机器人动作相关的方法,运动任务可以阻塞调用也可以非阻塞调用
①刚度控制
设置关节的刚度相当于电机的转矩限制。
刚度为0.0时,关节位置不受电机控制,关节是自由的。刚度为1.0时,关节使用最大功率转到指定位置。刚度∈(0.0,1.0)时,如果关节移动到目标位置所需的转矩高于刚度的限制,关节不会达到目标位置。
可以通过肢体名称控制一组关节的刚度,也可以单独设置某一关节的刚度
from naoqi import ALProxy
motionProxy = ALProxy("ALMotion","192.168.1.170",9559)
# 唤醒机器人,启动电机,刚度均为1
motionProxy.wakeUp()
# 获取机器人的唤醒状态
motionProxy.robotIsWakeUp()
# 机器人休息,关闭电机,刚度均为0
motionProxy.rest()
# 设置刚度,非阻塞调用
motionProxy.setStiffness({关节名s},{刚度})
# 获取刚度
motionProxy.getStiffness({关节名s})
# 按时间序列设置刚度,阻塞调用
motionProxy.stiffnessInterpolaition({关节名s},{刚度},{时间序列})
# 分别在1.2.3.4秒将HeadYaw的刚度设置为0.5,0.5,1.0,0.0
motionProxy.stiffnessInterpolation(['HeadYaw'],[0.25,0.5,1.0,0.0],[1.0,2.0,3.0,4.0])
②关节控制
关节控制用于精确控制机器人的关节位置,可以控制一个关节或同时控制多个关节。肢体通过关节从一个位置转到另一个位置起始速度和终止速度为0,转动的角度是非线性的。
关节控制通常要经历多个ALMotion周期(20ms),为使关节平稳转动,每个一个周期会重新计算电机电流和刚度变化
from naoqi import ALProxy
import almath
motionProxy = ALProxy("ALMotion","192.168.1.170",9559)
# 插值运动,阻塞调用,类似于动画,在起始位置和终止位置之间插入若干动作
# 参数1:关节列表,参数2:角度列表:参数3:时间列表,参数4:是否为绝对角度
motionProxy.angleInterpolation(["HeadYaw","HeadPitch"],[30 * almath.TO_RAD,30 * almath.TO_RAD],[1.0,2.0],true)
# 带速度限制的插值运动,阻塞调用
# 参数1:关节列表,参数2:角度列表,参数3:最大速度比
motionProxy.angleInterpolationWithSpeed(names,targetAngles,maxSpeedFraction)
# 贝塞尔角度插值,使用贝塞尔曲线(在起始和终止时间内使用平滑曲线控制,使关节运动过程更加平稳)
# 参数1:关节列表,参数2:时间列表,参数3:控制点列表
motionProxy.angleInterpolationBezier(names,times,controlPoints)
# 反应式控制,非阻塞调用,适合于反复控制场景(相互矛盾的命令序列),以保证运动平滑且连续
# 参数1:关节列表,参数2:角度列表,参数3:最大速度比
motionProxy.setAngles(names,angles,maxSpeedFraction)
# 参数1:关节列表,参数2:角度列表,参数3:最大速度比
motionProxy.changeAngles(names,angles,maxSpeedFraction)
# 获取关节角度
# 参数1:关节列表,参数2:是否返回传感器角度,true为传感器角度,false为执行器角度
motionProxy.getAngles(names,useSensors)
# 合上双掌,阻塞调用
motionProxy.closeHand(LHand/RHand)
# 打开双掌,阻塞带哦用
motionProxy,openHand(LHand/RHand)
关节运动与身体平衡:当关节运动时,机器人重心会发生变化,严重时会摔倒。为保持身体平衡,需要同时改变多个关节的角度并调节关节运动的速度。
③运动控制
运动控制用于控制机器人的行走,包括指定位置、速度、目标等行走方式。NAO的行走控制使用“线性倒立摆”模型,行走控制的目标是尽快达到一个平衡位置,且没有大的振荡和过大的角度和速度。
每步包括双腿支撑和单腿支撑两个阶段,其中双腿支撑占1/3。行走初始阶段和结束阶段双腿支撑时间为0.6s。脚运行的轨迹是一条平滑曲线,使用SE3插值计算出来。脚步沿该曲线运动既符合速度又能保持平稳。不管做哪种行走控制,都需要使用步态规划,指定步长、步频率、最大高度等。
步态参数
参数名 | 解释 | 默认值 | 范围 | 可修改 |
---|---|---|---|---|
MaxStepX | 沿X方向的最大前移距离 | 4cm | [1,8]cm | 是 |
MinStepX | 沿X方向的最小前移距离 | -4cm | 否 | |
MaxStepY | 沿Y轴的最大平移绝对值 | 14cm | [10.1,16]cm | 是 |
MinStepTheta | 沿Z轴旋转角度最大绝对值 | 0.349° | [0.001,0.524]° | 是 |
MaxStepFrequency | 最大步频 | 1 | [0,1] | 是 |
MinStepPeriod | 最小步周期 | 0.42 | 否 | |
MaxStepPeriod | 最大步周期 | 0.6 | 否 | |
StepHeight | Z方向抬脚最大高度 | 2cm | [0.5,4]cm | 是 |
TorsoWx | 躯干与X轴间最大角度 | 0° | [-0.122,0.122]° | 是 |
TorsoWy | 躯干与Y轴间最大角度 | 0° | [-0.122,0.122]° | 是 |
FootSeparation | Y方向两脚之间的距离 | 10cm | 否 | |
MinFootSeparation | Y方向两脚之间的最小距离 | 8.8cm | 否 |

from naoqi import ALProxy
import almath
motionProxy = ALProxy("ALMotion","192.168.1.170",9559)
# 设置步态,非阻塞调用
# 参数1:腿名LLeg/RLeg,参数2:[x,y,theta],参数3:时间列表,参数4:True清楚已有参数
motionProxy.setFootSteps(legName,footSteps,timeList,clearExisting)
# 设置步态,阻塞调用,带速度
motionProxy.setFootStepsWithSpeed(legName,footSteps,fractionMaxSpeed,clearExisting)
# 获取实际的步态,非阻塞调用
motionProxy.getFootSteps()
# 初始化运动进程,阻塞调用
motionProxy.moveInte()
# 是否运动,运动返回true,阻塞调用
motionProxy.moveIsActive()
# 等待,直到行走任务完成。用于阻塞程序向下运行直到行走任务结束
motionProxy.waitUntilMoveIsFinished()
# 获取步态参数,Max,Min,Default,阻塞调用
motionProxy.getMoveConfig("Max")
# 获取机器人位置,阻塞调用,True返回传感器值,阻塞调用
motionProxy.getRobotPosition(True)
# 获取机器人速度,返回值为x、y、z方向的速度,阻塞调用
motionProxy.getRobotVelocity()
# 设置运动过程中手臂是否可动,True可动,参数1、2分别为左右手臂,阻塞调用
motionProxy.setArmsEnabled(leftArmEnable,rightArmEnable)
# 获取运动过程中手臂可动,阻塞调用
getMoveArmsEnabled(LArm/RArm/Arms)
# 运动,阻塞调用
motionProxy.post.moveTo(1.0,0.0,0.0)
5.视觉模块
NAO头部有两台相机:前额相机,ID为0,主要用于拍摄远景图像;嘴部相机,ID为1,主要用于拍摄下方图像。其最大帧率为30,最大分辨率为1280×690。
1)NAO支持的色彩空间与分辨率


NAO如果按照传统的RGB颜色空间的话每帧图像会占用很大的空间,他支持多种图像压缩算法,可以将图像保存为不同格式。
2)ALPhotoCapture
ALPhoteCapture
模块主要用于拍摄图像(组)并保存
photoProxy = ALProxy("ALPhotoCapture","192.168.1.170",9559)
# 获取相机ID
photoProxy.getCameraID()
# 设置拍照相机,默认为上部相机
photoProxy.setCameraId(0/1)
# 设置拍照时间间隔,参数为整数,单位为ms,默认值为200
photoProxy.setCaptureInterval(200)
# 获取拍照时间间隔
photoProxy.getCaptureInterval()
# 设置颜色空间,参考上表ID
photoProxy.setColorSpace(colorSpace)
# 设置保存图像的格式,bmp、jpg、png、tiff等
photoProxy.setPictureFormat("{图像格式}")
# 拍摄图像
photoProxy.takePicture("{保存路径}","{文件名}",overwrite=False)
# 拍摄图像组
photoProxy.takePictures({拍摄图像数},"{保存路径}","{文件名}"[,overwrite=False])
3)ALVideoRecorder
ALVideoRecorder
模块用于录制视频,保存为avi
格式
videoRecorderProxy = ALProxy("ALVideoRecorder","192.168.1.170",9559)
# 设置拍照相机,默认为上部相机
videoRecorderProxy.setCameralID(0/1)
# 获取拍照相机ID
videoRecorderProxy.getCameralID()
# 设置帧率,最大为30,VGA下最大为15
videoRecorderProxy.setFrameRate(frameRate)
# 获取帧率
videoRecorderProxy.getFrameRate()
# 设置颜色空间,上表中的ID值
videoRecorderProxy.setColorSpace({})
# 设置视频格式,"IYUV"或"MJPG"
videoRecorderProxy.setVideoFormat({})
# 设置分辨率,参照上表ID
videoRecorderProxy.setResolution({})
# 开始摄像,非阻塞调用,调用后开启新线程录制视频
videoRecorderProxy.startRecording("{保存路径}","{保存文件名}"[,overwrite=False])
# 当前是否录制
videoRecorderProxy.isRecording()
# 结束拍摄
videoRecorderProxy.stopRecording()
4)ALVideoDevice
ALVideoDevice
负责从相机获取的源图像进行前期处理(将YUV图像转换为其他色彩空间图像、向ARV视频文件添加时间戳、封装图像数据等)
①NAO机器人相机的图像传感器为MT9M114
,相机驱动程序输出YUV422图像。视觉模块订阅ALVideoDevice
模块,并将YUV图像数据流转换为所需格式,若视觉模块需要的就是YUV模式,则可以直接访问原始数据。其不同格式数据流的处理时间依次为:YUV422 < Yuv < YUV < RGB/BGR < HSY。
NAO的CPU处理能力有限,在网络带宽较低时,远程传送每秒实际的最大帧数会小于30,且分辨率越大,帧数越少。
②图像封装数据格式
# ALImage 列表
# 整数,图像宽度;整数,图像高度;整数,图像层数,像素字节数;整数,色彩空间;整数,时间戳s;整数,时间戳ms;整数,图像数据;整数,相机ID;浮点数,相机左视角(弧度),FOV;浮点数,相机上视角;浮点数,相机右视角;浮点数,相机下视角
image[0],image[1],image[2],image[3],image[4],image[5],image[6],image[7],image[8],image[9],image[10],image[11]
③订阅图像
ALVideoDevice
模块是视觉模块中最复杂,方法最多的模块,包括一些弃用的方法在内有100多种API,可以分为订阅管理、相机管理、单数据流管理和多数据流管理等,以下仅给出订阅图像的示例:
流程:订阅ALVideoDevice
模块 → 获取图像 → 释放图像 → 解除订阅
videoDeviceProxy = ALProxy("ALVideoDevice","192.168.1.170",9559)
# 设置相机
videoDeviceProxy.setActiveCamera(0/1)
# 获取当前使用的相机
videoDeviceProxy.getActiveCamera()
# 订阅拍摄,图像存储在缓冲区,返回订阅的句柄
videoClient = videoDeviceProxy.subscribeCameras("{订阅模块名,任意}",{相机ID},{分辨率},{色彩空间},{帧率})
# 远程获取拍摄后的图像,返回的是ALImage实例
image = videoDeviceProxy.getImageRemote(videoClient)
# 本地获取拍摄后的图像,返回的是ALImage实例
image = videoDeviceProxy.getImageLocal(videoClient)
# 释放图像缓存
videoDeviceProxy.releaseImage(videoClient)
# 解除订阅
videoDeviceProxy.unsubscribe(videoClient)
# 打开并初始化相机
videoDeviceProxy.openCamera({相机ID})
# 关闭相机并释放资源
videoDeviceProxy.closeCamera({相机ID})
5)视频检测
视频检测包括红球检测ALRedBallDetection
,标识检测ALLandMarkDetection
,运动检测ALMovementDetection
,检测并读取二维码ALBarcodeReader
,人脸检测ALFaceDetection
,黑光检测ALBlacklightingDetection
,黑暗检测ALDarknessDetection
,3D分割检测ALSegmentation3D
,视觉罗盘ALVisualCompass
等十余种。
这些模块都继承于ALVisionExtractor
模块,其整个继承关系如下图:

6)视频识别
NAO能够识别事先学习过的Object,对应模块为视频识别ALVisionRecognition
,其识别过程为:
①学习过程:通过Choregraphe抽取识别对象的关键特征并存储(标定学习对象、特征提取、指定对象名称和方位、存储、将数据库导入到机器人中)
②识别过程
③数据结构