Python:从二维图像生成三维轮廓 - pylab 和 contourf

3 投票
3 回答
10710 浏览
提问于 2025-04-17 20:01

我有一个关于Python(pylab)和绘图的问题——我可以加载并显示一张图片(下面的代码可以加载这张图片),但是我不知道怎么把它绘制成3D的等高线图。我知道使用pylab.contourf(x,y,z)需要一个数组,但我不太清楚怎么从加载的图片数据中得到这个数组。

有没有什么建议和帮助呢?我的代码如下:

from PIL import Image
import pylab

fileName = "image1.png"
im = Image.open(fileName)
#pylab.contourf(im) # don't work - needs an array but how
pylab.axis('off')
pylab.imshow(im)
pylab.show()

image1.png

3 个回答

2

编辑: 抱歉,我之前误解了提问者的原始问题。要从一个PIL的Image对象获取一个numpy数组,通常只需要调用np.array(im)就可以了。不过,我处理了很多显微镜数据,发现对于某些图像格式(特别是16位的TIFF格式),这个方法并不总是有效。在这种情况下,我会使用np.asarray(im.getdata()).reshape(*im.shape[::-1])

这里有一个修正后的例子:

import numpy as np
from matplotlib import pylab as pl
from mpl_toolkits.mplot3d import Axes3D
from PIL import Image

def getimarray(path):
    im = Image.open(path,'r')
    return np.array(im)

def doplots(path='tmp/cell.png'):

    mydata = getimarray(path)
    mydata = mydata[::5,::5]
    fig = pl.figure(facecolor='w')
    ax1 = fig.add_subplot(1,2,1)
    im = ax1.imshow(mydata,interpolation='nearest',cmap=pl.cm.jet)
    ax1.set_title('2D')
    ax2 = fig.add_subplot(1,2,2,projection='3d')
    x,y = np.mgrid[:mydata.shape[0],:mydata.shape[1]]
    ax2.plot_surface(x,y,mydata,cmap=pl.cm.jet,rstride=1,cstride=1,linewidth=0.,antialiased=False)
    ax2.set_title('3D')
    ax2.set_zlim3d(0,255)

    return fig,ax1,ax2

if __name__ == '__main__':
    doplots()
5

你的图像可以用等高线图表示的原因是,它明显是一个伪彩色图像,也就是说,这种图像使用完整的RGB颜色范围来表示一个单一的变量。等高线图也表示只有一个变量的数据,这个变量决定了颜色(也就是Z轴),所以你可能也可以把你的图像数据表示成等高线图。

这就是我建议你使用等高线图的原因。(不过,你在这个问题中实际想要的东西,通常是不存在的:没有普遍有效的方法可以把彩色图像转换成等高线图,因为彩色图像通常有三种独立的颜色,RGB,而等高线图只有一个(Z轴),也就是说,这只适用于伪彩色图像。)

具体来说,要解决你的问题:

1) 如果你有用于创建你所展示的伪彩色图像的z轴数据,直接在等高线图中使用这些数据。这是最好的解决方案。

2) 如果你没有z数据,那就麻烦了,因为你需要把图像中的颜色反转成z值,然后再放入等高线图中。你展示的图像几乎肯定使用了colormap matplotlib.cm.jet,而我认为没有比unubtu在这里说的更好的反转方法了。

最后,你需要理解等高线图和图像之间的区别,才能把细节搞清楚。

为什么convert不工作的演示:
这里我通过一个完整的测试案例,使用从左到右的z值梯度。很明显,z值现在完全混乱,因为原本最大的值现在变成了最小的,等等。

也就是说,目标是图2和图4匹配,但它们非常不同。问题当然是convert没有正确地将jet映射到原始的z值集合。

enter image description here

import numpy as np
import matplotlib.pyplot as plt
import Image

fig, axs = plt.subplots(4,1)

x = np.repeat(np.linspace(0, 1, 100)[np.newaxis,:], 20, axis=0)

axs[0].imshow(x, cmap=plt.cm.gray)
axs[0].set_title('1: original z-values as grayscale')

d = axs[1].imshow(x, cmap=plt.cm.jet)
axs[1].set_title('2:original z-values as jet')    
d.write_png('temp01.png')  # write to a file

im = Image.open('temp01.png').convert('L')  # use 'convert' on image to get grayscale
data = np.asarray(im)  # make image into numpy data
axs[2].imshow(data, cmap=plt.cm.gray)
axs[2].set_title("3: 'convert' applied to jet image")

img = Image.open('temp01.png').convert('L')
z   = np.asarray(img)
mydata = z[::1,::1]  # I don't know what this is here for
axs[3].imshow(mydata,interpolation='nearest',cmap=plt.cm.jet)
axs[3].set_title("4: the code that Jake French suggests")

plt.show()

不过,按照我上面的建议,正确地做到这一点并不难。

5

好的,经过一些研究和简化代码,关键在于使用convert('L'),也就是把彩色图像转换成灰度图像,这样Ali_m的代码就能正常工作了:

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pylab as pl
from PIL import Image
import numpy as np
import pylab

img = Image.open('40.jpg').convert('L')
z   = np.asarray(img)
mydata = z[::1,::1]
fig = pl.figure(facecolor='w')
ax1 = fig.add_subplot(1,2,1)
im = ax1.imshow(mydata,interpolation='nearest',cmap=pl.cm.jet)
ax1.set_title('2D')

ax2 = fig.add_subplot(1,2,2,projection='3d')
x,y = np.mgrid[:mydata.shape[0],:mydata.shape[1]]
ax2.plot_surface(x,y,mydata,cmap=pl.cm.jet,rstride=1,cstride=1,linewidth=0.,antialiased=False)
ax2.set_title('3D')
ax2.set_zlim3d(0,100)
pl.show()

output here

撰写回答