Matplotlib - 从wav文件绘制波形
我正在开发一个程序,目的是在wav文件中隐藏用户指定的数据(这是一种隐写术程序,但只是用于教育目的,并不是特别复杂)。除了进行隐写操作外,我还需要可视化原始和输出的wav文件内容,但我不知道怎么以可行的方式做到这一点。
起初,我想用tkinter
中的canvas
小部件,但这几乎无法使用,因为输入的wav文件可能非常大,绘制这么多数据几乎不现实,更不用说我还需要处理缩放、滚动等问题。
我发现了matplotlib
,我觉得它可以解决我的问题。我加载了一个10MB的wav文件(16位,立体声),分离了两个声道的样本,并将它们转换为有符号的16位整数。然后我尝试绘制第一个声道的数据,但似乎matplotlib无法处理这么多的点进行绘图——起初我能看到波形图(但仍然需要花费相当长的时间),但是当我调整窗口大小时(这会导致重新绘制图形),就出现了以下异常:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python33\lib\tkinter\__i`enter code here`nit__.py", line 1475, in __call__
return self.func(*args)
File "C:\Python33\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 276, in resize
self.show()
File "C:\Python33\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 348, in draw
FigureCanvasAgg.draw(self)
File "C:\Python33\lib\site-packages\matplotlib\backends\backend_agg.py", line 451, in draw
self.figure.draw(self.renderer)
File "C:\Python33\lib\site-packages\matplotlib\artist.py", line 56, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python33\lib\site-packages\matplotlib\figure.py", line 1035, in draw
func(*args)
File "C:\Python33\lib\site-packages\matplotlib\artist.py", line 56, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python33\lib\site-packages\matplotlib\axes.py", line 2088, in draw
a.draw(renderer)
File "C:\Python33\lib\site-packages\matplotlib\artist.py", line 56, in draw_wrapper
draw(artist, renderer, *args, **kwargs)
File "C:\Python33\lib\site-packages\matplotlib\lines.py", line 563, in draw
drawFunc(renderer, gc, tpath, affine.frozen())
File "C:\Python33\lib\site-packages\matplotlib\lines.py", line 939, in _draw_lines
self._lineFunc(renderer, gc, path, trans)
File "C:\Python33\lib\site-packages\matplotlib\lines.py", line 979, in _draw_solid
renderer.draw_path(gc, path, trans)
File "C:\Python33\lib\site-packages\matplotlib\backends\backend_agg.py", line 145, in draw_path
self._renderer.draw_path(gc, path, transform, rgbFace)
OverflowError: Allocated too many blocks
当我尝试加载一个更大的WAV文件(50MB)时也出现了同样的错误,即使没有绘制波形。因此,我需要采取不同的方法,但我不太知道该怎么做。当我先加载样本时,我可能可以绘制输入样本的平均值,这样matplotlib应该能接受。但我不知道如何处理在缩放/滚动图形时的情况,这意味着需要根据实际的缩放级别和视图位置(“窗口”)重新计算平均值,这可能在性能上会很糟糕。
而且这只是一个样本图,所以我无法想象绘制四倍于此的数据量(2个声道,原始和输出数据)而不遇到性能问题,甚至是之前提到的失败/异常。在较小的文件(几百kB)上效果不错(但还是有点慢)。
你对此有什么建议吗?
编辑:我发现我对16位样本的输入数据在struct.pack()
中的解释有误(我用了字符串<H
而不是<h
),因此我在处理10MB的WAV文件时似乎没有问题,速度也有所提升,但绘制波形仍然比理想的要慢。50MB的WAV文件似乎绘制得不错,但当我调整窗口大小(因此matplotlib画布也会调整)时,之前提到的异常又出现了,当我尝试缩放到某个区域或将窗口调整回之前的大小时,重新绘制就不再发生了。
这是我用来了解matplotlib的一些代码(基于简单的matplotlib示例):
(编辑2:我修改了代码,使其行为正确,但现在简单多了,希望如此。)
import matplotlib
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg
from tkinter import tix
from tkinter.tix import *
from random import randrange
matplotlib.use('TkAgg')
samples = [randrange(-32768, 32768) for i in range(int(1e7))]
fig = Figure(figsize=(20,8), dpi=50)
subplot1 = fig.add_subplot(111)
subplot1.plot(samples, "r")
root = tix.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
toolbar = NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
root.mainloop()
有没有什么建议可以解决这个问题,以及如何以合理的性能和内存消耗绘制WAV数据(这个例子在异常发生之前使用了超过800MB的内存,这意味着我对这个问题的处理方法并不好)。
1 个回答
你可以把这个过程简化得更简单一点,以便在交互式提示符中运行,不过我有点跑题了。
import matplotlib
from matplotlib import pyplot as plt
from random import randrange
samples = [randrange(-32768, 32768) for i in range(int(1e7))]
fig, ax = plt.subplots(1, 1)
ax.plot(samples, "r-")
问题在于,你试图绘制的线段数量超过了Agg库能处理的范围(我不太确定具体的限制是什么,而且在把路径交给Agg之前应该做一些路径简化,所以这可能并不是点的数量限制)。
在某种程度上,这并不是个大问题,因为你的屏幕宽度大约只有1000个像素。如果你把所有的点都绘制出来,每个像素上会有1万(1e4)个点,这样就显得有些荒谬了,所以你需要减少点的数量。
你可以通过多种方式来做到这一点(具体用哪种方法取决于你绘制这些点的目的),比如:简单地减少点的数量(x = x[::1000]
),对某些部分取平均值(x = np.mean(x[::n * (len(x)//n)].reshape(-1, n), axis=1)
),或者做一些更复杂的操作(比如进行傅里叶变换,然后过滤掉高频部分,只保留低频部分)。
如果你需要在放大时能够看到放大区域内的所有点,你可能需要做一些更高级的处理,以便在放大时用未减少数量的数据来替换原有数据。