Python:如何从相机产生的字节到实际图像
Python: how to go from the bytes produced by a camera to an actual image
我正在通过其 SDK 控制显微镜相机,并以我认为是字节格式的图像数据结束。以下是SDK如何使用Qt显示相机图像:
image = QImage(img_buf, img_width, img_height, (img_width * 24 + 31) // 32 * 4, QImage.Format_RGB888)
由于我对在 Qt 环境中显示相机流并不感兴趣,而是对保存此相机制作的视频感兴趣,因此我想知道从 'byte format image' 到实际 image/movie?有人建议我研究 PIL,但我找不到关于如何使用它的好例子。大多数示例首先将实际图像转换为字节,然后将其处理回不同压缩的图像,但是如何像我的情况一样从原始字节数据构建图像?
提前感谢您的帮助
当我们提到“实际图像”时,选择很少。
QImage
对象可能被视为“实际图像”。
使用 PIL,我们将获得 PIL Image 对象而不是 QImage。
我更喜欢将“实际图像”视为 NumPy 数组。
NumPy 表示中的 RGB 图像是:
img_height
行,img_width
列和 3
颜色通道。
(数组的形状是 img_height
x img_width
x 3)。
我们可以将RGB图像表示如下:
<-- img_width r,g,b triples -->
r00,g00,b00, r01,g01,b01, r02,g02,b02, ... ^
r10,g10,b10, r11,g11,b11, r12,g12,b12, ... | img_height rows
r20,g20,b20, r21,g21,b21, r22,g22,b22, ... |
r30,g30,b30, r31,g31,b31, r32,g32,b32, ... V
字节数组 (img_buf
) 中的相同 RGB 图像可能表示为长一维数组。
(整形为图像时,线扫描是从左到右,从上到下):
r00,g00,b00, r01,g01,b01, r02,g02,b02, ..., r10,g10,b10, r11,g11,b11, r12,g12,b12, ..., r20,g20,b20, r21,g21,b21, r22,g22,b22, ..., r30,g30,b30, r31,g31,b31, r32,g32,b32, ...
假设 img_width
是 4 的倍数(忽略:(img_width * 24 + 31) // 32 * 4
)。
从 img_buf
到 RGB NumPy 数组的转换分两步完成:
将 img_buf
从字节数组转换为 NumPy 数组:
buf_as_np_array = np.frombuffer(img_buf, np.uint8)
将 NumPy 数组重塑为 img_width
列、img_height
行和 3 个颜色通道:
rgb = buf_as_np_array.reshape(img_height, img_width, 3)
在一个声明中:rgb = np.frombuffer(img_buf, np.uint8).reshape(img_height, img_width, 3)
我们可以将rgb
转换为PIL图像对象:
im = PIL.Image.fromarray(rgb)
我更喜欢使用 OpenCV 包,因为它原生使用 NumPy 数组。
唯一的问题是 OpenCV 颜色排序约定是 BGR 而不是 RGB。
我们可以将 RGB 转换为 BGR:
img = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
使用 OpenCV 将 img
写入 PNG 文件的示例:
cv2.imwrite('img.png', bgr)
当img_width
不是4的倍数时,我们可以用np.lib.stride_tricks.as_strided
:
img_stride = (img_width * 24 + 31) // 32 * 4
buf_as_np_array = np.frombuffer(img_buf, np.uint8) # Convert the bytes array to NumPy array
rgb = np.lib.stride_tricks.as_strided(buf_as_np_array, (img_height, img_width, 3), (img_stride, 3, 1)) # Use "stride_tricks.as_strided" because img_width*3 != bytesPerLine
我希望不是这样,因为很难解释“步幅”的概念...
为了演示,我创建了两个代码示例。
图片宽度是4的倍数:
import cv2
import numpy as np
img_width = 128
img_height = 80
# Create sample bytes array for demonstration:
################################################################################
cols, rows = img_width, img_height
sample_img = np.full((rows, cols, 3), 60, np.uint8)
cv2.putText(sample_img, 'R', (cols//2-60, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (255, 0, 0), 3)
cv2.putText(sample_img, 'G', (cols//2-20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 255, 0), 3)
cv2.putText(sample_img, 'B', (cols//2+20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 0, 255), 3)
img_buf = sample_img.tobytes() # Convert the image to bytes array
################################################################################
buf_as_np_array = np.frombuffer(img_buf, np.uint8) # Convert the bytes array to NumPy array
rgb = buf_as_np_array.reshape(img_height, img_width, 3) # Reshape the 1D array to img_width columns by img_height rows and 3 color channels.
# Convert from RGB to BGR, and show image (for testing).
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) # Converting from RGB to BGR (only because OpenCV convension is BGR)
cv2.imshow('bgr', bgr)
cv2.waitKey()
cv2.destroyAllWindows()
图片宽度不能是 4 的倍数:
import cv2
import numpy as np
img_width = 130 # Width is not a multiple of 4 (bytesPerLine is going to be 392 instead of 390=130*3 due to padding)
img_height = 80
img_stride = (img_width * 24 + 31) // 32 * 4 # 392 In QImage the name is "bytesPerLine"
# Create sample bytes array for demonstration:
################################################################################
cols, rows = img_width, img_height
sample_img = np.full((rows, cols, 3), 60, np.uint8)
cv2.putText(sample_img, 'R', (cols//2-60, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (255, 0, 0), 3)
cv2.putText(sample_img, 'G', (cols//2-20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 255, 0), 3)
cv2.putText(sample_img, 'B', (cols//2+20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 0, 255), 3)
sample_img = sample_img.reshape(img_height, img_width*3) # Reshape from 130x80x3 to 390x80
sample_img = np.pad(sample_img, ((0, 0), (0, img_stride-img_width*3)), mode='constant') # Pad 2 columns to 392x80
img_buf = sample_img.tobytes() # Convert the image to bytes array
################################################################################
buf_as_np_array = np.frombuffer(img_buf, np.uint8) # Convert the bytes array to NumPy array
rgb = np.lib.stride_tricks.as_strided(buf_as_np_array, (img_height, img_width, 3), (img_stride, 3, 1)) # Use "stride_tricks.as_strided" because img_width*3 != bytesPerLine
# Convert from RGB to BGR, and show image (for testing).
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) # Converting from RGB to BGR (only because OpenCV conversion is BGR)
cv2.imshow('bgr', bgr)
cv2.waitKey()
cv2.destroyAllWindows()
示例输出:
我正在通过其 SDK 控制显微镜相机,并以我认为是字节格式的图像数据结束。以下是SDK如何使用Qt显示相机图像:
image = QImage(img_buf, img_width, img_height, (img_width * 24 + 31) // 32 * 4, QImage.Format_RGB888)
由于我对在 Qt 环境中显示相机流并不感兴趣,而是对保存此相机制作的视频感兴趣,因此我想知道从 'byte format image' 到实际 image/movie?有人建议我研究 PIL,但我找不到关于如何使用它的好例子。大多数示例首先将实际图像转换为字节,然后将其处理回不同压缩的图像,但是如何像我的情况一样从原始字节数据构建图像?
提前感谢您的帮助
当我们提到“实际图像”时,选择很少。
QImage
对象可能被视为“实际图像”。
使用 PIL,我们将获得 PIL Image 对象而不是 QImage。
我更喜欢将“实际图像”视为 NumPy 数组。
NumPy 表示中的 RGB 图像是:
img_height
行,img_width
列和 3
颜色通道。
(数组的形状是 img_height
x img_width
x 3)。
我们可以将RGB图像表示如下:
<-- img_width r,g,b triples -->
r00,g00,b00, r01,g01,b01, r02,g02,b02, ... ^
r10,g10,b10, r11,g11,b11, r12,g12,b12, ... | img_height rows
r20,g20,b20, r21,g21,b21, r22,g22,b22, ... |
r30,g30,b30, r31,g31,b31, r32,g32,b32, ... V
字节数组 (img_buf
) 中的相同 RGB 图像可能表示为长一维数组。
(整形为图像时,线扫描是从左到右,从上到下):
r00,g00,b00, r01,g01,b01, r02,g02,b02, ..., r10,g10,b10, r11,g11,b11, r12,g12,b12, ..., r20,g20,b20, r21,g21,b21, r22,g22,b22, ..., r30,g30,b30, r31,g31,b31, r32,g32,b32, ...
假设 img_width
是 4 的倍数(忽略:(img_width * 24 + 31) // 32 * 4
)。
从 img_buf
到 RGB NumPy 数组的转换分两步完成:
将
img_buf
从字节数组转换为 NumPy 数组:buf_as_np_array = np.frombuffer(img_buf, np.uint8)
将 NumPy 数组重塑为
img_width
列、img_height
行和 3 个颜色通道:rgb = buf_as_np_array.reshape(img_height, img_width, 3)
在一个声明中:rgb = np.frombuffer(img_buf, np.uint8).reshape(img_height, img_width, 3)
我们可以将rgb
转换为PIL图像对象:
im = PIL.Image.fromarray(rgb)
我更喜欢使用 OpenCV 包,因为它原生使用 NumPy 数组。
唯一的问题是 OpenCV 颜色排序约定是 BGR 而不是 RGB。
我们可以将 RGB 转换为 BGR:
img = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
使用 OpenCV 将 img
写入 PNG 文件的示例:
cv2.imwrite('img.png', bgr)
当img_width
不是4的倍数时,我们可以用np.lib.stride_tricks.as_strided
:
img_stride = (img_width * 24 + 31) // 32 * 4
buf_as_np_array = np.frombuffer(img_buf, np.uint8) # Convert the bytes array to NumPy array
rgb = np.lib.stride_tricks.as_strided(buf_as_np_array, (img_height, img_width, 3), (img_stride, 3, 1)) # Use "stride_tricks.as_strided" because img_width*3 != bytesPerLine
我希望不是这样,因为很难解释“步幅”的概念...
为了演示,我创建了两个代码示例。
图片宽度是4的倍数:
import cv2
import numpy as np
img_width = 128
img_height = 80
# Create sample bytes array for demonstration:
################################################################################
cols, rows = img_width, img_height
sample_img = np.full((rows, cols, 3), 60, np.uint8)
cv2.putText(sample_img, 'R', (cols//2-60, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (255, 0, 0), 3)
cv2.putText(sample_img, 'G', (cols//2-20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 255, 0), 3)
cv2.putText(sample_img, 'B', (cols//2+20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 0, 255), 3)
img_buf = sample_img.tobytes() # Convert the image to bytes array
################################################################################
buf_as_np_array = np.frombuffer(img_buf, np.uint8) # Convert the bytes array to NumPy array
rgb = buf_as_np_array.reshape(img_height, img_width, 3) # Reshape the 1D array to img_width columns by img_height rows and 3 color channels.
# Convert from RGB to BGR, and show image (for testing).
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) # Converting from RGB to BGR (only because OpenCV convension is BGR)
cv2.imshow('bgr', bgr)
cv2.waitKey()
cv2.destroyAllWindows()
图片宽度不能是 4 的倍数:
import cv2
import numpy as np
img_width = 130 # Width is not a multiple of 4 (bytesPerLine is going to be 392 instead of 390=130*3 due to padding)
img_height = 80
img_stride = (img_width * 24 + 31) // 32 * 4 # 392 In QImage the name is "bytesPerLine"
# Create sample bytes array for demonstration:
################################################################################
cols, rows = img_width, img_height
sample_img = np.full((rows, cols, 3), 60, np.uint8)
cv2.putText(sample_img, 'R', (cols//2-60, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (255, 0, 0), 3)
cv2.putText(sample_img, 'G', (cols//2-20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 255, 0), 3)
cv2.putText(sample_img, 'B', (cols//2+20, 60), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 0, 255), 3)
sample_img = sample_img.reshape(img_height, img_width*3) # Reshape from 130x80x3 to 390x80
sample_img = np.pad(sample_img, ((0, 0), (0, img_stride-img_width*3)), mode='constant') # Pad 2 columns to 392x80
img_buf = sample_img.tobytes() # Convert the image to bytes array
################################################################################
buf_as_np_array = np.frombuffer(img_buf, np.uint8) # Convert the bytes array to NumPy array
rgb = np.lib.stride_tricks.as_strided(buf_as_np_array, (img_height, img_width, 3), (img_stride, 3, 1)) # Use "stride_tricks.as_strided" because img_width*3 != bytesPerLine
# Convert from RGB to BGR, and show image (for testing).
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) # Converting from RGB to BGR (only because OpenCV conversion is BGR)
cv2.imshow('bgr', bgr)
cv2.waitKey()
cv2.destroyAllWindows()
示例输出: