为什么不渲染来自字节流的图像?

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 红色图像: