首页 > 解决方案 > 为什么不渲染来自字节流的图像?

问题描述

我正在使用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意思?

标签: python-3.xbase64python-imaging-library

解决方案


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

  • 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)

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

在此处输入图像描述


推荐阅读