OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi

30 tháng 1 - XNUMX tháng XNUMX tại Nizhny Novgorod được tổ chức Cuộc thi hackathon OpenVINO. Những người tham gia được yêu cầu tạo nguyên mẫu của giải pháp sản phẩm bằng bộ công cụ Intel OpenVINO. Ban tổ chức đã đề xuất một danh sách các chủ đề gần đúng có thể được hướng dẫn khi lựa chọn nhiệm vụ nhưng quyết định cuối cùng vẫn thuộc về các đội. Ngoài ra, việc sử dụng các mẫu không có trong sản phẩm cũng được khuyến khích.

OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi

Trong bài viết này, chúng tôi sẽ cho bạn biết về cách chúng tôi tạo ra nguyên mẫu sản phẩm mà cuối cùng chúng tôi đã giành được vị trí đầu tiên.

Hơn 10 đội đã tham gia hackathon. Thật tuyệt khi một số người trong số họ đến từ các vùng khác. Địa điểm tổ chức hackathon là khu phức hợp “Kremlinsky trên Pochain”, nơi những bức ảnh cổ của Nizhny Novgorod được treo bên trong cùng một đoàn tùy tùng! (Tôi xin nhắc bạn rằng hiện tại văn phòng trung tâm của Intel được đặt tại Nizhny Novgorod). Những người tham gia có 26 giờ để viết mã và cuối cùng họ phải trình bày giải pháp của mình. Một lợi thế riêng biệt là sự hiện diện của phiên demo để đảm bảo rằng mọi thứ đã lên kế hoạch đều được thực hiện thực sự và không còn ý tưởng nào trong bản trình bày. Hàng hóa, đồ ăn nhẹ, đồ ăn, mọi thứ đều ở đó!

Ngoài ra, Intel tùy chọn cung cấp camera, Raspberry PI, Neural Computer Stick 2.

lựa chọn nhiệm vụ

Một trong những phần khó khăn nhất khi chuẩn bị cho cuộc thi hackathon dạng tự do là chọn thử thách. Chúng tôi ngay lập tức quyết định nghĩ ra một thứ chưa có trong sản phẩm, vì thông báo cho biết rằng điều này rất được hoan nghênh.

Đã phân tích mô hình, được bao gồm trong sản phẩm trong bản phát hành hiện tại, chúng tôi đi đến kết luận rằng hầu hết chúng đều giải quyết được các vấn đề về thị giác máy tính khác nhau. Hơn nữa, rất khó để đưa ra một vấn đề trong lĩnh vực thị giác máy tính mà không thể giải quyết được bằng OpenVINO, và ngay cả khi có thể phát minh ra một vấn đề thì cũng rất khó tìm được các mô hình được đào tạo trước trong phạm vi công cộng. Chúng tôi quyết định đào theo hướng khác - hướng tới xử lý và phân tích giọng nói. Hãy xem xét một nhiệm vụ thú vị là nhận biết cảm xúc từ lời nói. Phải nói rằng OpenVINO đã có mô hình xác định cảm xúc của một người dựa trên khuôn mặt của họ, nhưng:

  • Về lý thuyết, có thể tạo ra một thuật toán kết hợp hoạt động trên cả âm thanh và hình ảnh, giúp tăng độ chính xác.
  • Camera thường có góc nhìn hẹp; cần nhiều hơn một camera để bao phủ một khu vực rộng lớn; âm thanh không có giới hạn như vậy.

Hãy phát triển ý tưởng: hãy lấy ý tưởng cho mảng bán lẻ làm cơ sở. Bạn có thể đo lường sự hài lòng của khách hàng khi thanh toán tại cửa hàng. Nếu một trong những khách hàng không hài lòng với dịch vụ và bắt đầu lên tiếng, bạn có thể gọi ngay cho quản trị viên để được trợ giúp.
Trong trường hợp này, chúng tôi cần thêm tính năng nhận dạng giọng nói của con người, điều này sẽ cho phép chúng tôi phân biệt nhân viên cửa hàng với khách hàng và cung cấp phân tích cho từng cá nhân. Ngoài ra, có thể phân tích hành vi của chính nhân viên cửa hàng, đánh giá bầu không khí trong đội, nghe hay đấy!

Chúng tôi xây dựng các yêu cầu cho giải pháp của mình:

  • Kích thước nhỏ của thiết bị mục tiêu
  • Hoạt động thời gian thực
  • Giá thấp
  • Khả năng mở rộng dễ dàng

Kết quả là chúng tôi chọn Raspberry Pi 3 c làm thiết bị mục tiêu Intel NCS 2.

Ở đây, điều quan trọng cần lưu ý là một tính năng quan trọng của NCS - nó hoạt động tốt nhất với kiến ​​trúc CNN tiêu chuẩn, nhưng nếu bạn cần chạy một mô hình có các lớp tùy chỉnh trên đó thì hãy mong đợi tối ưu hóa ở mức độ thấp.

Chỉ có một việc nhỏ cần làm: bạn cần có một chiếc micro. Một micrô USB thông thường sẽ làm được nhưng nó sẽ không đẹp khi kết hợp với RPI. Nhưng ngay cả ở đây, giải pháp theo nghĩa đen là “nằm gần đây”. Để ghi âm giọng nói, chúng tôi quyết định sử dụng bảng Voice Bonnet trong bộ sản phẩm Bộ giọng nói AIY của Google, trên đó có micrô âm thanh nổi có dây.

Tải xuống Raspbian từ Kho dự án AIY và tải nó lên ổ đĩa flash, kiểm tra xem micrô có hoạt động hay không bằng lệnh sau (nó sẽ ghi âm thanh dài 5 giây và lưu vào một tệp):

arecord -d 5 -r 16000 test.wav

Tôi cần lưu ý ngay rằng micrô rất nhạy và thu tiếng ồn tốt. Để khắc phục điều này chúng ta vào alsamixer, chọn Capture devices và giảm mức tín hiệu đầu vào xuống 50-60%.

OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi
Chúng tôi sửa đổi nội dung bằng một tệp và mọi thứ đều phù hợp, bạn thậm chí có thể đóng nó bằng nắp

Thêm nút chỉ báo

Khi tách AIY Voice Kit ra, chúng tôi nhớ rằng có một nút RGB, đèn nền của nút này có thể được điều khiển bằng phần mềm. Chúng tôi tìm kiếm “Google AIY Led” và tìm tài liệu: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Tại sao không dùng nút này để hiển thị cảm xúc đã nhận biết, chúng ta chỉ có 7 lớp, và nút này có 8 màu, vừa đủ!

Chúng tôi kết nối nút qua GPIO với Voice Bonnet, tải các thư viện cần thiết (chúng đã được cài đặt trong bộ phân phối từ các dự án AIY)

from aiy.leds import Leds, Color
from aiy.leds import RgbLeds

Hãy tạo một dict trong đó mỗi cảm xúc sẽ có một màu tương ứng dưới dạng RGB Tuple và một đối tượng thuộc lớp aiy.leds.Leds, qua đó chúng ta sẽ cập nhật màu:

led_dict = {'neutral': (255, 255, 255), 'happy': (0, 255, 0), 'sad': (0, 255, 255), 'angry': (255, 0, 0), 'fearful': (0, 0, 0), 'disgusted':  (255, 0, 255), 'surprised':  (255, 255, 0)} 
leds = Leds()

Và cuối cùng, sau mỗi dự đoán mới về một cảm xúc, chúng tôi sẽ cập nhật màu sắc của nút phù hợp với nó (theo phím).

leds.update(Leds.rgb_on(led_dict.get(classes[prediction])))

OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi
Nút, đốt đi!

Làm việc với giọng nói

Chúng tôi sẽ sử dụng pyaudio để ghi luồng từ micrô và webrtcvad để lọc tiếng ồn và phát hiện giọng nói. Ngoài ra, chúng tôi sẽ tạo một hàng đợi để thêm và xóa các đoạn trích giọng nói một cách không đồng bộ.

Vì webrtcvad có giới hạn về kích thước của đoạn được cung cấp - nó phải bằng 10/20/30ms và việc đào tạo mô hình để nhận biết cảm xúc (như chúng ta sẽ tìm hiểu sau) đã được thực hiện trên tập dữ liệu 48kHz, chúng tôi sẽ chụp các khối có kích thước 48000×20ms/1000×1(mono)=960 byte. Webrtcvad sẽ trả về Đúng/Sai cho từng đoạn này, tương ứng với sự hiện diện hay vắng mặt của phiếu bầu trong đoạn đó.

Hãy thực hiện logic sau:

  • Chúng tôi sẽ thêm vào danh sách những khối có phiếu bầu; nếu không có phiếu bầu, thì chúng tôi sẽ tăng bộ đếm của các khối trống.
  • Nếu bộ đếm của các khối trống là >=30 (600 ms), thì chúng ta xem xét kích thước của danh sách các khối tích lũy; nếu nó >250 thì chúng ta thêm nó vào hàng đợi; nếu không, chúng ta coi đó là độ dài của bản ghi không đủ để cung cấp cho mô hình nhằm xác định người nói.
  • Nếu bộ đếm của các khối trống vẫn < 30 và kích thước của danh sách các khối tích lũy vượt quá 300 thì chúng tôi sẽ thêm đoạn đó vào hàng đợi để dự đoán chính xác hơn. (vì cảm xúc có xu hướng thay đổi theo thời gian)

 def to_queue(frames):
    d = np.frombuffer(b''.join(frames), dtype=np.int16)
    return d

framesQueue = queue.Queue()
def framesThreadBody():
    CHUNK = 960
    FORMAT = pyaudio.paInt16
    CHANNELS = 1
    RATE = 48000

    p = pyaudio.PyAudio()
    vad = webrtcvad.Vad()
    vad.set_mode(2)
    stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)
    false_counter = 0
    audio_frame = []
    while process:
        data = stream.read(CHUNK)
        if not vad.is_speech(data, RATE):
            false_counter += 1
            if false_counter >= 30:
                if len(audio_frame) > 250:              
                    framesQueue.put(to_queue(audio_frame,timestamp_start))
                    audio_frame = []
                    false_counter = 0

        if vad.is_speech(data, RATE):
            false_counter = 0
            audio_frame.append(data)
            if len(audio_frame) > 300:                
                    framesQueue.put(to_queue(audio_frame,timestamp_start))
                    audio_frame = []

Đã đến lúc tìm kiếm các mô hình được đào tạo trước trong phạm vi công cộng, hãy truy cập github, Google, nhưng hãy nhớ rằng chúng tôi có một hạn chế về kiến ​​trúc được sử dụng. Đây là phần khá khó khăn, vì bạn phải kiểm thử các mô hình trên dữ liệu đầu vào của mình, đồng thời phải chuyển đổi chúng sang định dạng nội bộ của OpenVINO - IR (Intermediate Reexpressation). Chúng tôi đã thử khoảng 5-7 giải pháp khác nhau từ github và nếu mô hình nhận dạng cảm xúc hoạt động ngay lập tức thì với tính năng nhận dạng giọng nói, chúng tôi phải đợi lâu hơn - họ sử dụng các kiến ​​trúc phức tạp hơn.

Chúng tôi tập trung vào những điều sau đây:

  • Cảm xúc từ giọng nói - https://github.com/alexmuhr/Voice_Emotion
    Nó hoạt động theo nguyên tắc sau: âm thanh được cắt thành các đoạn có kích thước nhất định, đối với mỗi đoạn này chúng ta chọn MFCC và sau đó gửi chúng làm đầu vào cho CNN
  • Nhận diện giọng nói - https://github.com/linhdvu14/vggvox-speaker-identification
    Ở đây, thay vì MFCC, chúng tôi làm việc với một biểu đồ phổ, sau FFT, chúng tôi cung cấp tín hiệu cho CNN, ở đầu ra, chúng tôi nhận được biểu diễn vectơ của giọng nói.

Tiếp theo chúng ta sẽ nói về việc chuyển đổi mô hình, bắt đầu từ lý thuyết. OpenVINO bao gồm một số mô-đun:

  • Mở Sở thú mô hình, các mô hình có thể được sử dụng và đưa vào sản phẩm của bạn
  • Trình tối ưu hóa mô hình, nhờ đó bạn có thể chuyển đổi mô hình từ các định dạng khung khác nhau (Tensorflow, ONNX, v.v.) sang định dạng Biểu diễn trung gian mà chúng tôi sẽ làm việc thêm
  • Công cụ suy luận cho phép bạn chạy các mô hình ở định dạng IR trên bộ xử lý Intel, chip Myriad và bộ tăng tốc Neural Computer Stick
  • Phiên bản OpenCV hiệu quả nhất (có hỗ trợ Công cụ suy luận)
    Mỗi mô hình ở định dạng IR được mô tả bằng hai tệp: .xml và .bin.
    Các mô hình được chuyển đổi sang định dạng IR thông qua Trình tối ưu hóa mô hình như sau:

    python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py --input_model speaker.hdf5.pb --data_type=FP16 --input_shape [1,512,1000,1]

    --data_type cho phép bạn chọn định dạng dữ liệu mà mô hình sẽ hoạt động. FP32, FP16, INT8 được hỗ trợ. Việc chọn loại dữ liệu tối ưu có thể giúp tăng hiệu suất tốt.
    --input_shape cho biết kích thước của dữ liệu đầu vào. Khả năng thay đổi linh hoạt nó dường như đã có trong API C++, nhưng chúng tôi đã không đào sâu đến mức đó và chỉ sửa nó cho một trong các mô hình.
    Tiếp theo, hãy thử tải mô hình đã được chuyển đổi ở định dạng IR thông qua mô-đun DNN vào OpenCV và chuyển tiếp nó tới nó.

    import cv2 as cv
    emotionsNet = cv.dnn.readNet('emotions_model.bin',
                              'emotions_model.xml')
    emotionsNet.setPreferableTarget(cv.dnn.DNN_TARGET_MYRIAD)

    Dòng cuối cùng trong trường hợp này cho phép bạn chuyển hướng các phép tính đến Thanh điện toán thần kinh, các phép tính cơ bản được thực hiện trên bộ xử lý, nhưng trong trường hợp Raspberry Pi, điều này sẽ không hoạt động, bạn sẽ cần một thanh.

    Tiếp theo, logic như sau: chúng tôi chia âm thanh của mình thành các cửa sổ có kích thước nhất định (đối với chúng tôi là 0.4 giây), chúng tôi chuyển đổi từng cửa sổ này thành MFCC, sau đó chúng tôi đưa vào lưới:

    emotionsNet.setInput(MFCC_from_window)
    result = emotionsNet.forward()

    Tiếp theo, hãy chọn lớp phổ biến nhất cho tất cả các cửa sổ. Một giải pháp đơn giản, nhưng đối với hackathon, bạn không cần phải nghĩ ra thứ gì đó quá trừu tượng, chỉ khi bạn có thời gian. Chúng ta vẫn còn rất nhiều việc phải làm, vì vậy hãy tiếp tục - chúng ta sẽ giải quyết vấn đề nhận dạng giọng nói. Cần phải tạo ra một loại cơ sở dữ liệu nào đó để lưu trữ các biểu đồ phổ của giọng nói được ghi âm trước. Vì còn rất ít thời gian nên chúng tôi sẽ giải quyết vấn đề này tốt nhất có thể.

    Cụ thể, chúng tôi tạo một tập lệnh để ghi một đoạn trích giọng nói (nó hoạt động theo cách tương tự như mô tả ở trên, chỉ khi bị gián đoạn từ bàn phím, nó sẽ lưu giọng nói vào một tệp).

    Hãy thử:

    python3 voice_db/record_voice.py test.wav

    Chúng tôi ghi lại giọng nói của một số người (trong trường hợp của chúng tôi là ba thành viên trong nhóm)
    Tiếp theo, đối với mỗi giọng nói được ghi, chúng tôi thực hiện một phép biến đổi phạm vi nhanh, thu được một biểu đồ phổ và lưu nó dưới dạng một mảng có nhiều mảng (.npy):

    for file in glob.glob("voice_db/*.wav"):
            spec = get_fft_spectrum(file)
            np.save(file[:-4] + '.npy', spec)

    Thêm chi tiết trong tập tin create_base.py
    Kết quả là, khi chúng tôi chạy tập lệnh chính, chúng tôi sẽ nhận được các phần nhúng từ các biểu đồ phổ này ngay từ đầu:

    for file in glob.glob("voice_db/*.npy"):
        spec = np.load(file)
        spec = spec.astype('float32')
        spec_reshaped = spec.reshape(1, 1, spec.shape[0], spec.shape[1])
        srNet.setInput(spec_reshaped)
        pred = srNet.forward()
        emb = np.squeeze(pred)

    Sau khi nhận được phần nhúng từ phân đoạn âm thanh, chúng tôi sẽ có thể xác định nó thuộc về ai bằng cách lấy khoảng cách cosin từ đoạn văn đến tất cả các giọng nói trong cơ sở dữ liệu (càng nhỏ thì càng có nhiều khả năng) - đối với bản demo, chúng tôi đặt ngưỡng đến 0.3):

            dist_list = cdist(emb, enroll_embs, metric="cosine")
            distances = pd.DataFrame(dist_list, columns = df.speaker)

    Cuối cùng, tôi muốn lưu ý rằng tốc độ suy luận nhanh và có thể thêm 1-2 mô hình nữa (đối với một mẫu dài 7 giây thì phải mất 2.5 cho suy luận). Chúng tôi không còn thời gian để thêm các mô hình mới và tập trung vào việc viết nguyên mẫu của ứng dụng web.

    Ứng dụng web

    Một điểm quan trọng: chúng tôi mang theo bộ định tuyến ở nhà và thiết lập mạng cục bộ, nó giúp kết nối thiết bị và máy tính xách tay qua mạng.

    Phần phụ trợ là kênh tin nhắn đầu cuối giữa mặt trước và Raspberry Pi, dựa trên công nghệ websocket (giao thức http qua tcp).

    Giai đoạn đầu tiên là nhận thông tin đã được xử lý từ Raspberry, tức là các yếu tố dự đoán được đóng gói trong json, được lưu trong cơ sở dữ liệu trong suốt hành trình của họ để có thể tạo số liệu thống kê về nền tảng cảm xúc của người dùng trong khoảng thời gian đó. Gói này sau đó được gửi đến giao diện người dùng, sử dụng đăng ký và nhận các gói từ điểm cuối websocket. Toàn bộ cơ chế phụ trợ được xây dựng bằng ngôn ngữ golang; nó được chọn vì nó rất phù hợp cho các tác vụ không đồng bộ mà goroutine xử lý tốt.
    Khi truy cập vào điểm cuối, người dùng được đăng ký và nhập vào cấu trúc thì tin nhắn của anh ta sẽ được nhận. Cả người dùng và tin nhắn đều được nhập vào một trung tâm chung, từ đó các tin nhắn đã được gửi tiếp (đến mặt trước đã đăng ký) và nếu người dùng đóng kết nối (mâm xôi hoặc mặt trước), thì đăng ký của anh ta sẽ bị hủy và anh ta sẽ bị xóa khỏi trung tâm.

    OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi
    Chúng tôi đang chờ đợi một kết nối từ phía sau

    Front-end là một ứng dụng web được viết bằng JavaScript sử dụng thư viện React để tăng tốc và đơn giản hóa quá trình phát triển. Mục đích của ứng dụng này là trực quan hóa dữ liệu thu được bằng thuật toán chạy ở mặt sau và trực tiếp trên Raspberry Pi. Trang này đã triển khai định tuyến theo từng phần bằng bộ định tuyến phản ứng, nhưng trang quan tâm chính là trang chính, nơi nhận được luồng dữ liệu liên tục trong thời gian thực từ máy chủ bằng công nghệ WebSocket. Raspberry Pi phát hiện giọng nói, xác định xem giọng nói đó có thuộc về một người cụ thể từ cơ sở dữ liệu đã đăng ký hay không và gửi danh sách xác suất cho khách hàng. Khách hàng hiển thị dữ liệu liên quan mới nhất, hiển thị hình đại diện của người có nhiều khả năng đã nói vào micrô nhất, cũng như cảm xúc khi anh ta phát âm các từ đó.

    OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi
    Trang chủ với những dự đoán được cập nhật

    Kết luận

    Không thể hoàn thành mọi thứ theo kế hoạch, đơn giản là chúng tôi không có thời gian, vì vậy hy vọng chính là ở bản demo rằng mọi thứ sẽ hoạt động. Trong phần trình bày, họ nói về cách mọi thứ hoạt động, những mô hình họ đã sử dụng, những vấn đề họ gặp phải. Tiếp theo là phần demo - các chuyên gia đi quanh phòng theo thứ tự ngẫu nhiên và tiếp cận từng đội để xem nguyên mẫu đang hoạt động. Họ cũng đặt câu hỏi cho chúng tôi, mọi người đều trả lời phần của mình, họ rời web trên máy tính xách tay và mọi thứ thực sự hoạt động như mong đợi.

    Hãy để tôi lưu ý rằng tổng chi phí cho giải pháp của chúng tôi là 150 USD:

    • Raspberry Pi 3 ~ $35
    • Google AIY Voice Bonnet (bạn có thể mất phí phát lại) ~ 15$
    • Intel NCS 2 ~ 100$

    Làm cách nào để cải thiện:

    • Sử dụng đăng ký từ khách hàng - yêu cầu đọc văn bản được tạo ngẫu nhiên
    • Thêm một vài mẫu nữa: bạn có thể xác định giới tính, độ tuổi bằng giọng nói
    • Tách biệt các giọng nói phát ra đồng thời (diarization)

    Kho: https://github.com/vladimirwest/OpenEMO

    OpenVINO hackathon: nhận dạng giọng nói và cảm xúc trên Raspberry Pi
    Mệt nhưng chúng ta vui

    Cuối cùng, tôi muốn gửi lời cảm ơn đến ban tổ chức và những người tham gia. Trong số các dự án của các nhóm khác, cá nhân chúng tôi thích giải pháp giám sát chỗ đậu xe miễn phí. Đối với chúng tôi, đó là một trải nghiệm cực kỳ thú vị khi đắm mình vào sản phẩm và quá trình phát triển. Tôi hy vọng rằng sẽ ngày càng có nhiều sự kiện thú vị được tổ chức ở các khu vực, bao gồm cả về chủ đề AI.

Nguồn: www.habr.com

Thêm một lời nhận xét