智能玩具机器人的工作原理

发布时间:2016-06-08 09:43:52   来源:文档文库   
字号:

智能玩具机器人的工作原理

 

    人工智能(AI)可以说是最令人兴奋的在智能玩具机器人技术领域。这当然是最具争议的:每个人都同意,一个智能玩具机器人可以在装配线上工作,但是没有共识智能玩具机器人是否能够聪明。

像“智能玩具机器人”一词本身,人工智能是很难定义的。最终人工智能将是一个娱乐的人类思维过程——一个人造机器与我们的知识能力。这将包括学习任何东西的能力,能力的原因,使用语言的能力和制定独到的见解的能力。智能玩具机器人是远远没有达到这个水平的人工智能,但是他们取得了很多的进步更有限的人工智能。今天的人工智能机器可以复制一些特定元素的知识能力。

 

   电脑已经可以解决问题在有限的领域。人工智能解决问题的基本思想非常简单,尽管它的执行是复杂的。首先,人工智能智能玩具机器人或电脑收集事实情况通过传感器或人工输入。电脑比较这些信息存储数据并决定信息意味着什么。电脑运行通过各种可能的行动和预测行动将是最成功的基于收集到的信息。当然,计算机只能解决问题的程序来解决,它没有任何广义分析能力。国际象棋计算机这类机器的一个例子。

 

一些现代智能玩具机器人也有能力学习能力有限。学习智能玩具机器人识别如果某个行动(以某种方式将其腿,例如)取得了预期的结果(导航障碍物)。智能玩具机器人商店这一信息,并尝试成功的行动,下次遇到同样的情况。再次,现代计算机只能在非常有限的情况下做到这一点。他们不能像人类可以吸收任何类型的信息。有些智能玩具机器人可以通过模仿人类的行为。在日本,智能玩具机器人专家教智能玩具机器人舞蹈通过展示自己移动。

 

一些智能玩具机器人可以社会互动。在M.I.天命,一个智能玩具机器人T的人工智能实验室,道出了人类肢体语言和声音音调变化和适当的响应。命运的创造者感兴趣的人类如何和孩子互动,只有基于讲话的语气和视觉线索。这种低级的交互可能类似人类的学习系统的基础。

 

命运和其他人形智能玩具机器人在麻省理工学院人工智能实验室操作使用一个非传统的控制结构。而不是指挥每一个行动都使用一个中央计算机,智能玩具机器人控制低级的行为与低级别的电脑。程序的导演,罗德尼•布鲁克斯认为,这是人类智慧的更精确的模型。自动大多数事情;我们不决定他们意识的最高水平。

 

人工智能的真正的挑战是了解自然的情报是如何工作的。发展人工智能不像建立一个人工心脏——科学家们没有一个简单的,具体的模型。我们知道大脑包含数十亿数十亿的神经元,我们认为,通过建立学习不同神经元之间的电气连接。但我们不知道如何所有这些连接添加到更高的推理,甚至低级操作。复杂的电路似乎难以理解。

 

因此,人工智能主要是理论研究。科学家们假设如何以及为什么我们学习和思考,并使用智能玩具机器人他们尝试自己的想法。布鲁克斯和他的团队专注于人形智能玩具机器人,因为他们认为能够体验世界像人类是至关重要的发展类似人类的智能。这也使人们更容易与智能玩具机器人交互,这可能让智能玩具机器人更容易学习。

 

正如物理智能玩具机器人的设计是一个方便的工具,了解动物和人体解剖学,人工智能的研究有助于理解自然的情报是如何工作的。对于一些智能玩具机器人,这观点是智能玩具机器人设计的终极目标。其他人想象一个世界,我们生活与智能机器和使用各种各样的小智能玩具机器人对体力劳动,卫生保健和沟通。大量的智能玩具机器人专家预测,智能玩具机器人进化最终会把我们变成半机械人,人类与机器集成。可以想象,人们在未来可能决定加载到坚固的智能玩具机器人,活了几千年!

 

在任何情况下,智能玩具机器人一定会发挥更大的作用在未来在我们的日常生活中。在未来的几十年里,智能玩具机器人将逐渐走出工业和科学世界和日常生活中,以同样的方式,电脑在1980年代蔓延到家里。

 

了解智能玩具机器人的最佳方法是看具体设计。下一个页面上的链接将向您展示世界上目前的智能玩具机器人项目。

机器人、迷宫和包容体系结构

机器人模拟器既是严肃的研究工具,也是能够通过 Java™ 编程创造乐趣的领域。学习如何使用 Simbad(一种基于 Java 3D 技术的开放源码机器人模拟器)和 Java 语言创建能够跟踪光源和走迷宫的虚拟机器人,并了解包容体系结构的机器人设计概念。

简介

机器人学很早就超出了科学幻想的领域,并在工业自动化、医疗、太空探索等领域发挥着重要作用。软件机器人模拟器不但简化了机器人工程师的开发工作,还为研究人工智能(artificial intelligenceAI)算法和机器学习提供了工具。以这种研究为中心的模拟器之一是开放源码的 Simbad 项目,它基于 Java 3D 技术(参见 )。本文讲解如何使用 Simbad 工具箱编写虚拟机器人,并介绍 的机器人设计原理。

本文首先简要概述机器人学并解释包容体系结构概念。然后介绍 Simbad 工具箱并演示如何在 Simbad 中实现包容体系结构。然后使用这个体系结构编写一个简单的机器人。最后,讨论迷宫并编写第二个机器人,与 Homer Simpson 不同(参见 ),这个机器人会自己走出迷宫。这里的机器人不是现实存在的,但是 Simbad 的虚拟世界中。

机器人编程

 这个词没有被普遍接受的定义。根据本文的目的,可以认为一个机器人由三个部分组成:

一个 集合

一个定义机器人的行为的程序

一个  集合

传统的机器人学

在传统的机器人学中(也就是 1986 年之前的机器人学),机器人拥有一个中央大脑,这个大脑构建并维护环境的地图,然后根据地图制定计划。首先,机器人的传感器(例如接触传感器、光线传感器和超声波传感器)从它的环境中获得信息。机器人的大脑将传感器收集的所有信息 起来并更新它的环境地图。然后,机器人决定运动的路线。它通过传动器和受动器执行动作。传动器基本上是一些发动机,它们连接到受动器,受动器与机器人的环境交互。受动器包括轮子和机械臂等( 这个词常常用来泛指传动器或受动器)。

简单地说,传统的机器人接收来自传感器(可能有多个传感器)的输入,组合传感器信息,更新环境地图,根据它当前掌握的环境视图制定计划,最后执行动作。但是,这种方法是有问题的。问题之一是它需要进行大量计算。另外,因为外部环境总是在变化,所以很难让环境地图符合最新情况。一些生物(比如昆虫)不掌握外部世界的地图,甚至没有记忆,但是它们却活得非常自在;模仿它们会不会更好呢?这些问题引出了一种新型的机器人学,称为BBR 在当今的机器人实验室中占主要地位。

包容体系结构

可以使用包容体系结构(subsumption architecture)实现 BBR。包容体系结构的发明者是 Rodney A. BrooksMIT AI Lab 的现任领导)在 1986 年的文章 “Elephants Don't Play Chess” 中提出了包容体系结构(参见 )。基于行为的机器人依赖于一组独立的简单的。行为的定义包括触发它们的条件(常常是一个传感器读数)和采取的动作(常常涉及一个受动器)。一个行为建立在其他行为之上。当两个行为发生冲突时,一个中央 决定哪个行为应该优先。机器人的总体行为是,根据 BBR 支持者的说法,它的效果好于其部分之和。较高层行为 较低层行为。我们并不创建整个机器人,只需添加行为并看看会发生什么。

Simbad:一个机器人模拟环境

LEGO Mindstorms

本文主要关注软件机器人的构建,但是如果希望构建物理机器人,那么可以考虑使用 LEGO Mindstorms

LEGO Mindstorms 总部的宣言说,我们将让机器人学发生巨变,就像 iPod 给音乐界带来的影响。” LEGO 1998 年推出了 Mindstorms 机器人学工具箱的第一版。这个工具箱很快就变得畅销了,大大超出了 LEGO 的预期。尽管它的价格似乎不便宜($250),但是要知道这只相当于 iPod Classic 的价格,而且 都拥有 iPod

但是,iPod Mindstorms 那么容易破解。在 Mindstorms 发布后不久,硬件黑客就开始取出 Mindstorms RCX brickMindstorms 机器人的大脑)并进行反向工程。LEGO 没有预料到这种情况,而且不确定是否应该制止这种做法。最后,LEGO 的管理层决定允许 Mindstorms 黑客的这种行为。

由此产生了一个兴旺的 Mindstorms 社区(参见 )。尽管 Mindstorms 只附带一种拖放式图形化编程语言 NXT-G,但是软件黑客很快就在 Mindstorms 中添加了对 C Java 等其他语言的支持。结果,大约一半的 Mindstorms 由成年人使用。

可以使用 Simbad 在软件中模拟机器人。该项目 Web 站点指出,它让程序员能够编写自己的机器人控制器、修改环境和使用可用的传感器。它主要向研究人员/程序员提供一个基本工具,用来在自治机器人学和自治代理上下文中研究情景人工智能(Situated Artificial Intelligence)、机器学习和其他 AI 算法。

Simbad 是由 Louis Hugues Nicolas Bredeche Java 语言编写的。在符合 GNU General Public License 的条件下,可以免费使用和修改这个项目(可从 SourceForge.net 上获得)。

技术细节

Simbad 环境可以包含代理(Agent,也就是机器人)和固定对象(盒子、墙、灯等等)。Simbad 环境中的时间划分成离散的嘀嗒(tickSimbad 在代理之间调度时间分配。与物理机器人一样,Simbad 代理也有传感器(距离、接触、光线等等)和传动器(常常是轮子)。在每个嘀嗒时刻,一个机器人可以执行动作。

代理通过覆盖  方法决定它们的行为。在  中,机器人可以读取传感器读数并设置它的平移和旋转速度。 执行的时间很短,所以不能发出前进一米这样的命令。为了解决这个限制,通常必须跟踪机器人的状态。还可以使用计时器变量记录保持当前状态的时钟嘀嗒数。

Simbad API

在本文的练习中,主要使用两个 Simbad API 包:

:这个包中的类代表机器人和它所在的环境。包括:

o  就是机器人。

o :机器人可以绕过或从下面通过的拱形结构。

o :可以作为机器人的环境中的障碍物。

o :可以从机器人的视角查看机器人的环境。

o :代表环境,可以在其中添加机器人和墙或盒子等对象。

o :可以添加到机器人上的灯。

o :感应光线的强度。

o :包含机器人周围的一组距离传感器。

o :用这个类在机器人上添加传感器。

o :另一种障碍物。

:这个包中的类显示机器人环境并提供控制手段。这些类包括:

o :显示机器人环境、传感器输入和控制的框架。

Simbad 中实现包容体系结构

Roomba

Roomba 用真空吸尘器打扫地毯。它是由 iRobot 开发的,这是一家由三位 MIT 校友(Rodney BrooksColin Angle Helen Greiner)创办的公司。Roomba 是使用包容体系结构构建的并提供一个开放接口,可以通过这个接口实现各种有趣的效果。Tod E. Kurt 的书  介绍了许多有意思的应用(参见 )。

要在 Simbad 中实现包容体系结构,首先定义  的一个子类 包含一个  数组和一个 矩阵,这个矩阵决定   优先级:

private Behavior[] behaviors;

private boolean suppresses[][];

 作为  的调度器。清单 1 中的代码循环处理各个行为(使用  类变量记录接下来应该执行哪个行为)并在它们之间做出仲裁:

清单 1. 循环处理行为并进行仲裁的代码

protected void performBehavior() {

boolean isActive[] = new boolean[behaviors.length];

for (int i = 0; i < isActive.length; i++) {

isActive[i] = behaviors[i].isActive();

}

boolean ranABehavior = false;

while (!ranABehavior) {

boolean runCurrentBehavior = isActive[currentBehaviorIndex];

if (runCurrentBehavior) {

for (int i = 0; i < suppresses.length; i++) {

if (isActive[i] && suppresses[i][currentBehaviorIndex]) {

runCurrentBehavior = false;

break;

}

}

}

if (runCurrentBehavior) {

if (currentBehaviorIndex < behaviors.length) {

Velocities newVelocities = behaviors[currentBehaviorIndex].act();

this.setTranslationalVelocity(newVelocities

.getTranslationalVelocity());

this

.setRotationalVelocity(newVelocities

.getRotationalVelocity());

}

ranABehavior = true;

}

if (behaviors.length > 0) {

currentBehaviorIndex = (currentBehaviorIndex + 1)

% behaviors.length;

}

}

}

注意, 覆盖了 

 有两个  方法:

 返回一个布尔值,表示根据当前的传感器状态,是否应该激活这个行为(所有  共享一组传感器)。

 返回两个速度(依次为平移速度和旋转速度),表示希望发动机执行的动作。

示例:追逐光线的漫游机器人

现在创建一个机器人示例,它有以下四个 (按照优先级的降序列出),见清单 2 到清单 5 本文使用的源代码)

:在发生冲突之后或为了避免冲突,改变方向。

:向光线移动。

:随机地改变方向。

:沿直线移动。

清单 2.  类(基于 Simbad SingleAvoiderDemo.java 演示代码)

public boolean isActive() {

return getSensors().getBumpers().oneHasHit()

|| getSensors().getSonars().oneHasHit();

}

public Velocities act() {

double translationalVelocity = 0.8;

double rotationalVelocity = 0;

RangeSensorBelt sonars = getSensors().getSonars();

double rotationalVelocityFactor = Math.PI / 32;

if (getSensors().getBumpers().oneHasHit()) {

// if ran into something

translationalVelocity = -0.1;

rotationalVelocity = Math.PI / 8

- (rotationalVelocityFactor * Math.random());

} else if (sonars.oneHasHit()) {

// reads the three front quadrants

double left = sonars.getFrontLeftQuadrantMeasurement();

double right = sonars.getFrontRightQuadrantMeasurement();

double front = sonars.getFrontQuadrantMeasurement();

// if obstacle near

if ((front < 0.7) || (left < 0.7) || (right < 0.7)) {

double maxRotationalVelocity = Math.PI / 4;

if (left < right)

rotationalVelocity = -maxRotationalVelocity

- (rotationalVelocityFactor * Math.random());

else

rotationalVelocity = maxRotationalVelocity

- (rotationalVelocityFactor * Math.random());

translationalVelocity = 0;

} else {

rotationalVelocity = 0;

translationalVelocity = 0.6;

}

}

return new Velocities(translationalVelocity, rotationalVelocity);

}

清单 3.  类(基于 Simbad LightSearchDemo.java 演示代码)

public boolean isActive() {

float llum = getSensors().getLightSensorLeft().getAverageLuminance();

float rlum = getSensors().getLightSensorRight().getAverageLuminance();

double luminance = (llum + rlum) / 2.0;

// Seek light if it's near.

return luminance > LUMINANCE_SEEKING_MIN;

}

public Velocities act() {

// turn towards light

float llum = getSensors().getLightSensorLeft().getAverageLuminance();

float rlum = getSensors().getLightSensorRight().getAverageLuminance();

double translationalVelocity = 0.5 / (llum + rlum);

double rotationalVelocity = (llum - rlum) * Math.PI / 4;

return new Velocities(translationalVelocity, rotationalVelocity);

}

清单 4.  

public boolean isActive() {

return random.nextDouble() < WANDERING_PROBABILITY;

}

public Velocities act() {

return new Velocities(0.8, random.nextDouble() * 2 * Math.PI);

}

清单 5.  

public boolean isActive() {

return true;

}

public Velocities act() {

return new Velocities(0.8, 0.0);

}

清单 6 指定行为的优先次序:

清单 6. 指定行为的优先次序

private void initBehaviorBasedAgent(BehaviorBasedAgent behaviorBasedAgent) {

Sensors sensors = behaviorBasedAgent.getSensors();

Behavior[] behaviors = { new Avoidance(sensors),

new LightSeeking(sensors), new Wandering(sensors),

new StraightLine(sensors), };

boolean subsumes[][] = { { false, true, true, true },

{ false, false, true, true }, { false, false, false, true },

{ false, false, false, false } };

behaviorBasedAgent.initBehaviors(behaviors, subsumes);

}

注意,尽管这个示例中的  具有总体优先次序,但是不一定要这样做。

作为练习,您可以试试以下改进:

添加一个社会化行为:向朋友移动,而远离敌人。

添加一个避光行为。

在一些机器人上添加灯,让它们能够相互吸引。

迷宫

有了!我们可以用 Tremaux 的算法走出这个迷宫!” — Lisa Simpson

在现有的几种解迷宫算法中,有两种常用算法使用固定数量的内存,而与迷宫的大小无关。这两种算法是   (由英国埃克塞特的 Jon Pledge 12 岁时发明)。Lisa Simpson 选择的算法)是一种出色的算法,但是为了简单,本文主要关注沿墙走 Pledge 算法。

迷宫生成算法

不但有许多解迷宫算法,还有许多用来生成迷宫的算法。本文所考虑的迷宫被称为。在完美迷宫中,从任何一点到另一点都有且只有一条路径(这条规则排除了那些包含环路、孤岛或封闭部分的迷宫)。大多数完美迷宫生成算法首先生成一个只有外墙的迷宫,然后让墙向内一段段地生长。为了确保迷宫是完美的,每当添加一段新墙时,需要确保不会造成环路或封闭部分。

沿墙走

沿墙走是一种简单的解迷宫算法,您可能在小时候就学过了。在用这种算法解迷宫时,只需要把左手一直放在左边的墙上(或者把右手一直放在右边的墙上),然后沿着墙走,直到走出迷宫。很容易看出,如果迷宫的边界上有一个入口和一个出口,那么这种算法总是有效的。但是,如果目标在一个(即迷宫中不与其他部分相连的部分)中,那么这种算法无法找到解决方案,因为它无法到孤岛上。

Pledge 算法

Pledge 算法比沿墙走算法复杂,能够解决更多的迷宫类型,因为它可以在孤岛之间跳跃。Pledge 算法的基本思想是,先选择一个绝对方向(比如东、南、西或北),然后总是尽可能朝这个方向走。我把这个方向称为。当遇到墙时,向右转身并采用左手沿墙走方式,直到面向偏好方向 转身数的总和为零(顺时针转身是负值,逆时针转身是正值)为止。此时,继续沿偏好方向向前走。转身数的总和必须是零,这是为了避免陷入某些环路,比如大写字母  形状的环路(在纸上试着走一下这个形状,就会明白我的意思)。

Algernon:一个解迷宫机器人

现在该让您的朋友大吃一惊了,我们来构建一个解迷宫机器人,它的名字是 Algernon

设计机器人

为了实现沿墙走或 Pledge 算法,需要知道机器人什么时候遇到交叉路口,以及在遇到交叉路口时应该走哪个方向。

有多种实现这一目标的方法,但是我们采用的方法是在机器人的左边安装一个声纳传感器。当左边出现通道时,这个传感器会发出警报。为了了解机器人正在走的通道什么时候到头了(即,机器人遇到了墙),在机器人的前面添加一个接触传感器。

实现沿墙走算法

我们使用  包编写 Algernon 源代码)。Algernon 是一个非常简单的机器人,可以按照简单的过程式方式编写它。但是,即使对于这么简单的机器人,使用包容编程也会使代码更加清晰,更模块化,更容易理解。

为了简化算法实现,假设墙的转弯都是直角的。所以,机器人的所有转身都是 90 度左转或 90 度右转。

如果分析一下(左手)沿墙走算法,就会发现它可以分解成四个行为:

向前直走。

当遇到墙时,向右转。

如果看到左边有通道,就向左转。

当到达目标时,停止。

现在需要决定这四个行为的优先次序。前面的列表正好是按照优先级的降序排列的。我们将编写四个类,它们都扩展 

清单 7   的代码。 是一个设置为  的常量:

清单 7. 向前直走的行为代码

public boolean isActive() {

return true;

}

public Velocities act() {

double rotationalVelocity = 0.0;

return new Velocities(TRANSLATIONAL_VELOCITY, rotationalVelocity);

}

清单 8   的代码。 返回的值是以指定的旋转速度旋转 90 度所需的时钟嘀嗒数。

清单 8. 向右转的行为代码

public boolean isActive() {

if (turningRightCount > 0) {

return true;

}

RangeSensorBelt bumpers = getSensors().getBumpers();

// Check the front bumper.

if (bumpers.hasHit(0)) {

backingUpCount = 10;

turningRightCount = getRotationCount();

return true;

} else {

return false;

}

}

public Velocities act() {

if (backingUpCount > 0) {

// We back up a bit (we just ran into a wall) before turning right.

backingUpCount--;

return new Velocities(-TRANSLATIONAL_VELOCITY, 0.0);

} else {

turningRightCount--;

return new Velocities(0.0, -Math.PI / 2);

}

}

Algernon 向左转时,它先向前走一点儿,让它的背部超过左边通道的墙。,再向左转。最后,它需要再前进一点儿,让墙再次出现在它左边。清单 9   的代码:

清单 9. 向左转的行为代码

public boolean isActive() {

if (postGoingForwardCount > 0) {

return true;

}

RangeSensorBelt sonars = getSensors().getSonars();

// Check the sonar on the left.

if (sonars.getMeasurement(1) > 1.0) {

// There is a passageway to the left.

preGoingForwardCount = 20;

postGoingForwardCount = 40;

turnLeftCount = getRotationCount();

return true;

} else {

return false;

}

}

public Velocities act() {

if (preGoingForwardCount > 0) {

preGoingForwardCount--;

return new Velocities(TRANSLATIONAL_VELOCITY, 0.0);

} else if (turnLeftCount > 0) {

turnLeftCount--;

return new Velocities(0.0, Math.PI / 2);

} else {

postGoingForwardCount--;

return new Velocities(TRANSLATIONAL_VELOCITY, 0.0);

}

}

清单 10   的代码:

清单 10. 到达目标的行为代码

public boolean isActive() {

RangeSensorBelt sonars = getSensors().getSonars();

// Is there open space all around us? That is, are we out of the maze?

double clearDistance = 1.2;

return sonars.getMeasurement(0) > clearDistance

&& sonars.getMeasurement(1) > clearDistance

&& sonars.getMeasurement(3) > clearDistance

&& sonars.getMeasurement(2) > clearDistance;

}

public Velocities act() {

// Stop

return new Velocities(0.0, 0.0);

}

清单 11 Algernon 的主行为代码:

清单 11. Algernon 的行为控制代码

private void initBehaviorBasedAgent(

algernon.subsumption.BehaviorBasedAgent behaviorBasedAgent) {

algernon.subsumption.Sensors sensors = behaviorBasedAgent.getSensors();

algernon.subsumption.Behavior[] behaviors = { new ReachGoal(sensors),

new TurnLeft(sensors), new TurnRight(sensors),

new GoStraightAlways(sensors) };

boolean subsumes[][] = { { false, true, true, true },

{ false, false, true, true }, { false, false, false, true },

{ false, false, false, false } };

behaviorBasedAgent.initBehaviors(behaviors, subsumes);

}

1 显示 Algernon 正在走一个迷宫:

1. Algernon 正在走迷宫

注意,尽管这个机器人根本不了解关于迷宫(甚至墙)的任何信息,但是它能够走出迷宫。Algernon 没有计算迷宫出路的中央大脑。这就是包容体系结构能够取得的效果:用一组简单的层次化的行为构成复杂的看似有目的的行为。

结束语

在本文中,我们开发了虚拟机器人。开发真实的物理机器人要困难得。物理世界有各种各样的麻烦。例如,很容易让我们的沿墙走机器人平行于左边的墙移动。但是,在真实世界中墙的表面不是绝对平的,如何避免机器人靠向墙壁或离墙太远本身就是个难题。即使您喜欢编程,也不一定会喜欢开发真实的机器人。开发有趣的机器人不但需要编程技能,还需要机械技能。

如果您有兴趣构建自己的机器人,那么可以考虑使用 LEGO Mindstorms。另一种成本比较低的方法是构建 Biological Electronic Aesthetics MechanicsBEAM)机器人。BEAM 进一步发展了基于行为的机器人思想,并完全避免了编程。通过硬连线构成总体行为,模拟反射响应行为。只需 $30 或更少的成本,再参考一些书,比如 Gareth Branwyn  (参见 ),就可以用 BEAM 工具箱构建出您的第一个机器人。还可以购买并使用 Roomba

在编写机器人并查看其他人的机器人代码之后,我很快就发现了一个令人吃惊的情况:只需很少的代码,就能够让机器人做许多事情(但是,为了让这些代码精确地实现目标,可能需要做许多分析和实验)。使用 LEGO Mindstorms 工具箱,只需半天时间,就能够构建一个简单的机器人。

对机器人学感兴趣的爱好者可以通过许多渠道提高技能,比如机器人图书、机器人竞赛、机器人视频资料和当地的机器人俱乐部。

本文来源:https://www.2haoxitong.net/k/doc/8a6861bd27d3240c8447eff3.html

《智能玩具机器人的工作原理.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式