【问题标题】:How to read image file from S3 bucket directly into memory?如何将 S3 存储桶中的图像文件直接读取到内存中?
【发布时间】:2017-10-17 23:44:51
【问题描述】:

我有以下代码

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 中读取文件并将其解码为图像?

【问题讨论】:

    标签: python matplotlib amazon-s3 boto3


    【解决方案1】:

    我建议使用io module 将文件直接读入内存,而根本不必使用临时文件。

    例如:

    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

    【讨论】:

    • object.download_fileobj(file_stream) 给我一个错误,TypeError: unicode argument expected, got 'str'
    • 我得到同样的错误:TypeError: string argument expected, got 'bytes'
    • 如果“字符串参数需要,得到字节”是你的错误,记得尝试io.BytesIO()而不是io.StringIO()。对于 boto3 和 python 3,这是关键
    • 执行最后一行代码时出现read past end of file 错误
    • @NeeleshkumarSrinivasanMannur 我得到了同样的错误。你找到解决办法了吗?
    【解决方案2】:

    根据 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())
    

    【讨论】:

    【解决方案3】:

    Greg Merritt 下面的回答是更好的方法。

    我建议在 tempfile 模块中使用 Python 的 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
    

    【讨论】:

    • 这应该可以正常工作,但在后台,会创建一个真实文件,并在关闭后立即销毁。
    • @NoamG 谢谢!我误解了tempfile 模块的工作原理。更新了我的答案。
    • 创建一个文件,即使是临时性的,对于关心下载大于 512 MB 文件的 AWS Lambda 用户来说很重要,因为 lambda 在 /tmp 中将用户限制为 512 MB
    【解决方案4】:

    通过在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)
    

    【讨论】:

      【解决方案5】:

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

      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()
      

      【讨论】:

        【解决方案6】:
        object = bucket.Object('tiles/10/S/DG/2015/12/7/0/B01.jp2')
        img_data = object.get().get('Body').read()
        

        【讨论】:

        • 感谢您提供此代码 sn-p,它可能会提供一些即时帮助。正确解释would greatly improve 其教育价值,说明为什么这是一个很好的问题解决方案,并将使其对未来有类似但不相同问题的读者更有用。请编辑您的答案以添加解释,并说明适用的限制和假设。
        【解决方案7】:

        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)
        
        

        【讨论】:

          【解决方案8】:

          我在这里看到了很多好的答案。如果您快速测试解决方案,这是我使用 AWS Config 的代码 sn-p。请注意,不建议将您的 AWS 凭证放在代码正文中,而应该来自 .env 文件或 AWS Keystore。

          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")
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-05-16
            • 2021-07-20
            • 1970-01-01
            • 2021-05-14
            • 1970-01-01
            相关资源
            最近更新 更多