python,读取图像文件并获取到像素数组的效率测试

        场景需求:读取一个bmp图像文件,并把像素数据转换为RGB*uint8的numpy数组,这是一个很常用的操作。本次测试的目的是使用不同的读取方式,找到其中效率最高的。

素材:demo.bmp,4624*3742像素,RGB格式。


方法1:使用opencv直接读取,就可以获取到像素的数组

import cv2 from PyQt5.QtCore import QElapsedTimer # 创建定时器,统计用时 timer = QElapsedTimer() timer.start() img = cv2.imread("demo.bmp") print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img.shape)

计时结果:

读取bmp文件并转换为数组的时间: 54 ms (3472, 4624, 3) 

方法2:使用pillow读取,再转换为像素数组

import cv2 import numpy as np from PIL import Image from PyQt5.QtCore import QElapsedTimer timer = QElapsedTimer() timer.start() img = Image.open('demo.bmp') print(f"读取bmp文件的时间: {timer.elapsed()} ms") img_array = np.array(img) print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)

计时结果:

读取bmp文件并转换为数组的时间: 14 ms 读取bmp文件并转换为数组的时间: 117 ms (3472, 4624, 3)

方法3:使用qt

  • 读取为QImage对象,再转换为像素数组
import numpy as np from PyQt5.QtCore import QElapsedTimer from PyQt5.QtGui import QImage timer = QElapsedTimer() timer.start() # 转换为BGR888格式 qimage = QImage('demo.bmp').convertToFormat(QImage.Format_BGR888) w, h = qimage.width(), qimage.height() # 4624 3472 print("读取bmp文件的耗时", timer.elapsed(), " ms") bits = qimage.bits() bits.setsize(qimage.byteCount()) img_array = np.frombuffer(bits, dtype=np.uint8).reshape(h, w, 3) print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)

计时结果:

读取bmp文件并转换为数组的时间: 110 ms (3472, 4624, 3) 
  • 不创建QImage对象,只读取像素数据并转换为数组
import numpy as np from PyQt5.QtCore import QElapsedTimer from PyQt5.QtGui import QImage, QImageReader timer = QElapsedTimer() timer.start() reader = QImageReader('demo.bmp') reader.setAutoTransform(False) img_bytes = reader.read().convertToFormat(QImage.Format_RGB888) h =img_bytes.height() w = img_bytes.width() bits = img_bytes.bits() bits.setsize(img_bytes.byteCount()) img_array = np.frombuffer(bits, dtype=np.uint8).reshape(h, w, 3) print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)

计时结果:

读取bmp文件并转换为数组的时间: 109 ms (3472, 4624, 3) 

方法4:使用python的原生文件打开方法

  • 一次性全部读出数据后截取数据切片:
import numpy as np from PyQt5.QtCore import QElapsedTimer timer = QElapsedTimer() timer.start() with open("demo.bmp", "rb") as f: read_bytes = f.read() # 解析像素区的偏移量:第10-13字节,小端序,4字节整数 offset = int.from_bytes(read_bytes[10:14], byteorder='little') # 解析宽度:第18-21字节,小端序,4字节整数 w = int.from_bytes(read_bytes[18:22], byteorder='little') # 解析高度:第22-25字节,小端序,4字节整数 h = int.from_bytes(read_bytes[22:26], byteorder='little') # 截取bmp图片数据 image_bytes = read_bytes[offset:] img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将字节数据转换为numpy数组 img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻转(bmp图像像素是从左下角开始存储的),使用np.ascontiguousarray提高效率 print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)

计时结果:

读取bmp文件并转换为数组的时间: 53 ms (3472, 4624, 3)
  • 分两次读取,先读取头文件再读取数据:
import numpy as np from PyQt5.QtCore import QElapsedTimer from os.path import getsize timer = QElapsedTimer() timer.start() file_size = getsize("demo.bmp") # 获取文件大小 with open("demo.bmp", "rb") as f: # 读取BMP文件头 header = f.read(26) # 解析像素区的偏移量:第10-13字节,小端序,4字节整数 offset = int.from_bytes(header[10:14], byteorder='little') # 解析宽度:第18-21字节,小端序,4字节整数 w = int.from_bytes(header[18:22], byteorder='little') # 解析高度:第22-25字节,小端序,4字节整数 h = int.from_bytes(header[22:26], byteorder='little') image_size = file_size - offset # 继续读取bmp图片数据 f.seek(offset) image_bytes = f.read() # print(f"读取bmp文件的时间: {timer.elapsed()} ms") img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将字节转换为numpy数组 img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻转(bmp图像像素是从左下角开始存储的) print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)

用时接近:

读取bmp文件并转换为数组的时间: 51 ms (3472, 4624, 3)

上面这两个方法的用时与opencv非常接近。

  • 分两次读取,先读取文件头,在读取数据时指定数据长度(上面方法的改进):
import numpy as np from PyQt5.QtCore import QElapsedTimer timer = QElapsedTimer() timer.start() with open("demo.bmp", "rb") as f: # 读取BMP文件头 header = f.read(26) # 解析像素区的偏移量:第10-13字节,小端序,4字节整数 offset = int.from_bytes(header[10:14], byteorder='little') # 解析宽度:第18-21字节,小端序,4字节整数 w = int.from_bytes(header[18:22], byteorder='little') # 解析高度:第22-25字节,小端序,4字节整数 h = int.from_bytes(header[22:26], byteorder='little') image_size = w * h * 3 # 继续读取bmp图片数据 f.seek(offset) image_bytes = f.read(image_size) img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将字节转换为numpy数组 img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻转(bmp图像像素是从左下角开始存储的) print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)

计时结果:

读取bmp文件并转换为数组的时间: 36 ms (3472, 4624, 3)

        与之前的代码相比,唯一改进的地方:image_bytes = f.read(image_size),在读取像素数据的时候指定了数据长度,用时就有了明显的减少。


阶段总结:

读取方法用时(ms)备注
使用opencv读取53与python的原生打开方法接近
使用pillow读取117最慢
使用qt读取110次慢
python的原生打开方法打开图像的像素字节53与opencv接近
python的原生打开方法打开图像的像素字节,指定读取长度36最快

        所以,使用python的原生打开方法打开图像的像素字节,并指定长度来读取,是最快的方法。但是这个方法的局限性在于,只对bmp文件最适用,因为bmp文件是逐字节存放像素的原始数据。


两个实用代码demo

  • 一个兼容性强的也比较快的代码demo:

上面的代码都是针对bmp格式,下面的代码可以打开多种图像文件。

import cv2 import numpy as np from PyQt5.QtCore import QElapsedTimer # 创建定时器,统计用时 timer = QElapsedTimer() timer.start() with open("demo.bmp", "rb") as f: image_bytes = f.read() # 获取二进制数据 # 将二进制数据转为numpy数组,再用imdecode解码 img_array = np.frombuffer(image_bytes, dtype=np.uint8) img_array = cv2.imdecode(img_array, cv2.IMREAD_COLOR) # 解码参数同imread,也可以进行别的颜色设置 print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms") print(img_array.shape)
读取bmp文件并转换为数组的时间: 50 ms (3472, 4624, 3)

        该段代码使用python原生的文件打开方法获取二进制数据,并使用opencv进行解码后获得像素阵列。读取速度与使用opencv直接读取接近,并且兼容所有opencv支持的图像格式,优势是可以同时获取到二进制数据的字节流,这个字节流可用来网络传输或保存为本地二进制文件。

  • 一套自用的基于二进制数据字节流的极快的读写方法:

        首先,保存文件为自定义的格式,文件分为两部分:文件头(head)和文件二进制字节(image_bytes)两部分。

文件头共14个字节,文件头数据的结构:
        [0:2]:图像高度
        [2:4]:图像宽度
        [4:6]:图像色彩通道数
        [6:10]:图像像素的字节数
        [10:14]:像素格式(“BGR8"、"BAY8")

文件头后面所有数据是图像的像素字节,无论原图像是哪种格式都转为BGR 3*unit8格式。

将图像文件保存为我的格式的代码:

import cv2 img_array = cv2.imread("demo.bmp") # 读取一个图像文件,并默认转换为BGR*unit8的numpy数组 h, w, _ = img_array.shape # 高宽 c = 3 # 通道数 l = h * w * c # 像素字节数 t = "BGR8" # 图像类型(BGR) # 把hwclt转换为字节(高宽等图像参数) h_bytes = h.to_bytes(2, byteorder='little') w_bytes = w.to_bytes(2, byteorder='little') c_bytes = c.to_bytes(2, byteorder='little') l_bytes = l.to_bytes(4, byteorder='little', signed=False) t_bytes = t.encode('utf-8') print(t_bytes) # 获取图像的BGR字节流 bgr_bytes = img_array.tobytes() # image_bytes是图像的字节流 image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bgr_bytes]) # 拼接字节流,hwclt在前,像素在后 with open("demo2.raw", "wb") as f: f.write(image_bytes)

读取我的格式的文件并转换为数列:

import cv2 import numpy as np from PyQt5.QtCore import QElapsedTimer timer = QElapsedTimer() timer.start() with open("demo2.raw", "rb") as f: head = f.read(14) h = int.from_bytes(head[:2], byteorder='little') w = int.from_bytes(head[2:4], byteorder='little') c = int.from_bytes(head[4:6], byteorder='little') l = int.from_bytes(head[6:10], byteorder='little') t = head[10:14].decode('utf-8') print(h, w, c, l, t) image_bytes = f.read(l) if t == "BGR8": img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, c)) # elif t == "BAY3": # img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w)) # img_array = cv2.cvtColor(img_array, cv2.COLOR_BAYER_RG2RGB) print("图像格式:", t) print(f"读取raw文件并转换为数组的时间: {timer.elapsed()} ms") cv2.imshow("img", img_array) cv2.waitKey(0)

速度展示:

读取raw文件并转换为数组的时间: 19 ms

上面代码中的image_bytes像素字节流,除了可以从本地读取,还可以是来自网络或者相机。

        上面的方法,支持常见的图像格式,并将其转为BGR排列的unit8格式字节文件。唯一缺点就是保存为本地文件时,文件比较大(与同像素格式的bmp格式大小相同),为了减小文件体积,可将RGB格式的彩色图像文件保存为bayer格式,bayer格式的文件体积只有bmp文件的1/3。

  • 将图像文件保存为bayer-RG8格式二进制文件的代码:

使用pillow:

import numpy as np from PIL import Image img = Image.open("red.bmp") # 读取一个彩色RGB图像 w, h = img.size # 宽高 c = 3 # 通道数 l = w * h # 像素字节数 t = "BAY8" # 图像类型(Bayer) # 把hwclt转换为字节(高宽等图像参数) h_bytes = h.to_bytes(2, byteorder='little') w_bytes = w.to_bytes(2, byteorder='little') c_bytes = c.to_bytes(2, byteorder='little') l_bytes = l.to_bytes(4, byteorder='little', signed=False) t_bytes = t.encode('utf-8') # 获取图像的RGB数组 rgb_array = np.array(img.convert('RGB')) ###########创建Bayer-rg格式数组 (RGGB排列)################# bayer_array = np.zeros((h, w), dtype=np.uint8) # 按照Bayer-rg (RGGB) 模式填充数据 # 偶数行偶数列 (0,0), (0,2)... - R通道 bayer_array[::2, ::2] = rgb_array[::2, ::2, 0] # 偶数行奇数列 (0,1), (0,3)... - G通道 bayer_array[::2, 1::2] = rgb_array[::2, 1::2, 1] # 奇数行偶数列 (1,0), (1,2)... - G通道 bayer_array[1::2, ::2] = rgb_array[1::2, ::2, 1] # 奇数行奇数列 (1,1), (1,3)... - B通道 bayer_array[1::2, 1::2] = rgb_array[1::2, 1::2, 2] ########################################################## # 将Bayer数组转换为字节流并保存 bayer_bytes = bayer_array.tobytes() image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bayer_bytes]) # 拼接字节流,hwclt在前,像素在后 with open("demo3.raw", "wb") as f: f.write(image_bytes)

使用opencv完成同样功能:

import cv2 import numpy as np from PyQt5.QtCore import QElapsedTimer # from PIL import Image timer = QElapsedTimer() timer.start() rgb_array = cv2.imread("red16.png", cv2.IMREAD_COLOR_RGB) # 读取一个图像文件并转为 RGB * 8uint 数列 h, w, _ = rgb_array.shape # 高宽 c = 3 # 通道数 l = w * h # bayer格式像素字节数 t = "BAY8" # 图像类型(Bayer-RG) # 把hwclt转换为字节(高宽等图像参数) h_bytes = h.to_bytes(2, byteorder='little') w_bytes = w.to_bytes(2, byteorder='little') c_bytes = c.to_bytes(2, byteorder='little') l_bytes = l.to_bytes(4, byteorder='little', signed=False) t_bytes = t.encode('utf-8') ###########创建Bayer-rg格式数组 (RGGB排列)################# bayer_array = np.zeros((h, w), dtype=np.uint8) # 按照Bayer-rg (RGGB) 模式填充数据 # 偶数行偶数列 (0,0), (0,2)... - R通道 bayer_array[::2, ::2] = rgb_array[::2, ::2, 0] # 偶数行奇数列 (0,1), (0,3)... - G通道 bayer_array[::2, 1::2] = rgb_array[::2, 1::2, 1] # 奇数行偶数列 (1,0), (1,2)... - G通道 bayer_array[1::2, ::2] = rgb_array[1::2, ::2, 1] # 奇数行奇数列 (1,1), (1,3)... - B通道 bayer_array[1::2, 1::2] = rgb_array[1::2, 1::2, 2] ########################################################## # 将Bayer数组转换为字节流并保存 bayer_bytes = bayer_array.tobytes() image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bayer_bytes]) # 拼接字节流,hwclt在前,像素在后 with open("demo3.raw", "wb") as f: f.write(image_bytes) print(f"写入raw文件的时间: {timer.elapsed()} ms")
  • 读取BGR或bayer格式二进制文件的代码:

分两次读:

import cv2 import numpy as np from PyQt5.QtCore import QElapsedTimer timer = QElapsedTimer() timer.start() with open("demo3.raw", "rb") as f: head = f.read(14) h = int.from_bytes(head[:2], byteorder='little') w = int.from_bytes(head[2:4], byteorder='little') c = int.from_bytes(head[4:6], byteorder='little') l = int.from_bytes(head[6:10], byteorder='little') t = head[10:14].decode('utf-8') image_bytes = f.read(l) if t == "BGR8": img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, c)) elif t == "BAY8": img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w)) img_array = cv2.cvtColor(img_array, cv2.COLOR_BAYER_RG2RGB) print("图像格式:", t) print(f"读取raw文件并转换为数组的时间: {timer.elapsed()} ms") cv2.imshow("img", img_array) # cv2.imwrite("demo3__.bmp", img_array) cv2.waitKey(0) cv2.destroyAllWindows()

或一次读完:

import cv2 import numpy as np from PyQt5.QtCore import QElapsedTimer timer = QElapsedTimer() timer.start() with open("demo2.raw", "rb") as f: image_bytes = np.frombuffer(f.read(), dtype=np.uint8) head = image_bytes[:14].tobytes() h = int.from_bytes(head[:2], byteorder='little') w = int.from_bytes(head[2:4], byteorder='little') c = int.from_bytes(head[4:6], byteorder='little') l = int.from_bytes(head[6:10], byteorder='little') t = head[10:14].decode('utf-8') if t == "BGR8": img_array = np.frombuffer(image_bytes[14:], dtype=np.uint8).reshape((h, w, c)) elif t == "BAY8": img_array = np.frombuffer(image_bytes[14:], dtype=np.uint8).reshape((h, w)) img_array = cv2.cvtColor(img_array, cv2.COLOR_BAYER_RG2RGB) print("图像格式:", t) print(f"读取raw文件并转换为数组的时间: {timer.elapsed()} ms") cv2.imshow("img", img_array) # cv2.imwrite("demo3__.bmp", img_array) cv2.waitKey(0) cv2.destroyAllWindows()

读取速度展示:

图像格式: BAY (3472, 4624, 3) 读取raw文件并转换为数组的时间: 15 ms

总结,最佳实践:

1、保存时,将文件保存为Bayer-RG8格式的二进制文件,文件由文件头和像素数据两部分组成;

2、打开时,使用python原生的open(“image”, "rb")方法打开文件,先打开文件头,指定像素数据长度后再继续打开像素字节流;

3、结合以上两点,可获得兼顾到文件体积、存取速度的方法;

4、此处的bayer-RG8字节流,与相机常用的bayer-RG8字节流兼容,可直接将相机的帧字节数据加上文件头以后直接存为raw文件,读写效率很高。其他常见的RG10、RG12以及packed格式日后完善。

Read more

AI 前端到底是什么?为什么说AI前端是未来趋势?

AI 前端到底是什么?为什么说AI前端是未来趋势?

⭐ 一、AI 前端和普通前端有什么区别? 下面是一张非常直观的对比: 内容普通前端AI 前端功能核心UI 展示 + 用户交互UI 展示 + 用户交互 + 智能内容生成与后端交互调用普通 REST API调用 大模型 API / AI 服务输出形式页面固定页面可动态生成 / 布局可变化原型制作Figma → 人工写页面Figma → AI 自动生成代码前端逻辑手写逻辑部分逻辑由 AI 执行(智能体 UI)用户体验按钮 + 表单对话式 UI / 多模态交互技术要求JS / Vue / ReactJS + AI SDK + Prompt + 多模态理解能力 一句话: 👉 普通前端 = 静态 UI 👉 AI 前端 = 会思考的 UI ⭐ 二、AI 前端需要学习哪些技术? AI 前端不是新语言,而是 前端

By Ne0inhk
零代码接入:DMXAPI+Next-Web搭建私人AI助手

零代码接入:DMXAPI+Next-Web搭建私人AI助手

欢迎来到小灰灰的博客空间!Weclome you! 博客主页:IT·小灰灰 爱发电:小灰灰的爱发电 热爱领域:前端(HTML)、后端(PHP)、人工智能、云服务 目录 核心方案:为什么是 DMXAPI + Next-Web? 1. 后端引擎:DMXAPI——一个Key,连接全世界 2. 前端应用:Next-Web——最美的“外壳” 实战搭建:三步拥有私人AI助手 第一步:注册DMXAPI,获取“万能钥匙” 第二步:一键部署Next-Web(Vercel无服务器部署) 第三步:绑定自定义域名与配置模型 进阶玩法:让助手更“私人” 结语 在2026年的今天,大模型已经不再是极客手中的技术玩具,而是逐渐演变为像电力一样的基础设施。然而,对于大多数普通用户乃至创业者来说,

By Ne0inhk

Jetbrains系列工具 Idea Websotrm中使用Claude Code 可白嫖

市面上很多AI工具都是基于vsCode 习惯Idea的用户使用起来会特别别扭 本文将展示idea中如何使用ClaudeCode 1. 注册api (二选一) 1. 智普AI国内转发 GLM-4.6 地址: 直达链接 价格: 首年200+ 2. 硅基流动 地址: 直达链接 白嫖模型: Qwen/Qwen3-8B 配置: {"env":{"ANTHROPIC_AUTH_TOKEN":"sk-xxx","ANTHROPIC_BASE_URL":"https://api.siliconflow.cn","ANTHROPIC_DEFAULT_HAIKU_MODEL"

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 jaspr 为鸿蒙端开启极速渲染的现代 Web 开发新范式(Dart Web 框架首选)

Flutter for OpenHarmony: Flutter 三方库 jaspr 为鸿蒙端开启极速渲染的现代 Web 开发新范式(Dart Web 框架首选)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 开发时,我们偶尔需要跳出原生的 HAP 容器,寻找更轻量、更适合在移动端 Web 加载的方案。虽然 Flutter Web 极其强大,但其生成的 Canvas/Wasm 产物体积巨大,在鸿蒙系统加载较慢。是否存在一种方案,既能使用 Dart 的声明式开发体验,又能产出纯正、轻量的 HTML/CSS/JS 节点? jaspr 就是这个问题的终极答案。它是一个模仿 Flutter 语法、但专注于渲染原生 Web DOM 的现代框架。通过 Jaspr,鸿蒙开发者可以利用熟悉的 Widget、Component 和生命周期,

By Ne0inhk