当前位置: 首页 > news >正文

PyQt:从图像文件或字节流生成QImage的速度测试

一幅从相机获取的3072 * 2048像素的图片,原始像素格式为bayer-rb,将其分别保存为png、bmp、jpg以及raw(直接存储元素的原始bayer-rb字节数据)格式的本地文件,然后使用Qt转换为QImage并显示,统计用时。

  • 使用QImage()直接读取png格式并生成QImage对象:

import sys import cv2 import numpy as np from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() q_image = QImage("out.png") print(f"转换QImage耗时:{timer.elapsed()} ms") timer.restart() pixmap = QPixmap.fromImage(q_image) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

用时:

转换QImage耗时:136 ms 转换QPixmap耗时:0 ms 设置QPixmap耗时:0 ms
  • 使用QImage()直接读取bmp格式并生成QImage对象:

import sys import cv2 import numpy as np from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() q_image = QImage("out.bmp") print(f"转换QImage耗时:{timer.elapsed()} ms") timer.restart() pixmap = QPixmap.fromImage(q_image) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

用时:

转换QImage耗时:56 ms 转换QPixmap耗时:0 ms 设置QPixmap耗时:0 ms
  • jpg格式:

import sys import cv2 import numpy as np from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() q_image = QImage("out.jpg") print(f"转换QImage耗时:{timer.elapsed()} ms") timer.restart() pixmap = QPixmap.fromImage(q_image) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

用时:

转换QImage耗时:91 ms 转换QPixmap耗时:0 ms 设置QPixmap耗时:0 ms
  • bayer格式字节流

由于Qt不直接支持bayer格式,使用numpy进行转换:

import sys import cv2 import numpy as np from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) def bayer_bytes_to_qimage(bayer_bytes, width, height): bayer_array = np.frombuffer(bayer_bytes, dtype=np.uint8).reshape((height, width)) # 将bayer格式字节流转换为numpy数组 rgb_array = cv2.cvtColor(bayer_array, cv2.COLOR_BAYER_RG2BGR) # 将bayer格式数组转换为RGB格式数组(如果raw是使用opencv创建的,有可能cv2.COLOR_BAYER_RG2BGR格式转换) q_image = QImage(rgb_array.data, rgb_array.shape[1], rgb_array.shape[0], w * 3, QImage.Format.Format_RGB888) return q_image if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() # 模拟字节流:读取文件为字节(实际场景可能是网络返回、数据库读取) with open("out.raw", "rb") as f: bayer_bytes = f.read() print(f"读取raw文件时间: {timer.elapsed()} ms") h =2048 w = 3072 timer.restart() q_image = bayer_bytes_to_qimage(bayer_bytes, w, h) print(f"转换QImage耗时:{timer.elapsed()} ms") timer.restart() pixmap = QPixmap.fromImage(q_image) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

用时:

读取raw文件时间: 2 ms cv2转换耗时:4 ms 转换QPixmap耗时:7 ms
  • 用时对比:
读取格式读文件用时(ms)转QImage耗时(ms)转换QPixmap耗时(ms)总耗时(ms)
png--1360136
bmp--56056
jpg--91091
raw24713

使用原始图像字节流并使用numpy转换,效率远远比Qt直接读取图像文件高,其中的原因是numpy支持不创建副本的内存视图引用和矩阵转换。

验证,直接读取bmp图像文件的像素字节并用numpy转换为数组后转为QImage:

import sys import cv2 import numpy as np from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() h = 2048 w = 3072 with open("out.bmp", "rb") as f: f.seek(54) image_bytes = f.read() # 去除bmp头部信息 print(f"读取bmp文件时间: {timer.elapsed()} ms") timer.restart() rgb_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将字节流转换为numpy数组 rgb_arrayy = np.flipud(rgb_array) # 垂直翻转(bmp图像像素是从左下角开始存储的) q_image = QImage(rgb_array.data, w, h, w * 3, QImage.Format.Format_RGB888) # 将numpy数组转换为QImage print(f"转换QImage耗时:{timer.elapsed()} ms") timer.restart() pixmap = QPixmap.fromImage(q_image) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

总耗时17ms:

读取bmp文件时间: 9 ms 转换QImage耗时:0 ms 转换QPixmap耗时:8 ms 设置QPixmap耗时:0 ms

然后也使用Qt从图像文件的原始字节流生成QPixmap进行比对:

import sys import cv2 import numpy as np from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() with open("out.bmp", "rb") as f: image_bytes = f.read() print(f"读取bmp文件时间: {timer.elapsed()} ms") timer.restart() pixmap = QPixmap() pixmap.loadFromData(image_bytes) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

总耗时45ms:

读取bmp文件时间: 9 ms 转换QPixmap耗时:36 ms 设置QPixmap耗时:0 ms

当然也可以分两步,先用原始字节流生成QImage再转换QPixmap,结果差不多:

import sys from PySide6.QtCore import QElapsedTimer from PySide6.QtGui import QPixmap, QImage from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel class MyWidget(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel() layout.addWidget(self.label) self.setLayout(layout) if __name__ == "__main__": app = QApplication(sys.argv) widget = MyWidget() widget.show() # 计时器 timer = QElapsedTimer() timer.start() h = 2048 w = 3072 with open("out.bmp", "rb") as f: image_bytes = f.read() print(f"读取bmp文件时间: {timer.elapsed()} ms") timer.restart() q_image = QImage() q_image.loadFromData(image_bytes) print(f"转换QImage耗时:{timer.elapsed()} ms") timer.restart() pixmap = QPixmap.fromImage(q_image) print(f"转换QPixmap耗时:{timer.elapsed()} ms") timer.restart() widget.label.setPixmap(pixmap) print(f"设置QPixmap耗时:{timer.elapsed()} ms") # 宽[3072], 高[2048] sys.exit(app.exec())

总耗时54ms:

读取bmp文件时间: 11 ms 转换QImage耗时:43 ms 转换QPixmap耗时:0 ms 设置QPixmap耗时:0 ms
  • 结论:

无论是读取本地文件或者使用字节流,使用numpy把像素字节转换成数组再生成QImage的效率远远高于Qt本身的QImage生成功能(实测4倍以上)。

附:将相机原始bayer字节流保存为.raw文件的方法

def save_bayer_to_raw( pixel_data: bytes, width: int, height: int, pixel_format: str, # BayerRG8, BayerRG10, BayerRG10P, BayerRG12, BayerRG12P output_path: str = "output.raw" ): """ 海康相机Bayer裸数据 + 自定义二进制头 → 保存为 .raw 文件 自定义头格式:固定128字节,方便后续读取解析 """ # -------------------------- # 1. 构造自定义文件头(128字节) # -------------------------- header_size = 128 header_bytes = bytearray(header_size) # 偏移0:宽度 (4字节 int) w_bytes = width.to_bytes(4, byteorder='little') header_bytes[:4] = w_bytes # 偏移4:高度 (4字节 int) h_bytes = height.to_bytes(4, byteorder='little') header_bytes[4:8 ] = h_bytes # 偏移8:位深 (4字节 int) bit_depth = { "BayerRG8": 8, "BayerRG10": 10, "BayerRG10P": 10, "BayerRG12": 12, "BayerRG12P": 12 }[pixel_format] bit_depth_bytes = bit_depth.to_bytes(4, byteorder='little') # 位深 header_bytes[8:12] = bit_depth_bytes # 偏移12:像素格式字符串(最多32字节) fmt_bytes = pixel_format.encode("utf-8") # 像素格式 fmt_bytes = fmt_bytes.ljust(32, b'\x00') # 不足补0 header_bytes[12:44] = fmt_bytes # 偏移64:数据总长度(4字节 unsigned long long) data_len = len(pixel_data) l_bytes = data_len.to_bytes(4, byteorder='little', signed=False) header_bytes[64:68] = l_bytes # -------------------------- # 2. 拼接:头 + 裸像素数据 # -------------------------- raw_file_bytes = b''.join([header_bytes, pixel_data]) # -------------------------- # 3. 二进制写入文件 # -------------------------- with open(output_path, "wb") as f: f.write(raw_file_bytes) print(f"✅ 保存成功:{output_path}") print(f" 尺寸:{width}x{height}") print(f" 格式:{pixel_format}") print(f" 头长度:{header_size} 像素数据长度:{data_len}")

以及从含宽高信息的.raw文件读取bayer字节流和转换成RGB数组的方法:

import cv2 import numpy as np def read_bayer_raw(raw_file_path): with open(raw_file_path, "rb") as f: # 从本地文件读取bayer-rg格式字节流(这个字节流也可以是从相机获取的) head_bytes = f.read(128) # 读取文件头 # 解析头部数据 w = int.from_bytes(head_bytes[:4], byteorder='little') h = int.from_bytes(head_bytes[4:8], byteorder='little') bit_depth = int.from_bytes(head_bytes[8:12], byteorder='little') pixel_format = head_bytes[12:44].decode("utf-8").strip("\x00") data_len = int.from_bytes(head_bytes[64:68], byteorder='little') pixel_data = f.read(data_len) return w, h, bit_depth, pixel_format, data_len, pixel_data def bayer_to_rgb8(bayer_data: bytes, width: int, height: int, bit_depth:int) -> np.ndarray: """ 将海康MV相机的Bayer RG数据转换为RGB8格式图像 :param bayer_data: 相机输出的bayerRG原始字节数据 :param width: 图像宽度(像素) :param height: 图像高度(像素) :return: RGB8格式的numpy数组(uint8) """ # 步骤1:转成np数列 # rg_array = np.frombuffer(bayer_data) # 步骤2:数据归一化到8位(0~255) if bit_depth == 8: bayer_array = np.frombuffer(bayer_data, dtype=np.uint8).reshape((h, w)) uint8_array = (bayer_array << 2).astype(np.uint8) elif bit_depth in [10, 12]: bayer_array = np.frombuffer(bayer_data, dtype=np.uint16).reshape((h, w)) uint8_array = (bayer_array << 4).astype(np.uint8) else: raise ValueError("不支持的位深") # 步骤3:Bayer RG(对应OpenCV的BAYER_RGGB格式)转RGB8 rgb8_image = cv2.cvtColor(uint8_array, cv2.COLOR_BAYER_RG2RGB) return rgb8_image w, h, bit_depth, pixel_format, data_len, pixel_data = read_bayer_raw("bayer-rg12_with_info.raw") bgr_img = bayer_to_rgb8(pixel_data, w, h, bit_depth) cv2.imshow("bayer", bgr_img) # cv2.imwrite("bayer10.png", bgr_img) cv2.waitKey(0) cv2.destroyAllWindows() # rg10:12,582,912 3072*2048=6,291,456*2=12,582,912
http://www.jsqmd.com/news/496807/

相关文章:

  • JMeter实战2--阶梯线程组及计算逻辑
  • 链接脚本优化(lsl或ld),Map文件解析,内存分析软件MapSee免费下载
  • ROS2的核心概念A-节点
  • Windows如何阻止应用程序联网
  • 灵芝孢子粉哪个牌子好?从破壁率、成分、口碑分析.
  • 计算机毕业设计源码:Python基于大数据的租房价格分析平台 Django框架 Requests爬虫 可视化 房子 房源 大数据 大模型(建议收藏)✅
  • VMware安装教程带资料完整版
  • 【愚公系列】《剪映+DeepSeek+即梦:短视频制作》009-剪辑:把碎片素材串联成片(画面调整)
  • 2026年地方招投标新规密集出台:火眼审阅标书查重,精准响应政策要求的合规利器 - 资讯焦点
  • 红外热像仪优质厂家推荐:工业巡检机器人、手持红外热像仪、热成像相机、热成像红外夜视仪、电力巡视、管道巡检机器人选择指南 - 优质品牌商家
  • NMN哪个产品最好?2026选哪个NMN牌子性价最高?高活NMN推荐理由:转化NAD+吸收率高 - 资讯焦点
  • vue基于springboot高校教材管理网站_47nia
  • 2026云南钢材一站式供应权威之选:五大综合服务商全链赋能,省心之选 - 深度智识库
  • 2026UHPC构件板厂家推荐:UHPC外墙挂板/UHPC装饰板/UHPC挂板定制/UHPC挂板厂家精选 - 品牌推荐官
  • 2026不锈钢方型水箱专业品牌推荐指南 - 优质品牌商家
  • Windows上使用scp安装OpenSSH服务端 客户端
  • 二分查找(在排序数组查找元素)(2)
  • mysql事务以及MVCC相关原理
  • ubuntu工具之可视化录制的bag数据——PlotJuggler(ROS1版本下载安装)
  • 2026 年国内优质靠谱化工原料直销厂家实力盘点 - 深度智识库
  • 告别熬夜苦写论文:8款AI工具10分钟出万字,降重改稿全搞定 - 麟书学长
  • 代码随想录算法训练营day15| 110.平衡二叉树 (优先掌握递归)、 257. 二叉树的所有路径 (优先掌握递归)、 404.左叶子之和 (优先掌握递归)、 222.完全二叉树的节点个数(优先掌握
  • 第4章,[标签 Win32] :加入滚动条的 SysMets
  • 2026年玻璃钢盐酸储罐厂家推荐:玻璃钢酸碱储罐/卧式玻璃钢储罐/FRP/PP储罐/现场缠绕玻璃钢储罐/大型玻璃钢储罐专业供应商精选 - 品牌推荐官
  • 2026年合肥寻猫服务费用构成与价值解析 - 2026年企业推荐榜
  • 一篇关于mysql迁移达梦后相关sql的优化记录
  • [工具] 影子去除工具,可以批量去除影子,自动裁切透明,自动更新偏移坐标
  • Vue2框架基础配置逻辑和表单双向绑定
  • 2026不锈钢组合水箱应用白皮书:不锈钢保温水箱/不锈钢冲压板/不锈钢拼装压模板/不锈钢方型水箱/不锈钢材料/选择指南 - 优质品牌商家
  • C 语言 I/O 缓冲区详解:彻底解决 printf 不输出、scanf 读错问题