智能机器人(21):Hokuyo激光雷达hokuyo_node方式

0. 激光雷达分类

a) – 单点激光测距仪
激光位移传感器,比较常见,价格低,只能测量到目标上特定点的距离,例如建筑装修行业的手持式测距仪。

b) – 2D激光雷达
如果将测距仪安装在一个可以旋转的平台上,然后旋转扫描一周,就变成了2D激光雷达(LiDAR).
时间换空间,牺牲速度,比如Hokuyo的10Hz。
2D激光雷达使用单束的点状的激光,进行扫描,因此只能采集一个面的距离信息。

c) – 3D激光雷达
如果要测量3D的数据,可用2种方式扩充:1)采用一个线状激光器,然后旋转; 2)使用前述的2D激光雷达扫描,同时在另一个轴进行旋转。
第一种方式是改变激光器输出模式,由原先的一个点,变成一条型光。扫描仪通过测量这束线型光在待测目标物体上的反射从而一次性获得一个扫描截面的数据。从而扫描速度很快,精度比较高。缺点是由于一点激光变成了一条线段,强度将随着距离大幅衰减,因此测距范围有限。
第二种方式优点是很容易用2D激光雷达增加旋转平台进行改造,在相同的激光功率下扫描距离更远。但是串行的级联的那个额外自由度的转轴误差可能较大,同时扫描速度低。

d)- 如果需要4D、5D、6D激光雷达
按同样办法增加维度处理…
不过 超过三维的空间很难理解。

1. 两个驱动

1.1 urg_node is new package
ROS provides special driver for all kinds of Hokyuo LRF, such as URG, for example urg driver is referred to as urg_node, we can install the urg_node as follows:
$ sudo apt-get install ros-indigo-urg-*

1.2 hokuyo_node is old driver
But, it is also better possible to use the hokuyo_node and, for this you need to install hokuyo_node as follows:
$ sudo apt-get install ros-indigo-hokuyo-node

2. 老的 hokuyo_node

Here we discuss the old driver hokuyo_node
Here we discussed the older Hokuyo_node, which used for most usb, rather than rj45 ip port.
Urg_node is newer than hokuyo_node,
Hokuyo_node will remain maintained for current users and PR2s.
And urg_node is BSD while hokuyo_node is LGPL.
$ sudo apt-get install ros-indigo-hokuyo-node

Then hokuyo_node Published Topics on /scan.

3. Powered On, and Pluggedin

4. Configure Hokuyo

Make sure hokuyo_node will be able to access the Hokuyo laser scanner. Check it’s file permissions, they should be read/write/execute for all.
$ ls -l /dev/ttyACM0
— crw-rw-XX- 1 root dialout 166, 0 2009-10-27 14:18 /dev/ttyACM0

If XX be rw: the laser is configured properly, if not:
$ sudo chmod a+rw /dev/ttyACM0

5. CLI tools

The Command-Line Tools getID program can be used to get information about a hokuyo laser scanner:
$ rosrun hokuyo_node getID /dev/ttyACM0
— Device at /dev/ttyACM0 has ID H0807228

or, friendly in a script way:
$ rosrun hokuyo_node getID /dev/ttyACM0 —
— H0807228

7. Setup udev rules

The getID program can be used to get the hardware ID of a Hokuyo device given its port.
Combined with udev, this allows a consistent device name to be given to each device, even if the order in which they are plugged in varies.

7.1 50-laser.rules
Create the file /etc/udev/rules.d/50-laser.rules, then edit and add the following to it:
KERNEL==”ttyACM[0-9]*”, MODE=”0777″
or,
$ sudo echo ‘KERNEL==”ttyACM[0-9]*”, MODE=”0777″‘ >> /etc/udev/rules.d/50-laser.rules

So, restart udev to take on the new rules:
$ sudo /etc/init.d/udev restart

or,

7.2 hokuyo.rules
a)
$ lsusb
— Bus 001 Device 006: ID 15d1:0000
ID后面的部分idVendor:idProduct, 确认idVendor和idProduct

b)创建串口别名
新建 /etc/udev/rules.d/hokuyo.rules文件,内容如下:(别名为hokuyo,实际名称为:/dev/hokuyo)
KERNEL==”ttyACM[0-9]*”, ACTION==”add”, ATTRS{idVendor}==”15d1″, MODE=”0666″, GROUP=”dialout”, SYMLINK+=”hokuyo”

c)
增加当前用户对串口的默认访问权限:
$ sudo usermod -a -G dialout 用户名

d)
使UDEV配置生效:(使串口的默认访问权限生效,需要重启机器)
$ sudo service udev reload
$ sudo service udev restart

总之, 上述*.rules文件,位于/etc/udev/rules.d/。

Give an example here, which with urg_node:
On the PR2 we use the following udev rule:
SUBSYSTEMS==”usb”, KERNEL==”ttyACM[0-9]*”, ACTION==”add”, ATTRS{idVendor}==”15d1″, ATTRS{idProduct}==”0000″, MODE=”666″, PROGRAM=”/opt/ros/indigo/lib/urg_node/getID /dev/%k q”, SYMLINK+=”sensors/hokuyo_%c”, GROUP=”dialout”
This udev rule sets up a device name that is based on the Hokuyo’s hardware ID.

Then:
The PR2 then has a symlink to that name that gets changed if the Hokuyo is replaced:
$ ls -l /etc/ros/sensors/base_hokuyo
— lrwxrwxrwx 1 root root 28 2010-01-12 15:53 /etc/ros/sensors/base_hokuyo -> /dev/sensors/hokuyo_H0902620
$ ls -l /dev/sensors/hokuyo_H0902620
— lrwxrwxrwx 1 root root 10 2010-04-12 12:34 /dev/sensors/hokuyo_H0902620 -> ../ttyACM1

10. 动态配置参数

Make sure that we have the correct configurations loaded on the parameter server.

10.1 命令行
If your Hokuyo is not at the default /dev/ttyACM0, you have to indicate where it is below:
$ rosparam set hokuyo_node/port /dev/ttyACM0

Below option will speed up the startup of the driver, but, will lead to less acurate timestamps:
$ rosparam set hokuyo_node/calibrate_time false <— ignore 10.2 CLI Start by getting the dependencies and compiling dynamic_reconfigure. $ rosdep install dynamic_reconfigure $ rosmake dynamic_reconfigure Reconfigure the hokuyo_node parameters , by calling dynamic_reconfigure $ rosrun dynamic_reconfigure dynparam set /

For example, if the node is named hokuyo_node and the parameter is min_ang:
$ rosrun dynamic_reconfigure dynparam set /hokuyo_node min_ang -1.0

10.3 launch file

Here using dynparam from a launch file.
You can use dynparam with the set_from_parameter command to set parameters of an already running node from a launch file.

For example to set the hokuyo scanning range

10.4 源代码

Using dynamic_reconfigure from code:

Python code:
import dynamic_reconfigure.client
rospy.init_node(‘myconfig_py’, anonymous=True)
client = dynamic_reconfigure.client.Client()
params = { ‘my_string_parameter’ : ‘value’, ‘my_int_parameter’ : 5 }
config = client.update_configuration(params)

C++ code:
system(“rosrun dynamic_reconfigure dynparam set_from_parameters camera_synchronizer_node narrow_stereo_trig_mode 3”);

10.5 图形接口 GUI

First getting the dependencies and compiling dynamic_reconfigure.
$ rosdep install dynamic_reconfigure
$ rosmake dynamic_reconfigure

run the reconfigure_gui:
$ rosrun rqt_reconfigure rqt_reconfigure

now editable parameters be available:

11. 测试雷达数据
Running hokuyo_node
$ rosrun hokuyo_node hokuyo_node
— [ INFO] 1256687975.743438000: Connected to device with ID: H0807344

Viewing data
$ rosrun rviz rviz -d `rospack find hokuyo_node`/hokuyo_test.vcg

============================
Now, Hokuyo LiDAR LRF test tutorial:
==========================================

13. 初级方法

不通过在机器人模型urdf里面添加雷达模型joint/link,而是经由一个通过静态tf变换实现

13.1 准备一个加载driver的启动文件 – hokuyo_laser.launch

在turtlebot_navigation包之下建立启动文件 – hokuyo_laser.launch :
$ roscd turtlebot_navigation
$ mkdir -p laser/driver
复制这个启动文件:
$ sudo cp ~/turtlebot_ws/src/hokuyo_node/hokuyo_test.launch laser/driver/hokuyo_laser.launch
修改hokuyo_laser.launch
$ vi turtlebot_navigation hokuyo_laser.launch
* 检查框架frame_id是否指定为laser* 查看port是否指定正确端口,使用别名
检查端口:,设置好别名,或直接端口/dev/ttyACM0
* 增加TF变幻:

修改为args=”0.0 0.0 0.18 0 0.0 0.0 为自己的实际安装位置。详情查看: static_transform_publisher部分:
static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period_in_ms
这里假设底盘的中心点为0,雷达放在机器人托盘中心位置,X为0,高度为18CM,Z为0.18m, TF的单位使用米的,测量单位是CM.

完整代码:

13.2 准备一个建图的配置文件 – hokuyo_gmapping.launch.xml

在turtlebot_navigation包之下建立配置文件 – hokuyo_gmapping.launch.xml:
$ roscd turtlebot_navigation
$ touch launch/includes/gmapping/hokuyo_gmapping.launch.xml
$ vi launch/includes/gmapping/hokuyo_gmapping.launch.xml

13.3 准备建图的启动文件 hokuyo_gmapping_demo.launch,在其中加载hokuyo_laser.launch驱动

在turtlebot_navigation包建立hokuyo_gmapping_demo.launch文件用于启动gmapping :
$ roscd turtlebot_navigation
$ touch launch/hokuyo_gmapping_demo.launch
这个touch命令不常用,一般在使用make时,用来修改时间戳,或新建一个不存在的文件。如果创建一个新文件有很多种方法,vi cat echo, 而touch命令不仅可以创建新文件,而且可以修改文件的时间属性。

$ vi launch/hokuyo_gmapping_demo.launch

NOTE: 设置laser_type为hokuyo_laser.launch.

13.4 测试激光雷达的gmapping建图

启动turtlebot
$ roslaunch turtlebot_bringup minimal.launch
启动gmapping,用于构建地图
$ roslaunch turtlebot_navigation hokuyo_gmapping_demo.launch
启动键盘操作Turtlebot
$ roslaunch turtlebot_teleop keyboard_teleop.launch
启动rviz,实时查看建图情况
$ roslaunch turtlebot_rviz_launchers view_navigation.launch
保存地图
$ rosrun map_server map_saver -f /tmp/hokuyo_gmapping

13.5 利用地图进行AMCL

15. 中级方法

使用机器人模型urdf, 直接在其中添加laser

15.1 add LiDAR to the robot model

Edit ./urdf/turtlebot_library.urdf.xacro and add the following right above the tag
$ vi ./urdf/turtlebot_library.urdf.xacro

Above files will have your kinect’s virutal laser scan publishing on the same topic that other tutorials want to use, but here we would rather the laser data would come from the hokuyo laser.
This can be prevented by changing the topic that the Kinect laser publishes on with the following.

15.3 edit 3dsensor to change kinect date on /scan_topic rather than /scan.

$ vi turtlebot_bringup/launch/3dsensor.launch
edit 3dsensor.launch and look for the line that looks like this:

Remove it and replace it with:

15.5 edit minimal.launch to add laser node

Then edit minimal.launch, for we now need to add the hokuyo node to it.
At the bottom of the file right before , add the following:

This should bring up the hokuyo node when you launch it.

15.7 Test config

Bringup new config:
$ roslaunch turtlebot_bringup minimal.launch
— process[laser_driver-9]: started with pid [8288]

In another terminal, you can bring up the kinect sensor by running:
$ roslaunch turtlebot_bringup 3dsensor.launch

Check your results with rviz.
Now run rviz. You should now have a block that appears on your turtlebot.
Add two LaserScan visuilizations.
Subscribe one of them to the /kinect_scan topic and set it’s Color to “2; 255; 255”
Subscribe the other topic to /scan. Leave it’s Color “255; 255; 255”

17. 高级方法

建立激光雷达的dae模型,然后加入机器人模型。

We should make the changes in Turtlebot URDF, Two things:
one is description about Hokuyo that is in meshes folder,
and the other is the defining join with its location and its relation with parent frame (joint).

17.1 Make the hokuyo.dae file
We copy the hokuyo.dae file from the driver into /opt/ros/indigo/share/turtlebot_description/meshes folder.
It need administrative and write privileges to do it .
These meshes file contain all the properties that are needed to define hokuyo_node.
$ sudo cp …../hokuyo.dae /opt/ros/indigo/share/turtlebot_description/meshes

17.2 Create a new Hokuyo URDF file – hokuyo.urdf.xacro
Create and edit the Hokuyo URDF file – hokuyo.urdf.xacro
First go the folder /opt/ros/indigo/share/turtlebot_description/urdf/sensors ,
then , create and edit a new file named hokuyo.urdf.xacro

$ sudo vi /opt/ros/indigo/share/turtlebot_description/urdf/sensors/hokuyo.urdf.xacro

The code focuses on two things:
the one is description about Hokuyo that is in meshes folder. hokuyo.dae,
the other is the defining join with its location and its relation with parent frame (joint).
link is laser_joint and joint is hokuyo_joint.

For finding the location of hokuyo sensor, you may need to calculate the location that will be in meters and the representation is in x,y,z .

Turtlebot assumes 0 0 0 at it base center, like blow.

Above Collision do provide the opportunity to take a decision before going to collide.
In above code we have defined collisions for more explanation look into Adding Collisions.
It is necessary to set the parameters to avoid collisions.

Above code, first thing we did is specifying the joint name that is Hokuyo (but can be changed to any other thing), the type we select as fixed then we defined where the Hokuyo is attached with respect to origin,
Next thing the joints are defined by parent and child, You can also specify some fixed frame like map or base_footprint or make it link to default parent

17.3 Add LiDAR to robot
Add Hokuyo URDF description – hokuyo.urdf.xacro to Turtlebot URDF description – turtlebot_library.urdf.xacro

In order to be Hokuyo a part of URDF description, we need to add Hokuyo LRF description to the Turtlebot URDF so that it can be visualized in rivz and other simulators.
So, edit /opt/ros/indigo/share/turtlebot_description/urdf/turtlebot_library.urdf.xacro file and add some lines:

$ sudo vi /opt/ros/indigo/share/turtlebot_description/urdf/turtlebot_library.urdf.xacro
add:

Above will call the hokuyo description created in urdf.

17.4 minimal.launch

In order to launch the description , it is just required to launch mininal.launch and it will work.
NOTE:
In order to see a hokuyo sensor in rviz we need to add its definition in gazebo, but not covered here,
but can be borrowed from Building Visual Robot:
http://wiki.ros.org/urdf/Tutorials/Building%20a%20Visual%20Robot%20Model%20with%20URDF%20from%20Scratch

17.5 Create Launch File for Hokuyo Node driver

Its is better to create a Hokuyo Launch file in order to launch the Hokuyo node.
It will also allow Hokuyo node dynamic re-configuration of Hokuyo node.

$ vi hokuyo.launch

正常做法是把这个驱动图的launch文件在minimal.launch中包含, 但是单独运行也没问题。

17.6 Use SLAM With Hokuyo LIDAR Sensor

利用hokuyo laser创建地图.
In order to use Hokuyo for SLAM as described in tutorial Building SLAM, following below:

地图:
a)
$ roslaunch turtlebot_bringup minimal.launch
b) run the Hokuyo node
$ roslaunch turtlebot_test_launch hokuyo.launch

c) Launch the gmapping for map building
$ roslaunch turtlebot_navigation gmapping_demo.launch

d) View the map at workstation using rviz
$ roslaunch turtlebot_rviz_launchers view_navigation.launch
e)
$ roslaunch turtlebot_teleop keyboard_teleop.launch
f) 地图构建完成之后保存 :
$ rosrun map_server map_saver -f /tmp/my_map

导航:
a)
$ roslaunch turtlebot_navigation amcl_demo.launch map_file:=/tmp/my_map.ymal
b)
$ roslaunch turtlebot_rviz_launchers view_navigation.launch
c) 2D Pose Estimate
d) 2D Nav Goal

Available for UTM-30LX,UBG-04LX-F01, URG-04LX, URG-04LX-UG01, UTM-04G…
REF:
http://www.iroboapp.org/index.php?title=Adding_Hokuyo_Laser_Range_Finder_to_Turtlebot
http://www.voidcn.com/blog/jie_sky_2015/article/p-5698136.html
http://wiki.ros.org/turtlebot/Tutorials/hydro/Adding%20a%20Hokuyo%20laser%20to%20your%20Turtlebot
http://www.ncnynl.com/archives/201611/1097.html
http://amanbreakingthings.blogspot.hk/2014/11/adding-hokuyo-lidar-to-turtlebot-in-ros.html
http://www.iheartrobotics.com/2012/03/in-stock-even-more-turtlebot.html
http://blog.sina.com.cn/s/blog_c5a00db10102wg07.html

智能机器人(18):人工智能

人工智能AI,可以简单用模式匹配和全文检索来实现,前面说的爱丽丝Alice比较简洁,它有多种语言实现:programD是Java版本,programE是PHP版本,programQ是Cpp版本,programV是Perl,programY是Python等等,作网站应用Java和PHP不错,做智能终端C++和Python不错。

  • 1、语言选择

既然论及人工智能AI,使用 C/C++就有点不合时宜,这需要感谢肉丝ROS的语言独立性,从而可以用C作机器人的底盘控制,用C++作机器人视觉导航,用Python作机器人的人工智能,混搭,挺好。选择python还有考虑是这个program-Y版本是纯粹用python实现的,不依赖于第三方。不像C++版本的 programD还要依赖与Qt的库,比较干净一些。

Alice的Python版叫pyAIML,这个术语,如果颗粒度小一些:前面的py表示是python脚本语言实现的,中间的ai表明说用于人工智能的xml,最后的ml表示这是标记语言,标志语言常见的有xml和yaml;如果颗粒度大些:前面的py表明前端的解释器用pytthon实现,后面的aiml表明模式匹配使用标记语言实现。

  • 2、大体结构

ALICE主要包括三块内容:programx内核、AIML标记语言、解释器接口。Kernel核心有源代码,解释器接口主要是Mysql数据库导入aiml文件。复杂些的是AIML,也就是XML,扩展标记语言。

AIML主要用来实现模式匹配,pattern matching。在好多文献里,例如半导体行业计算机视觉行业等等,都会提到pattern这个词不好翻译。感觉就是规律,例如一个人总是七点二十出门六点四十回家,这个就是强模式。再比如一个人走路总是向左侧着身子,那么也就具有了模式性,好像这个可以用于刑事侦查。再比如种植行业怎么分拣梨子和苹果,圆度,这个也是用模式匹配。至于具有模式性是好事坏,很难讲,模式太强说明熵比较大,也不算是好事。自然界本来是浑沌的,现在弄的铁是铁硅是硅山是山水是水,熵一增大就不稳定了,看来三峡大坝不怎么好。
回到Alice,在它的后缀.aiml文件(有时也用.xml)里,常见标签有category,主要包含输入模式pattern和响应模板template两个节。

  • 3、AIML安装

首先确保python正确
$ python -V
检查源
$ sudo gedit /etc/apt/sources.list
需要的话修改
$ deb http://us.archive.ubuntu.com/ubuntu precise main universe
更新
$ sudo apt-get update
安装软件
$ sudo apt-get install python-aiml
测试,如果正确导入则安装okay
$ python
>>>import aiml #导入
如果未提示异常说明安装正确。

  • 4、HelloWorld

1、一般流程
AIML最重要的一个类是Kernel(),用来学习AIML数据文件,并从AIML数据集获取用户响应,常规流程是:
myBot = aiml.Kernel() #建立Kernel类的对象实例
mybot.setBotPredicate(“name”,”myBot”) #给机器人命名
mybot.learn(‘myaiml.aiml’) #学习一个或多个AIML文件(载入内存)
//mybot.learn(“myStartup.xml”) #如果导入多个文件用这种xml的方法
mybot.respond(” load aiml b”) #触发myStartup.xml中的load aiml b模式
while True: print k.respond( raw_input(“> “)) #通过命令行接口CLI的方式交互用户

2、建个最简单的hello.aiml,中英文混搭,注意要大写
<aiml version =”1.0.1″ encoding =”UTF-8”>
<category>
<pattern>你好</pattern>
<template>不错</template>
</category>
<category>
<pattern>天</pattern>
<template>地</template>
</category>
<category>
<pattern>HOW ARE YOU</pattern>
<template>I am fine</template>
</category>
</aiml>

3、建立这个最简单的脚本hello.py,注意Python2和3的版本区别,以及unicode和UTF-8的区别

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import aiml
import sys
mybot = aiml.Kernel()
mybot.learn(sys.argv[1])
while True:
print mybot.respond( raw_input(‘访客说: ‘)), ‘n’

4、给脚本加上可执行,测试
$ chmod + x hello.py
$ ./hello.py hello.aiml
对话如下:

当访客输入“天”的时候,mybot检索数据库,给出“地”的回答,这个很简单。然而就在如此简单的结构上,加入诸多辅助标签,就可以理解复杂难懂的问题。

  • 5、其他标签

1、在aiml文件加入以下内容,试验<that>辅助标签作用
<category>
<pattern>你 问 我 吧</pattern>
<template>平时 喜欢 喝酒 吗?</template>
</category>
<category>
<pattern>是 的</pattern>
<that>平时 喜欢 喝酒 吗?</that>
<template>一般 喝 哪种 酒?</template>
</category>
<category>
<pattern>是 的</pattern>
<that>平时 喜欢 喝茶 吗?</that>
<template>一般 喝 哪种 茶?</template>
</category>

对话如下:

从上面aiml可以看出,that就是上句的意思,也就是访客说是的,后台可以匹配出一大堆的pattern/template对,在上面的例子里面至少有A和B两条,访客都是回答是的,那么机器人具体要用哪个,得看上一句。

2、再例如在aiml文件加入以下内容,试验<srai>这个辅助标签:

<category>
<pattern>下雪 了</pattern>
<template>真 好 啊</template>
</category>
<category>
<pattern>有 啥 好 的</pattern>
<that>真 好 啊</that>
<template>
<srai>好 个 大头 鬼</srai>
</template>
</category>
<category>
<pattern>好 个 大头 鬼</pattern>
<template>呵呵,瑞雪 兆 丰年 么 </template>
</category>

对话如下:

通过上一句的真好啊,后台匹配到A,而A却并没有明确给出结果只是提供了一个srai,说明理解尚未结束匹配还得继续,于是后台继续检索数据库终于从B的pattern/template对匹配到呵呵瑞雪兆丰年,最终输出。

智能机器人(17):自动应答

聊天机器人chat-robot,如果掐掉输入部分的语音识别,扣除输出部分的语音合成,剩下的就是机器应答,机器应答一般不算强人工智能,甚至连人工智能也算不上,不过应用场合不少。

语言应答这块,属于自然语言理解NLU的问题,加上机器翻译等,大的方向属于自然语言NLP处理范畴,很早就抛弃了语法分析方式,转为基于隐士马尔科夫的统计方法。隐士马尔科夫就是在你输入“美酒加”三个字之后就可以预测到接下来应该是“咖啡”,因为这个概率最高。具体的概率取决于训练所用的语料库,用人民日报体和网易盖楼体训练,概率肯定不一样。

分词对于中日韩等亚洲语言也是必须的,这些文字不像英文一样有空格可以分隔。中文分词开始是用字典法,适当控制颗粒的粗糙度也有满意效果,但是解决不了歧义问题,比如“他把球拍卖了”到底分成他把球拍/卖了还是他把球/拍卖了,所以后来也是转向基于统计的方法。

然后就是机器应答chatbot,比较成熟产品有开源的aliceBot和阿帕奇基金会的lucene,现在流行的有韩国的小黄鸡。

自己做着玩,可以有不同实现方式:

1、最简单粗暴的用数据库,写好keyword和description,然后select * from表where客户的关键词就可以了,不过没有模糊性,差错一个字就失配,知识库需要很大。

2、借助开源项目,比如中文分词用中科院的ICTCLAS,应答系统用aliceBot的AIML方式,或者阿帕奇lucene的搜索引擎方式。

搜索引擎检索方式,其实就是用全文检索系统,首先需要建立索引,常用的是倒排索引,这里倒排的意思就是矩阵转置,正排是关键词映射到页面,而倒排是页面映射到关键词,其实也就是页面的特征向量,对页面特征向量与搜索关键词计算余弦距离,就可以获得页面和检索的匹配程度。

例如一个页面的特征向量是[我,喜欢,爱玛仕],客户要搜索的特征向量是[我,喜欢,爱玛仕],两个向量的余弦为1,没什么可说的,这就是要寻找的目标。如果另一客户搜索的特征向量是是[他,喜欢,爱玛仕],那么虽然余弦不是1可是匹配程度也相当高,也可以作为全文检索结果给出。(不考虑甲醛、词频等等细节)

智能机器人(16):讯飞方案

科大讯飞的ASR和TSS口碑这么好,在PC和Android资源很多,虽然对*nix似乎支持不很到位,不过还是尝试一下,以下为测试内容。先说结果:如果说某度号称比google更懂中文会让人笑落满口牙,那么说科大讯飞更懂中文,应该当之无愧。讯飞主要提供:
1、语音合成
包括在线与离线合成,支持中文、英文、粤语、中英混、粤英混等。可以设定不同音色,包括普通话女童、普通话男声、普通话女声、粤语、东北话、河南话、湖南话、四川话等。支持多字符集,包括GB2312、GBK、Big5、Unicode、UTF-8等。
2、语音识别
提供在线与离线两种,功能包括前端预处理:端点检测、噪音消除、智能打断,后端识别:连续语音识别、个性化语音识别、置信度输出、多识别结果、多槽识别等。
3、语义理解
能处理基于文法规则的实际业务,支持用户通过自然语言向机器发出某种业务需求,如打电话、发短信、查天气、查股票、查航班、订酒店等。具备基于相似问句语义距离度量的智能知识问答。具备基于本体库自动构建及推理的智能问答。这块画蛇添足。

  • 一、流程

1、注册
1、到讯飞云去注册帐号,关联手机关联威信,建App,获ID,下载zip。
$ wget xunfei.tar.bz2
$ tar xvf xunfei.tar.bz2
2、头文件
在下载下来的include文件夹下,有五个文件:msp_errors.h、msp_types.h、msp_cmn.h、qisr.h、qtts.h,前两个是通用数据结构,qisr.h是语音识别头文件,qtts.h是语音合成头文件,在开发代码中include即可。
3、库文件
bin文件夹比较多,主要的是libmsc.so和libspeex.so两个库,复制到/usr/lib或/usr/bin目录即可。
$ sudo cp ./libmsc.so /usr/lib
4、make
$ cd samples/asr_sample
$ ./32bit_make.sh
5、可以测试
$ cd bin
$ ./asr_sample

  • 二、一般开发流程

1、建立项目目录
$ mkdir prj1
2、将SDK/Linux_SDK的src和bin和include和libs都拷贝到prj1目录下
$ cp …
3、在src下建源文件
$ cd src/
$ vi main.c
参考samples代码编辑自己源文件。
4、在prj1目录下创建Makefile文件:
DIR_INC = ./include
DIR_BIN = ./bin
DIR_LIB = ./libs

TARGET  = prj1
BIN_TARGET = $(DIR_BIN)/$(TARGET)

CROSS_COMPILE =
CFLAGS = -g -Wall -I$(DIR_INC)

ifdef LINUX64
LDFLAGS := -L$(DIR_LIB)/x64
else
LDFLAGS := -L$(DIR_LIB)/x86
endif

LDFLAGS += -lmsc -lrt -ldl -lpthread

OBJECTS := $(patsubst %.c,%.o,$(wildcard *.c))

$(BIN_TARGET) : $(OBJECTS)
$(CROSS_COMPILE)gcc $(CFLAGS) $^ -o $@ $(LDFLAGS)

%.o : %.c
$(CROSS_COMPILE)gcc -c $(CFLAGS) $< -o $@
clean:
@rm -f *.o $(BIN_TARGET)

.PHONY:clean

5、指定库位置
复制SDK/Linux_SDK/samples目录下“32bit_make.sh”或“64bit_make.sh”文件到prj1目录下。
修改libmsc.so库搜索路径
$ make clean;make
$ export LD_LIBRARY_PATH=$(pwd)/libs/x86/
6、source
$ cd prj1/
$ source 32bit_make.sh
7、run
$ cd bin/
$ ./main
8、日志文件位于bin/msc

  • 三、背景知识

1、wav
wav格式是目前Windows最直接保存声音数据的文件格式,在涉及声音信号处理时大多是对WAV文件直接操作。
2、riff
在Windows环境下大部分多媒体文件都依循着一种特定结构来存放信息,这称为资源互换文件格式(Resources Interchange File Format),称为RIFF,比如声音的WAV文件,视频的AVI文件,动画的MMM文件等。
RIFF是一种树状结构,基本组成单位是chunk(块),每个chunk由“辨识码-数据大小-数据”组成,所以一个chunk的长度就是“数据”的大小加8Byte。这是一般情况,因为通常chunk本身不会内部再包含chunk,除了极少的例外。RIFF相当于一个根目录,而格式辨识码则相当于具体的盘符如C:、D:。
3、文件头
扩展名为.wav的音频文件是一种非常简单的RIFF文件,其格式辨识码为”WAVE”,整个wav文件包括文件头和数据块两部分,文件头一般会有两种格式。
3.1、标准44字节文件头

3.1.1、RIFF WAVE Chunk:
以’RIFF’作为标示;然后紧跟着为size字段,该size是整个wav文件大小减去ID和Size所占用的字节数,即size:=FileLen-8;再然后是Type字段,为’WAVE’,表示是wav文件。结构定义如下:
struct RIFF_HEADER
{
char szRiffID[4];  // ‘R’,’I’,’F’,’F’
DWORD dwRiffSize;
char szRiffFormat[4]; // ‘W’,’A’,’V’,’E’
};Format Chunk
3.1.2、Format Chunk
以’fmt ‘作为标示。一般情况下Size为16,此时最后附加信息没有;如果为18则最后多了2个字节的附加信息。结构定义如下:
struct WAVE_FORMAT
{
WORD wFormatTag;
WORD wChannels;
DWORD dwSamplesPerSec;
DWORD dwAvgBytesPerSec;
WORD wBlockAlign;
WORD wBitsPerSample;
};
struct FMT_BLOCK
{
char  szFmtID[4]; // ‘f’,’m’,’t’,’ ‘
DWORD  dwFmtSize;
WAVE_FORMAT wavFormat;
};
3.2、58字节的文件头
这不是Windows的标准WAV文件,而是经过了一些软件处理的。
它比44字节的多了一个fact子块,储存了关于WAV文件内容的重要信息。用不到,暂时不看。
4、数据
data块装的是真正的声音数据,除非安装其它特殊软件否则Windows仅提供WAVE_FORMAT_PCM一种数据格式,即脉冲编码调制PCM:Pulse Code Modulation,其数据存放形式如下图所示,根据声道数不同及取样位数的不同,安排4位的位置。

Data Chunk头结构定义如下:
struct DATA_BLOCK
{
char szDataID[4]; // ‘d’,’a’,’t’,’a’
DWORD dwDataSize;
};
Windows将16位值取值范围定为[-32768,32767],所以0并不一定代表无声,要注意声音格式是l6位还是8位。解压缩后得到的文件仅仅是裸数据,如果不能正常播放声音,可以按照标准的44字节格式,在解码数据前编写一个正确的文件头,使其成为一个有效文件。
5、例如一个真正的wave文件

6、继续说明
Wav全称是Wave,说明就是将音频文件的波形完整记录,要记录波形需要两个最基本参数:
6.1、采样率,以怎样的频率记录波形的变化,常见的WAVE主要有两种,分别对应于单声道的11.025KHz采样率和8Bit采样深度以及双声道的44.1KHz采样率和16Bit的采样值深度。单声道音频文件采样数据为八位的短整数(short int 00H-FFH),双声道立体声声音文件采样数据为16位的整数(int),高八位和低八位分别代表左右两个声道。所有CD一律采用44.1KHz,而DVD/BD视频音轨一律采用48KHz,原则上更高的采样率更为精准,但是一般认为44.1KHz就接近人耳极限。
6.2、采样深度,用多少字节的储存量来储存音频波形,一般采用的是16bit,及更高的24bit,再高的深度意义不大。这样就不难回答为什么常见WAV文件比特率是1141KBit/s了,44.1KHz * 32bit =1141.2。
6.3、还要注意不同采样频率最好整倍数切换。例如下图曲线一是原始采样音频,一共四段五点;如果像曲线二采样率增加一倍,即八段九点,基本无差异;如果采样率为1.5倍,即波形三的6段7点,则新采样波形四就不可接受了。

6.4、音频压缩
音频编码压缩分为有损无损两种,前者例如MP3波形发生了改变,后者例如FLAC其波形和源文件毫无差异。有损效率高,MP3常见比特率在200KBit/s只是WAV的1/6。

  • 五、讯飞的语音合成

按照riff格式讯飞定义的文件头结构:
typedef struct _wave_pcm_hdr
{
char            riff[4];                // = “RIFF”
int  size_8;                 // = FileSize – 8
char            wave[4];                // = “WAVE”

char            fmt[4];                 // = “fmt ”
int  fmt_size;  // = 下一个结构体的大小:16 | 18
short int       format_tag;             // = PCM : 1
short int       channels;               // = 通道数 : 1
int  samples_per_sec;        // = 采样率 : 8000 | 6000 | 11025 | 16000
int  avg_bytes_per_sec;      // = 每秒字节数 : samples_per_sec * bits_per_sample / 8
short int       block_align;            // = 每采样点字节数 : wBitsPerSample / 8
short int       bits_per_sample;        // = 量化比特数: 8 | 16

char            data[4];                // = “data”;
int  data_size;              // = 纯数据长度 : FileSize – 44
} wave_pcm_hdr;
默认文件头是这样:
wave_pcm_hdr default_wav_hdr =
{
{ ‘R’, ‘I’, ‘F’, ‘F’ },
0,
{‘W’, ‘A’, ‘V’, ‘E’},
{‘f’, ‘m’, ‘t’, ‘ ‘},
16,
1,
1,
16000,
32000,
2,
16,
{‘d’, ‘a’, ‘t’, ‘a’},
0
};
音频合成主要代码:
int text_to_speech(const char* src_text, const char* des_path, const char* params)
{
int          ret          = -1;
FILE*        fp           = NULL;
const char*  sessionID    = NULL;
unsigned int audio_len    = 0;
wave_pcm_hdr wav_hdr      = default_wav_hdr;   //默认文件头
int          synth_status = MSP_TTS_FLAG_STILL_HAVE_DATA; //状态机模型

if (NULL == src_text || NULL == des_path) …
fp = fopen(des_path, “wb”); …

//一、pre-合成
sessionID = QTTSSessionBegin(params, &ret); //(1)获得本次sessionID
if (MSP_SUCCESS != ret) …
ret = QTTSTextPut(sessionID, src_text, (unsigned int)strlen(src_text), NULL); //(2)put代合成的文本
if (MSP_SUCCESS != ret) …

//二、合成
fwrite(&wav_hdr, sizeof(wav_hdr) ,1, fp); //(3)添加文件头header
while (1)
{
//获取合成音频
const void* data = QTTSAudioGet(sessionID, &audio_len, &synth_status, &ret);
if (MSP_SUCCESS != ret) …
if (NULL != data)
{
fwrite(data, audio_len, 1, fp); //(4.1)添加音频数据
wav_hdr.data_size += audio_len; //(4.2)刷新大小
}
if (MSP_TTS_FLAG_DATA_END == synth_status) break; //出口
usleep(150*1000); //防止频繁占用CPU
}
if (MSP_SUCCESS != ret) …
//修正wav文件头
wav_hdr.size_8 += wav_hdr.data_size + (sizeof(wav_hdr) – 8); //刷新大小

//将修正过的数据写回文件头部,音频文件为wav格式
fseek(fp, 4, 0);
fwrite(&wav_hdr.size_8,sizeof(wav_hdr.size_8), 1, fp); //写入size_8的值
fseek(fp, 40, 0); //将文件指针偏移到存储data_size值的位置
fwrite(&wav_hdr.data_size,sizeof(wav_hdr.data_size), 1, fp); //写入data_size的值
fclose(fp);
fp = NULL;

//三、post-合成
ret = QTTSSessionEnd(sessionID, “Normal”);
if (MSP_SUCCESS != ret) …
return ret;
}
主程序:《iFlytek MSC Reference Manual》
int main(int argc, char* argv[])
{
int         ret                  = MSP_SUCCESS;
const char* login_params         = “appid = 5786f5??, work_dir = .”; //登录参数,appid与msc库绑定,勿随意改动
/*
* voice_name:    合成发音人,0:数值优先,1:完全数值,2:完全字符串,3:字符串优先。
* sample_rate:   合成音频采样率
* speed:         合成音频对应的语速
* volume:        合成音频的音量
* pitch:         合成音频的音调
* rdn:           合成音频数字发音方式
* text_encoding: 合成文本编码格式
*/
const char* session_begin_params = “voice_name = xiaoyan, text_encoding = UTF8, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn

= 2”;
const char* filename             = “tts_sample.wav”; //合成的语音文件名称
const char* text                 = “语音合成示例,”; //合成文本

//一、登录
ret = MSPLogin(NULL, NULL, login_params);//第一个参数是用户名,第二个参数是密码,第三个参数是登录参数
if (MSP_SUCCESS != ret) …
//二、合成
ret = text_to_speech(text, filename, session_begin_params);
if (MSP_SUCCESS != ret) …
//三、退出
MSPLogout();
return 0;
}
主要是倒腾这几个函数:
const char *MSPAPI  QTTSSessionBegin (const char *params, int *errorCode)
int MSPAPI  QTTSTextPut (const char *sessionID, const char *textString, unsigned int textLen, const char *params)
const void *MSPAPI  QTTSAudioGet (const char *sessionID, unsigned int *audioLen, int *synthStatus, int *errorCode)
int MSPAPI  QTTSGetParam (const char *sessionID, const char *paramName, char *paramValue, unsigned int *valueLen)
int MSPAPI  QTTSSessionEnd (const char *sessionID, const char *hints)
主要参数是下面这些:
voice_name,发音人。如男声、女声、童声等。参考《发音人》列表。
speed,合成音频语速。0-100之间的整数,数值越大语速越快。默认为50
volume,合成音频音量。0-100之间的整数,数值越大音量越大。默认为50
pitch,合成音频音调。0-100之间的整数,数值越大音调越高。默认为50
text_encoding,合成文本编码。GB2312;GBK;BIG5;UNICODE;GB18030;UTF8
background_sound,成音频中的背景音。0:无背景音乐 1:有背景音乐。默认为0
aue,音频编码格式和压缩等级。编码算法:raw;speex;speex-wb;编码等级:raw:无等级。speex系列:0-10;默认为speex-wb;7。speex对应sample_rate=8000
speex-wb对应sample_rate=16000。
sample_rate,合成音频格式。8000 16000,默认为16000
ttp,合成文本类型。text: 普通格式文本;ssml:ssml格式文本。默认为text
rdn,合成音频数字发音。0:数值优先,1:完全数值,2:完全字符串,3:字符串优先。默认为0
《发音人》列表:
xiaoyan  普通话  青年女声;yufeng  普通话  青年男声;唐老鸭  donaldduck  普通话  卡通;许小宝  baybyxu  普通话  童声;楠楠  nannan  普通话  童声;
晓倩  xiaoqian  东北话  青年女声;晓蓉  xiaorong  四川话  青年女声;小强  xiaoqiang  湖南话  青年男声;晓美  xiaomei  粤语  青年女声;等。

  • 六、讯飞的语音听写

要么上传语法文件,要么上传用户词表:
int upload_userwords()
{
char*  userwords = NULL;
unsigned int len  = 0;
unsigned int read_len = 0;
FILE*  fp  = NULL;
int  ret  = -1;

fp = fopen(“userwords.txt”, “rb”); …
fseek(fp, 0, SEEK_END);
len = ftell(fp); //获取音频文件大小
fseek(fp, 0, SEEK_SET);

userwords = (char*)malloc(len + 1);
if (NULL == userwords) …

read_len = fread((void*)userwords, 1, len, fp); //读取用户词表内容
if (read_len != len) …
userwords[len] = ‘�’;

MSPUploadData(“userwords”, userwords, len, “sub = uup, dtt = userword”, &ret); //上传用户词表
if (MSP_SUCCESS != ret) …

return ret;
}
核心的听写代码
void run_iat(const char* audio_file, const char* session_begin_params)
{
const char*  session_id   = NULL;
char   rec_result[BUFFER_SIZE]  = {NULL};
char   hints[HINTS_SIZE]  = {NULL}; //hints为结束本次会话的原因描述,由用户自定义
unsigned int  total_len   = 0;
int   aud_stat   = MSP_AUDIO_SAMPLE_CONTINUE ; //音频状态
int   ep_stat    = MSP_EP_LOOKING_FOR_SPEECH; //端点检测
int   rec_stat   = MSP_REC_STATUS_SUCCESS ; //识别状态
int   errcode    = MSP_SUCCESS ;

FILE*   f_pcm    = NULL;
char*   p_pcm    = NULL;
long   pcm_count   = 0;
long   pcm_size   = 0;
long   read_size   = 0;
if (NULL == audio_file) …

f_pcm = fopen(audio_file, “rb”);
if (NULL == f_pcm) …

fseek(f_pcm, 0, SEEK_END);
pcm_size = ftell(f_pcm); //获取音频文件大小
fseek(f_pcm, 0, SEEK_SET);

p_pcm = (char *)malloc(pcm_size);
if (NULL == p_pcm) …

read_size = fread((void *)p_pcm, 1, pcm_size, f_pcm); //读取音频文件内容
if (read_size != pcm_size) …

printf(“n开始语音听写 …n”);
session_id = QISRSessionBegin(NULL, session_begin_params, &errcode); //听写不需要语法,第一个参数为NULL
if (MSP_SUCCESS != errcode) …

while (1)
{
unsigned int len = 10 * FRAME_LEN; //每次写入10帧*20ms=200ms音频。16k采样率,16位音频,每帧音频20ms、大小640Byte
int ret = 0;

if (pcm_size < 2 * len) len = pcm_size;
if (len <= 0)  break; //出口,识别尾部

aud_stat = MSP_AUDIO_SAMPLE_CONTINUE;
if (0 == pcm_count) aud_stat = MSP_AUDIO_SAMPLE_FIRST;

printf(“>”);
ret = QISRAudioWrite(session_id, (const void *)&p_pcm[pcm_count], len, aud_stat, &ep_stat, &rec_stat);
if (MSP_SUCCESS != ret) …

pcm_count += (long)len;
pcm_size  -= (long)len;

if (MSP_REC_STATUS_SUCCESS == rec_stat) //已经有部分听写结果
{
const char *rslt = QISRGetResult(session_id, &rec_stat, 0, &errcode);
if (MSP_SUCCESS != errcode) …
if (NULL != rslt)
{
unsigned int rslt_len = strlen(rslt);
total_len += rslt_len;
if (total_len >= BUFFER_SIZE) …
strncat(rec_result, rslt, rslt_len);
}
}

if (MSP_EP_AFTER_SPEECH == ep_stat) break;//出口,到达静音
usleep(200*1000); //模拟人说话时间间隙。200ms对应10帧的音频
}
errcode = QISRAudioWrite(session_id, NULL, 0, MSP_AUDIO_SAMPLE_LAST, &ep_stat, &rec_stat);
if (MSP_SUCCESS != errcode) …

while (MSP_REC_STATUS_COMPLETE != rec_stat)
{
const char *rslt = QISRGetResult(session_id, &rec_stat, 0, &errcode);
if (MSP_SUCCESS != errcode) …
if (NULL != rslt)
{
unsigned int rslt_len = strlen(rslt);
total_len += rslt_len;
if (total_len >= BUFFER_SIZE) …
strncat(rec_result, rslt, rslt_len);
}
usleep(150*1000); //防止频繁占用CPU
}
printf(“n语音听写结束n”);
printf(“%sn”,rec_result);
printf(“=============================================================n”);

QISRSessionEnd(session_id, hints);
}
主程序:
int main(int argc, char* argv[])
{
int   ret  = MSP_SUCCESS;
int   upload_on = 1; //是否上传用户词表
const char*   login_params = “appid = 5786f5??, work_dir = .”; // 登录参数,appid与msc库绑定勿改

const char* session_begin_params = “sub = iat, domain = iat, language = zh_ch, accent = mandarin, sample_rate = 16000,

result_type = plain, result_encoding = utf8”;

/* 用户登录 */
ret = MSPLogin(NULL, NULL, login_params); //第一个用户名,第二个密码,第三个登录参数
if (MSP_SUCCESS != ret) …

printf(“演示示例选择:是否上传用户词表?n0:不使用n1:使用n”);
scanf(“%d”, &upload_on);
if (upload_on)
ret = upload_userwords();

run_iat(“wav/iflytek02.wav”, session_begin_params);
exit:
MSPLogout(); //退出登录
return 0;
}

  • 七、录音

为了测试,需要录音,用Sox。Sox是Lance Norskog创立的最著名的声音文件格式转换工具,跨平台,支持常见格式,能够进行声音滤波、采样频率转换等。Sox附带有rec,play两个程序,rec是用来录音的,play是用来播放的。
Sox的语法格式如下所示:
sox  全局参数  格式化参数  输入文件1  格式化参数  输入文件2 … 格式化参数   输出文件 效果器全局参数在最前面。每个输入文件都有相应的格式化参数,可以有多个输入文件,在每一个输出文件前面再加上格式化参数,最后是效果器。
1、一个简单的命令:
$ sox file1.wav -v 0.6 file2.wav
-v是调整音量的选项,是一种线性调整,0.6并不是调整到原先的0.6,而是幅值调整,如果-v后面的数字比1大,则增加音量,反之则减少音量,如果是负数那么在调整的同时还对音频进行反相变换,但也不是可以任意增加的,取值太大容易产生削波现象。
2、要取什么值好呢:
$ sox file1.wav -n stat -v
$ 1.003
这就是不失真最大调整量了。
作用是对音频文件做一个统计分析,并将结果打印到标准错误文件。参数方面,-n表示输出文件为空。stat为效果器,-v表示将要打印跟音量调整有关内容。
3、查看文件信息
$ sox 01.wav -n stat
4、提取
提取开头的10秒
$ sox 01.wav 011.wav trim 0 10
提取10-277秒的音频
$ sox 01.wav 012.wav trim 10 227
5、调整
调整采样率为48000:
$ sox file1.wav -r 48000 file2.wav
6、转换
wav转mp3
$ sox 01.wav sox 01.mp3
7、单声道转立体声:
$ sox file1.wav -c 2 file2.wav
其中-c就是声道转换选项,-c 2可写成-c2,所以-c1表示单声道,-c4表示4声道。
8、组合
打印细节,音量调整,采样率调整,声道转换,等等都结合起来:
$ sox -V4 -v 1.2   file1.wav -r 48000 -c 2 file2.wav
其中-V4表示打印最多细节。
9、效果器
样本用的是一小段自录声音(3.15分钟长,’wav’格式,44.1 kHz采样速率,16bit单声道)。
10、混合
$ play 01.wav mixer 0.3,0.5,0.8,0.6
采用了mixer效果器,它通过混合或者减少音轨从而减少音轨数,或者通过复制音轨而增加音轨数。
11、节拍器
$ play 01.wav tempo -q 0.8 82 20  16
下面例子则应用了tempo(节拍)效果器:
0.8设置新节拍相对于老节拍的比率,82设置所选算法要划分音频的片段大小,单位毫秒,20是音频长度,依靠它来搜索以寻找重叠点,16是重叠长度。
12颤动器
$ play 01.wav tremolo 3.5 60
3.5是颤音频率,,单位是赫兹Hz,60是深度百分比,具体来说就是”颤”到多长或深。
13、淡入淡出
$ play 01.wav fade t 00:00:100.09
上面例子中,fade是效果器名字,t是声波包络线形式,t是线性斜坡,选q则意味着是正弦波的四分之一,h表示正弦波一半,l为对数,p为倒置抛物线。默认是线性斜坡。00:00:100.09是以hh:mm:ss.fraq形式表示的时间,也可用采样数来算,如设为8000s则为8000个样本。
$ play 01.wav fade t 00:00:50.09 00:01:00 00:00:06
上面是淡入效果,那么要设置淡出效果。上例中t上面已讲,00:00:50.09是从0开始算起,到淡入结束所花费的时间;00:01:00是开始淡出的时间点,00:00:06是开始淡出到结束所花费的时间。也就是说,从00:01:00开始淡出,花费6秒即00:00:06的时间就结束了。上面的时间都可以选择以样本数量为单位,如上所述。
14、回响
自然界中,回声处处可见,比如站在高山上,向周围的山喊话,就会引起回声,在喊和回声之间的时间间隔就是延迟,它的响度就是衰减值,下面给出一个回响例子:
$ play file.xxx echo 0.8 0.88 60 0.4
上式听起来就象用两个乐器演奏同一个样本一样,0.8是输入音量,0.88是输出音量,60是延迟,单位是毫秒,0.4是相对于输入音量的衰减值。
如果延迟时间变长,听起来更象在山顶上的露天演唱会:
$ play file.wav echo 0.8 0.88 1000 0.4
衰减值最好不要大于0.5,否则可能引起输出饱和。
假如延迟很短,听起来象(金属的)机器人的表演。
$ play file.wav 0.8 0.88 6 0.4
想要更多回响也可以实现:
$ play file.wav echo 0.8 0.9 1000 0.3 1800 0.25
如果是站在群山之间,还可能会引起连续回响,即回响本身有碰到邻近山峰,反弹回来,又弹回去,这种效果就是回声,它是连续回响的意思,如果是单独应用一次回声,效果和回响是一样的,下面看一个两次回声例子:
$ play file.wav echos 0.8 0.7 700 0.25 700 0.3
在上式中,echos就是回声效果器,应用这个效果器,回响将被弹回来两次,因为两次延迟时间相同,都是700,这种回响叫对称回声,来一个不对称的回声:
$ play file.xxx echos 0.8 0.7 700 0.25 900 0.3
下面这个例子听起来就象在汽车里演奏一样:
$ play file.wav echos 0.8 0.7 40 0.25 63 0.3
上式由于延迟时间短,听起来感觉有点沉闷,不是吗?
15、rec
Sox附带的程序有rec,play两个程序,rec是用来录音的,play是用来试听效果的,它们语法跟sox是类似的,只是rec时候输入源就变成了内部或外部的设备。常规的,一般录wav用sox就ok,录制mp3可选用mpegrec。
最简单的指定文件名即可录音:
$ rec file.wav
带参数录音:
$ rec -r 16000 -b 16  -c 1  666.wav
以上符合讯飞要求:采样率16K或8KHz,采样位是16位,单声道,格式PCM或WAV。
主要参数:
-r 16000, -r指定采样速率,这里为16K
-b 16,-s指定采样位数,默认-s b为8位,这里-s w为16位。
-c 1,-c指定通道数, 这里-c 1为单通道
-v 12,-v指定音量。
-t mp3,-t指定文件类型,默认wav,这里为mp3

  • 八、综合测试

0、设置环境:
$ export PATH=~/XF_U1404401/Linux_voice_1.109/bin:$PATH
1、录制音频:
$ rec -r 16000 -b 16  -c 1  wav/dehaoSpeech.wav
2、识别称文本:
$ iat_sample Y
3、合成另外一种语音:
$ tts_sample  –voice_name=xiaomei   “我爱北京天安门”  dehaoSpeech.wav
4、播放这段语音:
$ play dehaoSpeech.wav
5、还是写个脚本吧:
#!/bin/sh
export PATH=~/XF_U1404401/Linux_voice_1.109/bin:$PATH
rm wav/dehaoSpeech.wav
rec -r 16000 -b 16  -c 1  wav/dehaoSpeech.wav
tts_sample  –voice_name=xiaomei   `iat_sample Y`  dehaoSpeech.wav
play dehaoSpeech.wav
说明:
1、脚本流程就是上面的手工流程一录、二识、三合、四播:(1)rec录制一段我的语音–>(2)文本识别iat_sample识别出这段语音成文字–>(3)语音合成程序tts_sample把这段文字合成某种方言–>(4)play播放这段方言。
2、对语音合成程序tts_sample添加了一些参数处理:

3、用了参数的最长的这句,“tts_sample”是语音合成 这个程序名,“–voice_name=xiaomei ”是第一个参数说明使用xiaomei这个粤语女声,两边用反颚符号“包围起来的`iat_sample Y`表示这个识别程序的文本结果将作为合成程序tts_sample的第二个参数,“dehaoSpeech.wav”作为第三个参数,指定了位置。