티스토리 뷰

반응형

라즈베리파이에서 한글 음성 출력은 이전 포스트에서 보듯이 나름 간단히 해결을 했습니다. 그런데, 진짜 문제는 음성 인식에서 막혔네요.. 기본적으로 윈도우 10에서 한국어 지원이 원할하지 않기 때문에, 여러가지 시도를 해보았지만, 결국 성공한 것은 Get started with speech recognition using REST API를 이용하는 방법이 였습니다. 

 

 

0. 기본적인 내용

 

UWP를 이용해서 언어 인식하는 작업은 SpeechAndTTS라는 셈플 앱만 보더라도 어렵지 않습니다. 다만, 한글이 정식 지원이 되지 않기 때문이 이러고 있는 것입니다.

 

Bing Speech이용하는 방법도 사실 Nuget package를 지원하기 때문에 사용하기 쉽습니다.  다만, 라즈베리 파이가 ARM을 사용하기 때문에 ARM에서 실행되는 Nuget package가 없어서 REST API를 이용하는 것입니다.

 

REST API 셈플대로 하면 간단할 것 같은데...라고 생각하실 수 있을 것 같습니다. 셈플에서는 HttpWebRequest를 이용하였고, 저는 HttpClient를 이용했는데, 이로인해 발생되는 차이점을 극복하는데 시간이 좀 걸렸습니다.

 

결국 엄청난 삽질의 결과라는 것을..

 

 

1. Azure서비스 가입 및 Bing Speech API 구독하기

 

우선 이 서비스는 Azure의 Cognitive Services 중 Bing Speech API라는 녀석으로, 사용하기 위해서는 여기서 사용자 등록을 해서 인증키를 받아야 합니다.

 

아래 이미지는 정상적으로 API 키를 발급 받은 모습입니다. 설명에는 20분당 5,000개의 음성 인식을 처리할 수 있고 남은 날짜가 25일이라고 나옵니다. 처음에는 30일이 였습니다.

 

 

 

 

2. Windows Template Studio를 이용해서 Blank앱을 생성 합니다.

 

Windows Template Studio에 대한 포스트는 블로그 검색으로 찾으시면 쉽게 하실 수 있습니다.

 

이번에 가을 업데이트가 되면서 버전이 올라가서 전에는 보이지 않던, Caliburn.Micro가 추가 되어 있습니다. MVVM Basic를 선택하시고 Next를 누르시고, 페이지는 추가하지 않고, 완료를 해서 프로젝트를 생성 합니다.

 

 

 

3. 화면 디자인

 

MainViewModel를 DataContext에 연결하고, 버튼 2개와 택스트 박스를 추가해 넣었습니다.

 

<Page
    x:Class="IoTSampleWithWTS.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewModels="using:IoTSampleWithWTS.ViewModels"
    Style="{StaticResource PageStyle}"
    mc:Ignorable="d">
    <Page.DataContext>
        <viewModels:MainViewModel />
    </Page.DataContext>

    <Grid
        x:Name="ContentArea"
        Margin="{StaticResource MediumLeftRightMargin}">

        <Grid.RowDefinitions>
            <RowDefinition x:Name="TitleRow" Height="48" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBlock
            x:Name="TitlePage"
            x:Uid="Main_Title"
            Style="{StaticResource PageTitleStyle}" />

        <Grid
            Grid.Row="1"
            Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}">

            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                    <Button Content="Start" FontSize="40" Margin="10" Command="{Binding StartRecodingCommand}" />
                    <Button Content="Stop" FontSize="40" Margin="10" Command="{Binding StopRecodingCommand}" />
                </StackPanel>
                <TextBox x:Name="Result" Text="{Binding ResponseText}" FontSize="20" />
            </StackPanel>

        </Grid>
    </Grid>
</Page>

 

 

 

4. 마이크 사용 권한 얻기 및 음성 파일 저장

 

우선 앱의 Package.appxmanifest 파일을 열어서, Capabilities를 선택하신 후 Microphone을 체크해 줍니다. 

 

그리고, 음성을 녹음한 후에 서비스로 전달 하고 결과를 반환 받아서 택스트를 출력합니다.

 

음성 녹음에 관련된 부분은 MicrophoneHelper이며, 오픈 소스를 참고 했으며, async - await 패턴으로 변경하고, 권한 체크하는 로징을 추가해서 완성 했습니다. (참고한 오픈 소스 링크가 남아 있지 않아서 링크를 추가하지 못했습니다.) 

 

 

5. BingSpeechHelper.cs

 

가장 고생한 부분이 이 부분입니다. 일단 저장된 파일을 열때 반드시 FileStream으로 열어야 제대로 전송이 됩니다.

 

그리고, 헤더 부분을 HttpWebRequest에서 처럼 구성 하는 부분이였습니다. 

 

한번 성공을 하고나서 인식율을 올리기 위해서 조정했던 옵션이 Interactive인지 Conversation인지 Dictation인지를 정하는 부분이 였습니다. 이 옵션에 따라서 반환되는 결과가 서로 다릅니다. DeserializeObject할 모델에 영향을 주는 부분이니 미리 택스트를 보시고 모델을 만들어 주셔야 합니다.

 

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Windows.Storage;
using IoTSampleWithWTS.Interfaces;
using IoTSampleWithWTS.Models;
using Newtonsoft.Json;

namespace IoTSampleWithWTS.Helpers
{
    internal class BingSpeechHelper
    {
        private const string INTERACTIVE = "interactive";
        private const string CONVERSATION = "conversation";
        private const string DICTATION = "dictation";

        private readonly string _language = "ko-KR";
        private readonly string _requestUri;
        private IAuthenticationService _authenticationService;

        public BingSpeechHelper()
        {
            //&format=detailed
            _requestUri =
                $@"https://speech.platform.bing.com/speech/recognition/{
                    INTERACTIVE}/cognitiveservices/v1?language={
                        _language}";

        }

        public async Task<string> GetTextResultAsync(string recordedFilename)
        {
            var file = await ApplicationData.Current.LocalFolder.GetFileAsync(recordedFilename);

            using (var fileStream = new FileStream(file.Path, FileMode.Open, FileAccess.Read))
            {
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
                    client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("text/xml"));
                    client.DefaultRequestHeaders.TransferEncoding.Add(TransferCodingHeaderValue.Parse("chunked"));
                    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "API키를 입력하세요");

                    var content = new StreamContent(fileStream);
                    content.Headers.Add("ContentType", new[] {"audio/wav", "codec=audio/pcm", "samplerate=16000"});

                    try
                    {
                        var response = await client.PostAsync(_requestUri, content);
                        var responseContent = await response.Content.ReadAsStringAsync();
                        var speechResults = JsonConvert.DeserializeObject<BinSpeechResult>(responseContent);

                        content.Dispose();

                        return speechResults.DisplayText;
                    }
                    catch (Exception e)
                    {
                        content.Dispose();
                        Console.WriteLine(e);
                        throw;
                    }
                }
            }
        }
    }
}

 

 

6. MainViewModel.cs

 

Start버튼을 클릭하면, test.wav 파일을 로컬스토리지에 저장하고, Stop 버튼을 클릭하면 해당 파일을 Speech API로 전송해서 결과를 받아오고 ResponseText에 넣어서 화면에 출력합니다.

 

using System.Windows.Input;
using IoTSampleWithWTS.Helpers;

namespace IoTSampleWithWTS.ViewModels
{
    public class MainViewModel : Observable
    {
        private string _responseText;

        public MainViewModel()
        {
            Init();
        }

        public ICommand StartRecodingCommand { get; set; }

        public ICommand StopRecodingCommand { get; set; }

        public string ResponseText
        {
            get => _responseText;
            set => Set(ref _responseText, value);
        }

        private void Init()
        {
            StartRecodingCommand = new RelayCommand(async () =>
            {
                await Singleton<MicrophoneHelper>.Instance.StartRecordingAsync("test.wav");
            });

            StopRecodingCommand = new RelayCommand(async () =>
            {
                await Singleton<MicrophoneHelper>.Instance.StopRecordingAsync();

                var result = await Singleton<BingSpeechHelper>.Instance.GetTextResultAsync("test.wav");
                if (result == null) return;
                ResponseText = result;

            });
        }
    }
}

 

 

7. 한계점

 

REST API는 15초 이상 오디오 전송 불가, 오디오 중간에 결과 반환 불가, 오디오를 LUIS로 직접 전달하는 것 불가라는 한계가 존재 합니다. 더 자세한 사항은 여기를 참고하세요.

 

아..그런데 방금 생각난 내용인데요..JavaScript용 라이브러리를 사용하면 ARM에서 사용이 가능할 것도 같습니다. 테스트를 해보아야 겠지만요..(테스트라 쓰고 삽질이라 읽어야 하지만..)

 

 

 

 

8. 소스

 

Speech API 키를 발급받아서 소스에 넣고 실행하셔야 정상적으로 결과를 보실 수 있습니다.

 

IoTSampleWithWTS.zip
다운로드

 

 

 

반응형
댓글