最新消息:20210917 已从crifan.com换到crifan.org

【已解决】Python中实现二进制数据的图片的压缩

图片 crifan 649浏览 0评论
折腾:
【记录】用Flask实现测评系统的后端
期间,现在情况是:
虽然前端小程序中可以通过url去访问flask后台中封装的mongodb的gridfs的文件了
但是有些图片比较大,导致前端加载很慢:
所以需要:
后台中想办法,对于gridfs中,获取到了图片的二进制数据后,想办法去压缩后,再返回给前端
不过先去搞清楚:
pip 和pillow的区别
python pil vs pillow
Image Manipulation — The Hitchhiker’s Guide to Python
PIL=Python Imaging Library,很好,不过2009年就停止开发了
后续fork出分支,继续开发到现在的是:Pillow
先去安装:
➜  EvaluationSystemServer git:(master) ✗ pipenv install pillow
Installing pillow…
Looking in indexes: 
https://pypi.tuna.tsinghua.edu.cn/simple
Collecting pillow
  Downloading 
https://pypi.tuna.tsinghua.edu.cn/packages/d1/21/bef2816809fac16754e07ed935469fc65f42ced1a94766de7c804179311d/Pillow-5.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl
 (3.6MB)
Installing collected packages: pillow
Successfully installed pillow-5.3.0

Adding pillow to Pipfile's [packages]…
Pipfile.lock (cacb65) out of date, updating to (28e8c6)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (cacb65)!
Installing dependencies from Pipfile.lock (cacb65)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 19/19 — 00:00:29
➜  EvaluationSystemServer git:(master) ✗ pipenv graph
Flask-Cors==3.0.7
  - Flask [required: >=0.9, installed: 1.0.2]
    - click [required: >=5.1, installed: 7.0]
    - itsdangerous [required: >=0.24, installed: 1.1.0]
    - Jinja2 [required: >=2.10, installed: 2.10]
      - MarkupSafe [required: >=0.23, installed: 1.1.0]
    - Werkzeug [required: >=0.14, installed: 0.14.1]
  - Six [required: Any, installed: 1.12.0]
Flask-PyMongo==2.2.0
  - Flask [required: >=0.11, installed: 1.0.2]
    - click [required: >=5.1, installed: 7.0]
    - itsdangerous [required: >=0.24, installed: 1.1.0]
    - Jinja2 [required: >=2.10, installed: 2.10]
      - MarkupSafe [required: >=0.23, installed: 1.1.0]
    - Werkzeug [required: >=0.14, installed: 0.14.1]
  - PyMongo [required: >=3.0, installed: 3.7.2]
Flask-RESTful==0.3.7
  - aniso8601 [required: >=0.82, installed: 4.0.1]
  - Flask [required: >=0.8, installed: 1.0.2]
    - click [required: >=5.1, installed: 7.0]
    - itsdangerous [required: >=0.24, installed: 1.1.0]
    - Jinja2 [required: >=2.10, installed: 2.10]
      - MarkupSafe [required: >=0.23, installed: 1.1.0]
    - Werkzeug [required: >=0.14, installed: 0.14.1]
  - pytz [required: Any, installed: 2018.7]
  - six [required: >=1.3.0, installed: 1.12.0]
gevent==1.3.7
  - greenlet [required: >=0.4.14, installed: 0.4.15]
gunicorn==19.9.0
numpy==1.15.4
Pillow==5.3.0
python-dotenv==0.10.1
再去试试
先去解决是否支持从binary data中读取image:
【已解决】Python的Pillow如何从二进制数据中读取图像数据
接下来问题就是:
【已解决】Python的Pillow中如何压缩缩放图片且保持原图宽高比例
然后就是resize后thumbnail后,如何返回二进制数据:
【已解决】Python的Pillow如何返回图像的二进制数据
接下来就是:
【已解决】调试Pillow找到合适的性价比高的图片压缩尺寸
【总结】
此处用如下代码:
import io
from PIL import Image

fileBytes = fileObj.read()
log.debug("len(fileBytes)=%s", len(fileBytes))

if fileType == MongoFileType.IMAGE.value:
    imageStream = io.BytesIO(fileBytes)
    imageFile = Image.open(imageStream)
    log.debug("imageFile=%s", imageFile)
    log.debug("imageFile.size=%s", imageFile.size)
    # imageFile.thumbnail(IMAGE_COMPRESS_SIZE, Image.ANTIALIAS)
    imageFile.thumbnail(IMAGE_COMPRESS_SIZE, Image.LANCZOS)
    log.debug("imageFile=%s", imageFile)
    imageOutput = io.BytesIO()
    log.debug("imageOutput=%s", imageOutput)
    imageSuffix = fileObj.filename.split(".")[-1] # png
    log.debug("imageSuffix=%s", imageSuffix)
    imageFormat = imageSuffix.upper() # PNG
    if imageFormat == "JPG":
        imageFormat = "JPEG"
    log.debug("imageFormat=%s", imageFormat)
    # imageFile.save(imageOutput)
    imageFile.save(imageOutput, imageFormat)
    log.debug("imageFile=%s", imageFile)
    log.debug("imageFile.size=%s", imageFile.size)
    compressedImageBytes = imageOutput.getvalue()
    # log.debug("compressedImageBytes=%s", compressedImageBytes)
    log.debug("len(compressedImageBytes)=%s", len(compressedImageBytes))
    fileBytes = compressedImageBytes
实现了性价比高的,从原始的png,jpg的图片的二进制数据中,压缩图片,保存出压缩后图片的二进制数据
效果:
原图:
压缩后的:
jpg:
压缩后:
效果还是很不错的。
【后记】
后来单独封装成独立的函数,并且支持更多种功能和调用方式,代码如下:
python/crifanLib/crifanFile.py

def isFileObject(fileObj):
    """"check is file like object or not"""
    if sys.version_info[0] == 2:
        return isinstance(fileObj, file)
    else:
        # for python 3:
        # has read() method for:
        # io.IOBase
        # io.BytesIO
        # io.StringIO
        # io.RawIOBase
        return hasattr(fileObj, 'read')
以及:
python/crifanLib/crifanMultimedia.py
def resizeImage(inputImage,
                newSize,
                resample=Image.BICUBIC, # Image.LANCZOS,
                outputFormat=None,
                outputImageFile=None
                ):
    """
        resize input image
        resize normally means become smaller, reduce size
    :param inputImage: image file object(fp) / filename / binary bytes
    :param newSize: (width, height)
    :param resample: PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC, or PIL.Image.LANCZOS
        https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.thumbnail
    :param outputFormat: PNG/JPEG/BMP/GIF/TIFF/WebP/..., more refer:
        https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
        if input image is filename with suffix, can omit this -> will infer from filename suffix
    :param outputImageFile: output image file filename
    :return:
        input image file filename: output resized image to outputImageFile
        input image binary bytes: resized image binary bytes
    """
    openableImage = None
    if isinstance(inputImage, str):
        openableImage = inputImage
    elif isFileObject(inputImage):
        openableImage = inputImage
    elif isinstance(inputImage, bytes):
        inputImageLen = len(inputImage)
        openableImage = io.BytesIO(inputImage)

    imageFile = Image.open(openableImage) # <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=3543x3543 at 0x1065F7A20>
    imageFile.thumbnail(newSize, resample)
    if outputImageFile:
        # save to file
        imageFile.save(outputImageFile)
        imageFile.close()
    else:
        # save and return binary byte
        imageOutput = io.BytesIO()
        # imageFile.save(imageOutput)
        outputImageFormat = None
        if outputFormat:
            outputImageFormat = outputFormat
        elif imageFile.format:
            outputImageFormat = imageFile.format
        imageFile.save(imageOutput, outputImageFormat)
        imageFile.close()
        compressedImageBytes = imageOutput.getvalue()
        compressedImageLen = len(compressedImageBytes)
        compressRatio = float(compressedImageLen)/float(inputImageLen)
        print("%s -> %s, resize ratio: %d%%" % (inputImageLen, compressedImageLen, int(compressRatio * 100)))
        return compressedImageBytes

demo调用:
import sys
import os
curFolder = os.path.abspath(__file__)
parentFolder = os.path.dirname(curFolder)
parentParentFolder = os.path.dirname(parentFolder)
parentParentParentFolder = os.path.dirname(parentParentFolder)
sys.path.append(curFolder)
sys.path.append(parentFolder)
sys.path.append(parentParentFolder)
sys.path.append(parentParentParentFolder)

import datetime
from crifanMultimedia import resizeImage

def testFilename():
  imageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day.png"
  outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_300x300.png"
  print("imageFilename=%s" % imageFilename)
  beforeTime = datetime.datetime.now()
  resizeImage(imageFilename, (300, 300), outputImageFile=outputImageFilename)
  afterTime = datetime.datetime.now()
  print("procesTime: %s" % (afterTime - beforeTime))

  outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_800x800.png"
  beforeTime = datetime.datetime.now()
  resizeImage(imageFilename, (800, 800), outputImageFile=outputImageFilename)
  afterTime = datetime.datetime.now()
  print("procesTime: %s" % (afterTime - beforeTime))


def testFileObject():
  imageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day.png"
  imageFileObj = open(imageFilename, "rb")
  outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_600x600.png"
  beforeTime = datetime.datetime.now()
  resizeImage(imageFileObj, (600, 600), outputImageFile=outputImageFilename)
  afterTime = datetime.datetime.now()
  print("procesTime: %s" % (afterTime - beforeTime))


def testBinaryBytes():
  imageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/take tomato.png"
  imageFileObj = open(imageFilename, "rb")
  imageBytes = imageFileObj.read()
  # return binary bytes
  beforeTime = datetime.datetime.now()
  resizedImageBytes = resizeImage(imageBytes, (800, 800))
  afterTime = datetime.datetime.now()
  print("procesTime: %s" % (afterTime - beforeTime))
  print("len(resizedImageBytes)=%s" % len(resizedImageBytes))

  # save to file
  outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_750x750.png"
  beforeTime = datetime.datetime.now()
  resizeImage(imageBytes, (750, 750), outputImageFile=outputImageFilename)
  afterTime = datetime.datetime.now()
  print("procesTime: %s" % (afterTime - beforeTime))

  imageFileObj.close()


def demoResizeImage():
  testFilename()
  testFileObject()
  testBinaryBytes()

if __name__ == "__main__":
  demoResizeImage()

  # imageFilename=/Users/crifan/dev/tmp/python/resize_image_demo/hot day.png
  # procesTime: 0:00:00.619377
  # procesTime: 0:00:00.745228
  # procesTime: 0:00:00.606060
  # 1146667 -> 753258, resize ratio: 65%
  # procesTime: 0:00:00.773289
  # len(resizedImageBytes)=753258
  # procesTime: 0:00:00.738237

更多代码详见:
https://github.com/crifan/crifanLib/blob/master/python/crifanLib/crifanFile.py
https://github.com/crifan/crifanLib/blob/master/python/crifanLib/crifanMultimedia.py

转载请注明:在路上 » 【已解决】Python中实现二进制数据的图片的压缩

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
92 queries in 0.217 seconds, using 23.38MB memory