为什么不渲染来自字节流的图像?
Why image from a byte stream isn't being rendered?
我正在使用 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
是什么意思?
要查看哪里出错了,你需要区分:
RGB
“像素数据”,
- JPEG/PNG 编码图像。
“像素数据” 是一堆 RGB/RGBA 字节,仅此而已。没有高度或宽度信息来了解如何解释或布置屏幕上的像素。每个像素的数据只有 4 个 RGBA 字节。如果您知道您的图像是 720x1280 RGBA 像素,那么您将拥有 720x1280x4 或 3686400 字节。请注意,那里没有高度和宽度的空间,或者它是 RGBA。这就是您的变量 file_to_upload
中的内容。请注意,您必须另外告诉 PIL Image
高度和宽度以及它是 RGBA
的事实,以便 PIL 理解像素数据。
A JPEG/PNG 编码图像非常不同。首先,它以一个幻数开头,即 ff
d8
for JPEG, and the 3 letters PNG
and some other bits and pieces for PNG。然后它有高度和宽度、bytes/pixel 和色彩空间,可能还有你拍摄照片的日期和 GPS 位置、你的版权、相机制造商和镜头以及其他一些东西。然后它有压缩像素数据。一般来说,它会小于对应的像素数据。 JPEG/PNG 是 self-contained - 不需要额外的数据。
好的,您需要将 base64 编码的 JPEG 或 PNG 发送到浏览器。为什么?因为浏览器需要一个 image 里面有尺寸,否则它无法判断它是 720 px 宽和 1280 px 高,还是一条 921,600 RGBA 像素的直线,或者1,228,800 RGB 像素的单条直线。您的图像是 RGBA,因此您最好发送 PNG,因为 JPEG 不能包含透明度。
所以,你哪里做错了?您从 “像素数据” 开始,添加了您对高度和宽度的了解并制作了 PIL 图像。到目前为止,一切都很好。但是后来你出错了,因为你调用了 tobytes()
并使其恢复到你开始时的状态 - “像素数据” 具有与你所拥有的相同的长度和内容,并且没有宽度或高度信息。相反,您应该创建一个 in-memory PNG-encoded 图像,其中嵌入了高度和宽度,以便浏览器知道它的形状。然后进行 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()
此外,阅读 关于检查数据的前几个字节的内容,这样您就可以轻松检查发送的内容是否正确。
所以,这是完整的代码:
#!/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)
由此产生的 semi-transparent 红色图像:
我正在使用 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
是什么意思?
要查看哪里出错了,你需要区分:
RGB
“像素数据”,- JPEG/PNG 编码图像。
“像素数据” 是一堆 RGB/RGBA 字节,仅此而已。没有高度或宽度信息来了解如何解释或布置屏幕上的像素。每个像素的数据只有 4 个 RGBA 字节。如果您知道您的图像是 720x1280 RGBA 像素,那么您将拥有 720x1280x4 或 3686400 字节。请注意,那里没有高度和宽度的空间,或者它是 RGBA。这就是您的变量 file_to_upload
中的内容。请注意,您必须另外告诉 PIL Image
高度和宽度以及它是 RGBA
的事实,以便 PIL 理解像素数据。
A JPEG/PNG 编码图像非常不同。首先,它以一个幻数开头,即 ff
d8
for JPEG, and the 3 letters PNG
and some other bits and pieces for PNG。然后它有高度和宽度、bytes/pixel 和色彩空间,可能还有你拍摄照片的日期和 GPS 位置、你的版权、相机制造商和镜头以及其他一些东西。然后它有压缩像素数据。一般来说,它会小于对应的像素数据。 JPEG/PNG 是 self-contained - 不需要额外的数据。
好的,您需要将 base64 编码的 JPEG 或 PNG 发送到浏览器。为什么?因为浏览器需要一个 image 里面有尺寸,否则它无法判断它是 720 px 宽和 1280 px 高,还是一条 921,600 RGBA 像素的直线,或者1,228,800 RGB 像素的单条直线。您的图像是 RGBA,因此您最好发送 PNG,因为 JPEG 不能包含透明度。
所以,你哪里做错了?您从 “像素数据” 开始,添加了您对高度和宽度的了解并制作了 PIL 图像。到目前为止,一切都很好。但是后来你出错了,因为你调用了 tobytes()
并使其恢复到你开始时的状态 - “像素数据” 具有与你所拥有的相同的长度和内容,并且没有宽度或高度信息。相反,您应该创建一个 in-memory PNG-encoded 图像,其中嵌入了高度和宽度,以便浏览器知道它的形状。然后进行 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()
此外,阅读
所以,这是完整的代码:
#!/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)
由此产生的 semi-transparent 红色图像: