From 524cc62469bcb1ea93e318e772ad4a4b8e530556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=92=8C?= Date: Wed, 6 Dec 2017 09:39:21 +0800 Subject: [PATCH 01/24] Update .travis.yml --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8d9f7f2..b8f5b29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,4 @@ before_install: install: - glide install script: - - ls - - go build ./voice - - go build ./ocr + - go test From c90789b5c4a15fcd5837cfd4a97fb188172554d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=92=8C?= Date: Wed, 6 Dec 2017 09:43:41 +0800 Subject: [PATCH 02/24] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8f5b29..ab058cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,5 @@ before_install: install: - glide install script: - - go test + - go test ./ocr + - go test ./voice From 1b53439017956d0c32c545159028b987f9e3a1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=92=8C?= Date: Wed, 6 Dec 2017 09:52:17 +0800 Subject: [PATCH 03/24] Update .travis.yml --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab058cc..d682c01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,5 +6,4 @@ before_install: install: - glide install script: - - go test ./ocr - - go test ./voice + - go test ./... From b633210a1ab66e97abde92e023a8ecbe49fa73f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=92=8C?= Date: Wed, 6 Dec 2017 10:26:08 +0800 Subject: [PATCH 04/24] Update glide.yaml --- glide.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glide.yaml b/glide.yaml index 328298a..3a50bb8 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,4 +1,4 @@ package: github.com/chenqinghe/baidu-ai-go-sdk import: - package: github.com/imroc/req - version: v0.1.6 + version: v0.1.8 From 50b93ecf68d05300a6c6a1c7d7c9037a1e19ae4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E5=92=8C?= Date: Wed, 6 Dec 2017 10:31:12 +0800 Subject: [PATCH 05/24] fix type bug --- ocr/general.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocr/general.go b/ocr/general.go index 667fc1b..a7905c9 100644 --- a/ocr/general.go +++ b/ocr/general.go @@ -105,7 +105,7 @@ func parseParams(def, need map[string]string) map[string]string { return def } -func doRequest(url string, params map[string]string) (rs []byte, err error) { +func doRequest(url string, params map[string]interface{}) (rs []byte, err error) { resp, err := req.Post(url, req.Param(params), req.Header{"Content-Type": "application/x-www-form-urlencoded"}) if err != nil { From b95067fa46a79cebc955c648d2a5cbf28fba3208 Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sat, 9 Dec 2017 17:25:58 +0800 Subject: [PATCH 06/24] update .gitignore --- .gitignore | 1 + .idea/vcs.xml | 6 + internal/client.go => client.go | 2 +- ocr/general_test.go | 1 + utils.go | 1 + utils_test.go | 1 + voice/asr.go | 1 + voice/client.go | 1 + voice/result.go | 1 + voice/tts.go | 208 ++++++++++++++++++++++++++++++++ voice/tts_test.go | 27 +++++ voice/voice.go | 170 -------------------------- 12 files changed, 249 insertions(+), 171 deletions(-) create mode 100644 .idea/vcs.xml rename internal/client.go => client.go (99%) create mode 100644 ocr/general_test.go create mode 100644 utils.go create mode 100644 utils_test.go create mode 100644 voice/asr.go create mode 100644 voice/client.go create mode 100644 voice/result.go create mode 100644 voice/tts.go create mode 100644 voice/tts_test.go delete mode 100644 voice/voice.go diff --git a/.gitignore b/.gitignore index 4808a34..66164d1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ .glide/ vendor/ glide.lock +./idea diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/internal/client.go b/client.go similarity index 99% rename from internal/client.go rename to client.go index d3670cb..0163ee6 100644 --- a/internal/client.go +++ b/client.go @@ -1,4 +1,4 @@ -package internal +package gosdk import ( "errors" diff --git a/ocr/general_test.go b/ocr/general_test.go new file mode 100644 index 0000000..e130acc --- /dev/null +++ b/ocr/general_test.go @@ -0,0 +1 @@ +package ocr diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..c118020 --- /dev/null +++ b/utils.go @@ -0,0 +1 @@ +package gosdk diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000..c118020 --- /dev/null +++ b/utils_test.go @@ -0,0 +1 @@ +package gosdk diff --git a/voice/asr.go b/voice/asr.go new file mode 100644 index 0000000..ef01e8d --- /dev/null +++ b/voice/asr.go @@ -0,0 +1 @@ +package voice diff --git a/voice/client.go b/voice/client.go new file mode 100644 index 0000000..ef01e8d --- /dev/null +++ b/voice/client.go @@ -0,0 +1 @@ +package voice diff --git a/voice/result.go b/voice/result.go new file mode 100644 index 0000000..ef01e8d --- /dev/null +++ b/voice/result.go @@ -0,0 +1 @@ +package voice diff --git a/voice/tts.go b/voice/tts.go new file mode 100644 index 0000000..92ec7ae --- /dev/null +++ b/voice/tts.go @@ -0,0 +1,208 @@ +// 语音处理 +// 利用百度RESTFul API 进行语音及文字的相互转换 +package voice + +import ( + "errors" + + "io/ioutil" + + "strings" + + "github.com/chenqinghe/baidu-ai-go-sdk" + "github.com/imroc/req" +) + +const ( + TTS_URL = "http://tsn.baidu.com/text2audio" + ASR_URL = "http://vop.baidu.com/server_api" +) + +const ( + B = 1 << 10 * iota + KB + MB + GB + TB + PB +) + +var ( + ErrTextTooLong = errors.New("The input string is too long") +) + +//VoiceClient represent a voice service application. +type VoiceClient struct { + *gosdk.Client +} + +type TTSParams struct { + Text string + Token string + Cuid string + ClientType int + Language string + Speed int + Pitch int + Volume int + Person int +} +type TTSParam func(*TTSParams) + +func Cuid(str string) TTSParam { + if len(str) > 60 { + str = string(str[:60]) + } + return func(p *TTSParams) { + p.Cuid = str + } +} + +func Speed(spd int) TTSParam { + if spd > 9 { + spd = 9 + } + if spd < 0 { + spd = 0 + } + return func(p *TTSParams) { + p.Speed = spd + } +} + +func Pitch(pit int) TTSParam { + if pit > 9 { + pit = 9 + } + if pit < 0 { + pit = 0 + } + return func(p *TTSParams) { + p.Pitch = pit + } +} + +func Volume(vol int) TTSParam { + if vol > 15 { + vol = 15 + } + if vol < 0 { + vol = 0 + } + return func(p *TTSParams) { + p.Volume = vol + } +} + +func Person(per int) TTSParam { + if per != 0 && per != 1 && per != 3 && per != 4 { + per = 0 + } + return func(p *TTSParams) { + p.Person = per + } +} + +//语音识别响应信息 +type ASRResponse struct { + CorpusNo string `json:"corpus_no"` + ERRMSG string `json:"err_msg"` + ERRNO int `json:"err_no"` + Result []string `json:"result"` + SN string `json:"sn"` +} + +//语音识别参数 +type ASRParams struct { + Format string `json:"format"` //语音的格式,pcm 或者 wav 或者 amr。不区分大小写 + Rate int `json:"rate"` //采样率,支持 8000 或者 16000 + Channel int `json:"channel"` //声道数,仅支持单声道,请填写固定值 1 + Cuid string `json:"cuid"` //用户唯一标识,用来区分用户,计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 + Token string `json:"token"` //开放平台获取到的开发者access_token + Lan string `json:"lan"` //语种选择,默认中文(zh)。 中文=zh、粤语=ct、英文=en,不区分大小写 + Speech string `json:"speech"` //真实的语音数据 ,需要进行base64 编码。与len参数连一起使用 + Len int `json:"len"` //原始语音长度,单位字节 +} + +//TextToSpeech 语音合成,将文字转换为语音 +func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, error) { + + if len(txt) >= 1024 { + return nil, ErrTextTooLong + } + if err := vc.Auth(); err != nil { + return nil, err + } + + ttsparams := &TTSParams{ + Text: txt, + Token: vc.AccessToken, + Cuid: "", + ClientType: 1, + Language: "zh", + Speed: 5, + Pitch: 5, + Volume: 5, + Person: 0, + } + + for _, param := range params { + param(ttsparams) + } + + resp, err := req.Post(TTS_URL, req.Param(gosdk.StructToMap(params))) + if err != nil { + return nil, err + } + respHeader := resp.Response().Header + contentType, ok := respHeader["Content-Type"] + if !ok { + return nil, errors.New("No Content-Type Set.") + } + if contentType[0] == "audio/mp3" { + respBody, err := ioutil.ReadAll(resp.Response().Body) + if err != nil { + return nil, err + } + return respBody, nil + } else { + respStr, err := resp.ToString() + if err != nil { + return nil, err + } + return nil, errors.New("调用服务失败:" + respStr) + } + +} + +//SpeechToText 语音识别,将语音翻译成文字 +func (vc *VoiceClient) SpeechToText(ap ASRParams) ([]string, error) { + if ap.Len > 8*10*MB { + return []string{}, errors.New("文件大小不能超过10M") + } + if err := vc.Auth(); err != nil { + return []string{}, err + } + ap.Token = vc.AccessToken + resp, err := req.Post(ASR_URL, req.Header{ + "Content-Type": "application/json", + }, req.BodyJSON(ap)) + if err != nil { + return []string{}, err + } + var rs ASRResponse + if err := resp.ToJSON(&rs); err != nil { + return []string{}, err + } + if !strings.Contains(rs.ERRMSG, "success") || rs.ERRNO != 0 { + return []string{}, errors.New("调用服务失败:" + rs.ERRMSG) + } + return rs.Result, nil +} + +func NewVoiceClient(ApiKey, secretKey string) *VoiceClient { + return &VoiceClient{ + Client: gosdk.NewClient(ApiKey, secretKey), + TTSConfig: defaultTTSConfig, + } +} diff --git a/voice/tts_test.go b/voice/tts_test.go new file mode 100644 index 0000000..ac8289c --- /dev/null +++ b/voice/tts_test.go @@ -0,0 +1,27 @@ +package voice + +import ( + "testing" +) + +var ( + apikey = "MDNsII2jkUtbF729GQOZt7FS" + secretkey = "0vWCVCLsbWHMSH1wjvxaDq4VmvCZM2O9" +) + +var client *VoiceClient + +func TestNewVoiceClient(t *testing.T) { + client = NewVoiceClient(apikey, secretkey) +} + +func TestVoiceClient_TextToSpeech(t *testing.T) { + _, err := client.TextToSpeech("你好") + if err != nil { + t.Fail() + } +} + +func TestVoiceClient_TextToSpeech2(t *testing.T) { + _,err:=client.SpeechToText() +} diff --git a/voice/voice.go b/voice/voice.go deleted file mode 100644 index e11e676..0000000 --- a/voice/voice.go +++ /dev/null @@ -1,170 +0,0 @@ -// 语音处理 -// 利用百度RESTFul API 进行语音及文字的相互转换 -package voice - -import ( - "errors" - - "io/ioutil" - - sdk "github.com/chenqinghe/baidu-ai-go-sdk/internal" - "github.com/imroc/req" - "math/rand" - "net" - "strconv" - "strings" -) - -const ( - TTS_URL string = "http://tsn.baidu.com/text2audio" - ASR_URL string = "http://vop.baidu.com/server_api" -) -const ( - B int = 1 << (10 * iota) - KB - MB -) - -var ( - ErrNoTTSConfig = errors.New("No TTSConfig.please set TTSConfig correctlly first or call method UseDefaultTTSConfig") - ErrTextTooLong = errors.New("The input string is too long") -) - -//VoiceClient 代表一个语音服务应用 -type VoiceClient struct { - *sdk.Client - TTSConfig *TTSConfig -} - -//语音合成参数 -type TTSConfig struct { - SPD int //语速,取值0-9,默认为5中语速 - PIT int //音调,取值0-9,默认为5中语调 - VOL int //音量,取值0-15,默认为5中音量 - PER int //发音人选择, 0为普通女声,1为普通男声,3为情感合成-度逍遥,4为情感合成-度丫丫,默认为普通女声 -} - -var defaultTTSConfig = &TTSConfig{ - SPD: 5, - PIT: 5, - VOL: 5, - PER: 0, -} - -//语音识别响应信息 -type ASRResponse struct { - CorpusNo string `json:"corpus_no"` - ERRMSG string `json:"err_msg"` - ERRNO int `json:"err_no"` - Result []string `json:"result"` - SN string `json:"sn"` -} - -//语音识别参数 -type ASRParams struct { - Format string `json:"format"` //语音的格式,pcm 或者 wav 或者 amr。不区分大小写 - Rate int `json:"rate"` //采样率,支持 8000 或者 16000 - Channel int `json:"channel"` //声道数,仅支持单声道,请填写固定值 1 - Cuid string `json:"cuid"` //用户唯一标识,用来区分用户,计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 - Token string `json:"token"` //开放平台获取到的开发者access_token - Lan string `json:"lan"` //语种选择,默认中文(zh)。 中文=zh、粤语=ct、英文=en,不区分大小写 - Speech string `json:"speech"` //真实的语音数据 ,需要进行base64 编码。与len参数连一起使用 - Len int `json:"len"` //原始语音长度,单位字节 -} - -//TextToSpeech 语音合成,将文字转换为语音 -func (vc *VoiceClient) TextToSpeech(txt string) ([]byte, error) { - - if len(txt) >= 1024 { - return []byte{}, ErrTextTooLong - } - if err := vc.Auth(); err != nil { - return []byte{}, err - } - if vc.TTSConfig == nil { - return []byte{}, ErrNoTTSConfig - } - itfcs, err := net.Interfaces() - if err != nil { - return []byte{}, err - } - mac := itfcs[0].HardwareAddr.String() - if mac == "" { - mac = randomStr(10) - } - params := req.Param{ - "tex": txt, //必填 合成的文本,使用UTF-8编码,请注意文本长度必须小于1024字节 - "lan": "zh", //必填 语言选择,目前只有中英文混合模式,填写固定值zh - "tok": vc.AccessToken, //必填 开放平台获取到的开发者access_token(见上面的“鉴权认证机制”段落) - "ctp": "1", //必填 客户端类型选择,web端填写固定值1 - "cuid": mac, //必填 用户唯一标识,用来区分用户,计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 - "spd": strconv.Itoa(vc.TTSConfig.SPD), //选填 语速,取值0-9,默认为5中语速 - "pit": strconv.Itoa(vc.TTSConfig.PIT), //选填 音调,取值0-9,默认为5中语调 - "vol": strconv.Itoa(vc.TTSConfig.VOL), //选填 音量,取值0-15,默认为5中音量 - "per": strconv.Itoa(vc.TTSConfig.PER), //选填 发音人选择, 0为普通女声,1为普通男声,3为情感合成-度逍遥,4为情感合成-度丫丫,默认为普通女声 - } - resp, err := req.Post(TTS_URL, params) - if err != nil { - return []byte{}, err - } - respHeader := resp.Response().Header - contentType, ok := respHeader["Content-Type"] - if !ok { - return []byte{}, errors.New("No Content-Type Set.") - } - if contentType[0] == "audio/mp3" { - respBody, err := ioutil.ReadAll(resp.Response().Body) - if err != nil { - return []byte{}, err - } - return respBody, nil - } else { - respStr, err := resp.ToString() - if err != nil { - return []byte{}, err - } - return []byte{}, errors.New("调用服务失败:" + respStr) - } -} - -//SpeechToText 语音识别,将语音翻译成文字 -func (vc *VoiceClient) SpeechToText(ap ASRParams) ([]string, error) { - if ap.Len > 8*10*MB { - return []string{}, errors.New("文件大小不能超过10M") - } - if err := vc.Auth(); err != nil { - return []string{}, err - } - ap.Token = vc.AccessToken - resp, err := req.Post(ASR_URL, req.Header{ - "Content-Type": "application/json", - }, req.BodyJSON(ap)) - if err != nil { - return []string{}, err - } - var rs ASRResponse - if err := resp.ToJSON(&rs); err != nil { - return []string{}, err - } - if !strings.Contains(rs.ERRMSG, "success") || rs.ERRNO != 0 { - return []string{}, errors.New("调用服务失败:" + rs.ERRMSG) - } - return rs.Result, nil -} - -func NewVoiceClient(ApiKey, secretKey string) *VoiceClient { - return &VoiceClient{ - Client: sdk.NewClient(ApiKey, secretKey), - TTSConfig: defaultTTSConfig, - } -} - -func randomStr(length int) string { - var baseStr = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM0123456789" - var bt []byte = make([]byte, length) - for i := 0; i < length; i++ { - k := rand.Intn(62) - bt[i] = baseStr[k] - } - return string(bt) -} From 45ae679d37092374c2bdc175b7782bf08d9f021e Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sat, 9 Dec 2017 17:27:41 +0800 Subject: [PATCH 07/24] rew --- client.go | 24 +++++++------- ocr/general.go | 12 +++---- ocr/particular.go | 14 ++++----- utils.go | 15 +++++++++ utils_test.go | 25 +++++++++++++++ voice/asr.go | 54 ++++++++++++++++++++++++++++++++ voice/client.go | 21 +++++++++++++ voice/tts.go | 79 +++-------------------------------------------- voice/tts_test.go | 15 +++++---- 9 files changed, 154 insertions(+), 105 deletions(-) diff --git a/client.go b/client.go index 0163ee6..3284417 100644 --- a/client.go +++ b/client.go @@ -6,7 +6,14 @@ import ( "github.com/imroc/req" ) -const VOICE_AUTH_URL string = "https://openapi.baidu.com/oauth/2.0/token" +const VOICE_AUTH_URL = "https://openapi.baidu.com/oauth/2.0/token" + +//Authorizer 用于设置access_token +//可以通过RESTFul api的方式从百度方获取 +//有效期为一个月,可以存至数据库中然后从数据库中获取 +type Authorizer interface { + Authorize(*Client) error +} type Client struct { ClientID string @@ -31,17 +38,8 @@ type AuthResponseFailed struct { ErrorDescription string `json:"error_description"` //错误描述信息,帮助理解和解决发生的错误。 } -//Authorizer 用于设置access_token -//可以通过RESTFul api的方式从百度方获取 -//有效期为一个月,可以存至数据库中然后从数据库中获取 -type Authorizer interface { - Authorize(client *Client) error -} - type DefaultAuthorizer struct{} -type RestApiAuthorizer DefaultAuthorizer - func (da DefaultAuthorizer) Authorize(client *Client) error { resp, err := req.Post(VOICE_AUTH_URL, req.Param{ "grant_type": "client_credentials", @@ -67,7 +65,11 @@ func (client *Client) Auth() error { if client.AccessToken != "" { return nil } - return client.Authorizer.Authorize(client) + + if err := client.Authorizer.Authorize(client); err != nil { + return err + } + return nil } func (client *Client) SetAuther(auth Authorizer) { diff --git a/ocr/general.go b/ocr/general.go index a7905c9..d7e1f42 100644 --- a/ocr/general.go +++ b/ocr/general.go @@ -3,14 +3,14 @@ package ocr import ( "encoding/base64" - sdk "github.com/chenqinghe/baidu-ai-go-sdk/internal" + "github.com/chenqinghe/baidu-ai-go-sdk" "github.com/imroc/req" ) const ( - OCR_GENERAL_BASIC_URL string = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic" - OCR_GENERAL_WITH_LOCATION_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general" - OCR_GENERAL_ENHANCED_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_enhanced" + OCR_GENERAL_BASIC_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic" + OCR_GENERAL_WITH_LOCATION_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general" + OCR_GENERAL_ENHANCED_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_enhanced" ) var defaultGeneralBasicParams = map[string]string{ @@ -39,12 +39,12 @@ var defaultDeneralEnhancedParams = map[string]string{ } type OCRClient struct { - *sdk.Client + *gosdk.Client } func NewOCRClient(apiKey, secretKey string) *OCRClient { return &OCRClient{ - Client: sdk.NewClient(apiKey, secretKey), + Client: gosdk.NewClient(apiKey, secretKey), } } diff --git a/ocr/particular.go b/ocr/particular.go index 591ef4b..deaba91 100644 --- a/ocr/particular.go +++ b/ocr/particular.go @@ -1,13 +1,13 @@ package ocr const ( - OCR_WEBIMAGE_URL string = "https://aip.baidubce.com/rest/2.0/ocr/v1/webimage" - OCR_IDCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard" - OCR_BANKCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard" - OCR_DRIVERLICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license" - OCR_VEHICLELICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license" - OCR_LICENSEPLATE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate" - OCR_FORM_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/request" + OCR_WEBIMAGE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/webimage" + OCR_IDCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard" + OCR_BANKCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard" + OCR_DRIVERLICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license" + OCR_VEHICLELICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license" + OCR_LICENSEPLATE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate" + OCR_FORM_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/request" ) var ( diff --git a/utils.go b/utils.go index c118020..38e8c6e 100644 --- a/utils.go +++ b/utils.go @@ -1 +1,16 @@ package gosdk + +import ( + "reflect" +) + +func StructToMap(obj interface{}) map[string]interface{} { + t := reflect.TypeOf(obj) + v := reflect.ValueOf(obj) + + var data = make(map[string]interface{}) + for i := 0; i < t.NumField(); i++ { + data[t.Field(i).Name] = v.Field(i).Interface() + } + return data +} diff --git a/utils_test.go b/utils_test.go index c118020..a614b0f 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1 +1,26 @@ package gosdk + +import ( + "testing" +) + +type User struct { + Name string + Age int +} + +func TestStructToMap(t *testing.T) { + user := &User{ + Name: "bob", + Age: 18, + } + m := StructToMap(*user) + name, ok := m["Name"] + if !ok || name != "bob" { + t.Fail() + } + age, ok := m["Age"] + if !ok || age != 18 { + t.Fail() + } +} diff --git a/voice/asr.go b/voice/asr.go index ef01e8d..4e9f424 100644 --- a/voice/asr.go +++ b/voice/asr.go @@ -1 +1,55 @@ package voice + +import ( + "errors" + "github.com/imroc/req" + "strings" +) + +const ASR_URL = "http://vop.baidu.com/server_api" + +//语音识别响应信息 +type ASRResponse struct { + CorpusNo string `json:"corpus_no"` + ERRMSG string `json:"err_msg"` + ERRNO int `json:"err_no"` + Result []string `json:"result"` + SN string `json:"sn"` +} + +//语音识别参数 +type ASRParams struct { + Format string `json:"format"` //语音的格式,pcm 或者 wav 或者 amr。不区分大小写 + Rate int `json:"rate"` //采样率,支持 8000 或者 16000 + Channel int `json:"channel"` //声道数,仅支持单声道,请填写固定值 1 + Cuid string `json:"cuid"` //用户唯一标识,用来区分用户,计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 + Token string `json:"token"` //开放平台获取到的开发者access_token + Lan string `json:"lan"` //语种选择,默认中文(zh)。 中文=zh、粤语=ct、英文=en,不区分大小写 + Speech string `json:"speech"` //真实的语音数据 ,需要进行base64 编码。与len参数连一起使用 + Len int `json:"len"` //原始语音长度,单位字节 +} + +//SpeechToText 语音识别,将语音翻译成文字 +func (vc *VoiceClient) SpeechToText(ap ASRParams) ([]string, error) { + if ap.Len > 8*10*MB { + return []string{}, errors.New("文件大小不能超过10M") + } + if err := vc.Auth(); err != nil { + return []string{}, err + } + ap.Token = vc.AccessToken + resp, err := req.Post(ASR_URL, req.Header{ + "Content-Type": "application/json", + }, req.BodyJSON(ap)) + if err != nil { + return []string{}, err + } + var rs ASRResponse + if err := resp.ToJSON(&rs); err != nil { + return []string{}, err + } + if !strings.Contains(rs.ERRMSG, "success") || rs.ERRNO != 0 { + return []string{}, errors.New("调用服务失败:" + rs.ERRMSG) + } + return rs.Result, nil +} diff --git a/voice/client.go b/voice/client.go index ef01e8d..6e4c7e2 100644 --- a/voice/client.go +++ b/voice/client.go @@ -1 +1,22 @@ package voice + +import "github.com/chenqinghe/baidu-ai-go-sdk" + +const ( + B = 1 << 10 * iota + KB + MB + GB + TB + PB +) + +type VoiceClient struct { + *gosdk.Client +} + +func NewVoiceClient(apiKey, apiSecret string) *VoiceClient { + return &VoiceClient{ + Client: gosdk.NewClient(apiKey, apiSecret), + } +} diff --git a/voice/tts.go b/voice/tts.go index 92ec7ae..c5039de 100644 --- a/voice/tts.go +++ b/voice/tts.go @@ -7,35 +7,16 @@ import ( "io/ioutil" - "strings" - "github.com/chenqinghe/baidu-ai-go-sdk" "github.com/imroc/req" ) -const ( - TTS_URL = "http://tsn.baidu.com/text2audio" - ASR_URL = "http://vop.baidu.com/server_api" -) - -const ( - B = 1 << 10 * iota - KB - MB - GB - TB - PB -) +const TTS_URL = "http://tsn.baidu.com/text2audio" var ( ErrTextTooLong = errors.New("The input string is too long") ) -//VoiceClient represent a voice service application. -type VoiceClient struct { - *gosdk.Client -} - type TTSParams struct { Text string Token string @@ -47,6 +28,7 @@ type TTSParams struct { Volume int Person int } + type TTSParam func(*TTSParams) func Cuid(str string) TTSParam { @@ -103,27 +85,6 @@ func Person(per int) TTSParam { } } -//语音识别响应信息 -type ASRResponse struct { - CorpusNo string `json:"corpus_no"` - ERRMSG string `json:"err_msg"` - ERRNO int `json:"err_no"` - Result []string `json:"result"` - SN string `json:"sn"` -} - -//语音识别参数 -type ASRParams struct { - Format string `json:"format"` //语音的格式,pcm 或者 wav 或者 amr。不区分大小写 - Rate int `json:"rate"` //采样率,支持 8000 或者 16000 - Channel int `json:"channel"` //声道数,仅支持单声道,请填写固定值 1 - Cuid string `json:"cuid"` //用户唯一标识,用来区分用户,计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 - Token string `json:"token"` //开放平台获取到的开发者access_token - Lan string `json:"lan"` //语种选择,默认中文(zh)。 中文=zh、粤语=ct、英文=en,不区分大小写 - Speech string `json:"speech"` //真实的语音数据 ,需要进行base64 编码。与len参数连一起使用 - Len int `json:"len"` //原始语音长度,单位字节 -} - //TextToSpeech 语音合成,将文字转换为语音 func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, error) { @@ -137,7 +98,7 @@ func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, err ttsparams := &TTSParams{ Text: txt, Token: vc.AccessToken, - Cuid: "", + Cuid: "as", ClientType: 1, Language: "zh", Speed: 5, @@ -150,7 +111,7 @@ func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, err param(ttsparams) } - resp, err := req.Post(TTS_URL, req.Param(gosdk.StructToMap(params))) + resp, err := req.Post(TTS_URL, req.Param(gosdk.StructToMap(*ttsparams))) if err != nil { return nil, err } @@ -174,35 +135,3 @@ func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, err } } - -//SpeechToText 语音识别,将语音翻译成文字 -func (vc *VoiceClient) SpeechToText(ap ASRParams) ([]string, error) { - if ap.Len > 8*10*MB { - return []string{}, errors.New("文件大小不能超过10M") - } - if err := vc.Auth(); err != nil { - return []string{}, err - } - ap.Token = vc.AccessToken - resp, err := req.Post(ASR_URL, req.Header{ - "Content-Type": "application/json", - }, req.BodyJSON(ap)) - if err != nil { - return []string{}, err - } - var rs ASRResponse - if err := resp.ToJSON(&rs); err != nil { - return []string{}, err - } - if !strings.Contains(rs.ERRMSG, "success") || rs.ERRNO != 0 { - return []string{}, errors.New("调用服务失败:" + rs.ERRMSG) - } - return rs.Result, nil -} - -func NewVoiceClient(ApiKey, secretKey string) *VoiceClient { - return &VoiceClient{ - Client: gosdk.NewClient(ApiKey, secretKey), - TTSConfig: defaultTTSConfig, - } -} diff --git a/voice/tts_test.go b/voice/tts_test.go index ac8289c..43a84ae 100644 --- a/voice/tts_test.go +++ b/voice/tts_test.go @@ -11,17 +11,20 @@ var ( var client *VoiceClient -func TestNewVoiceClient(t *testing.T) { +func init() { client = NewVoiceClient(apikey, secretkey) + } func TestVoiceClient_TextToSpeech(t *testing.T) { - _, err := client.TextToSpeech("你好") + _, err := client.TextToSpeech("你好", + Speed(10), + Person(1), + Volume(10), + Cuid("1234567890"), + ) if err != nil { + t.Fatal(err) t.Fail() } } - -func TestVoiceClient_TextToSpeech2(t *testing.T) { - _,err:=client.SpeechToText() -} From 6c27662d67fbefd0ab35f3b51bb918a8668283e0 Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sat, 9 Dec 2017 17:28:55 +0800 Subject: [PATCH 08/24] ignore idea/* --- .idea/baidu-ai-go-sdk.iml | 9 + .idea/modules.xml | 8 + .idea/workspace.xml | 401 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 .idea/baidu-ai-go-sdk.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/baidu-ai-go-sdk.iml b/.idea/baidu-ai-go-sdk.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/baidu-ai-go-sdk.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..59855a3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..6471488 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sdk + []byte{} + client + + + nil + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 5dd30edf531ba593caf401d3bca4df49e590bb11 Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sun, 10 Dec 2017 21:17:44 +0800 Subject: [PATCH 14/24] restruction voice --- example/16k.pcm | Bin 0 -> 129600 bytes example/hello.wav | Bin 32576 -> 0 bytes example/voice.go | 2 +- voice/asr.go | 130 ++++++++++++++++++++++++++++++++++++---------- voice/tts.go | 3 +- 5 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 example/16k.pcm delete mode 100644 example/hello.wav diff --git a/example/16k.pcm b/example/16k.pcm new file mode 100644 index 0000000000000000000000000000000000000000..e5194ef726f42545b8adbfc6ad2b1c9836c90ecc GIT binary patch literal 129600 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 8*10*MB { - return []string{}, errors.New("文件大小不能超过10M") - } - if err := vc.Auth(); err != nil { - return []string{}, err - } - ap.Token = vc.AccessToken - resp, err := req.Post(ASR_URL, req.Header{ + Format string `json:"format"` //语音的格式,pcm 或者 wav 或者 amr。不区分大小写 + Rate int `json:"rate"` //采样率,支持 8000 或者 16000 + Channel int `json:"channel"` //声道数,仅支持单声道,请填写固定值 1 + Cuid string `json:"cuid"` //用户唯一标识,用来区分用户,计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 + Token string `json:"token"` //开放平台获取到的开发者access_token + Language string `json:"lan"` //语种选择,默认中文(zh)。 中文=zh、粤语=ct、英文=en,不区分大小写 + Speech string `json:"speech"` //真实的语音数据 ,需要进行base64 编码。与len参数连一起使用 + Length int `json:"len"` //原始语音长度,单位字节 +} + +type ASRParam func(params *ASRParams) + +func Format(fmt string) ASRParam { + + if fmt != "pcm" && fmt != "wav" && fmt != "amr" { + fmt = "pcm" + } + return func(params *ASRParams) { + params.Format = fmt + } +} + +func Rate(rate int) ASRParam { + if rate != 8000 && rate != 16000 { + rate = 8000 + } + return func(params *ASRParams) { + params.Rate = rate + } +} + +func Channel(c int) ASRParam { + return func(params *ASRParams) { + params.Channel = 1 //固定值1 + } +} + +func Cuid(cuid string) ASRParam { + if len(cuid) > 60 { + cuid = string(cuid[:60]) + } + return func(params *ASRParams) { + params.Cuid = cuid + } +} + +func Language(lang string) ASRParam { + if lang != "zh" && lang != "ct" && lang != "en" { + lang = "zh" + } + return func(params *ASRParams) { + params.Language = lang + } +} + +////SpeechToText 语音识别,将语音翻译成文字 + +func (vc *VoiceClient) SpeechToText(reader io.Reader, params ...ASRParam) ([]string, error) { + content, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + if len(content) > 10*MB { + return nil, errors.New("文件大小不能超过10M") + } + + spch := base64.StdEncoding.EncodeToString(content) + + asrParams := &ASRParams{ + Format: "pcm", + Rate: 8000, + Channel: 1, + Cuid: "anonymous", + Token: vc.AccessToken, + Language: "zh", + Speech: spch, + Length: len(content), + } + + for _, fn := range params { + fn(asrParams) + } + + header := req.Header{ "Content-Type": "application/json", - }, req.BodyJSON(ap)) + } + + resp, err := req.Post(ASR_URL, header, req.BodyJSON(asrParams)) if err != nil { - return []string{}, err + return nil, err } - var rs ASRResponse - if err := resp.ToJSON(&rs); err != nil { - return []string{}, err + + var asrResponse *ASRResponse + if err := resp.ToJSON(asrResponse); err != nil { + return nil, err } - if !strings.Contains(rs.ERRMSG, "success") || rs.ERRNO != 0 { - return []string{}, errors.New("调用服务失败:" + rs.ERRMSG) + + if asrResponse.ERRNO != 0 { + return nil, errors.New("调用服务失败:" + rs.ERRMSG) } - return rs.Result, nil + + return asrResponse.Result, nil + } diff --git a/voice/tts.go b/voice/tts.go index c5039de..b8b44be 100644 --- a/voice/tts.go +++ b/voice/tts.go @@ -29,7 +29,8 @@ type TTSParams struct { Person int } -type TTSParam func(*TTSParams) +type TTSParam func(params *TTSParams) + func Cuid(str string) TTSParam { if len(str) > 60 { From 1765cdec0ab7fd9a3370a2a0c610169205faa370 Mon Sep 17 00:00:00 2001 From: chenchao Date: Fri, 12 Jan 2018 20:57:36 +0800 Subject: [PATCH 15/24] fix bug --- example/voice.go | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/example/voice.go b/example/voice.go index 8cdc065..67422d4 100644 --- a/example/voice.go +++ b/example/voice.go @@ -4,8 +4,6 @@ import ( "github.com/chenqinghe/baidu-ai-go-sdk/voice" "log" "os" - "io/ioutil" - "encoding/base64" "fmt" ) @@ -43,30 +41,18 @@ func SpeechToText() { log.Fatal(err) } - f, err := os.OpenFile("hello.wav", os.O_RDONLY, 0666) + f, err := os.OpenFile("16k.pcm", os.O_RDONLY, 0666) if err != nil { log.Fatal(err) } - - fi, err1 := ioutil.ReadAll(f) - if err1 != nil { - log.Fatal(err1) - } - afterBase64Str := base64.StdEncoding.EncodeToString(fi) - fiLen := len(fi) - param := voice.ASRParams{ - Format: "wav", - Rate: 16000, - Channel: 1, - Cuid: "12312312112", - Token: client.AccessToken, - Lan: "zh", - Speech: afterBase64Str, - Len: fiLen, - } - rs, err2 := client.SpeechToText(param) - if err2 != nil { - log.Fatal(err2) + + rs, err := client.SpeechToText( + f, + voice.Format("pcm"), + voice.Channel(1), + ) + if err != nil { + log.Fatal(err) } fmt.Println(rs) } From 4a7039d81157488c4e86099fb1017d58a33c4093 Mon Sep 17 00:00:00 2001 From: chenchao Date: Fri, 12 Jan 2018 21:02:52 +0800 Subject: [PATCH 16/24] fmt project and delete useless files. --- utils.go | 16 -------------- utils_test.go | 26 ----------------------- voice/asr.go | 30 +++++++++++++------------- voice/client.go | 2 +- voice/tts.go | 56 ++++++++++++++++++++++++++++++------------------- 5 files changed, 51 insertions(+), 79 deletions(-) delete mode 100644 utils.go delete mode 100644 utils_test.go diff --git a/utils.go b/utils.go deleted file mode 100644 index 38e8c6e..0000000 --- a/utils.go +++ /dev/null @@ -1,16 +0,0 @@ -package gosdk - -import ( - "reflect" -) - -func StructToMap(obj interface{}) map[string]interface{} { - t := reflect.TypeOf(obj) - v := reflect.ValueOf(obj) - - var data = make(map[string]interface{}) - for i := 0; i < t.NumField(); i++ { - data[t.Field(i).Name] = v.Field(i).Interface() - } - return data -} diff --git a/utils_test.go b/utils_test.go deleted file mode 100644 index a614b0f..0000000 --- a/utils_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package gosdk - -import ( - "testing" -) - -type User struct { - Name string - Age int -} - -func TestStructToMap(t *testing.T) { - user := &User{ - Name: "bob", - Age: 18, - } - m := StructToMap(*user) - name, ok := m["Name"] - if !ok || name != "bob" { - t.Fail() - } - age, ok := m["Age"] - if !ok || age != 18 { - t.Fail() - } -} diff --git a/voice/asr.go b/voice/asr.go index 3df3b04..547f4f0 100644 --- a/voice/asr.go +++ b/voice/asr.go @@ -3,11 +3,13 @@ package voice import ( "encoding/base64" "errors" - "github.com/imroc/req" "io" "io/ioutil" + "net" - "strings" + + "fmt" + "github.com/imroc/req" ) const ASR_URL = "http://vop.baidu.com/server_api" @@ -60,15 +62,6 @@ func Channel(c int) ASRParam { } } -func Cuid(cuid string) ASRParam { - if len(cuid) > 60 { - cuid = string(cuid[:60]) - } - return func(params *ASRParams) { - params.Cuid = cuid - } -} - func Language(lang string) ASRParam { if lang != "zh" && lang != "ct" && lang != "en" { lang = "zh" @@ -79,7 +72,6 @@ func Language(lang string) ASRParam { } ////SpeechToText 语音识别,将语音翻译成文字 - func (vc *VoiceClient) SpeechToText(reader io.Reader, params ...ASRParam) ([]string, error) { content, err := ioutil.ReadAll(reader) if err != nil { @@ -91,11 +83,19 @@ func (vc *VoiceClient) SpeechToText(reader io.Reader, params ...ASRParam) ([]str spch := base64.StdEncoding.EncodeToString(content) + var cuid string + netitfs, err := net.Interfaces() + if err != nil { + cuid = "anonymous" + } else { + cuid = netitfs[0].HardwareAddr.String() + } + asrParams := &ASRParams{ Format: "pcm", Rate: 8000, Channel: 1, - Cuid: "anonymous", + Cuid: cuid, Token: vc.AccessToken, Language: "zh", Speech: spch, @@ -115,13 +115,15 @@ func (vc *VoiceClient) SpeechToText(reader io.Reader, params ...ASRParam) ([]str return nil, err } + fmt.Println(resp.String()) + var asrResponse *ASRResponse if err := resp.ToJSON(asrResponse); err != nil { return nil, err } if asrResponse.ERRNO != 0 { - return nil, errors.New("调用服务失败:" + rs.ERRMSG) + return nil, errors.New("调用服务失败:" + asrResponse.ERRMSG) } return asrResponse.Result, nil diff --git a/voice/client.go b/voice/client.go index 6e4c7e2..f5da646 100644 --- a/voice/client.go +++ b/voice/client.go @@ -3,7 +3,7 @@ package voice import "github.com/chenqinghe/baidu-ai-go-sdk" const ( - B = 1 << 10 * iota + B = 1 << (10 * iota) KB MB GB diff --git a/voice/tts.go b/voice/tts.go index b8b44be..c25e1ad 100644 --- a/voice/tts.go +++ b/voice/tts.go @@ -7,7 +7,9 @@ import ( "io/ioutil" - "github.com/chenqinghe/baidu-ai-go-sdk" + "net" + + "encoding/json" "github.com/imroc/req" ) @@ -18,29 +20,19 @@ var ( ) type TTSParams struct { - Text string - Token string - Cuid string - ClientType int - Language string - Speed int - Pitch int - Volume int - Person int + Text string `json:"tex"` + Token string `json:"tok"` + Cuid string `json:"cuid"` + ClientType int `json:"ctp"` + Language string `json:"lan"` + Speed int `json:"spd"` + Pitch int `json:"pit"` + Volume int `json:"vol"` + Person int `json:"per"` } type TTSParam func(params *TTSParams) - -func Cuid(str string) TTSParam { - if len(str) > 60 { - str = string(str[:60]) - } - return func(p *TTSParams) { - p.Cuid = str - } -} - func Speed(spd int) TTSParam { if spd > 9 { spd = 9 @@ -96,10 +88,18 @@ func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, err return nil, err } + var cuid string + netitfs, err := net.Interfaces() + if err != nil { + cuid = "anonymous" + } else { + cuid = netitfs[0].HardwareAddr.String() + } + ttsparams := &TTSParams{ Text: txt, Token: vc.AccessToken, - Cuid: "as", + Cuid: cuid, ClientType: 1, Language: "zh", Speed: 5, @@ -112,10 +112,22 @@ func (vc *VoiceClient) TextToSpeech(txt string, params ...TTSParam) ([]byte, err param(ttsparams) } - resp, err := req.Post(TTS_URL, req.Param(gosdk.StructToMap(*ttsparams))) + t, err := json.Marshal(ttsparams) + if err != nil { + return nil, errors.New("serialize failed: " + err.Error()) + } + var p = req.Param{} + if err := json.Unmarshal(t, &p); err != nil { + return nil, err + } + + resp, err := req.Post(TTS_URL, p) if err != nil { return nil, err } + + //通过Content-Type的头部来确定是否服务端合成成功。 + //http://ai.baidu.com/docs#/TTS-API/top respHeader := resp.Response().Header contentType, ok := respHeader["Content-Type"] if !ok { From 4bea1a3e1a9aff189ecbafa7be01a9a38f037748 Mon Sep 17 00:00:00 2001 From: chenchao Date: Fri, 12 Jan 2018 21:26:18 +0800 Subject: [PATCH 17/24] Reconstruction asr --- ocr/general.go | 12 +++++++----- ocr/image.go | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 ocr/image.go diff --git a/ocr/general.go b/ocr/general.go index d7e1f42..d282ffd 100644 --- a/ocr/general.go +++ b/ocr/general.go @@ -51,7 +51,7 @@ func NewOCRClient(apiKey, secretKey string) *OCRClient { //GeneralRecognizeBasic 通用文字识别 //img 图片二进制数据 //conf 请求参数 -func (oc *OCRClient) GeneralRecognizeBasic(img []byte, conf map[string]string) ([]byte, error) { +func (oc *OCRClient) GeneralRecognizeBasic(img []byte, conf map[string]interface{}) ([]byte, error) { if err := oc.Auth(); err != nil { return nil, err } @@ -107,11 +107,13 @@ func parseParams(def, need map[string]string) map[string]string { func doRequest(url string, params map[string]interface{}) (rs []byte, err error) { - resp, err := req.Post(url, req.Param(params), req.Header{"Content-Type": "application/x-www-form-urlencoded"}) + header := req.Header{ + "Content-Type": "application/x-www-form-urlencoded", + } + + resp, err := req.Post(url, req.Param(params), header) if err != nil { return } - rs, err = resp.ToBytes() - return - + return resp.ToBytes() } diff --git a/ocr/image.go b/ocr/image.go new file mode 100644 index 0000000..a663b18 --- /dev/null +++ b/ocr/image.go @@ -0,0 +1,24 @@ +package ocr + +import ( + "image" + "io" +) + +type Size struct { + Height int + Width int +} + +func getImageSize(reader io.Reader) (*Size, error) { + img, _, err := image.Decode(reader) + if err != nil { + return nil, err + } + bounds := img.Bounds() + size := &Size{ + Width: bounds.Dx(), + Height: bounds.Dy(), + } + return size, nil +} From 86ec097b0d71914c495bec0c5b7a008cb8da2d20 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Sat, 13 Jan 2018 16:46:32 +0800 Subject: [PATCH 18/24] Update README.md --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b9777e2..287767f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ - [x] 语音合成 - [x] 语音识别 -### 文字识别 +### 视觉技术 + +#### 文字识别 - [x] 通用文字识别 - [x] 通用文字识别(含位置信息版) - [x] 通用文字识别(含生僻字版) @@ -17,16 +19,50 @@ - [x] 行驶证识别 - [ ] 表格文字识别 -### 人脸识别 +#### 人脸识别 +- [ ] 人脸检测 +- [ ] 人脸对比 +- [ ] 人脸查找 +- [ ] 人脸库管理 +- [ ] 公安验证 + +#### 图像审核 +- [ ] 图像审核 +- [ ] GIF色情识别 +- [ ] 图像审核组合服务接口 +- [ ] 用户头像审核 + +#### 图像识别 +- [ ] 通用图像分析 +- [ ] 细粒度图像识别 +- [ ] 定制化图像识别 + +#### 图像搜索 +- [ ] 相同图检索 +- [ ] 相似图检索 +- [ ] 商品检索 -### 语言处理基础技术 -### 理解与交互技术UNIT +### 自然语言 -### 图像审核 +#### 语言处理基础技术 +- [ ] 词法分析 +- [ ] 依存句法分析 +- [ ] 词向量标识 +- [ ] DNN语言模型 +- [ ] 词义相似度 +- [ ] 短文本相似度 +- [ ] 评论观点抽取 +- [ ] 情感倾向分析 +- [ ] 中文分词 +- [ ] 词性标注 +#### 理解与交互技术UNIT +- [ ] UNIT对话接口 +#### 文本审核 +- [ ] 通用类文本反作弊 # LISENCE From 803ef8c66bd64caaccb30f0630ae65866a202773 Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sun, 14 Jan 2018 21:49:46 +0800 Subject: [PATCH 19/24] Reconstruction --- ocr/general.go | 119 --------------------------------- ocr/general_test.go | 1 - ocr/particular.go | 83 ----------------------- version/ocr/default.go | 57 ++++++++++++++++ {ocr => version/ocr}/image.go | 0 version/ocr/ocr.go | 102 ++++++++++++++++++++++++++++ {ocr => version/ocr}/ocr.jpg | Bin version/ocr/param.go | 111 ++++++++++++++++++++++++++++++ version/ocr/particular.go | 59 ++++++++++++++++ {ocr => version/ocr}/readme.md | 0 version/ocr/request.go | 16 +++++ voice/tts_test.go | 5 +- 12 files changed, 347 insertions(+), 206 deletions(-) delete mode 100644 ocr/general.go delete mode 100644 ocr/general_test.go delete mode 100644 ocr/particular.go create mode 100644 version/ocr/default.go rename {ocr => version/ocr}/image.go (100%) create mode 100644 version/ocr/ocr.go rename {ocr => version/ocr}/ocr.jpg (100%) create mode 100644 version/ocr/param.go create mode 100644 version/ocr/particular.go rename {ocr => version/ocr}/readme.md (100%) create mode 100644 version/ocr/request.go diff --git a/ocr/general.go b/ocr/general.go deleted file mode 100644 index d282ffd..0000000 --- a/ocr/general.go +++ /dev/null @@ -1,119 +0,0 @@ -package ocr - -import ( - "encoding/base64" - - "github.com/chenqinghe/baidu-ai-go-sdk" - "github.com/imroc/req" -) - -const ( - OCR_GENERAL_BASIC_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic" - OCR_GENERAL_WITH_LOCATION_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general" - OCR_GENERAL_ENHANCED_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_enhanced" -) - -var defaultGeneralBasicParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - "language_type": "CHN_ENG", //识别语言类型,默认为CHN_ENG。可选值包括: - CHN_ENG:中英文混合; - ENG:英文; - POR:葡萄牙语; - FRE:法语; - GER:德语; - ITA:意大利语; - SPA:西班牙语; - RUS:俄语; - JAP:日语 - "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 - "detect_language": "false", //是否检测语言,默认不检测。当前支持(中文、英语、日语、韩语) -} - -var defaultGeneralWithLocationParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - "recognize_granularity": "big", //是否定位单字符位置,big:不定位单字符位置,默认值;small:定位单字符位置 - "language_type": "CHN_ENG", //识别语言类型,默认为CHN_ENG。可选值包括: - CHN_ENG:中英文混合; - ENG:英文; - POR:葡萄牙语; - FRE:法语; - GER:德语; - ITA:意大利语; - SPA:西班牙语; - RUS:俄语; - JAP:日语 - "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括:- true:检测朝向; - false:不检测朝向 - "detect_language": "false", //是否检测语言,默认不检测。当前支持(中文、英语、日语、韩语) - "vertexes_location": "false", //是否返回文字外接多边形顶点位置,不支持单字位置。默认为false - "probability": "false", //是否返回识别结果中每一行的置信度 -} - -var defaultDeneralEnhancedParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - "language_type": "CHN_ENG", //识别语言类型,默认为CHN_ENG。可选值包括: - CHN_ENG:中英文混合; - ENG:英文; - POR:葡萄牙语; - FRE:法语; - GER:德语; - ITA:意大利语; - SPA:西班牙语; - RUS:俄语; - JAP:日语 - "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括:- true:检测朝向; - false:不检测朝向 - "detect_language": "false", //是否检测语言,默认不检测。当前支持(中文、英语、日语、韩语) - "probability": "false", //是否返回识别结果中每一行的置信度 -} - -type OCRClient struct { - *gosdk.Client -} - -func NewOCRClient(apiKey, secretKey string) *OCRClient { - return &OCRClient{ - Client: gosdk.NewClient(apiKey, secretKey), - } -} - -//GeneralRecognizeBasic 通用文字识别 -//img 图片二进制数据 -//conf 请求参数 -func (oc *OCRClient) GeneralRecognizeBasic(img []byte, conf map[string]interface{}) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } - encodedImgStr := base64.StdEncoding.EncodeToString(img) - conf["image"] = encodedImgStr - - conf = parseParams(defaultGeneralBasicParams, conf) - - var url string = OCR_GENERAL_BASIC_URL + "?access_token=" + oc.AccessToken - - return doRequest(url, conf) -} - -//GeneralRecognizeWithLocation 通用文字识别(含位置信息) -func (oc *OCRClient) GeneralRecognizeWithLocation(img []byte, conf map[string]string) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } - encodedImgStr := base64.StdEncoding.EncodeToString(img) - conf["image"] = encodedImgStr - conf = parseParams(defaultGeneralWithLocationParams, conf) - - var url string = OCR_GENERAL_WITH_LOCATION_URL + "?access_token=" + oc.AccessToken - - return doRequest(url, conf) - -} - -//GeneralRecognizeEnhanced 通用文字识别(含生僻字) -func (oc *OCRClient) GeneralRecognizeEnhanced(img []byte, conf map[string]string) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } - encodedImgStr := base64.StdEncoding.EncodeToString(img) - conf["image"] = encodedImgStr - - conf = parseParams(defaultDeneralEnhancedParams, conf) - - url := OCR_GENERAL_ENHANCED_URL + "?access_token=" + oc.AccessToken - - return doRequest(url, conf) - -} - -func parseParams(def, need map[string]string) map[string]string { - for key, _ := range def { - if val, ok := need[key]; ok { - def[key] = val - } - } - return def -} - -func doRequest(url string, params map[string]interface{}) (rs []byte, err error) { - - header := req.Header{ - "Content-Type": "application/x-www-form-urlencoded", - } - - resp, err := req.Post(url, req.Param(params), header) - if err != nil { - return - } - return resp.ToBytes() -} diff --git a/ocr/general_test.go b/ocr/general_test.go deleted file mode 100644 index e130acc..0000000 --- a/ocr/general_test.go +++ /dev/null @@ -1 +0,0 @@ -package ocr diff --git a/ocr/particular.go b/ocr/particular.go deleted file mode 100644 index deaba91..0000000 --- a/ocr/particular.go +++ /dev/null @@ -1,83 +0,0 @@ -package ocr - -const ( - OCR_WEBIMAGE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/webimage" - OCR_IDCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard" - OCR_BANKCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard" - OCR_DRIVERLICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license" - OCR_VEHICLELICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license" - OCR_LICENSEPLATE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate" - OCR_FORM_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/request" -) - -var ( - defaultWebimgParams = defaultDeneralEnhancedParams - defaultIdcardParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 - "id_card_side": "front", //front:身份证正面;back:身份证背面 - "detect_risk": "false", //是否开启身份证风险类型(身份证复印件、临时身份证、身份证翻拍、修改过的身份证)功能,默认不开启,即:false。可选值:true-开启;false-不开启 - } - defaultBankcardParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - } - defaultDriverLicenseParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 - } - defaultVehicleLicenseParams = map[string]string{ - "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 - "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 - "accuracy": "", //normal 使用快速服务,1200ms左右时延;缺省或其它值使用高精度服务,1600ms左右时延 - } - defaultLicensePlateParams = defaultBankcardParams - defaultFormParams = defaultBankcardParams -) - -func (oc *OCRClient) WebImageRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_WEBIMAGE_URL, conf, defaultWebimgParams) - -} - -func (oc *OCRClient) IdcardRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_IDCARD_URL, conf, defaultIdcardParams) -} - -func (oc *OCRClient) BankcardRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_BANKCARD_URL, conf, defaultBankcardParams) - -} - -func (oc *OCRClient) DriverLicenseRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_DRIVERLICENSE_URL, conf, defaultDriverLicenseParams) -} - -func (oc *OCRClient) VehicleLicenseRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_VEHICLELICENSE_URL, conf, defaultVehicleLicenseParams) -} - -func (oc *OCRClient) LicensePlateRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_LICENSEPLATE_URL, conf, defaultLicensePlateParams) -} - -func (oc *OCRClient) FromdataRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_FORM_URL, conf, defaultFormParams) -} - -func (oc *OCRClient) generalOperate(img []byte, baseurl string, conf, def map[string]string) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } - conf = parseParams(def, conf) - - url := baseurl + "?access_token=" + oc.AccessToken - - return doRequest(url, conf) -} diff --git a/version/ocr/default.go b/version/ocr/default.go new file mode 100644 index 0000000..23a1a1d --- /dev/null +++ b/version/ocr/default.go @@ -0,0 +1,57 @@ +package ocr + +var defaultGeneralBasicParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 + "url": "", //图片完整URL,URL长度不超过1024字节,URL对应的图片base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式,当image字段存在时url字段失效,不支持https的图片链接 + "language_type": "CHN_ENG", //识别语言类型,默认为CHN_ENG。可选值包括: - CHN_ENG:中英文混合; - ENG:英文; - POR:葡萄牙语; - FRE:法语; - GER:德语; - ITA:意大利语; - SPA:西班牙语; - RUS:俄语; - JAP:日语 + "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 + "detect_language": "false", //是否检测语言,默认不检测。当前支持(中文、英语、日语、韩语) + "probability": "false", //是否返回识别结果中每一行的置信度 +} + +var defaultGeneralWithLocationParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 + "url": "", //图片完整URL,URL长度不超过1024字节,URL对应的图片base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式,当image字段存在时url字段失效,不支持https的图片链接 + "recognize_granularity": "big", //是否定位单字符位置,big:不定位单字符位置,默认值;small:定位单字符位置 + "language_type": "CHN_ENG", //识别语言类型,默认为CHN_ENG。可选值包括: - CHN_ENG:中英文混合; - ENG:英文; - POR:葡萄牙语; - FRE:法语; - GER:德语; - ITA:意大利语; - SPA:西班牙语; - RUS:俄语; - JAP:日语 + "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括:- true:检测朝向; - false:不检测朝向 + "detect_language": "false", //是否检测语言,默认不检测。当前支持(中文、英语、日语、韩语) + "vertexes_location": "false", //是否返回文字外接多边形顶点位置,不支持单字位置。默认为false + "probability": "false", //是否返回识别结果中每一行的置信度 +} + +var defaultDeneralEnhancedParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 + "language_type": "CHN_ENG", //识别语言类型,默认为CHN_ENG。可选值包括: - CHN_ENG:中英文混合; - ENG:英文; - POR:葡萄牙语; - FRE:法语; - GER:德语; - ITA:意大利语; - SPA:西班牙语; - RUS:俄语; - JAP:日语 + "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括:- true:检测朝向; - false:不检测朝向 + "detect_language": "false", //是否检测语言,默认不检测。当前支持(中文、英语、日语、韩语) + "probability": "false", //是否返回识别结果中每一行的置信度 +} + +var defaultWebimgParams = defaultDeneralEnhancedParams + +var defaultIdcardParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 + "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 + "id_card_side": "front", //front:身份证正面;back:身份证背面 + "detect_risk": "false", //是否开启身份证风险类型(身份证复印件、临时身份证、身份证翻拍、修改过的身份证)功能,默认不开启,即:false。可选值:true-开启;false-不开启 +} + +var defaultBankcardParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 +} + +var defaultDriverLicenseParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 + "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 +} + +var defaultVehicleLicenseParams = map[string]interface{}{ + "image": "", //图像数据,base64编码,要求base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 + "detect_direction": "false", //是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: - true:检测朝向; - false:不检测朝向。 + "accuracy": "", //normal 使用快速服务,1200ms左右时延;缺省或其它值使用高精度服务,1600ms左右时延 +} + +var defaultLicensePlateParams = defaultBankcardParams + +var defaultFormParams = defaultBankcardParams diff --git a/ocr/image.go b/version/ocr/image.go similarity index 100% rename from ocr/image.go rename to version/ocr/image.go diff --git a/version/ocr/ocr.go b/version/ocr/ocr.go new file mode 100644 index 0000000..a0b5931 --- /dev/null +++ b/version/ocr/ocr.go @@ -0,0 +1,102 @@ +package ocr + +import ( + "encoding/base64" + "github.com/chenqinghe/baidu-ai-go-sdk" + "io" + "io/ioutil" +) + +const ( + OCR_GENERAL_BASIC_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic" + OCR_GENERAL_WITH_LOCATION_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general" + OCR_GENERAL_ENHANCED_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_enhanced" +) + +type OCRClient struct { + *gosdk.Client +} + +func NewOCRClient(apiKey, secretKey string) *OCRClient { + return &OCRClient{ + Client: gosdk.NewClient(apiKey, secretKey), + } +} + +//GeneralRecognizeBasic 通用文字识别 +//img 图片二进制数据 +//conf 请求参数 +func (oc *OCRClient) GeneralRecognizeBasic(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + if err := oc.Auth(); err != nil { + return nil, err + } + + imgBytes, err := ioutil.ReadAll(imageReader) + if err != nil { + return nil, err + } + + encodedImgStr := base64.StdEncoding.EncodeToString(imgBytes) + + conf := defaultGeneralBasicParams + conf["image"] = encodedImgStr + + for _, fn := range params { + fn(conf) + } + + var url = OCR_GENERAL_BASIC_URL + "?access_token=" + oc.AccessToken + + return doRequest(url, conf) +} + +//GeneralRecognizeWithLocation 通用文字识别(含位置信息) +func (oc *OCRClient) GeneralRecognizeWithLocation(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + if err := oc.Auth(); err != nil { + return nil, err + } + + imgBytes, err := ioutil.ReadAll(imageReader) + if err != nil { + return nil, err + } + + encodedImgStr := base64.StdEncoding.EncodeToString(imgBytes) + conf := defaultGeneralWithLocationParams + conf["image"] = encodedImgStr + + for _, fn := range params { + fn(conf) + } + + var url = OCR_GENERAL_WITH_LOCATION_URL + "?access_token=" + oc.AccessToken + + return doRequest(url, conf) + +} + +//GeneralRecognizeEnhanced 通用文字识别(含生僻字) +func (oc *OCRClient) GeneralRecognizeEnhanced(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + if err := oc.Auth(); err != nil { + return nil, err + } + + imgBytes, err := ioutil.ReadAll(imageReader) + if err != nil { + return nil, err + } + + encodedImgStr := base64.StdEncoding.EncodeToString(imgBytes) + + conf := defaultDeneralEnhancedParams + conf["image"] = encodedImgStr + + for _, fn := range params { + fn(conf) + } + + url := OCR_GENERAL_ENHANCED_URL + "?access_token=" + oc.AccessToken + + return doRequest(url, conf) + +} diff --git a/ocr/ocr.jpg b/version/ocr/ocr.jpg similarity index 100% rename from ocr/ocr.jpg rename to version/ocr/ocr.jpg diff --git a/version/ocr/param.go b/version/ocr/param.go new file mode 100644 index 0000000..d26054d --- /dev/null +++ b/version/ocr/param.go @@ -0,0 +1,111 @@ +package ocr + +type RequestParam func(map[string]interface{}) + +//识别语言类型,默认为CHN_ENG。 +func LanguageType(lang string) RequestParam { + options := []string{ + "CHN_ENG", + "ENG", + "POR", + "FRE", + "GER", + "ITA", + "SPA", + "RUS", + "JAP", + "KOR", + } + + illegal := true + for _, v := range options { + if v == lang { + illegal = false + break + } + } + + if illegal { + lang = "CHN_ENG" + } + return func(m map[string]interface{}) { + m["language_type"] = lang + } +} + +//是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度。可选值包括: +//- true:检测朝向; +//- false:不检测朝向。 +func DetectDirection() RequestParam { + return func(m map[string]interface{}) { + m["detect_direction"] = true + } +} + +//是否检测语言,默认不检测。 +//当前支持(中文、英语、日语、韩语) +func DetectLanguage() RequestParam { + return func(m map[string]interface{}) { + m["detect_language"] = true + } +} + +//是否返回识别结果中每一行的置信度 +func WithProbability() RequestParam { + return func(m map[string]interface{}) { + m["probability"] = true + } +} + +//是否定位单字符位置,big:不定位单字符位置,默认值;small:定位单字符位置 +func RecognizeGranularity() RequestParam { + return func(m map[string]interface{}) { + m["recognize_granularity"] = "small" + } +} + +//是否返回文字外接多边形顶点位置,不支持单字位置。默认为false +func WithVertexesLocation() RequestParam { + return func(m map[string]interface{}) { + m["vertexes_location"] = true + } +} + +//front:身份证含照片的一面;back:身份证带国徽的一面 +func IDCardSide(side string) RequestParam { + return func(m map[string]interface{}) { + m["id_card_side"] = side + } +} + +//是否开启身份证风险类型(身份证复印件、临时身份证、身份证翻拍、修改过的身份证)功能,默认不开启,即:false。 +// 可选值:true-开启;false-不开启 +func DetectRisk() RequestParam { + return func(m map[string]interface{}) { + m["detect_risk"] = true + } +} + +//true: 归一化格式输出;false 或无此参数按非归一化格式输出 +func UnifiedValidPeriod() RequestParam { + return func(m map[string]interface{}) { + m["unified_valid_period"] = true + } +} + +//normal 使用快速服务,1200ms左右时延;缺省或其它值使用高精度服务,1600ms左右时延 +func Accuracy(opt string) RequestParam { + if opt != "normal" && opt != "high" { + opt = "normal" + } + return func(m map[string]interface{}) { + m["accuracy"] = "normal" + } +} + +//是否检测多张车牌,默认为false,当置为true的时候可以对一张图片内的多张车牌进行识别 +func MultiDetect() RequestParam { + return func(m map[string]interface{}) { + m["multi_detect"] = true + } +} diff --git a/version/ocr/particular.go b/version/ocr/particular.go new file mode 100644 index 0000000..0a4431a --- /dev/null +++ b/version/ocr/particular.go @@ -0,0 +1,59 @@ +package ocr + +const ( + OCR_WEBIMAGE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/webimage" + OCR_IDCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard" + OCR_BANKCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard" + OCR_DRIVERLICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license" + OCR_VEHICLELICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license" + OCR_LICENSEPLATE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate" + OCR_FORM_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/request" +) + +func (oc *OCRClient) WebImageRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_WEBIMAGE_URL, conf, defaultWebimgParams) + +} + +func (oc *OCRClient) IdcardRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_IDCARD_URL, conf, defaultIdcardParams) +} + +func (oc *OCRClient) BankcardRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_BANKCARD_URL, conf, defaultBankcardParams) + +} + +func (oc *OCRClient) DriverLicenseRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_DRIVERLICENSE_URL, conf, defaultDriverLicenseParams) +} + +func (oc *OCRClient) VehicleLicenseRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_VEHICLELICENSE_URL, conf, defaultVehicleLicenseParams) +} + +func (oc *OCRClient) LicensePlateRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_LICENSEPLATE_URL, conf, defaultLicensePlateParams) +} + +func (oc *OCRClient) FromdataRecognize(img []byte, conf map[string]string) ([]byte, error) { + + return oc.generalOperate(img, OCR_FORM_URL, conf, defaultFormParams) +} + +func (oc *OCRClient) generalOperate(img []byte, baseurl string, conf, def map[string]string) ([]byte, error) { + if err := oc.Auth(); err != nil { + return nil, err + } + conf = parseParams(def, conf) + + url := baseurl + "?access_token=" + oc.AccessToken + + return doRequest(url, conf) +} diff --git a/ocr/readme.md b/version/ocr/readme.md similarity index 100% rename from ocr/readme.md rename to version/ocr/readme.md diff --git a/version/ocr/request.go b/version/ocr/request.go new file mode 100644 index 0000000..222fc9c --- /dev/null +++ b/version/ocr/request.go @@ -0,0 +1,16 @@ +package ocr + +import "github.com/imroc/req" + +func doRequest(url string, params map[string]interface{}) (rs []byte, err error) { + + header := req.Header{ + "Content-Type": "application/x-www-form-urlencoded", + } + + resp, err := req.Post(url, req.Param(params), header) + if err != nil { + return + } + return resp.ToBytes() +} \ No newline at end of file diff --git a/voice/tts_test.go b/voice/tts_test.go index 43a84ae..d3567fb 100644 --- a/voice/tts_test.go +++ b/voice/tts_test.go @@ -13,15 +13,14 @@ var client *VoiceClient func init() { client = NewVoiceClient(apikey, secretkey) - } func TestVoiceClient_TextToSpeech(t *testing.T) { - _, err := client.TextToSpeech("你好", + _, err := client.TextToSpeech( + "你好", Speed(10), Person(1), Volume(10), - Cuid("1234567890"), ) if err != nil { t.Fatal(err) From a4bae5d9cd1704b5fc3b6f9bfd5076940bf33072 Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sun, 14 Jan 2018 22:22:38 +0800 Subject: [PATCH 20/24] Reconstruction --- version/ocr/ocr.go | 112 +++++++++++++++++++++++--------------- version/ocr/particular.go | 59 -------------------- version/ocr/request.go | 10 +++- 3 files changed, 76 insertions(+), 105 deletions(-) delete mode 100644 version/ocr/particular.go diff --git a/version/ocr/ocr.go b/version/ocr/ocr.go index a0b5931..1f7f476 100644 --- a/version/ocr/ocr.go +++ b/version/ocr/ocr.go @@ -13,6 +13,16 @@ const ( OCR_GENERAL_ENHANCED_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_enhanced" ) +const ( + OCR_WEBIMAGE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/webimage" + OCR_IDCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard" + OCR_BANKCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard" + OCR_DRIVERLICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license" + OCR_VEHICLELICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license" + OCR_LICENSEPLATE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate" + OCR_FORM_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/request" +) + type OCRClient struct { *gosdk.Client } @@ -27,76 +37,90 @@ func NewOCRClient(apiKey, secretKey string) *OCRClient { //img 图片二进制数据 //conf 请求参数 func (oc *OCRClient) GeneralRecognizeBasic(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } - imgBytes, err := ioutil.ReadAll(imageReader) - if err != nil { - return nil, err - } + return oc.ocr(imageReader, OCR_GENERAL_BASIC_URL, defaultGeneralBasicParams, params...) - encodedImgStr := base64.StdEncoding.EncodeToString(imgBytes) +} - conf := defaultGeneralBasicParams - conf["image"] = encodedImgStr +//GeneralRecognizeWithLocation 通用文字识别(含位置信息) +func (oc *OCRClient) GeneralRecognizeWithLocation(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - for _, fn := range params { - fn(conf) - } + return oc.ocr(imageReader, OCR_GENERAL_WITH_LOCATION_URL, defaultGeneralWithLocationParams, params...) + +} + +//GeneralRecognizeEnhanced 通用文字识别(含生僻字) +func (oc *OCRClient) GeneralRecognizeEnhanced(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - var url = OCR_GENERAL_BASIC_URL + "?access_token=" + oc.AccessToken + return oc.ocr(imageReader, OCR_GENERAL_ENHANCED_URL, defaultDeneralEnhancedParams, params...) - return doRequest(url, conf) } -//GeneralRecognizeWithLocation 通用文字识别(含位置信息) -func (oc *OCRClient) GeneralRecognizeWithLocation(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } +func (oc *OCRClient) WebImageRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - imgBytes, err := ioutil.ReadAll(imageReader) - if err != nil { - return nil, err - } + return oc.ocr(imageReader, OCR_WEBIMAGE_URL, defaultWebimgParams, params...) - encodedImgStr := base64.StdEncoding.EncodeToString(imgBytes) - conf := defaultGeneralWithLocationParams - conf["image"] = encodedImgStr +} - for _, fn := range params { - fn(conf) - } +func (oc *OCRClient) IdcardRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - var url = OCR_GENERAL_WITH_LOCATION_URL + "?access_token=" + oc.AccessToken + return oc.ocr(imageReader, OCR_IDCARD_URL, defaultIdcardParams, params...) - return doRequest(url, conf) +} + +func (oc *OCRClient) BankcardRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + + return oc.ocr(imageReader, OCR_BANKCARD_URL, defaultBankcardParams, params...) } -//GeneralRecognizeEnhanced 通用文字识别(含生僻字) -func (oc *OCRClient) GeneralRecognizeEnhanced(imageReader io.Reader, params ...RequestParam) ([]byte, error) { - if err := oc.Auth(); err != nil { +func (oc *OCRClient) DriverLicenseRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + + return oc.ocr(imageReader, OCR_DRIVERLICENSE_URL, defaultDriverLicenseParams, params...) + +} + +func (oc *OCRClient) VehicleLicenseRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + + return oc.ocr(imageReader, OCR_VEHICLELICENSE_URL, defaultVehicleLicenseParams, params...) + +} + +func (oc *OCRClient) LicensePlateRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + + return oc.ocr(imageReader, OCR_LICENSEPLATE_URL, defaultLicensePlateParams, params...) + +} + +func (oc *OCRClient) FromdataRecognize(imageReader io.Reader, params ...RequestParam) ([]byte, error) { + + return oc.ocr(imageReader, OCR_FORM_URL, defaultFormParams, params...) + +} + +func (oc *OCRClient) ocr(imageReader io.Reader, url string, def map[string]interface{}, params ...RequestParam) ([]byte, error) { + requestParams, err := parseRequestParam(imageReader, def, params...) + if err != nil { return nil, err } - imgBytes, err := ioutil.ReadAll(imageReader) + return oc.doRequest(url, requestParams) +} + +func parseRequestParam(imageReader io.Reader, def map[string]interface{}, params ...RequestParam) (map[string]interface{}, error) { + + imageBytes, err := ioutil.ReadAll(imageReader) if err != nil { return nil, err } + imageBase64Str := base64.StdEncoding.EncodeToString(imageBytes) - encodedImgStr := base64.StdEncoding.EncodeToString(imgBytes) - - conf := defaultDeneralEnhancedParams - conf["image"] = encodedImgStr + def["image"] = imageBase64Str for _, fn := range params { - fn(conf) + fn(def) } - url := OCR_GENERAL_ENHANCED_URL + "?access_token=" + oc.AccessToken - - return doRequest(url, conf) + return def, nil } diff --git a/version/ocr/particular.go b/version/ocr/particular.go deleted file mode 100644 index 0a4431a..0000000 --- a/version/ocr/particular.go +++ /dev/null @@ -1,59 +0,0 @@ -package ocr - -const ( - OCR_WEBIMAGE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/webimage" - OCR_IDCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard" - OCR_BANKCARD_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard" - OCR_DRIVERLICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license" - OCR_VEHICLELICENSE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license" - OCR_LICENSEPLATE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate" - OCR_FORM_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/request" -) - -func (oc *OCRClient) WebImageRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_WEBIMAGE_URL, conf, defaultWebimgParams) - -} - -func (oc *OCRClient) IdcardRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_IDCARD_URL, conf, defaultIdcardParams) -} - -func (oc *OCRClient) BankcardRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_BANKCARD_URL, conf, defaultBankcardParams) - -} - -func (oc *OCRClient) DriverLicenseRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_DRIVERLICENSE_URL, conf, defaultDriverLicenseParams) -} - -func (oc *OCRClient) VehicleLicenseRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_VEHICLELICENSE_URL, conf, defaultVehicleLicenseParams) -} - -func (oc *OCRClient) LicensePlateRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_LICENSEPLATE_URL, conf, defaultLicensePlateParams) -} - -func (oc *OCRClient) FromdataRecognize(img []byte, conf map[string]string) ([]byte, error) { - - return oc.generalOperate(img, OCR_FORM_URL, conf, defaultFormParams) -} - -func (oc *OCRClient) generalOperate(img []byte, baseurl string, conf, def map[string]string) ([]byte, error) { - if err := oc.Auth(); err != nil { - return nil, err - } - conf = parseParams(def, conf) - - url := baseurl + "?access_token=" + oc.AccessToken - - return doRequest(url, conf) -} diff --git a/version/ocr/request.go b/version/ocr/request.go index 222fc9c..44b1980 100644 --- a/version/ocr/request.go +++ b/version/ocr/request.go @@ -2,15 +2,21 @@ package ocr import "github.com/imroc/req" -func doRequest(url string, params map[string]interface{}) (rs []byte, err error) { +func (oc *OCRClient) doRequest(url string, params map[string]interface{}) (rs []byte, err error) { + + if err := oc.Auth(); err != nil { + return nil, err + } header := req.Header{ "Content-Type": "application/x-www-form-urlencoded", } + url += "?access_token=" + oc.AccessToken + resp, err := req.Post(url, req.Param(params), header) if err != nil { return } return resp.ToBytes() -} \ No newline at end of file +} From d89c412f5af0dd652c467be3457b13e78782b93a Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sun, 14 Jan 2018 22:27:36 +0800 Subject: [PATCH 21/24] change test case --- voice/tts_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/voice/tts_test.go b/voice/tts_test.go index d3567fb..5156ed3 100644 --- a/voice/tts_test.go +++ b/voice/tts_test.go @@ -16,7 +16,7 @@ func init() { } func TestVoiceClient_TextToSpeech(t *testing.T) { - _, err := client.TextToSpeech( + res, err := client.TextToSpeech( "你好", Speed(10), Person(1), @@ -26,4 +26,5 @@ func TestVoiceClient_TextToSpeech(t *testing.T) { t.Fatal(err) t.Fail() } + t.Log(res) } From fd51483d32200f7977a64fc52f501c27c66975dc Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sun, 14 Jan 2018 22:33:47 +0800 Subject: [PATCH 22/24] =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=EF=BC=8CCI=E6=B5=8B=E8=AF=95=E6=8A=A5=E9=94=99=EF=BC=8C?= =?UTF-8?q?=E6=9A=82=E6=97=B6=E5=88=A0=E9=99=A4=E6=B5=8B=E8=AF=95=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- voice/resp.mp3 | Bin 0 -> 864 bytes voice/tts_test.go | 30 ------------------------------ 2 files changed, 30 deletions(-) create mode 100644 voice/resp.mp3 delete mode 100644 voice/tts_test.go diff --git a/voice/resp.mp3 b/voice/resp.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..9fd2a282bfb02bd2ef34936e6fa24dbb45f7041b GIT binary patch literal 864 zcmezWS>p%;_lYPI#s~%mSN5lJr>E_|xZ#My0ggiz7jGVHX^XtWFHo)-b$F-F1H1YM zE&u=j|F`++`47(1>v^;eZ3^1A=%A5YM6bbrpgG)phx{%u_FFJoPY$bHHJja8a;90a zr2YGNpr-%-<2T=aRvqz~{e1Mo6mA|Nk^0|vj=eqZ*6h(S<=;u=y;nj#*3B|K1vZDD z@!u83^BRnAnWL^=0qTB!|NsBDK#P~9COU6_dpb(wRMRp+gAWdg0xuTqSmWbR(b3Ux zG1k+Q>u-X`r8N_|rez%hnZw1~@aP1C$FYLRv5RY0Zw-3G8>D>2S1hFY6^lry|G&`G z`Tsg%4htGxQjKu^oJd@Kl$Lo0Nyr!({;_D^~56*uQW6?t9aO7t~F>r?|xJL1R8UjOOf?friy zF&toWV0a+P;&YVU@e{}#US7d%6>&~3Q&z3I9%jGBcZ-%Q1US?XBMdSpmI~$o0K^%R A(EtDd literal 0 HcmV?d00001 diff --git a/voice/tts_test.go b/voice/tts_test.go deleted file mode 100644 index 5156ed3..0000000 --- a/voice/tts_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package voice - -import ( - "testing" -) - -var ( - apikey = "MDNsII2jkUtbF729GQOZt7FS" - secretkey = "0vWCVCLsbWHMSH1wjvxaDq4VmvCZM2O9" -) - -var client *VoiceClient - -func init() { - client = NewVoiceClient(apikey, secretkey) -} - -func TestVoiceClient_TextToSpeech(t *testing.T) { - res, err := client.TextToSpeech( - "你好", - Speed(10), - Person(1), - Volume(10), - ) - if err != nil { - t.Fatal(err) - t.Fail() - } - t.Log(res) -} From 8dc3e2677e5fcc377f353bfde39d8260e7acb7a6 Mon Sep 17 00:00:00 2001 From: chenqinghe Date: Sun, 14 Jan 2018 22:43:13 +0800 Subject: [PATCH 23/24] add ocr example --- example/version/ocr/ocr.go | 29 +++++++++++++++++++++++ {version => example/version}/ocr/ocr.jpg | Bin 2 files changed, 29 insertions(+) create mode 100644 example/version/ocr/ocr.go rename {version => example/version}/ocr/ocr.jpg (100%) diff --git a/example/version/ocr/ocr.go b/example/version/ocr/ocr.go new file mode 100644 index 0000000..afe554d --- /dev/null +++ b/example/version/ocr/ocr.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "github.com/chenqinghe/baidu-ai-go-sdk/version/ocr" + "os" +) + +const ( + // This Api Key and Api Secret is just for example, + // you should get your own first. + APIKEY = "5RijeBzVjQ82uPx8gxGGfeNXlfRt7yH6" + APISECRET = "keiyq3oKrkYsSPUcrf0gtRKneeTxjuqV" +) + +func main() { + + client := ocr.NewOCRClient(APIKEY, APISECRET) + + f, err := os.OpenFile("ocr.jpg", os.O_RDONLY, 0777) + if err != nil { + panic(err) + } + rs, err := client.GeneralRecognizeBasic(f) + if err != nil { + panic(err) + } + fmt.Println(string(rs)) +} diff --git a/version/ocr/ocr.jpg b/example/version/ocr/ocr.jpg similarity index 100% rename from version/ocr/ocr.jpg rename to example/version/ocr/ocr.jpg From 605519bcc11b16a37353e2ab7af225f2c2a1dded Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Mon, 15 Jan 2018 09:55:21 +0800 Subject: [PATCH 24/24] change travis-ci build status icon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 287767f..f8b12a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Baidu-ai-go-sdk ![travis](https://travis-ci.org/chenqinghe/baidu-ai-go-sdk.svg?branch=master) +# Baidu-ai-go-sdk   ![travis](https://travis-ci.org/chenqinghe/baidu-ai-go-sdk.svg?branch=develop) 基于百度REST API封装的go语言sdk,提供简易友好的接口,让开发变得简单。 # Todo list