在Django中获取matplotlib生成的PNG失败

2 投票
1 回答
583 浏览
提问于 2025-04-18 16:30

我想用 Django 来展示 matplotlib 生成的图片。

如果这个图片是一个静态的 PNG 文件,下面的代码就能很好地工作:

from django.http import HttpResponse

def static_image_view(request):
    response = HttpResponse(mimetype='image/png')
    with open('test.png', 'rb') as f:
        response.write(f.read())
    return response

但是,如果这个图片是动态生成的:

import numpy as np
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt

def dynamic_image_view(request):
    response = HttpResponse(mimetype='image/png')
    fig = plt.figure()
    plt.plot(np.random.rand(100))
    plt.savefig(response, format='png')
    plt.close(fig)
    return response

在 Chrome 浏览器(版本 36.0)中访问这个网址时,图片会显示几秒钟,然后消失,变成替代文本。看起来浏览器不知道图片已经加载完成,反而等到超时才反应。通过查看 Chrome 的开发者工具中的网络部分可以支持这个猜想:虽然图片大约在 1 秒后就出现了,但对应的 HTTP 请求状态在大约 5 秒后变成了“失败”。

再强调一下,这种奇怪的现象只发生在动态生成的图片上,所以这应该不是 Chrome 的问题(虽然在 IE 或 Firefox 中没有发生这种情况,可能是因为它们处理超时请求的规则不同)。


更麻烦的是(也就是更难重现),这个问题似乎和网络速度有关。如果我从中国的 IP 访问这个网址,就会出现这个问题,但如果通过美国的代理访问(那里的速度似乎更快),就不会出现这个问题……


根据 @HSquirrel 的建议,我测试了把 PNG 写入临时磁盘文件。奇怪的是,用 matplotlib 保存文件没有成功,

plt.savefig('MPL.png', format='png')
with open('MPL.png', 'rb') as f:
    response.write(f.read())

但用 PIL 保存文件就成功了:

import io
from PIL import Image
f = io.BytesIO()
plt.savefig(f, format='png')
f.seek(0)
im = Image.open(f)
im.save('PIL.png', 'PNG')

尝试去掉临时文件的做法失败了:

im.save(response, 'PNG')

不过,如果我用 PIL 而不是 matplotlib 来生成图片数据流,就不需要临时磁盘文件。下面的代码可以正常工作:

from PIL import Image, ImageDraw
im = Image.new('RGBA', (256,256), (0,255,0,255)) 
draw = ImageDraw.Draw(im) 
draw.line((100,100, 150,200), fill=128, width=3)
im.save(response, 'PNG')

最后,savefig(response, 'jpeg') 完全没有问题。

1 个回答

0

你有没有试过把图片保存到硬盘上,然后再返回那个图片?(你可以定期清理硬盘上这些生成的图片,按照它们创建的时间来清理)

如果这样做还是有同样的问题,那可能是生成png的方式出了问题。你可以使用一些图像库(比如PIL)来确保你所有的png都是以一种适合所有浏览器的方式重新生成的。

编辑: 我查看了你链接的png文件,并且用不同的程序和PIL对它进行了些操作。每次我得到的二进制数据都不一样。看起来每个程序都会决定保留哪些部分,去掉哪些部分。它们对png图像数据的编码方式也不一样(就我所见,我并不是这方面的专家,只是根据规格看了一下二进制数据)。

你可以选择几种不同的解决方案:

1. 快速且简单的方法:

import io
from PIL import Image
f = io.BytesIO()
plt.savefig(f, format='png')
f.seek(0)
im = Image.open(f)
tempfilename = generatetempfilename()
im.save(tempfilename, 'PNG')
with open(tempfilename, 'rb') as f:
    response.write(f.read())

2. 调整matplotlib生成PNG文件的方式(可能只需使用PIL)。可以参考 http://matplotlib.org/users/customizing.html#customizing-matplotlib

3. 如果可以的话,使用jpeg格式。

4. 找出matplotlib生成的PNG文件有什么问题,并尝试修复它的二进制数据(我不推荐这样做)。你可以使用xxd(linux命令:xxd test.png)来查看文件的二进制内容,然后根据png的规格来了解情况: 概述 块规格

撰写回答