【问题标题】:Need to increase speed of image creation需要提高图像创建速度
【发布时间】:2017-11-03 05:06:21
【问题描述】:

我正在尝试创建具有所有可能颜色的图像。它将从一个种子像素开始,然后在其周围放置随机生成的 RGB 像素。未来的放置将基于其周围像素的平均值最接近要放置的新颜色的任何开放点。

from PIL import Image
import numpy as np
from random import randint
import sys
import random
import itertools

sys.setcheckinterval(10000)

def moddistance3(x1,y1,z1,x2,y2,z2):  #get relative distance between two 3D points
    x = abs(x1 - x2)
    y = abs(y1 - y2)
    z = abs(z1 - z2)
    return (x + y + z)

def genColor(unused): #generate random color (not used anymore)
    test = 0
    while test == 0:
        red = randint(0,255)
        green = randint(0,255)
        blue = randint(0,255)
        if unused[red,green,blue] == 1:
            test = 1
    return (red,green,blue)

def surroundAvg(points,unfilled):
    surrounding = {}
    count = len(points)
    for inc in xrange(count):
        neighbors = filledNeighbors(points[inc][0],points[inc][1],unfilled)
        nearcount = len(neighbors)
        pixred = 0
        pixgreen = 0
        pixblue = 0
        for num in xrange(nearcount):
            (temp_red,temp_green,temp_blue) = pixels[neighbors[num][0],neighbors[num][1]]
            pixred = pixred + temp_red
            pixgreen = pixgreen + temp_green
            pixblue = pixblue + temp_blue
        pixred = pixred / nearcount
        pixgreen = pixgreen / nearcount
        pixblue = pixblue / nearcount
        surrounding[(points[inc][0],points[inc][1])] = (pixred,pixgreen,pixblue)
    return surrounding

def genPoint(perim,unfilled,averages,red,green,blue):
    num_test = len(perim)
    test = 0
    least_diff = 9999
    nearby = []
    for point in xrange(num_test):
        i = perim[point][0]
        j = perim[point][1]
        pixred = averages[(i,j)][0]
        pixgreen = averages[(i,j)][1]
        pixblue = averages[(i,j)][2]
        diff = abs(red - pixred) + abs(green - pixgreen) + abs(blue - pixblue)
        if diff < least_diff or test == 0:
            least_diff = diff
            newx = i
            newy = j
            test = 1
    return newx,newy

def cubegen():  #create the cube of colors with each color having its own number
    cube = np.zeros(16777216,dtype=np.object)
    num = 0
    for red in xrange(0,256):
        for green in xrange(0,256):
            for blue in xrange(0,256):
                cube[num] = [red,green,blue]
                num += 1
    return cube

def getNeighbors(x,y,unfilled):
    Prod = itertools.product
    toremove = []
    neighbors = list(Prod(range(x-1,x+2),range(y-1,y+2)))
    for num in xrange(len(neighbors)):
        i,j = neighbors[num]
        if j > 4095 or i > 4095 or unfilled[(i,j)] == 0 or j < 0 or i < 0:
            toremove.append((i,j))
    map(neighbors.remove,toremove)
    return neighbors

def filledNeighbors(x,y,unfilled):
    Prod = itertools.product
    toremove = []
    neighbors = list(Prod(range(x-1,x+2),range(y-1,y+2)))
    #neighbors = filter(lambda i,j: j < 4096 and i < 4096 and unfilled[i,j] == 0 and j > -1 and i > -1,allneighbors)
    for num in xrange(len(neighbors)):
        i,j = neighbors[num]
        if j > 4095 or i > 4095 or unfilled[(i,j)] == 1 or j < 0 or i < 0:
            toremove.append((i,j))
    map(neighbors.remove,toremove)
    return neighbors

img = Image.new('RGB', (4096,4096)) # create a new black image
pixels = img.load() # create the pixel map

colorList = range(16777216)
colorCube = cubegen()
print("Color cube created successfully")
unfilled = {}
for x in xrange(4096):
    for y in xrange(4096):
        unfilled[(x,y)] = 1
startx = 2048
starty = 2048
random.shuffle(colorList)
print("Color list shuffled successfully")
color = colorList[0]
(red,green,blue) = colorCube[color]
pixels[startx,starty] = (red,green,blue)
unfilled[(startx,starty)] = 0
perim_empty = getNeighbors(startx,starty,unfilled)
edge = []
#edge.append((startx,starty))
avg = surroundAvg(perim_empty,unfilled)
print("First point placed successfully.")
#appendEdge = edge.append
#removeEdge = edge.remove
appendPerim = perim_empty.append
removePerim = perim_empty.remove
updateAvg = avg.update


for iteration in xrange(1,16777216):
    temp = {}
    color = colorList[iteration]
    (red,green,blue) = colorCube[color]
    (i,j) = genPoint(perim_empty,unfilled,avg,red,green,blue)
    unfilled[(i,j)] = 0
    pixels[i,j] = (red,green,blue)
    new_neighbors = getNeighbors(i,j,unfilled)
    map(appendPerim,new_neighbors)
    temp = surroundAvg(new_neighbors,unfilled)
    updateAvg(temp)
    removePerim((i,j))
    #appendEdge((i,j))

    #if iteration % 20 == 0:
    #   toremove = []
    #   appendToRemove = toremove.append
    #   for num in xrange(len(edge)):
    #       nearby = getNeighbors(edge[num][0],edge[num][1],unfilled)
    #       if len(nearby) == 0:
    #           appendToRemove(edge[num])
        #for num in xrange(len(toremove)):
        #   edge.remove(toremove[num])
    #   map(removeEdge,toremove)

    if iteration % 500 == 0:
        print("Iteration %d complete" %iteration)
    if iteration == 100000 or iteration == 500000 or iteration ==1000000 or iteration == 5000000 or iteration == 10000000 or iteration == 15000000:
        img.save("Perimeter Averaging -- %d iterations.bmp" %iteration)
img.save("Perimeter Averaging Final.bmp")
img.show()

问题是,当我尝试运行它时,甚至需要数天才能通过 1,000,000 种颜色,并且运行速度会大大降低。我不知道如何让它花费更少的时间,而且我知道必须有一种方法可以做到这一点,而不需要几个月的时间。我是编码新手并且正在自学,所以请原谅我完全忽略的任何明显的修复。

【问题讨论】:

  • 在没有专门查看您的代码的情况下,您是否考虑过 cython-izing 或使用像 numba 这样的 JIT 编译器?
  • 你是对的,这可以运行得更快。我猜想将庞大的 字典从一个函数扔到另一个函数可能是一个相当大的瓶颈。该程序在进入迭代部分之前会消耗大量内存。肯定有一些领域你可以更有效地处理。如果我有时间的话,我今晚会去列个清单。
  • 无论何时尝试加速您的代码,最好对其进行概要分析以确定其大部分时间都花在哪里......所以您知道在这里花费大部分时间来优化它。见How can you profile a python script?也就是说,答案通常是完全使用不同的算法并避免瓶颈,不管它是什么。
  • 您需要学习您的数据结构...您正在使用列表或 numpy 数组,但它们的优势却不尽如人意。我认为这个问题属于Code Review,而不是这里。而且您在那里比在这里更有可能获得切实可行的帮助。
  • 我尝试使用 cProfile 对其进行分析,大部分时间似乎都被 genPoint 消耗了,特别是因为它被频繁调用。问题是我找不到在不增加花费时间的情况下更改它的方法。您会推荐哪些数据结构来更好地利用它们的优势?

标签: python image numpy colors


【解决方案1】:

好的,我已经花了一些时间研究这个问题,并进行了一些更改以加快速度。我真的很喜欢你在这个程序中实现的想法,输出非常可爱。我可能会从一开始就做一些不同的事情,但事实上,我已经把它留在了你展示它的结构中,并进行了一些调整。这绝不代表代码可能是最快或最有效的,但它足以满足我的加速。 Note - 那里可能有一些错误的打印语句,我用来理解代码中发生了什么。希望对您有所帮助。

  1. 我清理了代码以删除任何未使用的内容并更改循环访问某些对象的方式。这些都是风格的改变。

  2. 我将您的颜色立方体从 numpy 数组更改为元组,因为不需要 np 数组。我认为立方体现在生成得更快,但我还没有进行任何测试来确认这一点。

  3. 重要的是要了解,根据您希望处理颜色的方式,随着“周长”候选者数量的增加,该算法会随着时间的推移而变慢。当我第一次运行代码时,由于没有正确删除坐标,周长列表的大小大幅增加。我无法找出它发生的原因,所以我在程序中添加了额外的检查以避免覆盖任何已经写入的像素。如果发现不应该是周界列表成员的点,我还添加了一些多余的点删除。这在我的系统上加快了速度,尽管我仍然无法找到错误的来源。

  4. 为了进一步解决处理时间与周长成比例增长的问题,我在 genPoint 中引入了一个阈值。这允许函数在“足够接近”到一个好位置时退出。这样做会影响质量,因此如果您想要速度,可以将值设置得更高,或者如果您想要更高质量的图像,可以将值设置得更低。这是一个魔鬼的交易,但替代方案是一个永远不会完成的程序。当我第一次运行脚本时,我的计算机花了大约 14 秒来完成迭代 14000-14500。由于它目前正在运行,我正在进行 2,330,000 次迭代,每 500 次迭代大约需要 2-3 秒。

  5. 我摆脱了未填充的字典,而是直接从函数中引用图像。不是我希望这样做的方式,但它简化了代码并摆脱了大内存块。

  6. 另一种更明显的加速方法是多线程迭代 genPoint() 函数,但我不愿意花时间去做它的那一部分,因为它需要对代码进行大量修改。为了使其工作,您必须实现很多线程锁,但它会使其更快。

--

from PIL import Image
import sys
import random
import itertools
import datetime as dt


#sys.setcheckinterval(10000)

def surroundAvg(points):
    surrounding = {}
    for inc in points:
        neighbors = filledNeighbors(inc[0],inc[1])
        nearcount = len(neighbors)
        pixred = 0
        pixgreen = 0
        pixblue = 0
        for n in neighbors:
            (temp_red,temp_green,temp_blue) = pixels[n[0],n[1]]
            pixred += temp_red
            pixgreen += temp_green
            pixblue += temp_blue
        pixred = pixred / nearcount
        pixgreen = pixgreen / nearcount
        pixblue = pixblue / nearcount
        surrounding[(inc[0],inc[1])] = (pixred,pixgreen,pixblue)
    return surrounding

def genPoint(perim,averages,red,green,blue):
    test = 0
    least_diff = 9999
    threshold = 35
    for point in perim:
        i = point[0]
        j = point[1]
        if pixels[i,j] == (0,0,0):
            pixred = averages[(i,j)][0]
            pixgreen = averages[(i,j)][1]
            pixblue = averages[(i,j)][2]
            diff = abs(red - pixred) + abs(green - pixgreen) + abs(blue - pixblue)
            if diff < least_diff or test == 0:
                least_diff = diff
                newx = i 
                newy = j 
                test = 1
                if least_diff < threshold:
                    return newx,newy,perim
        else:
            perim.pop(perim.index(point))
    return newx,newy,perim

def cubegen():  #create the cube of colors with each color having its own number
    cube = []
    num = 0
    for red in xrange(0,256):
        for green in xrange(0,256):
            for blue in xrange(0,256):
                cube.append((red,green,blue))
                num += 1
    return tuple(cube)

def getNeighbors(x,y):
    Prod = itertools.product
    toremove = []
    neighbors = list(Prod(range(x-1,x+2),range(y-1,y+2)))
    for num in xrange(len(neighbors)):
        i,j = neighbors[num]
        if j > 4095 or i > 4095 or pixels[i,j] != (0,0,0) or j < 0 or i < 0:
            toremove.append((i,j))
    map(neighbors.remove,toremove)
    return neighbors

def filledNeighbors(x,y):
    Prod = itertools.product
    toremove = []
    neighbors = list(Prod(range(x-1,x+2),range(y-1,y+2)))
    for num in xrange(len(neighbors)):
        i,j = neighbors[num]
        if j > 4095 or i > 4095 or pixels[i,j] == (0,0,0) or j < 0 or i < 0:
            toremove.append((i,j))
    map(neighbors.remove,toremove)
    return neighbors

img = Image.new('RGB', (4096,4096)) # create a new black image
pixels = img.load() # create the pixel map

print("Making list")
colorList = range(16777216)
random.shuffle(colorList)
print("Color list shuffled successfully")

print("Making cube")
colorCube = cubegen()
print("Color cube created successfully")

startx = 2048
starty = 2048
color = colorList[0]

(red,green,blue) = colorCube[color]
#start with a random color
pixels[startx,starty] = (red,green,blue)

#get it's neighboring pixels
perim_empty = getNeighbors(startx,starty)

#calc avg values (original pixel)
avg = surroundAvg(perim_empty)
print("First point placed successfully.")
appendPerim = perim_empty.append
removePerim = perim_empty.remove
updateAvg = avg.update

start = dt.datetime.now()
for iteration in xrange(1,16777216):
    temp = {}
    color = colorList[iteration]
    (red,green,blue) = colorCube[color]
    i,j,perim_empty = genPoint(perim_empty,avg,red,green,blue)
    pixels[i,j] = (red,green,blue)
    new_neighbors = getNeighbors(i,j)
    map(appendPerim,new_neighbors)
    temp = surroundAvg(new_neighbors)
    updateAvg(temp)
    removePerim((i,j))
    for p in perim_empty:
        if p[0] == i and p[1] == j:
            perim_empty.remove(p)

    if iteration % 1000 == 0:
        end = dt.datetime.now()
        print("Iteration %d complete: %f" %(iteration,(end-start).total_seconds()))
        print("Perimeter size: %d"%len(perim_empty))
        print("Averages size: %d"%sys.getsizeof(avg))
        start = dt.datetime.now()
        img.save("%06d.png" % (iteration/1000))

img.save("Perimeter Averaging Final.bmp")
img.show()

编辑:忘记包含我当前的图像(仍在增长):

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 2017-07-25
    相关资源
    最近更新 更多