痞子衡嵌入式:语音处理工具Jays-PySPEECH诞生记(3)- 音频显示实现(Matplotlib, NumPy1.15.0)
大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是语音处理工具pzh-py-speech诞生之音频显示实现。
音频显示是pzh-py-speech的主要功能,pzh-py-speech借助的是Matplotlib以及NumPy来实现的音频显示功能,今天痞子衡为大家介绍音频显示在pzh-py-speech中是如何实现的。
一、SciPy工具集
SciPy是一套Python科学计算相关的工具集,其本身也是一个Python库,这个工具集主要包含以下6大Python库,pzh-py-speech所用到的Matplotlib以及NumPy均属于SciPy工具集。
1.1 NumPy
NumPy是一套最基础的Python科学计算包,它主要用于数组与矩阵运算,它是一个开源项目,被收录进 NumFOCUS 组织维护的 Sponsored Project 里。pzh-py-speech使用的是NumPy 1.15.0。
NumPy库的官方主页如下:
- NumPy官方主页: http://www.numpy.org/
- NumPy安装方法: https://pypi.org/project/numpy/
NumPy的快速上手可参考这个网页 https://docs.scipy.org/doc/numpy/user/quickstart.html
1.2 Matplotlib
Matplotlib是一套Python高质量2D绘图库,它的初始设计者为John Hunter,它也是一个开源项目,被同样收录进 NumFOCUS 组织维护的 Sponsored Project 里。pzh-py-speech使用的是Matplotlib 2.2.3。
Matplotlib库的官方主页如下:
- Matplotlib官方主页: https://matplotlib.org/
- Matplotlib安装方法: https://pypi.org/project/matplotlib/
Matplotlib绘图功能非常强大,但是作为一般使用,我们没有必要去通读其官方文档,其提供了非常多的example代码,这些example都在 https://matplotlib.org/gallery/index.html, 我们只要找到能满足我们需求的example,在其基础上简单修改即可。 下面就是一个最简单的正弦波示例:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='About as simple as it gets, folks')
ax.grid()
fig.savefig("test.png")
plt.show()
二、pzh-py-speech音频显示实现
pzh-py-speech关于音频显示功能实现主要有四点:选择.wav文件、读取.wav文件、绘制.wav波形、添加光标功能,最终pzh-py-speech效果如下图所示,痞子衡为逐一为大家介绍实现细节。
2.1 选择.wav文件功能
选择wav文件主要借助的是wxPython里的genericDirCtrl控件提供的功能实现的,我们使用genericDirCtrl控件创建了一个名为m_genericDirCtrl_audioDir的对象,借助其SetFilter()方法实现了仅显示.wav文件格式的过滤,并且我们为m_genericDirCtrl_audioDir还创建了一个event,即viewAudio(),这个event的触发条件是选中m_genericDirCtrl_audioDir里列出的.wav文件,当viewAudio()被触发时,我们通过GetFilePath()方法即可获得选中的.wav文件路径。
class mainWin(win.speech_win):
def __init__(self, parent):
win.speech_win.__init__(self, parent)
# ...
self.m_genericDirCtrl_audioDir.SetFilter("Audio files (*.wav)|*.wav")
def viewAudio( self, event ):
self.wavPath = self.m_genericDirCtrl_audioDir.GetFilePath()
2.2 读取.wav文件功能
读取.wav文件主要借助的是python自带的标准库wave,以及第三方的NumPy库。痞子衡创建了一个名为wavCanvasPanel的类,在这个类中定义了readWave(self, wavPath, wavInfo)方法,其中参数wavPath即是要读取的.wav文件路径,参数wavInfo是GUI状态栏对象,用于直观显示读取到的.wav文件信息。
在wavCanvasPanel.readWave()方法中,痞子衡首先使用了wave库里的功能获取到.wav文件的所有信息以及所有PCM数据,然后借助NumPy库将PCM数据按channel重新组织,便于后续图形显示。关于数据重新组织,有一个地方需要特别说明,即int24类型(3-byte)是不被NumPy中的fromstring()原生支持,因此痞子衡自己实现了一个非标准类型数据的fromstring()。
import numpy
import wave
class wavCanvasPanel(wx.Panel):
def fromstring(self, wavData, alignedByte):
if alignedByte <= 8:
src = numpy.ndarray(len(wavData), numpy.dtype('>i1'), wavData)
dest = numpy.zeros(len(wavData) / alignedByte, numpy.dtype('>i8'))
for i in range(alignedByte):
dest.view(dtype='>i1')[alignedByte-1-i::8] = src.view(dtype='>i1')[i::alignedByte]
[hex(x) for x in dest]
return True, dest
else:
return False, wavData
def readWave(self, wavPath, wavInfo):
if os.path.isfile(wavPath):
# Open the wav file to get wave data and parameters
wavFile = wave.open(wavPath, "rb")
wavParams = wavFile.getparams()
wavChannels = wavParams[0]
wavSampwidth = wavParams[1]
wavFramerate = wavParams[2]
wavFrames = wavParams[3]
wavInfo.SetStatusText('Opened Audio Info = ' +
'Channels:' + str(wavChannels) +
', SampWidth:' + str(wavSampwidth) + 'Byte' +
', SampRate:' + str(wavFramerate) + 'kHz' +
', FormatTag:' + wavParams[4])
wavData = wavFile.readframes(wavFrames)
wavFile.close()
# Transpose the wav data if wave has multiple channels
if wavSampwidth == 1:
dtype = numpy.int8
elif wavSampwidth == 2:
dtype = numpy.int16
elif wavSampwidth == 3:
dtype = None
elif wavSampwidth == 4:
dtype = numpy.float32
else:
return 0, 0, 0
if dtype != None:
retData = numpy.fromstring(wavData, dtype = dtype)
else:
# Implement int24 manually
status, retData = self.fromstring(wavData, 3)
if not status:
return 0, 0, 0
if wavChannels != 1:
retData.shape = -1, wavChannels
retData = retData.T
# Calculate and arange wave time
retTime = numpy.arange(0, wavFrames) * (1.0 / wavFramerate)
retChannels = wavChannels
return retChannels, retData, retTime
else:
return 0, 0, 0
2.3 绘制.wav波形功能
绘制.wav波形是最主要的功能。痞子衡在wavCanvasPanel类中实现了showWave(self, wavPath, wavInfo)方法,这个方法会在GUI控件m_genericDirCtrl_audioDir的事件函数viewAudio()中被调用。
在wavCanvasPanel.showWave()方法中,痞子衡首先使用了readWave()获取.wav文件中经过重新组织的PCM数据,然后借助Matplotlib中的figure类中的add_axes()方法逐一将各channel的PCM数据绘制出来,并辅以各种信息(x、y轴精度、标签等)一同显示出来。由于GUI控件里专门用于显示波形的Panel对象尺寸为720*360 inch,痞子衡限制了最多显示.wav的前8通道。
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
MAX_AUDIO_CHANNEL = 8
#unit: inch
PLOT_PANEL_WIDTH = 720
PLOT_PANEL_HEIGHT = 360
#unit: percent
PLOT_AXES_WIDTH_TITLE = 0.05
PLOT_AXES_HEIGHT_LABEL = 0.075
class wavCanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
dpi = 60
width = PLOT_PANEL_WIDTH / dpi
height = PLOT_PANEL_HEIGHT / dpi
self.wavFigure = Figure(figsize=[width,height], dpi=dpi, facecolor='#404040')
self.wavCanvas = FigureCanvas(self, -1, self.wavFigure)
self.wavSizer = wx.BoxSizer(wx.VERTICAL)
self.wavSizer.Add(self.wavCanvas, 1, wx.EXPAND|wx.ALL)
self.SetSizerAndFit(self.wavSizer)
self.wavAxes = [None] * MAX_AUDIO_CHANNEL
def readWave(self, wavPath, wavInfo):
# ...
def showWave(self, wavPath, wavInfo):
self.wavFigure.clear()
waveChannels, waveData, waveTime = self.readWave(wavPath, wavInfo)
if waveChannels != 0:
# Note: only show max supported channel if actual channel > max supported channel
if waveChannels > MAX_AUDIO_CHANNEL:
waveChannels = MAX_AUDIO_CHANNEL
# Polt the waveform of each channel in sequence
for i in range(waveChannels):
left = PLOT_AXES_HEIGHT_LABEL
bottom = (1.0 / waveChannels) * (waveChannels - 1 - i) + PLOT_AXES_HEIGHT_LABEL
height = 1.0 / waveChannels - (PLOT_AXES_WIDTH_TITLE + PLOT_AXES_HEIGHT_LABEL)
width = 1 - left - 0.05
self.wavAxes[i] = self.wavFigure.add_axes([left, bottom, width, height], facecolor='k')
self.wavAxes[i].set_prop_cycle(color='#00F279', lw=[1])
self.wavAxes[i].set_xlabel('time (s)', color='w')
self.wavAxes[i].set_ylabel('value', color='w')
if waveChannels == 1:
data = waveData
else:
data = waveData[i]
self.wavAxes[i].plot(waveTime, data)
self.wavAxes[i].grid()
self.wavAxes[i].tick_params(labelcolor='w')
self.wavAxes[i].set_title('Audio Channel ' + str(i), color='w')
# Note!!!: draw() must be called if figure has been cleared once
self.wavCanvas.draw()
class mainWin(win.speech_win):
def __init__(self, parent):
win.speech_win.__init__(self, parent)
self.wavPanel = wavCanvasPanel(self.m_panel_plot)
# ...
def viewAudio( self, event ):
self.wavPath = self.m_genericDirCtrl_audioDir.GetFilePath()
self.wavPanel.showWave(self.wavPath, self.statusBar)
2.4 添加光标功能
光标定位功能不是必要功能,但其可以让软件看起来高大上,痞子衡创建了一个名为wavCursor类来实现它,主要在这个类中实现了moveMouse方法,这个方法将会被FigureCanvasWxAgg类中的mpl_connect()方法添加到各通道axes中。
MAX_AUDIO_CHANNEL = 8
class wavCursor(object):
def __init__(self, ax, x, y):
self.ax = ax
self.vline = ax.axvline(color='r', alpha=1)
self.hline = ax.axhline(color='r', alpha=1)
self.marker, = ax.plot([0],[0], marker="o", color="crimson", zorder=3)
self.x = x
self.y = y
self.xlim = self.x[len(self.x)-1]
self.text = ax.text(0.7, 0.9, '', bbox=dict(facecolor='red', alpha=0.5))
def moveMouse(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
if x > self.xlim:
x = self.xlim
index = numpy.searchsorted(self.x, [x])[0]
x = self.x[index]
y = self.y[index]
self.vline.set_xdata(x)
self.hline.set_ydata(y)
self.marker.set_data([x],[y])
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.text.set_position((x,y))
self.ax.figure.canvas.draw_idle()
class wavCanvasPanel(wx.Panel):
def __init__(self, parent):
# ...
self.wavAxes = [None] * MAX_AUDIO_CHANNEL
# 定义光标对象
self.wavCursor = [None] * MAX_AUDIO_CHANNEL
def showWave(self, wavPath, wavInfo):
# ...
if waveChannels != 0:
# ...
for i in range(waveChannels):
# ...
self.wavAxes[i].set_title('Audio Channel ' + str(i), color='w')
# 实例化光标对象,并使用mpl_connect()将moveMouse()动作加入光标对象
self.wavCursor[i] = wavCursor(self.wavAxes[i], waveTime, data)
self.wavCanvas.mpl_connect('motion_notify_event', self.wavCursor[i].moveMouse)
# ...
至此,语音处理工具pzh-py-speech诞生之音频显示实现痞子衡便介绍完毕了,掌声在哪里~~~
参考文档
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。
痞子衡嵌入式:语音处理工具Jays-PySPEECH诞生记(3)- 音频显示实现(Matplotlib, NumPy1.15.0)的更多相关文章
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(2)- 界面构建(wxFormBuilder3.8.0)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之界面构建. 一个软件的UI界面是非常重要的,这是软件与用户交互的接口,软件功能即使再强大,但如果没 ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记 - 索引
大家好,我是痞子衡,是正经搞技术的痞子.本系列痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生. 串口调试助手是嵌入式开发里非常常用的小工具,市面上有非常多流行的串口调试工具,比如TeraTe ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(1)- 环境搭建(Python2.7.14 + pySerial3.4 + wxPython4.0.3)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之环境搭建. 在写Jays-PyCOM时需要先搭好开发和调试环境,下表列出了开发过程中会用到的所有软 ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(6)- 打包发布(PyInstaller3.3.1)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之打包发布. 经过上一篇软件优化之后,Jays-PyCOM已经初长成,该到了出去历练的时候了,只有经 ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(3)- 串口功能实现(pySerial)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之串口功能实现. 串口调试助手是最核心的当然是串口数据收发与显示的功能,Jays-PyCOM借助的是 ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(5)- 软件优化
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之软件优化. 前面痞子衡已经初步实现了Jays-PyCOM的串口功能,并且通过了最基本的测试,但目前 ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(4)- 联合调试(vspd, sscom, PyCharm2018.2)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之联合调试. 软件开发离不开调试,调试手段分两种:一是黑盒调试,即直接从输入/输出角度测试软件功能是 ...
- 痞子衡嵌入式:语音处理工具Jays-PySPEECH诞生记 - 索引
大家好,我是痞子衡,是正经搞技术的痞子.本系列痞子衡给大家介绍的是语音处理工具Jays-PySPEECH诞生. 智能语音交互市场近年来发展迅速,其典型的应用之一智能音箱产品如今已走入千家万户,深受大家 ...
- 痞子衡嵌入式:语音处理工具Jays-PySPEECH诞生记(1)- 环境搭建(Python2.7.14 + PyAudio0.2.11 + Matplotlib2.2.3 + SpeechRecognition3.8.1 + pyttsx3 2.7)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是语音处理工具Jays-PySPEECH诞生之环境搭建. 在写Jays-PySPEECH时需要先搭好开发环境,下表列出了开发过程中会用到的 ...
随机推荐
- IaaS,PaaS和SaaS
云计算的三种服务模式:IaaS,PaaS和SaaS IaaS: Infrastructure-as-a-Service(基础设施即服务)是第一层. PaaS: Platform-as-a-Servic ...
- 记录一种下载https网址中的mp4文件的方法
需要下载一个网页中的视频, 页面中的视频播放器为 JW player, 通过搜索发现可以下载对应的视频. 1. 使用chrome浏览器分析 网页中的视频地址: F12或者右键-->检查, 在打开 ...
- pygame学习
http://eyehere.net/2011/python-pygame-novice-professional-3/ http://www.pygame.org/docs/ref/event.ht ...
- HBase rebalance 负载均衡源码角度解读使用姿势
关键词:hbase rebalance 负载均衡 参考源码版本:apache-hbase-1.1.2 什么是HBase Rebalance ? 随着数据写入越来越多以及不均衡,即使一开始每个Regio ...
- Java EE 导图
- [LeetCode] All Nodes Distance K in Binary Tree 二叉树距离为K的所有结点
We are given a binary tree (with root node root), a target node, and an integer value K. Return a li ...
- 航班座位_hihocoder
题目2 : 航班座位 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi在给一个家庭旅游团订某次航班的机票.该航班的飞机一共有N排座位,每排座位有10个座位,从左到右 ...
- 31 ArcGIS中后缀一览表(持续更新中……)
- [tkinter]为列表框添加滚动条
为了给列表框配备滚动条,看来很多别人的博客 终于解决了问题 ,现在我总结一下 from tkinter import * root = Tk() lb = Listbox(root) scr = Sc ...
- js深度复制三种方法
1.用递归的方式进行深度复制 2.用JSON.stringify加上JSON.parse()进行深度复制 3.用jquery中自带的方法$.extend()进行深度复制 具体实现代码可百度自行查询