python-3.x - 为什么不渲染来自字节流的图像?
问题描述
我正在使用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
的前 10 个字节file_to_upload
:
b'YPO\xffYPO\xffVQ'
我似乎无法获得 with 的前 10 个字节,im_base64
因为print(image.tobytes()[: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 了解像素数据PIL Image
的事实。RGBA
JPEG/PNG 编码的图像非常不同。首先,它以一个ff
d8
用于JPEG的幻数开头,以及用于 PNG的 3 个字母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()
此外,请在此处阅读有关检查数据的前几个字节的内容,以便您可以轻松检查是否发送了正确的内容。
所以,这是完整的代码:
#!/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)
以及由此产生的半透明红色图像:
推荐阅读
- android - Android TextView 水平滚动延迟
- vba - 错误消息:“操作值必须是数字”,在创建新的 JIRA 问题时
- r - 如何获得每个因子的 p 值,而不是零膨胀泊松模型中每个因子水平的 p 值?
- php - 查询缓存类型在 X-cart 应用程序中显示为 ON/not
- javascript - 将元数据添加到 Stripe 客户对象
- sql - T-SQL 获取连续行
- reactjs - 如何让 create-react-app 在脚本路径中包含前导点?
- c++ - 使用 Windows Sensor API 从多个传感器检索信息
- angular - 包含异步管道的 Angular 组件:屏幕上的值从不刷新。如何处理?
- java - 如果他重生,我想传送玩家,但它不起作用,玩家不会被传送