将PIL图像对象上传到Amazon S3 Python

23

我想从网上获取一张图片并将其上传到Amazon s3。在此过程中,我想检查图片的尺寸。以下是我在Python 3中的代码:

from PIL import Image
import requests

# Get response
response = requests.get(url, stream= True)

# Open image
im = Image.open(response.raw)

# Get size
size = im.size

# Upload image to s3
S3.Client.upload_fileobj(
    im, # This is what i am trying to upload
    AWS_BUCKET_NAME,
    key,
    ExtraArgs={
        'ACL': 'public-read'
    }
)

问题在于PIL图像对象不支持读取。当我尝试上传PIL图像对象im时,会出现以下错误。

The problem is that the PIL image object does not support read. I get the following error when I try to upload the PIL Image object im.
ValueError: Fileobj must implement read

当我尝试上传'response.raw'时,它可以工作,但我需要获取图像的尺寸大小。如何将PIL图像对象更改为类似文件的对象?是否有更简单的方法,在仍然能够将图像上传到s3的同时获得其尺寸大小?

因此问题是; 如何在获取图像尺寸后上传图像至s3?

4个回答

42

您需要使用类似文件的对象,但不应调用getValue()函数,与接受的答案相反。使用以下代码片段,当调用upload_fileobj时,您可以使用in_mem_file将图像上传到S3:

from PIL import Image
import io

# Open image
pil_image = Image.open(response.raw)

# Save the image to an in-memory file
in_mem_file = io.BytesIO()
pil_image.save(in_mem_file, format=pil_image.format)
in_mem_file.seek(0)

# Upload image to s3
client_s3.upload_fileobj(
    in_mem_file, # This is what i am trying to upload
    AWS_BUCKET_NAME,
    key,
    ExtraArgs={
        'ACL': 'public-read'
    }
)

如果您发现上传的文件大小为0kB,.seek(0) 部分是需要将文件指针重置以便倒回文件对象开头的。


1
这是准确的答案。感谢您提供这个。我一直在尝试被接受的答案,但不知道哪里出了问题。这真的帮了我很多。 - hax
'ACL': 'public-read' - 需要 s3:PutObjectAcl 权限。另外,对文件进行操作后,pil_image.format 数据可能会丢失。因此,在打开文件后立即将其保存在某个位置是很好的做法。 - Serhii Kushchenko

20

不必调用read()函数来获取文件内容,你可以将文件“保存”到一个真实的文件对象或者内存中的类似文件的对象中。然后在其上调用getValue()。

这里有一个例子函数,你可以将文件内容传递给它,打印出高度和宽度,然后以符合AWS客户端put_object函数所需的Body参数格式返回文件数据。

from PIL import Image
import io

def modify_image(image, format):
    pil_image = Image.open(image)

    # Prints out (1280, 960) 
    print(pil_image.size)

    in_mem_file = io.BytesIO()

    # format here would be something like "JPEG". See below link for more info.
    pil_image.save(in_mem_file, format=format)
    return in_mem_file.getvalue()

这里还有单独的宽度和高度属性:http://pillow.readthedocs.io/en/3.4.x/reference/Image.html#attributes

了解更多文件格式,请参见http://pillow.readthedocs.io/en/3.4.x/handbook/image-file-formats.html

注意:示例使用Python 3.6.1。


6
提醒下一个人:format=format 可能应该改为 format=pil_image.format - alphazwest

0

你应该使用 io.BufferIO

response = requests.get(url, stream= True)
f = io.BytesIO(response.content)
image = Image.open(f)

这并不会改变任何事情。图片可以正常打开。问题出现在我想将图片上传到S3时。 - john
你尝试将 f 加载到 AWS 上了吗?使用 f 代替 im - amarynets
是的,我尝试过了。首先,response没有名为read的属性,我猜你指的是response.raw.read()。当我尝试这样做时,我上传了一个空的file_obj,因为在使用Image.open()打开时,f已经被清空了。 - john
尝试从响应中使用 content 属性怎么样? - amarynets
它仍然只上传一张空白图片。如果我完全删除Image.open(f)部分,它能正常工作。 - john
这根本没用,请求文档说明“如果在发出请求时将stream设置为True,则除非您使用所有数据(即调用response.content)或调用Response.close,否则Requests无法释放连接回池。这可能导致连接效率低下。”因此通过调用response.content,您就打败了最初请求流的目的,因为这意味着您正在尝试读取整个内容。 - Samuel Prevost

0
如果您正在使用来自werkzeug.datastructures的FileStorage,并使用reqparse解析图像,则无需将图像转换为PIL.Image,您可以在FileStorage本身上使用seek(0)。
另外,请不要忘记指定文件的Content-type。
parser = reqparse.RequestParser()
parser.add_argument('image', help='image cannot be blank', type=FileStorage, 
location='files', required=True)

args = parser.parse_args()
image = args['image']

image.seek(0)
s3_client.upload_fileobj(image, self.BUCKET_NAME, filename, ExtraArgs={'ContentType': 'image/jpeg'})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接