Surfarray模块介绍(Surfarray Introduction)
Author: Pete Shinners
Contact: pete@shinners.org
介绍(Introduction)
本教程将尝试向用户介绍NumPy和pygame surfarray模块。对于初学者来说,使用surfarray的代码可能非常令人生畏。但实际上只有少数几个概念需要理解,然后你就跟上了并可以继续前进。使用surfarray模块,可以从直接的python代码执行像素级别的操作。性能可以非常接近C中代码的执行水平。
您可能只想跳到“示例”部分,了解该模块的可能性,然后从头开始,逐步开始工作。
现在我不会试图欺骗你认为一切都很容易。通过修改像素值来获得更高级的效果是非常棘手的。仅仅掌握Numeric Python(SciPy的原始数组包是Numeric,NumPy的前身)就需要大量的学习。在本教程中,我将坚持使用基础知识并使用大量示例来尝试植入智慧的种子。完成教程后,您应该基本掌握有关surfarray如何工作。
NumPy(Numeric Python)
如果您没有安装python NumPy包,你需要立即执行此操作。 您可以从NumPy下载页面下载该包。为了确保NumPy在你的机子上起作用,您应该从交互式python提示符中获得类似的内容。
>>> from numpy import * #导入numeric
>>> a = array((1,2,3,4,5)) #创建一个数组
>>> a #展示数组
array([1, 2, 3, 4, 5])
>>> a[2] #数组索引
3
>>> a*2 #带有两倍值得新数组
array([ 2, 4, 6, 8, 10])
如您所见,NumPy模块为我们提供了一种新的数据类型,即数组。 该对象包含一个固定大小的数组,其中的所有值都是相同的类型。 数组也可以是多维的,这就是我们将它们与图像一起使用的方式。 当然,还有比这更多的内容,但它足以让我们开始。
如果查看上面的最后一个命令,您将看到NumPy数组上的数学运算适用于数组中的所有值。 这称为“按元素操作【element-wise operations】”。 这些数组也可以像普通列表一样切片。 切片语法与标准python对象上使用的语法相同。 (如果你需要,请仔细研究:])。 以下是使用数组的更多示例。
>>> len(a) #得到数组大小
5
>>> a[2:] #从第三个元素到末尾
array([3, 4, 5])
>>> a[:-2] #除了最后两个,其它都囊括
array([1, 2, 3])
>>> a[2:] + a[:-2] #加起来
array([4, 6, 8])
>>> array((1,2,3)) + array((3,4)) #加上了错误大小的数组
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (3,) (2,)
我们在最后一行时遇到错误,因为我们尝试将两个不同大小的数组相加。 为了使两个数组彼此相互操作,包括比较和赋值,它们必须具有相同的尺寸/大小。 非常重要的是要知道新的数组被创建,来自原始数组被切片时所有引用的相同值。 因此,更改切片中的值也会更改原始值。 知道这怎么发生非常重要。
>>> a #展示我们开始的数组
array([1, 2, 3, 4, 5])
>>> aa = a[1:3] #切片中间两个元素
>>> aa #展示切片
array([2, 3])
>>> aa[1] = 13 #在切片中改变值
>>> a #展示原始的数组
array([ 1, 2, 13, 4, 5])
>>> aaa = array(a) #复制一份数组
>>> aaa #展示
array([ 1, 2, 13, 4, 5])
>>> aaa[1:4] = 0 #把中间值设为0
>>> aaa #展示数组
array([1, 0, 0, 0, 5])
>>> a #展示原始数组
array([ 1, 2, 13, 4, 5])
现在我们来看看有两个维度的小数组。 不要太担心,开始时它就像有一个二维元组(元组里面的元组)一样。 让我们开始使用二维数组。
>>> row1 = (1,2,3) #创建元组
>>> row2 = (3,4,5) #另一个元组
>>> (row1,row2) #作为二维元组展示
((1, 2, 3), (3, 4, 5))
>>> b = array((row1, row2)) #创建二维数组
>>> b #展示数组
array([[1, 2, 3],
[3, 4, 5]])
>>> array(((1,2),(3,4),(5,6))) #展示新的二维数组
array([[1, 2],
[3, 4],
[5, 6]])
现在使用这个二维数组(从现在开始作为“2D”),我们可以索引特定值并在两个维度上进行切片。 简单地使用逗号分隔索引允许我们在多个维度中查找/切片。 只要使用“:”作为索引(或不提供足够的索引)就可以得到该维度中的所有值。 让我们看看它是如何工作的。
>>> b #展示我们之前的数组
array([[1, 2, 3],
[3, 4, 5]])
>>> b[0,1] #索引单个值
2
>>> b[1,:] #第二行的切片
array([3, 4, 5])
>>> b[1] #和上面一样,第二行的切片
array([3, 4, 5])
>>> b[:,2] #最后一列的切片
array([3, 5])
>>> b[:,:2] #切成2X2的数组
array([[1, 2],
[3, 4]])
好的,在这里请跟我呆在一起进行下去。这几乎是很难做到的。 当使用NumPy时,还有一个切片功能。 切片数组还允许您指定切片增量。 具有增量的切片的语法是start_index: end_index: increment(起始索引:终止索引:增量)。
>>> c = arange(10) #跟range一样,不过产生一个数组
>>> c #展示数组
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> c[1:6:2] #从1到6的奇数切片
array([1, 3, 5])
>>> c[4::4] #从第4个元素起,每次隔4个元素
array([4, 8])
>>> c[8:1:-1] #从1-8,反向切片
array([8, 7, 6, 5, 4, 3, 2])
好吧就是这样。 那里有足够的信息可以让你开始使用NumPy和surfarray模块。 NumPy当然还有很多知识,但这只是一个介绍。 此外,我们想要了解有趣的东西,对吗?
导入 Surfarray(Import Surfarray)
为了使用surfarray模块,我们需要导入它。 由于surfarray和NumPy都是pygame的可选组件,因此在使用它们之前确保它们正确导入是很好的。 在这些例子中,我将把NumPy导入一个名为N的变量。这将让你知道我正在使用哪些函数来自NumPy包。 (并且比在每个函数之前键入NumPy要短很多)
try:
import numpy as N
import pygame.surfarray as surfarray
except ImportError:
raise ImportError, "NumPy and Surfarray are required."
surfarray 介绍(Surfarray Introduction)
在surfarray中有两种主要类型的功能。一组用于创建数组的函数,该数组是surface像素数据的副本。其他函数创建数组像素数据的引用副本,以便对数组的更改直接影响原始surface。还有其他函数允许您作为数组访问任何每像素alpha值以及一些其他有用的函数。我们稍后会看看这些其他功能。
使用这些surface arrays时,有两种表示像素值的方法。第一种,它们可以表示为映射整数。这种类型的数组是一个简单的2D数组,其中一个整数表示surface的映射颜色值。这种类型的数组适用于移动图像的部分。另一种类型的数组使用三个RGB值来表示每个像素颜色。这种类型的数组使得改变每个像素颜色的效果类型非常简单。这种类型的数组处理起来也有点棘手,因为它本质上是一个3D数字数组。尽管如此,一旦你进入正确的模式,它并不比使用普通的2D数组困难得多。
NumPy模块使用机器的自然数字类型来表示数据值,因此NumPy数组可以包含8位,16位和32位的整数。 (数组也可以使用其他类型,如浮点数和双精度数,但对于我们的图像处理,我们主要需要担心整数类型)。由于整数大小的这种限制,您必须特别注意引用像素数据的数组类型可以正确映射到适当类型的数据。从surfaces创建这些数组的函数是:
1.surfarray.pixels2d(surface)
创建引用原始surface数据的2D数组(整数像素值)。 这适用于除24位之外的所有surface格式。
2.surfarray.array2d(surface)
创建从任何类型的surface复制的2D数组(整数像素值)。
3.surfarray.pixels3d(surface)
创建引用原始surface数据的3D数组(RGB像素值)。 这仅适用于具有RGB或BGR格式的24位和32位surface。
4.surfarray.array3d(surface)
创建从任何类型的surface复制的3D数组(RGB像素值)。
这是一个小图表,可以更好地说明应在哪些surface上使用哪些类型的函数。 如您所见,arrayXD函数都适用于任何类型的surface。
类型 | 32-bit | 24-bit | 16-bit | 8-bit(c-map) |
---|---|---|---|---|
pixel2d | yes | yes | yes | |
array2d | yes | yes | yes | yes |
pixel3d | yes | yes | ||
array3d | yes | yes | yes | yes |
示例(Examples)
有了这些信息,我们就可以开始尝试使用surfacec数组。 以下是创建NumPy数组并在pygame中显示它们的简短演示。 这些不同的测试可以在arraydemo.py示例中找到。 有一个名为surfdemo_show的简单函数,它在屏幕上显示一个数组。
(1)
我们的第一个例子创建了一个全黑数组。 每当您需要创建特定大小的新数字数组时,最好使用零功能。 在这里,我们创建一个全零的2D数组并显示它。
allblack = N.zeros((128, 128))
surfdemo_show(allblack, 'allblack')
(2)
这里我们正在处理3D数组。 我们首先创建一个全红色图像。 然后我们切出每隔三行并将其指定为蓝/绿色。 如您所见,我们可以将3D数组与2D数组完全相同,只需确保为它们分配3个值而不是单个映射的整数。
striped = N.zeros((128, 128, 3))
striped[:] = (255, 0, 0)
striped[:,::3] = (0, 255, 255)
surfdemo_show(striped, 'striped')
(3)
这里,我们使用image模块加载图像,然后将其转换为整数RGB颜色元素的3D数组。 表面的RGB副本总是将颜色排列为红色分量的a[r,c,0],绿色分量的a[r,c,1]和蓝色分量的a[r,c,2]。 然后可以在不关心如何配置实际surface的像素的情况下使用它,这与作为映射(原始)surface像素的副本的2D数组不同。 我们将在其余样本中使用此图像。
imgsurface = pygame.image.load('surfarray.png')
rgbarray = surfarray.array3d(imgsurface)
surfdemo_show(rgbarray, 'rgbarray')
(4)
这里,我们垂直翻转图像。 我们需要做的就是获取原始图像数组并使用负增量对其进行切片。
flipped = rgbarray[:,::-1]
surfdemo_show(flipped, 'flipped')
(5)
基于最后一个示例,缩小图像是非常合乎逻辑的。 我们只是在垂直和水平方向上使用2的增量切出所有像素。
scaledown = rgbarray[::2,::2]
surfdemo_show(scaledown, 'scaledown')
(6)
放大图像需要更多工作,但与之前的缩小类似,我们使用切片完成所有操作。 首先,我们创建一个比原始数量大两倍的数组。 首先,我们将原始数组复制到新数组的每个其他像素中。 然后我们再次为奇数列的每个其他像素执行此操作。 此时我们正确地缩放了图像,但是每隔一行都是黑色的,所以我们只需要将每一行复制到它下面的那一行。 然后我们有一个大小翻倍的图像。
shape = rgbarray.shape
scaleup = N.zeros((shape[0]*2, shape[1]*2, shape[2]))
scaleup[::2,::2,:] = rgbarray
scaleup[1::2,::2,:] = rgbarray
scaleup[:,1::2] = scaleup[:,::2]
surfdemo_show(scaleup, 'scaleup')
(7)
现在我们使用3D数组来改变颜色。 在这里,我们将绿色和蓝色的所有值设置为零。 这让我们只有红色通道。
redimg = N.array(rgbarray)
redimg[:,:,1:] = 0
surfdemo_show(redimg, 'redimg')
(8)
这里,我们执行一个3x3卷积滤镜,可以柔化我们的图像。 这看起来像很多步骤,但我们正在做的是将图像在每个方向上移动1个像素并将它们全部加在一起(加权一些乘法)。 然后平均所有值。 这不是高斯,但速度很快。 使用NumPy数组要注意的一点是,算术运算的精度由具有最大数据类型的数组决定。 因此,如果factor未被声明为numpy.int32类型的1元素数组,则将使用numpy.int8执行乘法,numpy.int8是每个rgbarray元素的8位整数类型。 这将导致值被截断。 这个被柔化的数组也必须声明为具有比rgbarray更大的整数大小以避免截断。
factor = N.array((8,), N.int32)
soften = N.array(rgbarray, N.int32)
soften[1:,:] += rgbarray[:-1,:] * factor
soften[:-1,:] += rgbarray[1:,:] * factor
soften[:,1:] += rgbarray[:,:-1] * factor
soften[:,:-1] += rgbarray[:,1:] * factor
soften //= 33
surfdemo_show(soften, 'soften')
(9)
![](https://i-blog.csdnimg.cn/blog_migrate/0766e3c01c44bd9a189b971d810c0bb1.png)
最后,我们在原始图像和纯蓝色图像之间交叉淡入淡出。 不会令人兴奋,但目标图像可以是任何东西,并且更改0.50乘数将允许您选择两个图像之间的线性交叉淡化中的任何步骤。
src = N.array(rgbarray)
dest = N.zeros(rgbarray.shape)
dest[:] = 20, 50, 100
diff = (dest - src) * 0.50
xfade = src + diff.astype(N.uint)
surfdemo_show(xfade, 'xfade')
希望在这一点上你开始看到如何使用surfarray来执行只能在像素级别进行的特殊效果和变换。 至少,您可以使用surfarray非常快速地执行很多Surface.set_at()Surface.get_at()类型的操作。 但是不要以为你还没完成,还有很多东西需要学习。
Surface锁定(Surface Locking)
与pygame的其余部分一样,surfarray会在访问像素数据时自动锁定所需的任何Surface。 但是还有一件事需要注意。 创建像素数组时,原始surface将在该像素数组的生命周期内被锁定。 这一点很重要。 一定要“del”像素数组或让它超出范围(即函数返回时等)。
另请注意,您确实不希望在硬件表面(HWSURFACE)上进行太多(如果有)直接像素访问。 这是因为实际的surface数据存在于图形卡上,并且通过PCI / AGP总线传输像素变化并不快。
透明度(Transparency)
surfarray模块有几种方法可以访问Surface的alpha / colorkey值。 没有Alpha函数受Surface的整体透明度影响,只有像素alpha值。 这是这些函数的列表。
1,surfarray.pixels_alpha(surface)
创建引用原始surface的alpha数据的2D数组(整数像素值)。 这仅适用于具有8位alpha分量的32位图像。
2.surfarray.array_alpha(surface)
创建从任何类型的surface复制的2D数组(整数像素值)。 如果曲面没有alpha值,则数组将是完全不透明的值(255)。
3.surfarray.array_colorkey(surface)
创建一个2D数组(整数像素值),只要该像素颜色与Surface颜色键匹配,就设置为透明(0)。
其它的Surfarray函数(Other Surfarray Functions)
在surfarray中只有少数其他函数可用。 您可以在surfarray参考页面上获得更多文档,而且获得更好地一个列表。 但是有一个非常有用的函数。
1.surfarray.blit_array(surface, array)
这会将任何类型的2D或3D surface数组转移到相同尺寸的surface上。 这种surfarray blit通常比将数组分配给引用的像素数组更快。 尽管如此,它应该不如普通的surface blitting快,因为它们非常优化。
更多高级的Numpy(More Advanced Numpy)
关于NumPy数组,你应该知道几件事。在处理非常大的数组时,比如640x480大的数组,还有一些额外的事情需要注意。主要是,虽然在数组上使用+和*等运算符使它们易于使用,但在大数组上也非常昂贵。这些运算符必须创建数组的新临时副本,然后通常将其复制到另一个数组中。这可能非常耗时。幸运的是,所有NumPy操作符都具有可以“就地”执行操作的特殊功能。例如,您可能希望用更快的添加(屏幕,亮图,屏幕)替换屏幕[:] =屏幕+亮图。无论如何,您需要阅读NumPy UFunc文档以获取更多相关信息。处理数组时很重要。
使用NumPy数组时需要注意的另一件事是数组的数据类型。某些数组(尤其是映射的像素类型)通常会返回具有无符号8位值的数组。如果你不小心,这些数组很容易溢出。 NumPy将使用您在C程序中找到的相同强制,因此将操作与8位数字和32位数字混合将得到32位数字的结果。您可以转换数组的数据类型,但绝对要知道您拥有的数组类型,如果NumPy遇到精度会被破坏的情况,它将引发异常。
最后,请注意,在将值分配到3D数组时,它们必须介于0到255之间,否则您将获得一些未定义的截断。
毕业(Gradation)
好的你得到了它。 我在Numeric Python和surfarray上的快速入门。 希望现在你能看到可能的东西,即使你从未使用过它们,当你看到代码时也不必害怕。 查看vgrade示例以获取更多数值数组操作。 还有一些漂浮在周围的“火焰”演示使用冲浪阵列来创造实时火焰效果。
最重要的是,自己尝试一些事情。 一开始慢慢建立起来,我已经看到了一些很好的东西,其中surfarray已经像径向渐变等等。 祝你好运。
以上内容,自己翻译,可能有误,可参考:Tutorials - Surfarray模块介绍(Surfarray Introduction)
点我回顶部 ☚
Fin.