提高 getpixel 和 putpixel 的速度
Improve speed of getpixel and putpixel
使用 PIL,我使用 getpixel
和 setpixel
将彩虹滤镜应用于给定图像。一个问题,这种方法很慢。完成一张图像大约需要 10 秒。
def Rainbow(i):
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
if (i >= 0 and i < 60 ): r,g,b = 1, x, 0
if (i >= 60 and i < 120): r,g,b = x, 1, 0
if (i >= 120 and i < 180): r,g,b = 0, 1, x
if (i >= 180 and i < 240): r,g,b = 0, x, 1
if (i >= 240 and i < 300): r,g,b = x, 0, 1
if (i >= 300 and i < 360): r,g,b = 1, 0, x
res = (int(r * 255), int(g * 255), int(b * 255))
return res
def RainbowFilter(img):
for x in range(img.size[0]):
for y in range(img.size[1]):
intensity = sum(img.getpixel((x, y)))
img.putpixel((x, y), Rainbow(intensity + x + y))
return img
im = Image.open('cat.jpg')
rainbow_im = RainbowFilter(im)
rainbow_im.save('rainbow_im.png')
你能帮助我改进我的特定算法,使用专门的 Numpy 或 Pillow 功能来解决提到的问题吗?
您可以将 image
转换为 NumPy.array
,然后使用 numba
来提高速度,如下所示:
from PIL import Image
import numba as nb
import numpy as np
@nb.njit(parallel=True)
def new_RainbowFilter(img):
intensity = img.sum(axis=-1)
row , col = img.shape[:2]
for r in nb.prange(row):
for c in nb.prange(col):
i = (intensity[r,c] + r + c)
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
res = np.zeros(3)
if (i >= 0 and i < 60 ): res = np.array([1, x, 0])
elif (i >= 60 and i < 120): res = np.array([x, 1, 0])
elif (i >= 120 and i < 180): res = np.array([0, 1, x])
elif (i >= 180 and i < 240): res = np.array([0, x, 1])
elif (i >= 240 and i < 300): res = np.array([x, 0, 1])
elif (i >= 300 and i < 360): res = np.array([1, 0, x])
img[r,c] = res * 255
return img
im = Image.open('cat.jpg')
img = np.asarray(im)
img = new_RainbowFilter(img)
im = Image.fromarray(img)
im.save('rainbow_im.png')
我对此很感兴趣,并决定尝试优化@I'mahdi 的代码。
我的想法如下:
创建并归零输出图像up-front并避免在主循环中写入已经归零的元素
只对 outer 循环使用并行化的 nb.prange()
因为,如果你有 12 个 CPU 核心,那将已经创建 12线程
避免在每次迭代中创建新的 3 元素 Numpy 数组以分配给输出数组的 RGB 元素 - 只需直接分配两个 non-zero 值
大幅减少 if
语句中的测试数量。原始代码使用多达 12 次测试来确定 i
属于 6 个扇区中的哪个扇区。如果 i
在最后一个扇区,它将执行所有 12 个操作。我的代码在 2-4 次测试中完成,更像是二进制搜索。
#!/usr/bin/env python3
from PIL import Image
import numba as nb
import numpy as np
def Rainbow(i):
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
if (i >= 0 and i < 60 ): r,g,b = 1, x, 0
if (i >= 60 and i < 120): r,g,b = x, 1, 0
if (i >= 120 and i < 180): r,g,b = 0, 1, x
if (i >= 180 and i < 240): r,g,b = 0, x, 1
if (i >= 240 and i < 300): r,g,b = x, 0, 1
if (i >= 300 and i < 360): r,g,b = 1, 0, x
res = (int(r * 255), int(g * 255), int(b * 255))
return res
def RainbowFilter(img):
for x in range(img.size[0]):
for y in range(img.size[1]):
intensity = sum(img.getpixel((x, y)))
img.putpixel((x, y), Rainbow(intensity + x + y))
return img
@nb.njit(parallel=True)
def imahdi(img):
intensity = img.sum(axis=-1)
row , col = img.shape[:2]
for r in nb.prange(row):
for c in nb.prange(col):
i = (intensity[r,c] + r + c)
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
res = np.zeros(3)
if (i >= 0 and i < 60 ): res = np.array([1, x, 0])
elif (i >= 60 and i < 120): res = np.array([x, 1, 0])
elif (i >= 120 and i < 180): res = np.array([0, 1, x])
elif (i >= 180 and i < 240): res = np.array([0, x, 1])
elif (i >= 240 and i < 300): res = np.array([x, 0, 1])
elif (i >= 300 and i < 360): res = np.array([1, 0, x])
img[r,c] = res * 255
return img
@nb.njit(parallel=True)
def mark(img):
intensity = img.sum(axis=-1)
row , col = img.shape[:2]
# Create zeroed result image
res = np.zeros_like(img)
for r in nb.prange(row):
# Only make outer loop parallel else inner one will make more threads than cores
for c in range(col):
i = (intensity[r,c] + r + c)
x = 1 - abs(((i / 60) % 2) - 1)
x = int(x * 255)
i %= 360
# Split the problem space in half in one test - like binary search
if i < 180:
if i < 60:
# Don't create whole new array here
# Don't assign zeroes, array is empty already
res[r,c,0] = 255
res[r,c,1] = x
elif i < 120:
res[r,c,0] = x
res[r,c,1] = 255
else:
res[r,c,1] = 255
res[r,c,2] = x
else:
if i < 240:
res[r,c,1] = x
res[r,c,2] = 255
elif i < 300:
res[r,c,0] = x
res[r,c,2] = 255
else:
res[r,c,0] = 255
res[r,c,2] = x
return res
orig = Image.open('cat.jpg')
res = RainbowFilter(orig)
res.save('result.png')
im = np.asarray(orig)
res = imahdi(im)
Image.fromarray(res).save('imahdi.ppm')
res = mark(im)
Image.fromarray(res).save('mark.ppm')
时间安排如下:
In [17]: %timeit res = RainbowFilter(orig)
11.7 s ± 80 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [18]: %timeit res = imahdi(im)
1.52 s ± 4.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [13]: %timeit res=mark(im)
35.6 ms ± 928 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
使用 PIL,我使用 getpixel
和 setpixel
将彩虹滤镜应用于给定图像。一个问题,这种方法很慢。完成一张图像大约需要 10 秒。
def Rainbow(i):
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
if (i >= 0 and i < 60 ): r,g,b = 1, x, 0
if (i >= 60 and i < 120): r,g,b = x, 1, 0
if (i >= 120 and i < 180): r,g,b = 0, 1, x
if (i >= 180 and i < 240): r,g,b = 0, x, 1
if (i >= 240 and i < 300): r,g,b = x, 0, 1
if (i >= 300 and i < 360): r,g,b = 1, 0, x
res = (int(r * 255), int(g * 255), int(b * 255))
return res
def RainbowFilter(img):
for x in range(img.size[0]):
for y in range(img.size[1]):
intensity = sum(img.getpixel((x, y)))
img.putpixel((x, y), Rainbow(intensity + x + y))
return img
im = Image.open('cat.jpg')
rainbow_im = RainbowFilter(im)
rainbow_im.save('rainbow_im.png')
您可以将 image
转换为 NumPy.array
,然后使用 numba
来提高速度,如下所示:
from PIL import Image
import numba as nb
import numpy as np
@nb.njit(parallel=True)
def new_RainbowFilter(img):
intensity = img.sum(axis=-1)
row , col = img.shape[:2]
for r in nb.prange(row):
for c in nb.prange(col):
i = (intensity[r,c] + r + c)
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
res = np.zeros(3)
if (i >= 0 and i < 60 ): res = np.array([1, x, 0])
elif (i >= 60 and i < 120): res = np.array([x, 1, 0])
elif (i >= 120 and i < 180): res = np.array([0, 1, x])
elif (i >= 180 and i < 240): res = np.array([0, x, 1])
elif (i >= 240 and i < 300): res = np.array([x, 0, 1])
elif (i >= 300 and i < 360): res = np.array([1, 0, x])
img[r,c] = res * 255
return img
im = Image.open('cat.jpg')
img = np.asarray(im)
img = new_RainbowFilter(img)
im = Image.fromarray(img)
im.save('rainbow_im.png')
我对此很感兴趣,并决定尝试优化@I'mahdi 的代码。
我的想法如下:
创建并归零输出图像up-front并避免在主循环中写入已经归零的元素
只对 outer 循环使用并行化的
nb.prange()
因为,如果你有 12 个 CPU 核心,那将已经创建 12线程避免在每次迭代中创建新的 3 元素 Numpy 数组以分配给输出数组的 RGB 元素 - 只需直接分配两个 non-zero 值
大幅减少
if
语句中的测试数量。原始代码使用多达 12 次测试来确定i
属于 6 个扇区中的哪个扇区。如果i
在最后一个扇区,它将执行所有 12 个操作。我的代码在 2-4 次测试中完成,更像是二进制搜索。
#!/usr/bin/env python3
from PIL import Image
import numba as nb
import numpy as np
def Rainbow(i):
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
if (i >= 0 and i < 60 ): r,g,b = 1, x, 0
if (i >= 60 and i < 120): r,g,b = x, 1, 0
if (i >= 120 and i < 180): r,g,b = 0, 1, x
if (i >= 180 and i < 240): r,g,b = 0, x, 1
if (i >= 240 and i < 300): r,g,b = x, 0, 1
if (i >= 300 and i < 360): r,g,b = 1, 0, x
res = (int(r * 255), int(g * 255), int(b * 255))
return res
def RainbowFilter(img):
for x in range(img.size[0]):
for y in range(img.size[1]):
intensity = sum(img.getpixel((x, y)))
img.putpixel((x, y), Rainbow(intensity + x + y))
return img
@nb.njit(parallel=True)
def imahdi(img):
intensity = img.sum(axis=-1)
row , col = img.shape[:2]
for r in nb.prange(row):
for c in nb.prange(col):
i = (intensity[r,c] + r + c)
x = 1 - abs(((i / 60) % 2) - 1)
i %= 360
res = np.zeros(3)
if (i >= 0 and i < 60 ): res = np.array([1, x, 0])
elif (i >= 60 and i < 120): res = np.array([x, 1, 0])
elif (i >= 120 and i < 180): res = np.array([0, 1, x])
elif (i >= 180 and i < 240): res = np.array([0, x, 1])
elif (i >= 240 and i < 300): res = np.array([x, 0, 1])
elif (i >= 300 and i < 360): res = np.array([1, 0, x])
img[r,c] = res * 255
return img
@nb.njit(parallel=True)
def mark(img):
intensity = img.sum(axis=-1)
row , col = img.shape[:2]
# Create zeroed result image
res = np.zeros_like(img)
for r in nb.prange(row):
# Only make outer loop parallel else inner one will make more threads than cores
for c in range(col):
i = (intensity[r,c] + r + c)
x = 1 - abs(((i / 60) % 2) - 1)
x = int(x * 255)
i %= 360
# Split the problem space in half in one test - like binary search
if i < 180:
if i < 60:
# Don't create whole new array here
# Don't assign zeroes, array is empty already
res[r,c,0] = 255
res[r,c,1] = x
elif i < 120:
res[r,c,0] = x
res[r,c,1] = 255
else:
res[r,c,1] = 255
res[r,c,2] = x
else:
if i < 240:
res[r,c,1] = x
res[r,c,2] = 255
elif i < 300:
res[r,c,0] = x
res[r,c,2] = 255
else:
res[r,c,0] = 255
res[r,c,2] = x
return res
orig = Image.open('cat.jpg')
res = RainbowFilter(orig)
res.save('result.png')
im = np.asarray(orig)
res = imahdi(im)
Image.fromarray(res).save('imahdi.ppm')
res = mark(im)
Image.fromarray(res).save('mark.ppm')
时间安排如下:
In [17]: %timeit res = RainbowFilter(orig)
11.7 s ± 80 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [18]: %timeit res = imahdi(im)
1.52 s ± 4.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [13]: %timeit res=mark(im)
35.6 ms ± 928 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)