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

示例输出: