如何提高 Python 数组操作性能.

2022-09-23 16:02:57 +08:00
 jeeyong
最近正在写一个小系统. 涉及到把 CT 影像转换成 PNG.
CT 影像不是不是标准通用格式, 所以需要从读取 bytes 再转成数组..
这个过程中需要两次处理数组数据, 但是性能很慢..有相关经验的朋友可以给点建议嘛?
以下是详述:

我读出来的 bytes 数据处理成 uint8 后. 是这样的形式:
[208, 4, 208, 4, 208, 4...196, 8]
不懂图像处理的知识, 我的理解就是, 一个灰度, 一个 Alpha 通道值(透明值?).
第一次处理数组是要把上面的数组, 改成如下形式: <- 暂且叫 生成数组阶段.
[208, 208, 208, 4, 208, 208, 208, 4.......]
就是把 208 这个灰度值变成 rgb 的形式.
然后再通过一个循环变成 pillow 支持的格式, 如下: <- 暂且叫 数组转换阶段吧.
[
[ [r, g, b, a], [r, g, b, a], [r, g, b, a], ],
[ [r, g, b, a], [r, g, b, a], [r, g, b, a], ],
[ [r, g, b, a], [r, g, b, a], [r, g, b, a], ],
]

然后这个数组要通过 numpy 转换成 uint8 类型才能提交给 pillow 或者类似图片处理的库..

所以大致耗时分为三个阶段:
1. 生成数组
2. 转换数组
3. 通过 numpy 转换数组类型为 uint8.

数组大小为: 5022 * 4200 * 4 = 84,369,600

环境 A 介绍:
Win10, 10900K, 64GB 3600, NVME 2TB SSD
Python 3.10.4, pillow 最新版
阶段一耗时: 9 秒左右
阶段二耗时: 45 秒左右
阶段三耗时: 5 秒左右

觉得太慢, 尝试全部改成 numpy 操作.
但是阶段二慢的出奇, 后来查资料发现, numpy 在大量数组下标赋值操作的性能还不如 Python 原生 list.
阶段二, 上厕所, 洗把脸回来还没跑完, 就直接放弃了. 也尝试过 np.insert 操作, 一样慢.

后来尝试环境 B, 基于 pypy 的.
pypy3.8, numpy, pillow, 同样的硬件.
第一阶段耗时: 0.2 秒
第二阶段耗时: 9.7 秒
第三阶段耗时: 77 秒
查阅资料得知, pypy 的 C 扩展接口性能很差, 不如原生 python.
而 Numpy 就是 C 扩展的库..所以导致 Numpy 性能急剧下降.

好了..剩下的办法超出我的知识范畴了..
有朋友能分享一下经验或者给个可能的方向我去看也行.

需要转换的数据量大约有 23 万个 CT 文件. 按照一个文件 1 分钟, 我即便 4 个进程跑(文件转换的服务器 4 核 8GB 内存, 还要跑一些其他服务.), 5 万分钟 / 60 分钟 / 24 小时 一点意外没有的情况要跑 42 天...

顺便吐槽以下, 朋友给写了个 nodejs 的版本, 转换一个图 2.3 秒, WTF!!
5105 次点击
所在节点    Python
70 条回复
jeeyong
2022-09-24 13:24:10 +08:00
@Xs0ul 元素对调有什么好方法吗?
就是我想尝试把 208 当成 Alpha 通道, 4 当成灰度, 解析一次图像
Xs0ul
2022-09-24 13:31:11 +08:00
@jeeyong #61 test_image = np.array(test_image).reshape(2,3,2).astype(np.uint8)[:, :, ::-1]
encro
2022-09-24 14:32:39 +08:00
用 opencv 直接转应该效率会高一些?
encro
2022-09-24 14:34:27 +08:00
如果我没记错,转成 PNG 最慢的是保存!!!
jeeyong
2022-09-24 14:34:34 +08:00
@Xs0ul 嗯...208 是灰度, 4 是 alpha 我提高了 alpha 值, 255 - 原 alpha 值 也还是不如我期望的那张图那么清晰.
奇怪了...
jeeyong
2022-09-24 14:35:21 +08:00
@encro 存 jpg 会报错, 就没去管它了, 现存成 png, 解决问题再优化
MaybeRichard
2022-09-24 14:47:56 +08:00
同样是做医学图像的,我们老师带我们做的项目只用 c++,CT 格式给的是 DICOM 和 MHD ,读取用的是 VtkMetaImageReader
jeeyong
2022-09-24 17:53:23 +08:00
结帖! 感谢中间提供帮助和建议的大佬们.

中间经历的几个需要反思的问题点.
1. 没用弄懂图片的格式相关知识的情况下, 盲目上手, 导致做了很多很多无效的计算.
2. 没有用好 numpy


附上源码
from numpy import maximum,uint8
from pydicom import dcmread
from PIL.Image import fromarray
from time import perf_counter
import numpy as np
import PIL
import time


start = time.time()
dcm = dcmread('dcm/aaa.dcm')
imageSources = dcm.ReferencedImageBoxSequence[0].BasicGrayscaleImageSequence[0]
imageData = imageSources.PixelData
# *** 下面这句是最重要和相对来说最耗时的
image = np.frombuffer(imageData, dtype=np.uint8).reshape(imageSources.Rows, imageSources.Columns, 2)

img = PIL.Image.fromarray(image, mode='LA')
img = img.convert('RGB')
img.save('test1.png')
print(time.time() - start)

总计用时 1.6S, numpy 果然强大.


@Xs0ul 谢谢! 另外, 我最后问的关于生成图片的问题, 是我弄错了, 我用两个完全不同部位的图片做的对比..
头部的 CT 结果轮廓很清晰, 腹部的, 看起来就不清楚了..纯粹是我粗心导致的错误.
xgdgsc
2022-09-25 10:05:50 +08:00
laqow
2022-09-25 15:15:38 +08:00
转个 png 直接用 imagej 的宏不就完了,bioformat 把什么事情都做完了,就算 python 后面处理也是先把格式转统一了比如 tiff 之类的再做

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/882441

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX