python 速度 memmap_浅析Python 读取图像文件的性能对比
浅析Python 读取图像文件的性能对比
发布时间:2020-08-30 16:31:06
来源:脚本之家
阅读:57
作者:BriFuture''s Blog
使用 Python 读取一个保存在本地硬盘上的视频文件,视频文件的编码方式是使用的原始的 RGBA 格式写入的,即无压缩的原始视频文件。最开始直接使用 Python 对读取到的文件数据进行处理,然后显示在 Matplotlib 窗口上,后来发现视频播放的速度比同样的处理逻辑的 C++ 代码慢了很多,尝试了不同的方法,最终实现了在 Python 中读取并显示视频文件,帧率能够达到 120 FPS 以上。
读取一帧图片数据并显示在窗口上
最简单的方法是直接在 Python 中读取文件,然后逐像素的分配 RGB 值到窗口中,最开始使用的是 matplotlib 的 pyplot 组件。
一些用到的常量:
FILE_NAME = "I:/video.dat"
WIDTH = 2096
HEIGHT = 150
CHANNELS = 4
PACK_SIZE = WIDTH * HEIGHT * CHANNELS
每帧图片的宽度是 2096 个像素,高度是 150 个像素,CHANNELS 指的是 RGBA 四个通道,因此 PACK_SIZE 的大小就是一副图片占用空间的字节数。
首先需要读取文件。由于视频编码没有任何压缩处理,大概 70s 的视频(每帧约占 1.2M 空间,每秒 60 帧)占用达 4Gb 的空间,所以我们不能直接将整个文件读取到内存中,借助 Python functools 提供的 partial 方法,我们可以每次从文件中读取一小部分数据,将 partial 用 iter 包装起来,变成可迭代的对象,每次读取一帧图片后,使用 next 读取下一帧的数据,接下来先用这个方法将保存在文件中的一帧数据读取显示在窗口中。
with open( file, 'rb') as f:
e1 = cv.getTickCount()
records = iter( partial( f.read, PACK_SIZE), b'' ) # 生成一个 iterator
frame = next( records ) # 读取一帧数据
img = np.zeros( ( HEIGHT, WIDTH, CHANNELS ), dtype = np.uint8)
for y in range(0, HEIGHT):
for x in range( 0, WIDTH ):
pos = (y * WIDTH + x) * CHANNELS
for i in range( 0, CHANNELS - 1 ):
img[y][x][i] = frame[ pos + i ]
img[y][x][3] = 255
plt.imshow( img )
plt.tight_layout()
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
plt.xticks([])
plt.yticks([])
e2 = cv.getTickCount()
elapsed = ( e2 - e1 ) / cv.getTickFrequency()
print("Time Used: ", elapsed )
plt.show()
需要说明的是,在保存文件时第 4 个通道保存的是透明度,因此值为 0,但在 matplotlib (包括 opencv)的窗口中显示时第 4 个通道保存的一般是不透明度。我将第 4 个通道直接赋值成 255,以便能够正常显示图片。
这样就可以在我们的窗口中显示一张图片了,不过由于图片的宽长比不协调,使用 matplotlib 绘制出来的窗口必须要缩放到很大才可以让图片显示的比较清楚。
为了方便稍后的性能比较,这里统一使用 opencv 提供的 getTickCount 方法测量用时。可以从控制台中看到显示一张图片,从读取文件到最终显示大概要用 1.21s 的时间。如果我们只测量三层嵌套循环的用时,可以发现有 0.8s 的时间都浪费在循环上了。
读取并显示一帧图片用时 1.21s
在处理循环上用时 0.8s
约百万级别的循环处理,同样的代码放在 C++ 里面性能完全没有问题,在 Python 中执行起来就不一样了。在 Python 中这样的处理速度最多就 1.2 fps。我们暂时不考虑其他方法进行优化,而是将多帧图片动态的显示在窗口上,达到播放视频的效果。
连续读取图片并显示
这时我们继续读取文件并显示在窗口上,为了能够动态的显示图片,我们可以使用 matplotlib.animation 动态显示图片,之前的程序需要进行相应的改动:
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
try:
img = np.zeros( ( HEIGHT, WIDTH, CHANNELS ), dtype = np.uint8)
f = open( FILE_NAME, 'rb' )
records = iter( partial( f.read, PACK_SIZE ), b'' )
def animateFromData(i):
e1 = cv.getTickCount()
frame = next( records ) # drop a line data
for y in range( 0, HEIGHT ):
for x in range( 0, WIDTH ):
pos = (y * WIDTH + x) * CHANNELS
for i in range( 0, CHANNELS - 1 ):
img[y][x][i] = frame[ pos + i]
img[y][x][3] = 255
ax1.clear()
ax1.imshow( img )
e2 = cv.getTickCount()
elapsed = ( e2 - e1 ) / cv.getTickFrequency()
print( "FPS: %.2f, Used time: %.3f" % (1 / elapsed, elapsed ))
a = animation.FuncAnimation( fig, animateFromData, interval=30 ) # 这里不要省略掉 a = 这个赋值操作
plt.tight_layout()
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
plt.xticks([])
plt.yticks([])
plt.show()
except StopIteration:
pass
finally:
f.close()
和第 1 部分稍有不同的是,我们显示每帧图片的代码是在 animateFromData 函数中执行的,使用 matplotlib.animation.FuncAnimation 函数循环读取每帧数据(给这个函数传递的 interval = 30 这个没有作用,因为处理速度跟不上)。另外值得注意的是不要省略掉 a = animation.FuncAnimation( fig, animateFromData, interval=30 ) 这一行的赋值操作,虽然不太清楚原理,但是当我把 a = 删掉的时候,程序莫名的无法正常工作了。
控制台中显示的处理速度:
由于对 matplotlib 的了解不多,最开始我以为是 matplotlib 显示图像过慢导致了帧率上不去,打印出代码的用时后发现不是 matplotlib 的问题。因此我也使用了 PyQt5 对图像进行显示,结果依然是 1~2 帧的处理速度。因为只是换用了 Qt 的界面进行显示,逻辑处理的代码依然沿用的 matplotlib.animation 提供的方法,所以并没有本质上的区别。这段用 Qt 显示图片的代码来自于 github matplotlib issue,我对其进行了一些适配。
使用 Numpy 的数组处理 api
我们知道,显示图片这么慢的原因就是在于 Python 处理 2096 * 150 这个两层循环占用了大量时间。接下来我们换用一种 numpy 的 reshape 方法将文件中的像素数据读取到内存中。注意 reshape 方法接收一个 ndarray 对象。我这种每帧数据创造一个 ndarray 数组的方法可能会存在内存泄漏的风险,实际上可以调用一个 ndarray 数组对象的 reshape 方法。这里不再深究。
重新定义一个用于动态显示图片的函数 optAnimateFromData,将其作为参数传递个 FuncAnimation:
def optAnimateFromData(i):
e1 = cv.getTickCount()
frame = next( records ) # one image data
img = np.reshape( np.array( list( frame ), dtype = np.uint8 ), ( HEIGHT, WIDTH, CHANNELS ) )
img[ : , : , 3] = 255
ax1.clear()
ax1.imshow( img )
e2 = cv.getTickCount()
elapsed = ( e2 - e1 ) / cv.getTickFrequency()
print( "FPS: %.2f, Used time: %.3f" % (1 / elapsed, elapsed ))
a = animation.FuncAnimation( fig, optAnimateFromData, interval=30 )
效果如下,可以看到使用 numpy 的 reshape 方法后,处理用时大幅减少,帧率可以达到 8~9 帧。然而经过优化后的处理速度仍然是比较慢的:
优化过的代码执行结果
使用 Numpy 提供的 memmap
在用 Python 进行机器学习的过程中,发现如果完全使用 Python 的话,很多运算量大的程序也是可以跑的起来的,所以我确信可以用 Python 解决我的这个问题。在我不懈努力下找到 Numpy 提供的 memmap api,这个 API 以数组的方式建立硬盘文件到内存的映射,使用这个 API 后程序就简单一些了:
cv.namedWindow("file")
count = 0
start = time.time()
try:
number = 1
while True:
e1 = cv.getTickCount()
img = np.memmap(filename=FILE_NAME, dtype=np.uint8, shape=SHAPE, mode="r+", offset=count )
count += PACK_SIZE
cv.imshow( "file", img )
e2 = cv.getTickCount()
elapsed = ( e2 - e1 ) / cv.getTickFrequency()
print("FPS: %.2f Used time: %.3f" % (number / elapsed, elapsed ))
key = cv.waitKey(20)
if key == 27: # exit on ESC
break
except StopIteration:
pass
finally:
end = time.time()
print( 'File Data read: {:.2f}Gb'.format( count / 1024 / 1024 / 1024), ' time used: {:.2f}s'.format( end - start ) )
cv.destroyAllWindows()
将 memmap 读取到的数据 img 直接显示在窗口中 cv.imshow( "file", img),每一帧打印出显示该帧所用的时间,最后显示总的时间和读取到的数据大小:
执行效率最高的结果
读取速度非常快,每帧用时只需几毫秒。这样的处理速度完全可以满足 60FPS 的需求。
总结
Python 语言写程序非常方便,但是原生的 Python 代码执行效率确实不如 C++,当然了,比 JS 还是要快一些。使用 Python 开发一些性能要求高的程序时,要么使用 Numpy 这样的库,要么自己编写一个 C 语言库供 Python 调用。在实验过程中,我还使用 Flask 读取文件后以流的形式发送的浏览器,让浏览器中的 JS 文件进行显示,不过同样存在着很严重的性能问题和内存泄漏问题。这个过程留到之后再讲。
本文中的相应代码可以在 github 上查看。
Reference
functools
partial
opencv
matplotlib animation
numpy
numpy reshape
memmap
matplotlib issue on github
C 语言扩展
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持亿速云。
python 速度 memmap_浅析Python 读取图像文件的性能对比相关推荐
- python 速度 memmap_使用python测量文件的读写速度
我目前正在使用python来读写大型图像(使用OpenCV和numpy的memmap函数).具体地说,我正在制作图像金字塔.在 在python中,可以监视文件的当前读写速度吗?一. e.类似移动平均值 ...
- python 速度 memmap_从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例
<从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例>要点: 本文介绍了从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例,希望对您有用.如果有疑问,可 ...
- node python 速度_为什么python在递归上比node.js慢得多
如果您在Python中使用一个带记忆的fibonacci函数,您将看到它变得更快:import time beg = time.clock() def memoize(f): cache = {} d ...
- perl php python 速度,Perl,Php,python等各种脚本语言现状
Perl,Php,python等各种脚本语言现状 文章作者:网友投稿 发布时间:2008-09-01 13:38:51 来源:网络 曾经程序语言世界里的二等公民,脚本语言(也叫动态语言)正成为开发者的 ...
- [云炬python学习笔记]Numpy中内置函数min(),max(),sum()与Python中内置函数min(),max(),sum()性能对比分析
众所周知,Python有许多内置函数(例如min(),max(),sum()),Numpy也有自己的内置函数(np.min(),np.max(),np.sum()).由于Numpy的函数是在编译码中执 ...
- python求二项式系数的几种方法及性能对比
最近研究了python求二项式系数的几种方法,对比了一下他们的速度 1. 利用阶乘简洁求 #普通阶乘 def fact(n):if n == 0:return 1else:return n*fact( ...
- python运行电脑要求,Python初学者请注意!别这样直接运行python命令,否则电脑等于“...
Python初学者请注意!别这样直接运行python命令,否则电脑等于" Python初学者请注意!别这样直接运行python命令,否则电脑等于"裸奔" 点击上方&quo ...
- Java文件读取方式和效率性能对比
场景描述 文件大小 8.67G的json文件,一共670万行. 服务器内存:2g.(只能读取一定缓冲数据,然后处理后继续读取,不能一次性加载到内存) 读取文件的内容出来,并未做业务处理. 读取方式和性 ...
- Python 随笔:用 PIL 库读取图像文件像素长宽大小
Python 随笔:用 PIL 库读取图像文件像素宽高大小 1.前言 安装 PIL 库(全称是pillow),所以安装命令上的名称是pillow pip install pillow 2. 使用pil ...
最新文章
- 输入引脚时钟约束_Happy Design in Vivado 系列:时序分析入门三板斧(一):创建时钟...
- web第五章 json
- 深入学习jQuery选择器系列第四篇——过滤选择器之属性选择器
- CF643F-Bears and Juice【组合数学】
- 在独立Java应用程序中使用Tomcat JDBC连接池
- TypeScript 联合类型(union type)
- React开发(278):ant design message res保证正确信息提示
- Python+Selenium练习篇之2-利用ID定位元素
- TCP 三次握手的意义
- 动态填充html select tag的options
- Android Studio如何隐藏系统的状态栏
- flex 平铺布局_Flex布局的个人见解~阮一峰的网络日志
- 为什么游戏盒子源码那么重要?(不搞清楚游戏盒子源码,游戏代理很难顺利)(远离那些免费游戏盒子源码,会变得不幸)
- postman下载安装汉化及使用
- oracle存储过程if的使用,oracle存储过程if语句
- 玉渊潭赏樱花有感:从无到有写一个jQuery开源插件
- 数据库MSSQLSERVER_18456错误解决
- ArcGIS教程:创建面积图
- 【C++复习总结回顾】—— 【五】数组与指针
- python opencv图像阈值处理
热门文章
- mysql1215_MySQL全面瓦解15:视图
- vue实现竖式步骤条_手把手教你实现一个 Vue 进度条组件!
- @retention注解作用_分分钟带你玩转SpringBoot自定义注解
- 聊聊 cookie 管理那些事
- Linux安装无法运行install,linux 无法 安装swoole
- python中for循环的用法_浅谈Python的for循环
- python国产_Python勒索软件来袭,国产杀软集体失身
- NETCORE openSUSE docker 安装
- 什么叫Web前端?web前端HTML5学习方法分享
- [Android] QPST,解BL锁,刷Recovery,备份系统,root,刷框架.