如何直接从S3存储桶中读取图像文件到内存?

66

我有以下代码

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import boto3
s3 = boto3.resource('s3', region_name='us-east-2')
bucket = s3.Bucket('sentinel-s2-l1c')
object = bucket.Object('tiles/10/S/DG/2015/12/7/0/B01.jp2')
object.download_file('B01.jp2')
img=mpimg.imread('B01.jp2')
imgplot = plt.imshow(img)
plt.show(imgplot)

它能够工作。但问题是它首先将文件下载到当前目录中。是否有可能直接在RAM中读取文件并解码为图像?

10个回答

75
我建议使用io模块直接将文件读入内存,而不必使用临时文件。 例如: io模块
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import boto3
import io

s3 = boto3.resource('s3', region_name='us-east-2')
bucket = s3.Bucket('sentinel-s2-l1c')
object = bucket.Object('tiles/10/S/DG/2015/12/7/0/B01.jp2')

file_stream = io.StringIO()
object.download_fileobj(file_stream)
img = mpimg.imread(file_stream)
# whatever you need to do

如果您的数据是二进制的,您也可以使用io.BytesIO


6
运行 object.download_fileobj(file_stream) 时出现错误, TypeError: 预期为unicode参数,但得到了'str'。 - Shivam Batra
5
我也遇到了相同的错误:TypeError:期望字符串参数,但得到了'bytes'。 - Hephaestus
11
如果你的错误提示为“期望字符串参数,但获取到了字节类型”,请记得尝试使用io.BytesIO()而不是io.StringIO()。对于boto3和python 3来说,这是关键。 - Hawkins
5
当我执行代码最后一行时,出现了“读取文件末尾”的错误。 - Neeleshkumar S
3
我遇到了同样的错误。您找到解决方法了吗? - Tom
显示剩余5条评论

38

根据Greg Merritt的答案进一步开发以解决评论部分中的所有错误,使用BytesIO代替StringIO,使用PIL Image代替matplotlib.image

以下函数适用于python3boto3。类似地,write_image_to_s3函数是额外的奖励。

from PIL import Image
from io import BytesIO
import numpy as np

def read_image_from_s3(bucket, key, region_name='ap-southeast-1'):
    """Load image file from s3.

    Parameters
    ----------
    bucket: string
        Bucket name
    key : string
        Path in s3

    Returns
    -------
    np array
        Image array
    """
    s3 = boto3.resource('s3', region_name='ap-southeast-1')
    bucket = s3.Bucket(bucket)
    object = bucket.Object(key)
    response = object.get()
    file_stream = response['Body']
    im = Image.open(file_stream)
    return np.array(im)

def write_image_to_s3(img_array, bucket, key, region_name='ap-southeast-1'):
    """Write an image array into S3 bucket

    Parameters
    ----------
    bucket: string
        Bucket name
    key : string
        Path in s3

    Returns
    -------
    None
    """
    s3 = boto3.resource('s3', region_name)
    bucket = s3.Bucket(bucket)
    object = bucket.Object(key)
    file_stream = BytesIO()
    im = Image.fromarray(img_array)
    im.save(file_stream, format='jpeg')
    object.put(Body=file_stream.getvalue())

你能看一下这里吗?https://stackoverflow.com/questions/69838643/read-s3-file-into-a-buffer - user13067694

24

Greg Merritt的回答更好。

我建议使用Python tempfile模块中的NamedTemporaryFile函数。它创建的临时文件在关闭文件时将被删除(感谢@NoamG)。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import boto3
import tempfile

s3 = boto3.resource('s3', region_name='us-east-2')
bucket = s3.Bucket('sentinel-s2-l1c')
object = bucket.Object('tiles/10/S/DG/2015/12/7/0/B01.jp2')
tmp = tempfile.NamedTemporaryFile()

with open(tmp.name, 'wb') as f:
    object.download_fileobj(f)
    img=mpimg.imread(tmp.name)
    # ...Do jobs using img

2
这应该可以正常工作,但在幕后,一个真实的文件会被创建并在关闭时被销毁。 - NoamG
@NoamG 谢谢!我误解了tempfile模块的工作方式。我已经更新了我的答案。 - Hyeungshik Jung
4
对于关心下载大于512 MB文件的AWS Lambda用户来说,即使是临时文件也很重要,因为Lambda限制用户在/tmp目录下只能使用512 MB。 - Hawkins

13

通过在imread()中指定文件格式,可以实现图像的流传输。

import boto3
from io import BytesIO
import matplotlib.image as mpimg
import matplotlib.pyplot as plt

resource = boto3.resource('s3', region_name='us-east-2')
bucket = resource.Bucket('sentinel-s2-l1c')

image_object = bucket.Object('tiles/10/S/DG/2015/12/7/0/B01.jp2')
image = mpimg.imread(BytesIO(image_object.get()['Body'].read()), 'jp2')

plt.figure(0)
plt.imshow(image)

10

使用客户端的略有不同的方法:

import boto3
import io
from matplotlib import pyplot as plt

client = boto3.client("s3")

bucket='my_bucket'
key= 'my_key'

outfile = io.BytesIO()
client.download_fileobj(bucket, key, outfile)
outfile.seek(0)
img = plt.imread(outfile)

plt.imshow(img)
plt.show()

1
这突出了一个非常关键的点 - 一旦数据已经下载到缓冲对象中,在继续处理之前,必须将seek()返回到0 - jtlz2

5
object = bucket.Object('tiles/10/S/DG/2015/12/7/0/B01.jp2')
img_data = object.get().get('Body').read()

14
感谢您提供的这段代码片段,它可能提供了一些即时的帮助。一个适当的解释将极大地提高它的教育价值,因为它可以展示为什么这是解决问题的好方法,并且可以使它对将来有类似但不完全相同的问题的读者更加有用。请编辑您的答案添加解释,并指出哪些限制和假设适用。 - GrumpyCrouton

3

我在这里看到很多好的答案。以下是我的代码片段,其中包含AWS Config,如果您想快速测试解决方案,请注意不建议将AWS凭据放在代码主体中,而应该来自.env文件或AWS密钥库。

import os
import boto3
from PIL import Image
import io

AWS_ACCESS_KEY_ID = 'your-aws-access-key'
AWS_SECRET_ACCESS_KEY = 'your-aws-secret'

s3 = boto3.resource('s3',
                    aws_access_key_id=AWS_ACCESS_KEY_ID,
                    aws_secret_access_key=AWS_SECRET_ACCESS_KEY)

def image_from_s3(bucket, key):
    bucket = s3.Bucket(bucket)
    image = bucket.Object(key)
    img_data = image.get().get('Body').read()
    return Image.open(io.BytesIO(img_data))

# call the function
image_from_s3("your-aws-bucket-name", "file-path")

# example
image_from_s3("my-images", "profile/2022/123.png")

3
Hyeungshik Jung提出的临时文件解决方案看起来不错,但我注意到该文件似乎以懒惰的方式下载。这导致一种行为,即如果您调用img.shape(),则会得到一个空的维度元组作为返回值(),即使您已经调用了object.download_fileobj(f)。我通过对文件描述符应用f.seek(0,2)来解决此问题,然后所有后续操作都可以正常工作,例如返回所有正确的尺寸(704, 1024)
...
tmp = tempfile.NamedTemporaryFile()

with open(tmp.name, 'wb') as f:
    object.download_fileobj(f)
    f.seek(0,2) 
    img=mpimg.imread(tmp.name)
    print (img.shape)


0

请注意,您将从 S3 读取一个字节类型的数据,但 Tensorflow 需要一个字符串张量来转换为 uint8 图像。而且这种方法不需要使用 Pillow。

import boto3
import tensorflow as tf

credentials = boto3.Session(botocore_session=boto3.setup_default_session(), 
                                region_name="us-east-1").get_credentials()
    

s3 = boto3.Session(aws_access_key_id=credentials.access_key,
                      aws_secret_access_key=credentials.secret_key).client('s3') 

#file_on_s3 : 's3://mybucket/data/sample.jpg'
bucket_name = 'mybucket'
file_key = 'data/sample.jpg'


file_obj = s3.get_object(Bucket=bucket_name, Key=file_key)

# reading the file content in bytes
file_content = file_obj["Body"].read()  


img =  tf.io.decode_image(tf.convert_to_tensor(file_content, dtype=tf.string), 
                                channels=3, 
                                dtype=tf.dtypes.uint8, 
                                name=None, 
                                expand_animations=False)

img = tf.cast(img, tf.float32)
img_array = tf.image.resize(img, 
                            size=(224, 224),
                            method=tf.image.ResizeMethod.NEAREST_NEIGHBOR) 

0

您可以使用Python3中的pillow、ssl和urllib库来实现此功能
from PIL import Image import requests import ssl import urllib.request

img="https://{bucket}.s3.amazonaws.com/{folder}/"
context = ssl._create_unverified_context()
for i in range(1100,1102):
    image_url=img+str(i)+".png"
    im = Image.open(urllib.request.urlopen(image_url,context=context))
    im.show()`

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