人工智能-樹莓派小車(5)——用微信控制智能小車

之前所做的有一個(gè)特點(diǎn)就是需要在樹莓派上連接一個(gè)USB麥克風(fēng)蘸炸,通過這個(gè)麥克風(fēng)來進(jìn)行語(yǔ)音的輸入靠粪,但是在實(shí)際使用場(chǎng)景上來看震桶,這是不合理的休傍,因此需要一個(gè)可以遠(yuǎn)程操控智能小車的方案,因此萌發(fā)了將控制端移植到手機(jī)上的想法蹲姐,移植到手機(jī)上也有幾個(gè)不同的方案磨取。

  1. 通過Zigbee無線傳輸技術(shù)進(jìn)行數(shù)據(jù)的傳輸
    優(yōu)點(diǎn):易于進(jìn)行擴(kuò)展,可方便的與其他智能家居連接起來
    缺點(diǎn):傳輸?shù)臄?shù)據(jù)量很小柴墩,不適用于語(yǔ)音數(shù)據(jù)以及圖像數(shù)據(jù)的傳遞
  2. 通過搭建一個(gè)服務(wù)器忙厌,開發(fā)一個(gè)手機(jī)APP,通過服務(wù)器進(jìn)行數(shù)據(jù)的傳輸
    優(yōu)點(diǎn):自己搭建服務(wù)器拐邪, 可控制性強(qiáng)
    缺點(diǎn):開發(fā)門檻高(需要進(jìn)行服務(wù)器的搭建以及手機(jī)應(yīng)用的開發(fā)慰毅,不易實(shí)現(xiàn))以及使用門檻高(需要單獨(dú)在手機(jī)上安裝一個(gè)APP)
  3. 通過微信平臺(tái),進(jìn)行數(shù)據(jù)(文本扎阶、語(yǔ)音汹胃、圖像、視頻)的傳遞
    優(yōu)點(diǎn):人人都有微信东臀,使用門檻低着饥,功能相對(duì)全面
    缺點(diǎn):可控制性相對(duì)較弱

最終經(jīng)過權(quán)衡,選取第三個(gè)方案惰赋,通過微信控制智能小車宰掉。

itchat

itchat是一個(gè)開源的微信個(gè)人號(hào)接口,使用python調(diào)用微信從未如此簡(jiǎn)單赁濒」煅伲可以通過它進(jìn)行微信消息的讀取,回復(fù)拒炎,聯(lián)系人的讀取等等

1. 安裝

pip install itchat

2. 簡(jiǎn)單入門

1.登陸配置

登陸使用的是itchat提供了auto_login方法挪拟,調(diào)用即可完成登錄。
一般而言击你,會(huì)在完成消息的注冊(cè)之后再進(jìn)行登陸玉组。
這里需要特別強(qiáng)調(diào)的是兩點(diǎn)谎柄,分別是短時(shí)間關(guān)閉重連命令行二維碼惯雳。

  • itchat提供了登陸狀態(tài)暫存朝巫,關(guān)閉程序后一定時(shí)間內(nèi)不需要掃碼即可登錄。由于目前微信網(wǎng)頁(yè)版提供上一次登錄的微信號(hào)不掃碼直接手機(jī)確認(rèn)登陸石景,所以如果開啟登陸狀態(tài)暫存將會(huì)自動(dòng)使用這一功能劈猿。
  • 為了方便在無圖形界面使用itchat,程序內(nèi)置了命令行二維碼的顯示鸵钝。
短時(shí)間關(guān)閉程序后重連

這樣即使程序關(guān)閉糙臼,一定時(shí)間內(nèi)重新開啟也可以不用重新掃碼。
最簡(jiǎn)單的用法就是給auto_login方法傳入值為真的hotReload恩商。
該方法會(huì)生成一個(gè)靜態(tài)文件itchat.pkl,用于存儲(chǔ)登陸的狀態(tài)必逆。

import itchat
from itchat.content import TEXT

@itchat.msg_register(TEXT)
def simple_reply(msg):
    print(msg.text)

itchat.auto_login(hotReload=True)
itchat.run()
命令行二維碼顯示

通過以下命令可以在登陸的時(shí)候使用命令行顯示二維碼:

itchat.auto_login(enableCmdQR=True)

部分系統(tǒng)可能字幅寬度有出入怠堪,可以通過將enableCmdQR賦值為特定的倍數(shù)進(jìn)行調(diào)整:如部分的linux系統(tǒng),塊字符的寬度為一個(gè)字符(正常應(yīng)為兩字符)名眉,故賦值為2

itchat.auto_login(enableCmdQR=2)

2.聊天對(duì)象

在使用個(gè)人微信的過程當(dāng)中主要有三種賬號(hào)需要獲取粟矿,分別為:

  • 好友
  • 公眾號(hào)
  • 群聊

itchat為這三種賬號(hào)都提供了整體獲取方法與搜索方法。群聊多出獲取用戶列表方法以及創(chuàng)建群聊损拢、增加陌粹、刪除用戶的方法
接下來對(duì)三種分別介紹如何使用以及如何通過唯一的Uin確定好友與群聊福压。

好友

好友的獲取方法為get_friends掏秩,將會(huì)返回完整的好友列表。

  • 其中每個(gè)好友為一個(gè)字典
  • 列表的第一項(xiàng)為本人的賬號(hào)信息
  • 傳入update鍵為True將可以更新好友列表并返回

好友的搜索方法為search_friends荆姆,有四種搜索方式:

  1. 僅獲取自己的用戶信息
  2. 獲取特定UserName的用戶信息
  3. 獲取備注蒙幻、微信號(hào)、昵稱中的任何一項(xiàng)等于name鍵值的用戶
  4. 獲取備注胆筒、微信號(hào)邮破、昵稱分別等于相應(yīng)鍵值的用戶

下面是示例程序:

獲取自己的用戶信息,返回自己的屬性字典

itchat.search_friends()

獲取特定UserName的用戶信息

itchat.search_friends(userName='@abcdefg1234567')

獲取任何一項(xiàng)等于name鍵值的用戶

itchat.search_friends(name='littlecodersh')

獲取分別對(duì)應(yīng)相應(yīng)鍵值的用戶

itchat.search_friends(wechatAccount='littlecodersh')

其中3仆救、4項(xiàng)功能可以一同使用抒和,將返回同時(shí)滿足兩個(gè)條件的好友

itchat.search_friends(name='LittleCoder機(jī)器人', wechatAccount='littlecodersh')

更新用戶信息的方法為update_friend。

  • 該方法需要傳入用戶的UserName彤蔽,返回指定用戶的最新信息
  • 也可以傳入U(xiǎn)serName組成的列表摧莽,那么相應(yīng)的也會(huì)返回指定用戶的最新信息組成的列表
memberList = itchat.update_friend('@abcdefg1234567')
公眾號(hào)

公眾號(hào)的獲取方法為get_mps,將會(huì)返回完整的公眾號(hào)列表铆惑。

  • 其中每個(gè)公眾號(hào)為一個(gè)字典
  • 傳入update鍵為True將可以更新公眾號(hào)列表并返回

公眾號(hào)的搜索方法為search_mps范嘱,有兩種搜索方法:

  1. 獲取特定UserName的公眾號(hào)
  2. 獲取名字中含有特定字符的公眾號(hào)

如果兩項(xiàng)都做了特定送膳,將會(huì)僅返回特定UserName的公眾號(hào),下面是示例程序:

獲取特定UserName的公眾號(hào)丑蛤,返回值為一個(gè)字典

itchat.search_mps(userName='@abcdefg1234567')

獲取名字中含有特定字符的公眾號(hào)叠聋,返回值為一個(gè)字典的列表

itchat.search_mps(name='LittleCoder')

以下方法相當(dāng)于僅特定了UserName

itchat.search_mps(userName='@abcdefg1234567', name='LittleCoder')
群聊

群聊的獲取方法為get_chatrooms,將會(huì)返回完整的群聊列表受裹。

  • 其中每個(gè)群聊為一個(gè)字典
  • 傳入update鍵為True將可以更新群聊列表并返回通訊錄中保存的群聊列表
  • 群聊列表為后臺(tái)自動(dòng)更新碌补,如果中途意外退出存在極小的概率產(chǎn)生本地群聊消息與后臺(tái)不同步
  • 為了保證群聊信息在熱啟動(dòng)中可以被正確的加載,即使不需要持續(xù)在線的程序也需要運(yùn)行itchat.run()
  • 如果不想要運(yùn)行上述命令棉饶,請(qǐng)?jiān)谕顺龀绦蚯罢{(diào)用itchat.dump_login_status()厦章,更新熱拔插需要的信息

群聊的搜索方法為search_chatrooms肴掷,有兩種搜索方法:

  1. 獲取特定UserName的群聊
  2. 獲取名字中含有特定字符的群聊

如果兩項(xiàng)都做了特定男翰,將會(huì)僅返回特定UserName的群聊,下面是示例程序:

獲取特定UserName的群聊崔拥,返回值為一個(gè)字典

itchat.search_chatrooms(userName='@@abcdefg1234567')

獲取名字中含有特定字符的群聊幸缕,返回值為一個(gè)字典的列表

itchat.search_chatrooms(name='LittleCoder')

以下方法相當(dāng)于僅特定了UserName

itchat.search_chatrooms(userName='@@abcdefg1234567', name='LittleCoder')

群聊用戶列表的獲取方法為update_chatroom群发。

  • 如果想要更新該群聊的其他信息也可以用該方法
  • 群聊在首次獲取中不會(huì)獲取群聊的用戶列表,需要調(diào)用該命令才能獲取群聊的成員
  • 該方法需要傳入群聊的UserName发乔,返回特定群聊的詳細(xì)信息
  • 也可以傳入U(xiǎn)serName組成的列表熟妓,那么相應(yīng)的也會(huì)返回指定用戶的最新信息組成的列表
memberList = itchat.update_chatroom('@@abcdefg1234567', detailedMember=True)

創(chuàng)建群聊、增加栏尚、刪除群聊用戶的方法如下所示:

  • 由于之前通過群聊檢測(cè)是否被好友拉黑的程序起愈,目前這三個(gè)方法都被嚴(yán)格限制了使用頻率
  • 刪除群聊需要本賬號(hào)為群管理員,否則會(huì)失敗
  • 將用戶加入群聊有直接加入與發(fā)送邀請(qǐng)译仗,通過useInvitation設(shè)置
  • 超過40人的群聊無法使用直接加入的加入方式抬虽,特別注意
memberList = itchat.get_friends()[1:]
chatroomUserName = itchat.create_chatroom(memberList, 'test chatroom') # 創(chuàng)建群聊,topic鍵值為群聊名
itchat.delete_member_from_chatroom(chatroomUserName, memberList[0]) # 刪除群聊內(nèi)的用戶
itchat.add_member_into_chatroom(chatroomUserName, memberList[0], useInvitation=False) # 增加用戶進(jìn)入群聊
Uins

Uin 就是微信中用于標(biāo)識(shí)用戶的方式古劲,每一個(gè)用戶斥赋、群聊都有唯一且不同的Uin。通過Uin产艾,即使退出了重新登錄疤剑,也可以輕松的確認(rèn)正在對(duì)話的是上一次登陸的哪一個(gè)用戶。但注意闷堡,Uin與其他值不同隘膘,微信后臺(tái)做了一定的限制,必須通過特殊的操作才能獲取杠览。
最簡(jiǎn)單來說弯菊,首次點(diǎn)開登陸用的手機(jī)端的某個(gè)好友或者群聊,itchat就能獲取到該好友或者群聊的Uin踱阿。
如果想要通過程序獲取管钳,也可以用程序?qū)⒛硞€(gè)好友或者群聊置頂(取消置頂)钦铁。
這里提供一個(gè)提示群聊更新的程序:

import re, sys, json

import itchat
from itchat.content import *

itchat.auto_login(True)

@itchat.msg_register(SYSTEM)
def get_uin(msg):
    if msg['SystemInfo'] != 'uins': return
    ins = itchat.instanceList[0]
    fullContact = ins.memberList + ins.chatroomList + ins.mpList
    print('** Uin Updated **')
    for username in msg['Text']:
        member = itchat.utils.search_dict_list(
            fullContact, 'UserName', username)
        print(('%s: %s' % (
            member.get('NickName', ''), member['Uin']))
            .encode(sys.stdin.encoding, 'replace'))

itchat.run(True)

每當(dāng)Uin更新了,就會(huì)打印相應(yīng)的更新情況才漆。

3.消息處理

消息內(nèi)容

微信初始化消息牛曹、文本消息、圖片消息醇滥、小視頻消息黎比、地理位置消息、名片消息鸳玩、語(yǔ)音消息阅虫、動(dòng)畫表情、普通鏈接或應(yīng)用分享消息不跟、音樂鏈接消息颓帝、群消息、紅包消息躬拢、系統(tǒng)消息

回復(fù)方法
  • 方法:
send(msg='Text Message', toUserName=None)
  • 所需值:
    • msg:消息內(nèi)容
    • '@fil@文件地址'將會(huì)被識(shí)別為傳送文件
    • '@img@圖片地址'將會(huì)被識(shí)別為傳送圖片
    • '@vid@視頻地址'將會(huì)被識(shí)別為小視頻
    • toUserName:發(fā)送對(duì)象躲履,如果留空將會(huì)發(fā)送給自己
  • 返回值:發(fā)送成功->True, 失敗->False
  • 程序示例:使用的素材可以在這里下載
#coding=utf8
import itchat

itchat.auto_login()
itchat.send('Hello world!')
# 請(qǐng)確保該程序目錄下存在:gz.gif以及xlsx.xlsx
itchat.send('@img@%s' % 'gz.gif')
itchat.send('@fil@%s' % 'xlsx.xlsx')
itchat.send('@vid@%s' % 'demo.mp4')

4.注冊(cè)方法

itchat將根據(jù)接收到的消息類型尋找對(duì)應(yīng)的已經(jīng)注冊(cè)的方法。如果一個(gè)消息類型沒有對(duì)應(yīng)的注冊(cè)方法聊闯,該消息將會(huì)被舍棄。

注冊(cè)

可以通過兩種方式注冊(cè)消息方法

import itchat
from itchat.content import *

# 不帶具體對(duì)象注冊(cè)米诉,將注冊(cè)為普通消息的回復(fù)方法
@itchat.msg_register(TEXT)
def simple_reply(msg):
    return 'I received: %s' % msg['Text']

# 帶對(duì)象參數(shù)注冊(cè)菱蔬,對(duì)應(yīng)消息對(duì)象將調(diào)用該方法
@itchat.msg_register(TEXT, isFriendChat=True, isGroupChat=True, isMpChat=True)
def text_reply(msg):
    msg.user.send('%s: %s' % (msg.type, msg.text))
消息類型

向注冊(cè)方法傳入的msg包含微信返回的字典的所有內(nèi)容。

本api增加Text史侣、Type(也就是參數(shù))鍵值拴泌,方便操作。

itchat.content中包含所有的消息類型參數(shù)惊橱,內(nèi)容如下表所示:

參數(shù) 類型 Text鍵值
TEXT 文本 文本內(nèi)容
MAP 地圖 位置文本
CARD 名片 推薦人字典
NOTE 通知 通知文本
SHARING 分享 分享名稱
PICTURE 圖片/表情 下載方法
RECORDING 語(yǔ)音 下載方法
ATTACHMENT 附件 下載方法
VIDEO 小視頻 下載方法
FRIENDS 好友邀請(qǐng) 添加好友所需參數(shù)
SYSTEM 系統(tǒng)消息 更新內(nèi)容的用戶或群聊的UserName組成的列表

比如你需要存儲(chǔ)發(fā)送給你的附件:

@itchat.msg_register(ATTACHMENT)
def download_files(msg):
    msg['Text'](msg['FileName'])

值得注意的是蚪腐,群消息增加了三個(gè)鍵值:

  • isAt: 判斷是否@本號(hào)
  • ActualNickName: 實(shí)際NickName
  • Content: 實(shí)際Content

可以通過本程序測(cè)試:

import itchat
from itchat.content import TEXT

@itchat.msg_register(TEXT, isGroupChat=True)
def text_reply(msg):
    print(msg.isAt)
    print(msg.actualNickName)
    print(msg.text)

itchat.auto_login()
itchat.run()
注冊(cè)消息的優(yōu)先級(jí)

優(yōu)先級(jí)分別為:后注冊(cè)消息先于先注冊(cè)消息,帶參數(shù)消息先于不帶參數(shù)消息税朴。

以下面的兩個(gè)程序?yàn)槔?/p>

import itchat
from itchat.content import *

itchat.auto_login()

@itchat.msg_register(TEXT)
def text_reply(msg):
    return 'This is the old register'

@itchat.msg_register(TEXT)
def text_reply(msg):
    return 'This is a new one'

itchat.run()

在私聊發(fā)送文本時(shí)將會(huì)回復(fù)This is a new one回季。

import itchat
from itchat.content import *

itchat.auto_login()

@itchat.msg_register
def general_reply(msg):
    return 'I received a %s' % msg.type

@itchat.msg_register(TEXT)
def text_reply(msg):
    return 'You said to me one to one: %s' % msg.text

itchat.run()

僅在私聊發(fā)送文本時(shí)將會(huì)回復(fù)You said to me one to one,其余情況將會(huì)回復(fù)I received a ...正林。

STT (Speak To Text)

通過上述的itchat模塊泡一,就可以在樹莓派上用python進(jìn)行微信的信息接收等,文本消息我們可以利用之前類似的方法進(jìn)行識(shí)別觅廓、控制鼻忠,那么僅僅用文本消息控制就顯得不夠智能了,需要用語(yǔ)音來控制杈绸,那么如何用語(yǔ)音來控制小車呢帖蔓?這就要用到STT模塊矮瘟,這個(gè)模塊做的就是把語(yǔ)音識(shí)別為文本信息,目前有多家公司有開源項(xiàng)目可以使用塑娇,比如Google澈侠、百度、訊飛钝吮,綜合考慮語(yǔ)音識(shí)別準(zhǔn)確率埋涧、易用性上,我選擇了百度的API接口奇瘦。

百度AI平臺(tái)

要想使用百度的語(yǔ)音識(shí)別API棘催,有如下幾個(gè)準(zhǔn)備工作。

  1. 首先要成為百度開發(fā)者
  2. 創(chuàng)建應(yīng)用
  3. 獲取密鑰
  4. 合成Access Token

前三部分可以在百度開發(fā)者平臺(tái)上很簡(jiǎn)單的注冊(cè)申請(qǐng)耳标,不再贅述醇坝。要注意的是保留好上述申請(qǐng)到的密鑰(API KEY、Secret KEY)次坡,會(huì)在下面用到呼猪;第四部分下面會(huì)詳細(xì)介紹。

語(yǔ)音識(shí)別簡(jiǎn)介

百度語(yǔ)音識(shí)別通過 REST API 的方式給開發(fā)者提供一個(gè)通用的 HTTP 接口砸琅。 上傳需要完整的錄音文件宋距,錄音文件時(shí)長(zhǎng)不超過60s。

調(diào)用流程

1. 獲取Access Token

請(qǐng)求URL數(shù)據(jù)格式
向授權(quán)服務(wù)地址https://aip.baidubce.com/oauth/2.0/token發(fā)送請(qǐng)求(推薦使用POST)症脂,并在URL中帶上以下參數(shù):

  • grant_type: 必須參數(shù)谚赎,固定為client_credentials;
  • client_id: 必須參數(shù)诱篷,應(yīng)用的API Key壶唤;
  • client_secret: 必須參數(shù),應(yīng)用的Secret Key棕所;

Python獲取access_token示例代碼

import urllib, urllib2, sys
import ssl

# client_id 為官網(wǎng)獲取的AK闸盔, client_secret 為官網(wǎng)獲取的SK
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=【官網(wǎng)獲取的AK】&client_secret=【官網(wǎng)獲取的SK】'
request = urllib2.Request(host)
request.add_header('Content-Type', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
content= response.read()
if (content):
    print(content)

上述代碼中content是一個(gè)JSON格式信息,其中包含你請(qǐng)求到的授權(quán)碼琳省,會(huì)在調(diào)用API時(shí)用到迎吵,有限期一般為一個(gè)月。
服務(wù)器返回的JSON文本參數(shù)如下

  • access_token: 要獲取的Access Token岛啸;
  • expires_in: Access Token的有效期(秒為單位钓觉,一般為1個(gè)月);
  • 其他參數(shù)忽略坚踩,暫時(shí)不用;
    例如:
{
  "access_token": "25.b55fe1d287227ca97aab219bb249b8ab.315360000.1798284651.282335-8574074",
  "expires_in": 2592000,
  "scope": "public wise_adapt",
  "session_key": "9mzdDZXu3dENdFZQurfg0Vz8slgSgvvOAUebNFzyzcpQ5EnbxbF+hfG9DQkpUVQdh4p6HbQcAiz5RmuBAja1JJGgIdJI",
  "access_token": "24.6c5e1ff107f0e8bcef8c46d3424a0e78.2592000.1485516651.282335-8574074",
  "session_secret": "dfac94a3489fe9fca7c3221cbf7525ff"
}

其中access_token即為我們需要的信息荡灾,通過Python的json模塊,把它提取出來。

data = json.loads(content)
token = data["access_token"]
2. 請(qǐng)求方式
  • 如果音頻在本地批幌,需要將音頻數(shù)據(jù)放在body中础锐。(推薦方式)
  • 如果音頻在互聯(lián)網(wǎng)上,可以讓百度服務(wù)器下載荧缘,然后回調(diào)到自己服務(wù)器的接口
  • 音頻在本地皆警,有JSON和raw兩種方式提交。
  • 音頻在在互聯(lián)網(wǎng)上截粗,需要百度服務(wù)器下載信姓,只能通過JSON方式提交

這里只介紹通過json 方式,上傳本地文件(官方推薦绸罗,更高效)

JSON方式上傳

語(yǔ)音數(shù)據(jù)和其他參數(shù)通過標(biāo)準(zhǔn) JSON 格式串行化 POST 上傳意推, JSON 里包括的參數(shù):

字段名 是必填或選填 描述
format 必填 語(yǔ)音文件的格式,pcm 或者 wav 或者 amr珊蟀。不區(qū)分大小寫菊值。推薦pcm文件
rate 必填 采樣率, 8000 或者 16000育灸, 推薦 16000 采用率
channel 必填 聲道數(shù)腻窒,僅支持單聲道,請(qǐng)?zhí)顚懝潭ㄖ?1
cuid 必填 用戶唯一標(biāo)識(shí)磅崭,用來區(qū)分用戶儿子,計(jì)算UV值。建議填寫能區(qū)分用戶的機(jī)器 MAC 地址或 IMEI 碼砸喻,長(zhǎng)度為60字符以內(nèi)典徊。
token 必填 開放平臺(tái)獲取到的開發(fā)者[ access_token](http://yuyin.baidu.com/docs/tts/135#獲取 Access Token "access_token")
lan 選填 語(yǔ)種選擇,默認(rèn)中文(zh)恩够。 中文=zh、粵語(yǔ)=ct羡铲、英文=en蜂桶,不區(qū)分大小寫
url 選填 可下載的語(yǔ)音下載地址,與callback連一起使用也切,確保百度服務(wù)器可以訪問扑媚。
callback 選填 用戶服務(wù)器的識(shí)別結(jié)果回調(diào)地址,確保百度服務(wù)器可以訪問
speech 選填 本地語(yǔ)音文件的的二進(jìn)制語(yǔ)音數(shù)據(jù) 雷恃,需要進(jìn)行base64 編碼疆股。與len參數(shù)連一起使用。
len 選填 本地語(yǔ)音文件的的字節(jié)數(shù)倒槐,單位字節(jié)
上傳示例(speech, len 參數(shù))

JSON格式POST上傳本地文件,固定頭部header:Content-Type:application/json,4K大小的pcm文件(普通話錄音)請(qǐng)求: speech 參數(shù)填寫為文件內(nèi)容base64后的結(jié)果:

{
    "format":"pcm",
    "rate":16000,
    "channel":1,
    "token":xxx,
    "cuid":"baidu_workshop",
    "len":4096,
    "speech":"xxx", // xxx為 base64(FILE_CONTENT)
}
返回示例
{
    "corpus_no":"6433214037620997779",
    "err_msg":"success.",
    "err_no":0,
    "result":["北京科技館旬痹,"],
    "sn":"371191073711497849365"
}
注意事項(xiàng)
  • len 字段表示原始語(yǔ)音大小字節(jié)數(shù),不是 base64 編碼之后的長(zhǎng)度。
核心代碼
# -*- coding: utf-8 -*-
"""
語(yǔ)音識(shí)別功能模塊(語(yǔ)音輸入)
參考:http://open.duer.baidu.com/doc/dueros-conversational-service/device-interface/voice-input_markdown
"""

import urllib.request
import json
import base64


def listen(file):

    os.system('ffmpeg -y -i %s -acodec pcm_s16le -f s16le -ac 1 -ar 16000 16k_dai.pcm' % file)
    #打開音頻文件两残,并進(jìn)行編碼
    f = open(WAVE_FILE, "rb")
    speech = base64.b64encode(f.read()).decode('utf-8')
    size = os.path.getsize(WAVE_FILE)
    update = json.dumps({"format":WAVE_TYPE, "rate":VOICE_RATE, 'channel':1,'cuid':USER_ID,'token':token,'speech':speech,'len':size}).encode('utf-8')
    update_length = len(update)
    headers = { 'Content-Type' : 'application/json' } 
    url = "http://vop.baidu.com/server_api"
    req = urllib.request.Request(url)
    req.add_header("Content-Type", "application/json")
    req.add_header("Content-Length", update_length)
    r = urllib.request.urlopen(url=req, data=update)
    t = r.read().decode('utf-8')
    result = json.loads(t)
    if result['err_msg']=='success.':
        command = result['result'][0].encode('utf-8').decode()
        return command
    else:
        return '我沒有聽懂你說什么S酪恪!人弓!'

    
#設(shè)置應(yīng)用信息
baidu_server = "https://openapi.baidu.com/oauth/2.0/token?"
grant_type = "client_credentials"
client_id = "*****************" #填寫API Key   需自行申請(qǐng)
client_secret = "****************" #填寫Secret Key

#合成請(qǐng)求token的URL
url = baidu_server+"grant_type="+grant_type+"&client_id="+client_id+"&client_secret="+client_secret

#獲取token
res = urllib.request.urlopen(url).read().decode()
data = json.loads(res)
token = data["access_token"]
print ( token )

#設(shè)置音頻屬性沼死,根據(jù)百度的要求,采樣率必須為8000崔赌,壓縮格式支持pcm(不壓縮)意蛀、wav、opus健芭、speex县钥、amr
VOICE_RATE = 16000
WAVE_FILE = "16k_dai.pcm" #音頻文件的路徑
USER_ID = "hail_hydra" #用于標(biāo)識(shí)的ID,可以隨意設(shè)置
WAVE_TYPE = "pcm"

TTS(Text To Speak)

智能小車識(shí)別命令做出相應(yīng)動(dòng)作后要進(jìn)行反饋吟榴,同樣采用百度的語(yǔ)音合成API魁蒜,將要說的話合稱為語(yǔ)音,這部分我們采用與上面的REST API不同的方式吩翻,采用Python SDK的方式獲取兜看。

安裝語(yǔ)音合成 Python SDK

語(yǔ)音合成 Python SDK目錄結(jié)構(gòu)

├── README.md
├── aip                   //SDK目錄
│   ├── __init__.py       //導(dǎo)出類
│   ├── base.py           //aip基類
│   ├── http.py           //http請(qǐng)求
│   └── speech.py //語(yǔ)音合成
└── setup.py              //setuptools安裝

安裝使用Python SDK有如下方式:

  • 如果已安裝pip,執(zhí)行pip install baidu-aip即可狭瞎。
  • 如果已安裝setuptools细移,執(zhí)行python setup.py install即可。

此種方式比較簡(jiǎn)單熊锭,不做詳細(xì)介紹弧轧,直接上核心代碼

核心代碼

# -*- coding: utf-8-*-

import os
import sys
from imp import reload

reload(sys)

def say(what):
#配置賬戶信息

    from aip import AipSpeech
    """ 你的 APPID AK SK """
    APP_ID = '10462370'
    API_KEY = 
    SECRET_KEY = 
    client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)

    result  = client.synthesis( what, 'zh', 1, {
        'vol': 5,
        'per': 3,
    })
    # 識(shí)別正確返回語(yǔ)音二進(jìn)制 錯(cuò)誤則返回dict 參照下面錯(cuò)誤碼
    if not isinstance(result, dict):
        with open('auido.mp3', 'wb') as f:
            f.write(result)
    os.system("auido.mp3")
    
if __name__ == '__main__':
    say('李博是最最最帥的')

控制代碼

這部分在教程人工智能-樹莓派小車(4)——通過語(yǔ)音玩轉(zhuǎn)智能小車已經(jīng)介紹過,原理相同碗殷,這里只放代碼精绎。

def recognize():

        file = '/home/pi/Rascar-DuerOS-Python-Client/ord.txt' # 讀取文件
        f = open(file,'r')
        out = f.read()
        command = out.decode('unicode-escape').encode('utf-8')
        print command
        GPIO.output(LED_CTR, GPIO.LOW)
        ############################################
        ###############命令識(shí)別######################
        ############################################
        
       if command.find(u"開") !=-1 and command.find(u"大") !=-1 and command.find(u"燈") !=-1:
            print ("打開前大燈")
            say(random.choice(['已經(jīng)幫您開燈','開燈了','好的,開燈','是锌妻,正在開燈']))
            robot.Open_Flight()
            GPIO.output(LED_CTR, GPIO.HIGH)
            
        elif command.find(u"關(guān)") !=-1 and command.find(u"大") !=-1 and command.find(u"燈") !=-1:
                print ("關(guān)閉前大燈")
                say(random.choice(['已經(jīng)幫您關(guān)燈','關(guān)燈了','好的代乃,關(guān)燈','是,正在關(guān)燈']))
                robot.Close_Flight()
                GPIO.output(LED_CTR, GPIO.HIGH)
                
        elif command.find(u"前") !=-1 and command.find(u"進(jìn)") !=-1:
                print ("前進(jìn)")
                say(random.choice(['好的仿粹,前進(jìn)','向前出發(fā)','是搁吓,前進(jìn)一步']))
                robot.Motor_Forward()
                time.sleep(2)
                robot.Motor_Stop()
                GPIO.output(LED_CTR, GPIO.HIGH)
                
        elif command.find(u"后") !=-1 and command.find(u"退") !=-1:
                print ("后退")
                say(random.choice(['好的,后退','倒車請(qǐng)注意','是吭历,后退一點(diǎn)']))
                robot.Motor_Backward()
                time.sleep(2)
                robot.Motor_Stop()
                GPIO.output(LED_CTR, GPIO.HIGH)
                
        elif command.find(u"左") !=-1 and command.find(u"轉(zhuǎn)") !=-1:
                print ("左轉(zhuǎn)")
                say(random.choice(['好嘞堕仔,向左轉(zhuǎn)','拐啦,拐啦晌区,向左拐啦','好的摩骨,向左轉(zhuǎn)彎','是通贞,向左轉(zhuǎn)彎']))
                robot.Motor_TurnLeft()
                p = GPIO.PWM(11, 3)
                p.start(20)
                time.sleep(0.5)
                p.stop()
                robot.Motor_Stop()
                GPIO.output(11, GPIO.LOW)
                GPIO.output(LED_CTR, GPIO.HIGH)
                
                
        elif command.find(u"右") !=-1 and command.find(u"轉(zhuǎn)") !=-1:
                print ("右轉(zhuǎn)")
                say(random.choice(['好嘞,向右轉(zhuǎn)','拐啦仿吞,拐啦滑频,向右拐啦','好的,向右轉(zhuǎn)彎','是唤冈,向右轉(zhuǎn)彎']))
                robot.Motor_TurnRight()
                p = GPIO.PWM(8, 3)
                p.start(20)
                time.sleep(0.5)
                p.stop()
                robot.Motor_Stop()
                GPIO.output(8, GPIO.LOW)
                GPIO.output(LED_CTR, GPIO.HIGH)
                
        elif command.find(u"黑") !=-1 and command.find(u"線") !=-1:
                print ("黑線")
                robot.TrackLine()
        
        elif command == "狀態(tài)":
            os.system('cat deng.txt door.txt tv.txt >all.txt')
            itchat.send_file("/home/pi/dai/command/all.txt",toUserName='filehelper')
        return 

語(yǔ)音聊天

語(yǔ)音聊天國(guó)內(nèi)做的比較好的有圖靈機(jī)器人峡迷、小i機(jī)器人,百度的小度也不錯(cuò)你虹,不過還沒有什么開放接口绘搞,所以目前在使用圖靈機(jī)器人(采用WEB API 2.0 接口),下面簡(jiǎn)單介紹一下申請(qǐng)傅物、使用方法等基本流程夯辖。

簡(jiǎn)介

圖靈機(jī)器人API是在人工智能的核心能力(包括語(yǔ)義理解、智能問答董饰、場(chǎng)景交互蒿褂、知識(shí)管理等)的基礎(chǔ)上,為廣大開發(fā)者卒暂、合作伙伴和企業(yè)提供的一系列基于云計(jì)算和大數(shù)據(jù)平臺(tái)的在線服務(wù)和開發(fā)接口啄栓。
開發(fā)者可以利用圖靈機(jī)器人的API創(chuàng)建各種在線服務(wù),靈活定義機(jī)器人的屬性也祠、編輯機(jī)器人的智能問答內(nèi)容昙楚,打造個(gè)人專屬智能交互機(jī)器人。

使用流程

注冊(cè)申請(qǐng)圖靈帳號(hào)

登錄圖靈機(jī)器人官方網(wǎng)站http://www.tuling123.com/诈嘿,點(diǎn)擊右上角“注冊(cè)”按鈕進(jìn)行注冊(cè)并激活帳號(hào)堪旧,如下圖所示:

注冊(cè)申請(qǐng)圖靈帳號(hào)

獲取APIKEY

每一個(gè)激活用戶都可以通過圖靈機(jī)器人開放平臺(tái)獲取多個(gè)APIKEY(當(dāng)前每個(gè)用戶可最多獲取5個(gè)APIKEY),用戶可以根據(jù)自己的需要獲取不同的圖靈APIKEY來應(yīng)用于多種場(chǎng)景奖亚,獲取成功后就等于拿到了開啟圖靈服務(wù)的鑰匙淳梦。
登錄圖靈帳號(hào),進(jìn)入個(gè)人中心昔字,在“我的機(jī)器人》機(jī)器人詳情》接入”頁(yè)面即可看到每一個(gè)機(jī)器人的API KEY谭跨,如下圖所示:


獲取APIKEY
編碼方式
UTF-8(調(diào)用圖靈API的各個(gè)環(huán)節(jié)的編碼方式均為UTF-8)
接口地址
http://openapi.tuling123.com/openapi/api/v2
請(qǐng)求方式
HTTP POST
請(qǐng)求參數(shù)

請(qǐng)求參數(shù)格式為 json
請(qǐng)求示例:

{
    "reqType":0,
    "perception": {
        "inputText": {
            "text": "附近的酒店"
        },
        "inputImage": {
            "url": "imageUrl"
        },
        "selfInfo": {
            "location": {
                "city": "北京",
                "province": "北京",
                "street": "信息路"
            }
        }
    },
    "userInfo": {
        "apiKey": "",
        "userId": ""
    }
}

參數(shù)說明

參數(shù) 類型 是否必須 取值范圍 說明
reqType int N - 輸入類型:0-文本(默認(rèn))、1-圖片李滴、2-音頻
perception - Y - 輸入信息
userInfo - Y - 用戶參數(shù)

perception

參數(shù) 類型 是否必須 取值范圍 說明
inputText - N - 文本信息
inputImage - N - 圖片信息
inputMedia - N - 音頻信息
selfInfo - N - 客戶端屬性

注意:輸入?yún)?shù)必須包含inputText或inputImage或inputMedia!

inputText

參數(shù) 類型 是否必須 取值范圍 說明
text String Y 1-128字符 直接輸入文本

inputImage

參數(shù) 類型 是否必須 取值范圍 說明
url String Y 圖片地址

inputMedia

參數(shù) 類型 是否必須 取值范圍 說明
url String Y 音頻地址

selfInfo

參數(shù) 類型 是否必須 取值范圍 說明
location - N - 地理位置信息

location

參數(shù) 類型 是否必須 取值范圍 說明
city String Y - 所在城市
province String N - 省份
street String N - 街道

userInfo

參數(shù) 類型 是否必須 取值范圍 說明
apiKey String Y 32位 機(jī)器人標(biāo)識(shí)
userId String Y 長(zhǎng)度小于等于32位 用戶唯一標(biāo)識(shí)
groupId String N 長(zhǎng)度小于等于64位 群聊唯一標(biāo)識(shí)
userIdName String N 長(zhǎng)度小于等于64位 群內(nèi)用戶昵稱
輸出參數(shù)

輸出示例:

  {
    "intent": {
        "code": 10005,
        "intentName": "",
        "actionName": "",
        "parameters": {
            "nearby_place": "酒店"
        }
    },
    "results": [
        {
            "groupType": 1,
            "resultType": "url",
            "values": {
                "url": "http://m.elong.com/hotel/0101/nlist/#indate=2016-12-10&outdate=2016-12-11&keywords=%E4%BF%A1%E6%81%AF%E8%B7%AF"
            }
        },
        {
            "groupType": 1,
            "resultType": "text",
            "values": {
                "text": "親蛮瞄,已幫你找到相關(guān)酒店信息"
            }
        }
    ]
}

參數(shù)說明

參數(shù) 類型 是否必須 取值范圍 說明
intent - Y - 請(qǐng)求意圖
results - N - 輸出結(jié)果集

intent

參數(shù) 類型 是否包含 取值范圍 說明
code int Y - 輸出功能code
intentName String N - 意圖名稱
actionName String N - 意圖動(dòng)作名稱
parameters String N - 功能相關(guān)參數(shù)

results

參數(shù) 類型 是否包含 取值范圍 說明
resultType String Y 文本(text);連接(url);音頻(voice);視頻(video);圖片(image);圖文(news) 輸出類型
values - Y - 輸出值
groupType int Y - ‘組’編號(hào):0為獨(dú)立輸出所坯,大于0時(shí)可能包含同組相關(guān)內(nèi)容 (如:音頻與文本為一組時(shí)說明內(nèi)容一致)

核心代碼

#coding=utf8
import urllib.request
import json

KEY = 'd4473a5b08da492cad0bc98bb8bc77bb'

def get_response(msg):
    apiUrl = 'http://openapi.tuling123.com/openapi/api/v2'
    data = json.dumps({
                "reqType":0,
                "perception": {
                    "inputText": {
                        "text": msg
                    },
                    "selfInfo": {
                        "location": {
                            "city": "北京",
                            "province": "北京",
                            "street": "中關(guān)村南大街"
                        }
                    }
                },
                "userInfo": {
                    "apiKey": KEY,
                    "userId": "123555"
                }
            }).encode('utf-8')
    try:
        r = urllib.request.urlopen(url=apiUrl, data=data)
        t = r.read().decode('utf-8')
        result = json.loads(t)
        return result
    except:
        return
if __name__ == '__main__':
    #get_response('給我一張小狗照片').get('results')('image')
    print (get_response('給我一張小狗照片')['results'][0]['values']['url'].encode('utf-8').decode())

完整代碼

所有的代碼都上傳到了我的GitHub上
Rascar-Wechat — A COOL Raspberry Intelligent car based on the Wechat

系列教程

人工智能-樹莓派小車(1)——DuerOS語(yǔ)音喚醒
人工智能-樹莓派小車(2)——GPIO接口介紹
人工智能-樹莓派小車(3)——GPIO控制小車
人工智能-樹莓派小車(4)——通過語(yǔ)音玩轉(zhuǎn)智能小車
人工智能-樹莓派小車(5)——用微信控制智能小車

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挂捅,隨后出現(xiàn)的幾起案子芹助,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件状土,死亡現(xiàn)場(chǎng)離奇詭異无蜂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蒙谓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門斥季,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人累驮,你說我怎么就攤上這事酣倾。” “怎么了谤专?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵躁锡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我置侍,道長(zhǎng)映之,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任蜡坊,我火速辦了婚禮杠输,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘算色。我一直安慰自己抬伺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布灾梦。 她就那樣靜靜地躺著峡钓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪若河。 梳的紋絲不亂的頭發(fā)上能岩,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音萧福,去河邊找鬼拉鹃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鲫忍,可吹牛的內(nèi)容都是我干的膏燕。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼悟民,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼坝辫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起射亏,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤近忙,失蹤者是張志新(化名)和其女友劉穎竭业,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體及舍,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡未辆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锯玛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咐柜。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖更振,靈堂內(nèi)的尸體忽然破棺而出炕桨,到底是詐尸還是另有隱情,我是刑警寧澤肯腕,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布献宫,位于F島的核電站,受9級(jí)特大地震影響实撒,放射性物質(zhì)發(fā)生泄漏姊途。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一知态、第九天 我趴在偏房一處隱蔽的房頂上張望捷兰。 院中可真熱鬧,春花似錦负敏、人聲如沸贡茅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顶考。三九已至,卻和暖如春妖泄,著一層夾襖步出監(jiān)牢的瞬間驹沿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工蹈胡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渊季,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓罚渐,卻偏偏與公主長(zhǎng)得像却汉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荷并,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容