【问题标题】:Why image from a byte stream isn't being rendered?为什么不渲染来自字节流的图像?
【发布时间】:2020-09-22 06:39:34
【问题描述】:

我正在使用 base64 模块进行图像处理。

我有这个代码:

import flask, base64, webbrowser, PIL.Image
...
...

image = PIL.Image.frombytes(mode='RGBA', size=(cam_width, cam_height), data=file_to_upload)
im_base64 = base64.b64encode(image.tobytes())
    html = '<html><head><meta http-equiv="refresh" content="0.5"><title>Displaying Uploaded Image</title></head><body><h1>Displaying Uploaded Image</h1><img src="data:;base64,{}" alt="" /></body></html>'.format(im_base64.decode('utf8'))
html_url = '/home/mark/Desktop/FlaskUpload/test.html'
with open(html_url, 'w') as f:
    f.write(html)
webbrowser.open(html_url)

我也试过了:

html = '<html><head><meta http-equiv="refresh" content="0.5"><title>Displaying Uploaded Image</title></head><body><h1>Displaying Uploaded Image</h1><img src="data:;base64,"'+im_base64.decode('utf8')+'" alt="" /></body></html>'

标题被渲染得很好,但不是图像。 我错过了什么吗?

更新:

cam_width 是 720

cam_height 是 1280

file_to_upload 是 3686400

file_to_upload 的前 10 个字节:

b'YPO\xffYPO\xffVQ'

我似乎无法使用print(image.tobytes()[:10]) 获得im_base64 的前10 个字节,因为它会引发错误。

我更接近于确定出了什么问题。一旦我固定 我得到错误的引号:

Traceback (most recent call last):
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/mark/venv/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/mark/venv/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/mark/venv/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/mark/venv/server.py", line 28, in upload_file
    image = PIL.Image.frombytes(mode='RGBA', size=(cam_width, cam_height), data=file_to_upload)
  File "/home/mark/venv/lib/python3.7/site-packages/PIL/Image.py", line 2650, in frombytes
    im.frombytes(data, decoder_name, args)
  File "/home/mark/venv/lib/python3.7/site-packages/PIL/Image.py", line 797, in frombytes
    d.setimage(self.im)
ValueError: tile cannot extend outside image

我是第一次使用图像处理,所以我不知道我在做什么。 ValueError: tile cannot extend outside image 是什么意思?

【问题讨论】:

  • 您省略了一些有助于调试代码的细节。 cam_widthcam_height 的值是多少,file_to_upload 的字节长度是多少? file_to_upload 的前 10 个字节是什么? im_base64 的前 10 个字节是什么?
  • @MarkSetchell 我添加了更新。

标签: python-3.x base64 python-imaging-library


【解决方案1】:

要查看哪里出错了,您需要区分:

  • RGB“像素数据”,和
  • JPEG/PNG 编码图像。

“像素数据” 是一堆 RGB/RGBA 字节,仅此而已。没有高度或宽度信息可以知道如何解释或布置屏幕上的像素。每个像素的数据只有 4 个 RGBA 字节。如果您知道您的图像是 720x1280 RGBA 像素,那么您将有 720x1280x4 或 3686400 字节。请注意,那里没有高度和宽度的空间,或者它是 RGBA 的事实。这就是变量file_to_upload 中的内容。请注意,您必须另外告诉PIL Image 的高度和宽度以及事实是RGBA,PIL 才能理解像素数据。


JPEG/PNG 编码的图像非常不同。首先,它以一个幻数开头,即ff d8 对应JPEGthe 3 letters PNG and some other bits and pieces for PNG。然后它有高度和宽度、字节/像素和色彩空间,可能还有您拍摄照片的日期和 GPS 位置、您的版权、相机制造商和镜头以及一堆其他东西。然后它有压缩像素数据。一般来说,它会小于相应的像素数据。 JPEG/PNG 是独立的 - 不需要额外的数据。


好的,您需要将 base64 编码的 JPEG 或 PNG 发送到浏览器。为什么?因为浏览器需要一个包含尺寸的图像,否则它无法判断它是 720 px 宽和 1280 px 高,还是一条 921,600 RGBA 像素的直线,还是一条直线1,228,800 RGB 像素。您的图像是 RGBA,因此您最好发送 PNG,因为 JPEG 不能包含透明度。

那么,你哪里做错了?您从“像素数据”开始,添加了您对高度和宽度的了解并制作了 PIL 图像。到现在为止还挺好。但是后来你出错了,因为你调用了tobytes() 并把它恢复到你开始的样子——“像素数据”,长度和内容和你一样,没有宽度或高度信息。相反,您应该创建一个内存中的 PNG 编码图像,其中嵌入了高度和宽度,以便浏览器知道它的形状。然后base64编码并发送。所以你需要这样的东西:

image = PIL.Image.frombytes(mode='RGBA', size=(cam_width, cam_height), data=file_to_upload)
buffer = io.BytesIO()
image.save(buffer, format="PNG")
PNG = buffer.getvalue()

另外,请阅读 here 关于检查数据的前几个字节的内容,以便您可以轻松检查是否发送了正确的内容。


那么,完整的代码如下:

#!/usr/bin/env python3

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

cam_width, cam_height = 640, 480

# Simulate some semi-transparent red pixel data
PixelData = np.full((cam_height,cam_width,4), [255,0,0,128], np.uint8)

# Convert to PIL Image
im = Image.frombytes(mode='RGBA', size=(cam_width, cam_height), data=PixelData)

# Create in-memory PNG
buffer = BytesIO()
im.save(buffer, format="PNG")
PNG = buffer.getvalue()

# Base64 encode
b64PNG = base64.b64encode(PNG).decode("utf-8") 

# Create HTML
html = f'<html><head><meta http-equiv="refresh" content="0.5"><title>Displaying Uploaded Image</title></head><body><h1>Displaying Uploaded Image</h1><img src="data:;base64,{b64PNG}" alt="" /></body></html>'

# Write HTML
with open('test.html', 'w') as f:
    f.write(html)

以及由此产生的半透明红色图像:

【讨论】:

  • 我将图像从一行的字节转换为下一行的字节的原因是我在两行之间将图像旋转了 90 度。我只是没有在帖子中包含它,因为我觉得它不相关。
  • 解释得很好。我也做了笔记。由于我正在开发一个处理图像处理相当多的应用程序,您能否向我推荐一些资源,以帮助我更好地了解 Python 如何处理图像和不同类型的编码?换句话说,您是从哪里获得对这个主题的充分理解的?
  • 您是否只是简单地使用 Python 模块(例如 iobase64PIL)来掌握它?我全都听好了。
  • 我获得了卫星成像硕士学位。对我来说,最简单的学习方法是使用 ImageMagick,因为您只需使用终端中的命令行即可对图像执行几乎任何您想要的操作,而无需编写任何代码。 Anthony Thyssen 的页面非常有用,可以解释如何进行从裁剪到过滤和形态学的所有操作imagemagick.org/Usage,每个命令的详细在线帮助都在这里imagemagick.org/script/command-line-options.php
猜你喜欢
  • 1970-01-01
  • 2020-11-23
  • 1970-01-01
  • 2020-08-27
  • 1970-01-01
  • 2020-01-03
  • 1970-01-01
  • 2014-11-26
  • 1970-01-01
相关资源
最近更新 更多