python開發(fā)中,常使用繪圖工具matplotlib繪制帶有坐標(biāo)軸或者colorbar的圖像,比如下圖是結(jié)合librosa進(jìn)行頻譜分析的梅爾頻譜圖。
當(dāng)matplotlib繪制的圖形只有部分是我們需要的,或者當(dāng)我們沒有條件將其生成的圖像保存到本地,需要將像素?cái)?shù)據(jù)放入內(nèi)存中時(shí),就會(huì)想到是否有一種辦法可以將matplotlib繪制的圖片部分/全部像素點(diǎn)提取出來,以備使用。
下面按照小編的思維方式介紹一下走彎路的過程,如果需要直接查看正確答案,移步至文章最后的代碼,如有需要勘誤之處,請(qǐng)移步評(píng)論區(qū)。
首先介紹業(yè)務(wù)需求,基于如下的代碼,將頻譜圖中的頻譜窗口(中間的繪圖部分,不包括外側(cè)色度條、空白區(qū)域和坐標(biāo)軸)的像素點(diǎn)提取出來,且不能保存圖像到本地進(jìn)行二次讀取操作(沒有保存到本地的條件,需要到其他環(huán)境中運(yùn)行,只能在內(nèi)存中操作):
import matplotlib.pyplot as plt
import librosa
s = librosa.feature.melspectrogram(y=x, sr=fs)
fig, ax = plt.subplots()
s1 = librosa.power_to_db(s, ref=np.max)
img = librosa.display.specshow(s1, x_axis=xxx, y_axis='mel', sr=fs, ax=ax)
# 生成右側(cè)的漸變色板條
fig.colorbar(img, ax=ax, format='%+2.0f dB')
保存到本地的圖片如下:

第一次嘗試,想到了plt.subplots()或者librosa.display.specshow()返回的對(duì)象是否提供rgb三通道像素點(diǎn)的API?反復(fù)查看了幾遍,發(fā)現(xiàn)有一個(gè)get_array(),獲取代碼如下:
img = librosa.display.specshow(S_dB, x_axis='time', y_axis='mel', sr=fs, ax=ax)
a = img.get_array()
a其實(shí)就是s1,并不是什么像素點(diǎn)了。
第二次嘗試,想到是否可以定位到頻譜區(qū)域的位置進(jìn)行截圖,丟棄坐標(biāo)、色板條和空白部分。
考慮使用PIL的截圖API,具體代碼如下:
imageObject = Image.open(‘xxx’)
cropped = imageObject.crop((x1,y1,x2,y2))
cropped.save('xxx')
(x1,y1)和(x2,y2)分別是截圖區(qū)域的左上角和右下角,但是發(fā)現(xiàn),需要將圖像存儲(chǔ)到本地,不符合小編的要求。
需要注意的是,librosa 的所有繪圖功能都依賴于 matplotlib,一般需要導(dǎo)入 matplotlib 的 pyplot API。
最后查閱資料發(fā)現(xiàn),可以通過matplotlib.figure的canvas獲取像素?cái)?shù)據(jù),具體地通過tostring_rgb()方法,查看tostring_rgb()方法的源碼,發(fā)現(xiàn)僅有一行:
return np.asarray(self._renderer).take([0, 1, 2], axis=2).tobytes()
可以見得,是將self._renderer中的部分維度、部分位置的數(shù)據(jù)提取出來了,并不是直接解析某對(duì)象得到的。
由于目前還需要對(duì)像素?cái)?shù)據(jù)進(jìn)行截取,所以確定了[57:429,79:578:,]這個(gè)截取范圍,至于確定范圍的方法,需要根據(jù)圖像調(diào)整,但是基本可以確定的是,在前面繪圖參數(shù)和數(shù)據(jù)的尺寸不變的情況下,頻譜圖窗口的位置相對(duì)于整個(gè)圖像是不變的,也就是說截取范圍可以不變。
最后為了準(zhǔn)確,使用PIL Image對(duì)象,將像素點(diǎn)填充進(jìn)去,并show()出來,以查看截取的部分是否能夠正常成像。
import matplotlib.pyplot as plt
from PIL import Image
import librosa
def test():
# ---------原始頻譜圖------------------
# 1、加載音頻文件的時(shí)域信號(hào)、采樣率到內(nèi)存中
# 2、根據(jù)時(shí)域信號(hào)、采樣率等參數(shù)生成梅爾頻譜
s = librosa.feature.melspectrogram(y=x, sr=fs)
# 3、matplotlib構(gòu)建figure和一組子圖,返回圖形(matplotlib.figure)和坐標(biāo)軸(matplotlib.axes.Axes)
fig, ax = plt.subplots()
# 4、頻譜轉(zhuǎn)成分貝單位的值
S_dB = librosa.power_to_db(s, ref=np.max)
# 5、顯示頻譜圖像
img = librosa.display.specshow(S_dB, x_axis='time', y_axis='mel', sr=fs, ax=ax)
# ---------利用canvas對(duì)象獲取像素點(diǎn)------------------
# 6、繪制figure的邊界框
fig.canvas.draw()
# 7、提取rgb數(shù)據(jù),返回字節(jié)流對(duì)象
buf = fig.canvas.tostring_rgb()
# 8、獲取canvas的寬度、高度
ncols, nrows = fig.canvas.get_width_height()
# 9、PIL創(chuàng)建圖片對(duì)象,確定要截取的區(qū)域像素點(diǎn)的數(shù)量,這里是499×372
im = Image.new("RGB",(499,372))
# 10、將字節(jié)流轉(zhuǎn)成numpy數(shù)組,并形狀重置
d = np.fromstring(buf, dtype=np.uint8).reshape(nrows, ncols, 3)
# 11、截取目標(biāo)區(qū)域的rgb像素點(diǎn)
dd = d[57:429,79:578:,]
# ---------測(cè)試:查看像素點(diǎn)對(duì)應(yīng)的圖像------------------
# 12、賦值給圖片對(duì)象的像素點(diǎn),每一個(gè)像素點(diǎn)都由rgb三通道組成
for i in range(0,372):
for j in range(0,499):
im.putpixel((j,i),(int(dd[i][j][0]),int(dd[i][j][1]),int(dd[i][j][2])))
# 13、查看截取的圖像
im.show()