描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302525714
?注重读者在不同学习阶段的理解力差别,组织材料注重先易后难。对NAO机器人编程中的关键概念先介绍基础知识,再完成实际程序设计部分。
?书中给出大量的代码实例,并针对实例进行深入的解析,便于理解。
全书由浅入深地讲解知识点,有助于读者快速掌握机器人的基础知识、API调用方法及编程模式。书中内容既包括Choregraphe环境下的程序设计,也包括NAOqi框架下的API编程,对NAO机器人有不同了解程度的读者都可从中获益。
本书可以作为NAO用户的操作参考书和编程参考书,也可以作为高等学校计算机及相关专业的“NAO机器人程序设计”课程的教材。
目录Contents第1章NAO机器人概述1
1.1NAO机器人简介1
1.1.1NAO机器人系统1
1.1.2NAO关节运动模型4
1.1.3NAOqi框架6
1.2操作NAO机器人7
1.2.1无线网络连接设置7
1.2.2远程登录NAO8
第2章Python编程基础13
2.1Python语法13
2.1.1Python运行方式13
2.1.2Python程序书写格式15
2.1.3变量、数据类型、表达式15
2.1.4条件语句17
2.1.5while循环语句19
2.1.6列表21
2.1.7for循环语句23
2.1.8元组与字典24
2.2Python函数26
2.2.1函数定义26
2.2.2函数参数27
2.2.3Python模块29
2.3Python对象与类31
2.3.1类的定义与使用32
2.3.2类的继承33
2.4文件和异常34◆NAO机器人程序设计目录2.4.1文本文件读写34
2.4.2二进制文件读写36
2.4.3异常38
第3章NAO编程基础39
3.1使用NAOqi39
3.1.1NAOqi进程39
3.1.2使用模块40
3.1.3阻塞和非阻塞调用41
3.1.4内存42
3.2Choregraphe编程基础44
3.2.1Choregraphe应用程序界面44
3.2.2指令盒分类44
3.2.3Python语言指令盒46
3.2.4Say指令盒49
3.2.5指令盒参数51
3.2.6指令盒输入与输出53
3.2.7NAO机器人状态59
第4章运动控制61
4.1关节61
4.1.1头部关节62
4.1.2臂部关节62
4.1.3髋关节63
4.1.4腿部关节63
4.1.5电机64
4.2ALRobotPosture65
4.3Motion67
4.3.1刚度控制方法67
4.3.2关节控制方法71
4.3.3运动控制方法78
4.4时间轴指令盒87
4.4.1时间轴87
4.4.2帧87
4.4.3时间轴编辑器91
4.4.4Animation模式92
4.4.5行为层93
第5章音频处理97
5.1音频数据97
5.1.1存储音频97
5.1.2ALAudioRecorder98
5.1.3ALAudioPlayer99
5.1.4音频特征101
5.2ALAudioDevice102
5.2.1输出103
5.2.2自定义模块106
5.2.3输入109
5.2.4ALAudioDevice方法113
5.3声音检测与定位114
5.3.1ALSoundDetection114
5.3.2ALSoundLocalization116
5.4语音识别118
5.4.1语音识别系统组成118
5.4.2ALSpeechRecognition119
5.5语音合成与对话122
5.5.1语音合成系统组成122
5.5.2ALTextToSpeech123
5.5.3对话指令盒126
5.5.4ALDialog131
5.5.5综合实例131
第6章视觉处理136
6.1视频设备136
6.1.1设备参数136
6.1.2ALPhotoCapture141
6.1.3ALVideoRecorder142
6.2ALVideoDevice143
6.2.1ALVideoDevice功能143
6.2.2订阅图像144
6.3视频检测147
6.3.1Extractor147
6.3.2ALRedBallDetection149
6.3.3ALLandMarkDetection153
6.3.4ALBarcodeReader156
6.3.5ALFaceDetection158
6.4视频识别165
6.4.1识别过程165
6.4.2使用Vision Reco.指令盒进行视觉识别166
6.4.3ALVisionRecognition167
第7章传感器169
7.1ALSensor169
7.2ALBattery170
7.3DCM171
7.4ALSonar175
7.5ALLeds178
7.6ALTouch180
第8章使用C 编写程序183
8.1使用qiBuild编译远程模块183
8.2扩展NAO API186
附录A开发环境安装与配置193
附录BNAO机器人系统恢复与更新196
附录CNAOqi系统虚拟机199
附录DPython关键字和内置函数202
附录E传感器与执行器键表204
附录FNAO安装的Python库212
参考文献230
前言Foreword仿人机器人是综合运用机械、传感器、驱动器、计算机等技术设计的一种能模仿人的形态和行为的机械电子设备,是在电子、机械及信息技术的基础上发展而来的。仿人机器人仿人的四肢和头部,能够自主完成人类所赋予的任务与命令。
NAO机器人是一款高端仿人机器人,拥有讨人喜欢的外形,具有一定水平的人工智能,能够与人亲切互动。NAO是在世界范围学术领域内应用最广泛的仿人机器人,是机器人世界杯RoboCup组委会指定的比赛机器人。Aldebaran Robotics公司将NAO的技术开放给所有的高等教育项目,并于2010年成立基金会,支持在机器人及其应用领域的教学项目。NAO可以通过现成的指令块进行可视化编程,允许用户探索各种领域、运用各种复杂程度的程序达到用户想要体验的各种不同效果。
NAO可在Linux、Windows或Mac OS等操作系统下编程,拥有开放的编程构架,可以使用C 或Python语言控制NAO。不论使用者的专业水平如何,都能够通过图像编程平台为NAO机器人编程。本书介绍编写程序操作NAO的主要方法,全书分为8章,内容如下。
第1章NAO机器人概述。介绍NAO机器人系统、关节运动模型、机器人操作系统NAOqi框架、NAO的基本操作、网络连接设置和远程登录NAO。
第2章Python编程基础。介绍Python的基本语法、函数、对象与类、文件和异常。
第3章NAO编程基础。介绍NAOqi进程、模块、阻塞和非阻塞调用、内存等基本概念、工作机制及应用,使用Choregraphe进行NAO编程的基础知识,包括使用指令盒编程、指令盒的输入输出及参数设置。
第4章运动控制。包括对NAO的头部关节、臂部关节、髋关节、腿部关节的介绍,还介绍刚度控制、关节控制和运动控制的基本方法,使用时间轴进行运动编辑的基本概念和方法。◆NAO机器人程序设计第5章音频处理。介绍音频处理的基本概念、操作音频的ALAudioRecorder、ALAudioDevice等基础类模块,声音检测与定位,语音识别,语音合成与对话等内容。
第6章视觉处理。介绍视频设备编程中使用的设备参数,图像捕获、视频录制及视觉设备等基础类的使用方法,视频检测中的红球检测、地标检测、条形码检测和人脸检测等检测类的使用,视觉识别方法等内容。
第7章传感器。介绍使用电池、声呐、LED和接触传感器的编程方法,设备通信模块的工作机制与应用。
第8章使用C 编写程序。介绍使用qiBuild编译远程模块,使用C 编程扩展NAO API。
附录内容包括开发环境安装与配置、NAO机器人系统恢复与更新、NAOqi系统虚拟机、Python关键字和内置函数、传感器与执行器键表、NAO安装的Python库。
具有Python基础的读者可以跳过第2章,只对机器人舞蹈感兴趣的读者可以直接查看相应章节。只需要在计算机上安装Python编辑器(本书使用PyCharm)、NAO的Python库和Choregraphe就可以运行书中的所有Python代码。只有第3章示例代码给出了运行环境说明,调试其他章节示例程序时需要注意运行环境。希望每位读者都可以完成本书的学习并运行示例代码。
本书各章内容相对独立,但是在内容安排上按照先易后难原则编写。前面章节解释的语句后面再次出现时不做解释,读者在学习时尽量按照章节顺序阅读和调试程序。书后附录所列内容,如参数、软件安装步骤、刷机方法等,供需要时查阅。
本书面向初学者,内容并未包含Choregraphe提供的全部指令盒,对Python语言的介绍比较简单,仅选择了编写Choregraphe程序时必须掌握的Python基础知识,书中使用的示例程序也不涉及复杂算法。读者在掌握NAO系统的基础概念、开发设计思路后,可以参考NAO随机英文文档,查阅NAOqi提供的更多API。本书前7章示例用Python语言编写,读者如果使用C 语言,可以先学习第8章,再查阅、调试NAO随机英文文档中C 语言的示例代码。由于篇幅有限,部分内容未能在书中阐述,读者可在清华大学出版社网站(http://www.tup.tsinghua.edu.cn)下载相关电子文档及代码。
本书主要参考资料包括NAO随机文档,以及李睿强、孙漫漫、孟宪龙等的硕士论文,杨云程、孙明辰、纪卓志、董旭、蒲致威等同学对本书内容的形成启发很多,在此一并表示感谢。
本书可以作为NAO使用者的操作参考书和编程参考书,也可以作为高等学校计算机及相关专业的“NAO机器人程序设计”课程教材。
由于作者水平有限,书中难免存在欠妥之处,敬请读者批评指正。
编者
2018年9月
第5章chapter5
第5章音 频 处 理1.1微型计算机简介在学习NAO的音频功能之前,首先了解语音信号的一些相关知识。
人具有“说”和“听”的生理机能,或者说具有语音生成和语音/音频识别功能。人的发声器官由喉、声道和嘴三部分组成,发声器官产生不同强度的气流,控制喉中的声带产生不同频率和不同幅度的振动,产生声波。正常人的发音频率范围为100~5000Hz。人的听觉器官为耳。声音(空气振动)传入耳道引起鼓膜振动,经内耳把机械运动变换为神经信号,最后传给大脑,识别出不同的语音含义。
NAO也能够实现“说”和“听”,与人进行交流、会话,本章介绍NAO的语音识别和语音生成功能。
5.1音 频 数 据
语音识别和语音生成都涉及对音频数据的表示、存储、建模和处理。
5.1.1存储音频
利用话筒获取到的声音信号是模拟、连续的电信号,将这些电信号存储到计算机中需要经过采样、量化和编码三个处理过程。
1. 采样
由于不能记录一段时间音频信号的所有值,只能使用采集样本方法记录其中的一部分。采样是对模拟信号周期性地记录信号大小的方法。图5.1显示了从模拟信号上选择10个样本代表实际的声音信号。
图5.1一个音频信号的采样(1秒钟内采集10个样本值)
声音信号变化越快,单位时间中采集的样本数就需要越多,才能还原出原始信号。在图5.1中,最后一段声音信号变化较快,按照图示的采样频率,已经不能有效恢复原始信号了。依据采样定理(在一个信号周期中,至少需要采两次样,才能有效恢复原始信号),采样频率至少是声音最高频率的2倍。采样频率越高,还原的信号越接近于原始信号。
NAO提供的采样频率包括8000、11025、12000、16000、22000和24000Hz(赫兹)。◆NAO机器人程序设计第◆5章音频处理根据处理音频信号的频率范围,可以做相应选择。例如,在做语音识别时,由于语音信号最高频率只有几千Hz,采样频率可以选择8000Hz,8000Hz也是电话系统使用的语音采样频率。
2. 量化
从每次采样得到的测量值是真实的数字。量化指的是将样本的值截取为最接近的整数值的过程。例如,实际值为17.2,截取为17;实际值为17.8,截取为18。
3. 编码
量化后的数值需要被编码成位模式。NAO使用16位二进制数表示这些数字。编码二进制位数越多,数值表示就越精确。
4. 存储
NAO在存储录制的声音时,可以保存为以下两种不同的格式。
WAV格式: 采样的音频数据不做其他处理,仅仅加上一个文件头,说明采样频率、通道个数、编码长度等信息,存储的文件就是常见的WAVE文件。WAV格式文件由于没有压缩数据,占用的存储空间特别大。
OGG格式: 采用Vorbis压缩算法对采样音频数据进行压缩处理而得到的文件,文件扩展名为.ogg。所用算法是开源的、无专利费用。
采样频率为8000Hz,每秒产生的音频数据为8000×16b=128 000b=16 000B。
采样频率为22kHz,每秒产生的音频数据约为44KB,1min将产生大约2.5MB的音频数据,在双声道采样时将达到每分钟产生5MB音频数据。
5.1.2ALAudioRecorder
NAO头部安装了4个话筒,频率范围是150Hz~12kHz,每个话筒对应一个声道,具有独立的采样和编码电路。
NAOqi提供的录音模块为ALAudioRecorder,保存文件格式为WAV或OGG。
NAO可以录制如下声音:
(1) 四声道,48000Hz,OGG;
(2) 四声道,48000Hz,WAV;
(3) 单声道(frontright, frontleft, rearleft ,rearright), 16000Hz,OGG;
(4) 单声道(frontright, frontleft, rearleft ,rearright), 16000Hz,WAV。
使用ALAudioRecorder()方法如下。
(1) startMicrophonesRecording()开始录音,非阻塞调用方法。
格式: startMicrophonesRecording(filename,type,samplerate,channels)其中,filename为保存录音文件的绝对路径;type为录音的格式,也可以为WAV或OGG格式;samplerate为采样频率;channels为请求所用通道,元组类型。
(2) stopMicrophonesRecording()停止录音。
代码清单51录音class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self)
self.record = ALProxy(“ALAudioRecorder”)
def onLoad(self):
pass
def onUnload(self):
pass
def onInput_onStart(self):
record_path = ‘/home/nao/record.wav’
self.record.startMicrophonesRecording(record_path, ‘wav’, 16000, (0,0,1,0))
time.sleep(10)
self.record.stopMicrophonesRecording()
self.onStopped()
pass
def onInput_onStop(self):
self.onUnload()
self.onStopped()程序运行后,将使用C话筒录制10s的声音,采样频率16000Hz,以WAV格式存储在机器人/home/nao目录下。
5.1.3ALAudioPlayer
ALAudioPlayer可以播放多种格式的音频文件,提供常见的播放功能(播放、停止、暂停、循环等)。ALAudioPlayer生成的音频流最终驱动机器人的两个扬声器发声。ALAudioPlayer提供了三十余种方法,控制音频文件的播放,表5.1列出了部分方法。表5.1ALAudioPlayer部分方法
方法名说明playFile(filename)播放指定文件。filename为文件名。相当于执行loadFile和playgetCurrentPosition(taskId)返回当前播放位置(秒)。taskId为非阻塞调用播放方法返回值goTo(taskId, position)跳到播放文件的指定位置。position为跳转位置,实数getFileLength(taskId)获取当前播放文件的长度(秒)loadFile(fileName)预先装入声音文件,但不播放。返回值为播放文件的taskId值play(taskId)播放play(taskId, volume, pan)volume为音量,取值范围为[0.0~1.0];pan为立体声参数
(-1.0: 左声道/1.0: 右声道)playFileFromPosition(fileName,
position, volume, pan)指定位置播放◆NAO机器人程序设计第◆5章音频处理播放音频文件前,利用winscp工具,将待播放文件上传到机器人/home/nao目录下。ALAudioRecorder只能在实体机器人上运行,ALAudioPlayer可以在虚拟机器人上运行。但在虚拟机器人上ALAudioPlayer只能播放WAV和OGG格式的文件。
代码清单52播放并获取当前位置(播放上例录制文件)class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self)
self.aup=ALProxy(“ALAudioPlayer”)
def onLoad(self):
pass
def onUnload(self):
pass
def onInput_onStart(self):
fileId = self.aup.post.playFile(“/home/nao/record.wav”)
time.sleep(5)
currentPos = self.aup.getCurrentPosition(fileId)
self.logger.info(str(currentPos))
pass
def onInput_onStop(self):
self.onUnload()
self.onStopped()声音播放是阻塞调用,可以使用模块代理的post对象,在并行线程中创建任务。每个post调用产生一个任务ID值。本例中playFile在播放文件时产生一个ID值,在执行getCurrentPosition()方法时作为参数,读取当前文件播放位置。
打开日志查看器窗口,程序运行后可以看到输出结果为6.0。
5.1.4音频特征〖*2〗1. 语音时域特征声音信号是随时间变化的连续信号。如图5.2所示是NAO说“开始”时的声音信号。
图5.2NAO说“开始”的音频信号
一般情况下,发音时每个字对应一段声波,各个字之间会有一定的间隔时间,甚至一个字之间也有一定的间隔时间。图中信号前半部分振幅较大的部分为“开”的声音信号,后半部分为“始”的声音信号。
2. 傅里叶分析
法国数学家傅里叶证明: 任何正常的周期为T的函数g(t),都可以由(无限个)正弦和余弦函数合成: g(t)=a0 ∑∞n=1(ancos(2πnft) bnsin(2πnft)) 此处f=1/T是基频,an和bn是正弦和余弦函数的n次谐波的振幅,这种分解叫傅里叶级数。通过傅里叶级数可以重新合成原始函数。各项系数可以通过变换公式求出。
图5.3二进制信号1110001的前4次谐波合成结果
图5.3所示为二进制信号1110001的前4次谐波合成结果。从图中可以看出,使用的谐波数量越多,叠加的结果就越接近于原始函数。图中的前三列横坐标为时间,最后一列横坐标为频率,纵坐标为幅度的平方根(an与bn平方和开平方)。使用基频的n次谐波的振幅数据(频域),可以重塑原始信号(时域)。
利用傅里叶变换,可以将随时间变化的语音信号(时域信号),转换为一组与频率相关的振幅(频域信号),进而研究信号的频谱结构和变化规律。
3. 离散傅里叶变换
离散傅里叶变换(DFT)是信号在时域和频域上都是离散的。DFT运算结果也是将时域信号的采样值变换为频域不同谐波的振幅。
在实际应用中通常采用快速傅里叶变换(FFT)计算DFT。FFT采用了一些近似计算方法,误差很小,运算效率高于DFT,在C和Python语言中都有实现函数。通常情况下,FFT变换两端(时域和频域上)的序列是有限长的(如取512或1024个数值)。也就是说,一个包含512个元素的数组,输入FFT算法,输出也是512个元素的数组。输入数据表示的是随时间变化的信号采样值,输出数据表示的是这段音频信号在各个频率上的幅度。
4. 音频特征
在任意一个自动语音识别系统中,第一步就是提取特征。换句话说,需要把音频信号中具有辨识性的成分提取出来,然后把其他的信息扔掉,如背景等。
在语音识别技术中,最常用到的语音特征就是梅尔倒谱系数(MFCC)。计算梅尔倒谱系数需要经过预加重,分帧、加窗、快速傅里叶变换,Mel滤波器组,计算每个滤波器组输出的对数能量,离散余弦变换等过程。
Python语音处理库Librosa提供了MFCC的计算方法。
5.2ALAudioDevice
ALAudioDevice管理音频的输入和输出,提供了一组操作音频输入设备(话筒)和输出设备(扬声器)的API。其他的音频模块(ALAudioPlayer除外)做输入和输出时都使用ALAudioDevice提供的API,如图5.4所示。
图5.4麦克风位置
ALAudioDevice基于标准的Linux ALSA(Linux声音库)库,通过声音驱动程序,与话筒和扬声器通信。
ALAudioDevice与其他模块关系如图5.5所示。
图5.5ALAudioDevice与其他模块关系
5.2.1输出〖*2〗1. 数据格式ALAudioDevice可以通过以下帧速率之一将数据发送到扬声器:
两声道,交错数据(声道1数据,声道2数据,声道1数据,……),16000Hz(设置亚洲语言时默认);
两声道,交错数据,22050Hz(默认设置非亚洲语言时);
两声道,交错数据,44100Hz;
两声道,交错数据,48000Hz。
2. 输出方法
(1) sendLocalBufferToOutput(nbOfFrames, buffer),本地模块(运行在机器人上的模块)将存放在数据缓冲区声音数据发送到扬声器。数据格式为16b两声道交错数据,缓冲区长度不能超过16 384B。
其中,nbOfFrames为缓冲区中包括的两声道声音数据帧数。在两声道交错数据格式中,1帧长度为两声道的数据长度,4B;buffer 为发送缓冲区内存地址。
发送成功方法返回值为真,否则返回值为假。
(2) sendRemoteBufferToOutput(nbOfFrames, buffer),远程模块 (运行在机器人之外的模块)将存放在数据缓冲区声音数据发送到扬声器。
任何NAOqi模块都可以在需要时调用适当的方法向NAO的扬声器发送数据。如果数据格式正确,不必做其他配置就可以调用。
3. 输出相关方法
(1) setParameter(parameter,value),设置输出采样率。其中,parameter只能设置为”outputSampleRate”,value为设置的输出采样率,可以设置为16000, 22050, 44100 或 48000。
(2) setOutputVolume(volume),设置系统的输出音量。其中,volume取值为[0,100]。
系统音量可以由getOutputVolume()方法获取。
代码清单53播放WAV文件(文件内容发送给扬声器)import math
import os.path
class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self)
self.aad=ALProxy(“ALAudioDevice”)
def onLoad(self):
pass
def onUnload(self):
pass
def onInput_onStart(self):
self.aad.setParameter(“outputSampleRate”,16000)
songPath = “/home/nao/hongdou.wav”#播放前上传文件,hongdou.wav为两声
#道,采样频率为16 000Hz
size=os.path.getsize(songPath)
nframe=(size-44)/4#帧数,WAV头部为44B,每帧4B(两声道,每声道16b)
with open(songPath,”rb”) as file:
file.seek(0)
data=file.read()
data=data[44:] #头部以后为数据部分
self.aad.sendLocalBufferToOutput(int(nframe), id(data))#使用id()函数取数据在内存地址
pass
def onInput_onStop(self):
self.onUnload()
self.onStopped()代码清单54生成正弦波并播放import numpy as np
class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self)
self.aad=ALProxy(“ALAudioDevice”)
def onLoad(self):
pass
def onUnload(self):
pass
def onInput_onStart(self):
sineTableSize = 1024
outputBufferSize = 16384
sine=np.arange(sineTableSize,dtype=np.int16)
left_phase = 0
right_phase = 0
nbOfOutputChannels=2
sampleRate = 16000
freq =1000
duration =5.0
fOutputBuffer=np.arange(nbOfOutputChannelsoutputBufferSize,dtype=np.int16)
for i in range(0,sineTableSize):
sine[i]=int(32767 math.sin((float(i)/float(sineTableSize))math.pi2.0))
ratio= 1.0 /(1.0 / float(freq) float(sampleRate)/float(sineTableSize))
inc= int(math.ceil(ratio))
nbOfBuffersToSend =int(math.ceil(durationsampleRate/
outputBufferSize))
for d in range(0,nbOfBuffersToSend):
i=0
while ifOutputBuffer[i] =sine[left_phase]
fOutputBuffer[i 1] =sine[right_phase]
left_phase =left_phase inc
if left_phase >= sineTableSize:
left_phase =left_phase- sineTableSize
right_phase = right_phase inc
if right_phase >= sineTableSize :
right_phase =right_phase- sineTableSize
i=i nbOfOutputChannels
buffer=fOutputBuffer.tostring()
self.aad.sendLocalBufferToOutput(outputBufferSize,id(buffer))
pass
def onInput_onStop(self):
self.onUnload()
self.onStopped()5.2.2自定义模块
NAOqi模块都是库中的类。从autoload.ini加载库时,这些模块类将自动实例化。
除了使用NAOqi模块外,也可以自定义模块。自定义模块类的基础类是ALModule。从ALModule派生的类,可以“绑定”方法。模块名称和方法将被通告给代理,这样其他模块就可以使用自定义模块了。
1. 模块分类
自定义模块可以是远程模块也可以是本地模块。如果是远程的,在机器人外部运行。远程模块可以从外部调试,但在速度和内存使用方面效率较低。如果是本地的,在机器人本地运行。本地模块比远程模块效率高。
每个模块都可以包含多种方法,某些方法可以限定为从模块外部调用。不管模块是远程的还是本地的,调用这些限定方法的方式是一样的。
本地模块是在同一进程中启动的两个(或更多)模块,本地模块由NAOqi作为代理(Broker),统一管理,可以共享变量,可以直接调用彼此的方法。在做一些闭环控制时,必须使用本地模块。
远程模块使用网络进行通信。远程模块需要代理与其他模块通信。Broker负责所有网络通信。
2. 远程模块的连接
远程模块可以通过使用代理方法,将其代理连接到其他模块的代理上,实现与其他模块通信。连接方式如图5.6所示,可以是Broker到Broker,也可以是Proxy到Broker。
图5.6代理与模块关系
3. ALModule方法
ALModule模块类是自定义模块类的基类,负责为其子类通告方法。ALModule方法如表5.2所示。
评论
还没有评论。