在下面的代码中,我尝试使用多重处理生成 Mandelbrot 集的灰度图像。
为此,我设置了一个队列并命令多个进程来生成图像的单独“条带”,然后将它们放入队列中。最后,程序应该将它们全部组合成最终图像。
不幸的是,完成所有进程后,程序似乎冻结并且永远不会返回图像。即使当我更改所有流程以保存它们各自的条带时,检索它们也没有问题。
这是我的参考代码,它不是最小的,但我尝试保持所有内容可读和注释:
import cv2
import numpy as np
import math
import random
import multiprocessing
import time
path = "C:/Users/tymon/Pictures/Mandelbrot"
# Simple (i.e., not complex LMAO) class allowing representation and manipulation of complex numbers
class complex:
def __init__(self, a, b=0):
self.a = a
self.b = b
def __add__(self, other):
return complex(self.a + other.a, self.b + other.b)
def __sub__(self, other):
return complex(self.a - other.a, self.b - other.b)
def __mul__(self, other):
return complex(
self.a * other.a - self.b * other.b,
self.a * other.b + self.b * other.a)
def __truediv__(self, other):
nominator = other.a * other.a + other.b * other.b
return complex(
(self.a * other.a + self.b * other.b)/nominator,
(self.b * other.a - self.a * other.b)/nominator)
def modulusSqrd(self):
return self.a*self.a + self.b*self.b
def modulus(self):
return math.sqrt(self.modulusSqrd()) # heavy duty calculation
def __str__(self):
sign = "+"
if self.b < 0:
sign = "-"
self.b *= -1
return F"{self.a} {sign} {self.b}i"
# Mandelbrot set is created by recursively applying the simple equation Z_(n+1) = Z_n^2 - c where c is the initial number and Z_0=0 until the magnitude of the number becomes > 2
def simple_equation(c, ite=2):
z = complex(0)
i = 0
while i <= ite and z.modulusSqrd() < 4:
z_next = z*z - c
if math.isinf(z.a) or math.isinf(z.b) or math.isnan(z.a) or math.isnan(z.b):
return i+1
else:
z = z_next
i+=1
return i
# Maps a number n in the range <min1, max1> to the range <min2, max2> such that the ratio of its distance from both ends of the interval remains the same
def map(n, min1, max1, min2, max2):
if math.isinf(n): # just in case
return max2
return (n - min1) / (max1 - min1) * (max2 - min2) + min2
# Function controlling one of the processes generating the Mandelbrot set image
'''
res[resolution] - both the length and width of the image in pixels
Larger res provide greater detail in the image
ite[iterations] - after how many iterations the program decides that applying the simple equation more times won't make the magnitude of the result > 2 (point belongs to Mandelbrot set)
Higher ite values give more detailed shape
total - total number of processes
id - process identifier (from 0 to total-1)
Through id and total, each process knows which part of the image to generate
queue - queue to which the program will submit fragments
'''
def mandelbrot_process(res, ite, total, id, queue, path=None):
heigth = res//total
prebake_div = 4/total
array = np.zeros((heigth, res), dtype=np.uint8)
try:
progress = 0
# for each point in the image, check after how many iterations of the simple equation (if any) it has moved more than 2 units away from (0,0) and color it accordingly
for y in range(heigth):
for x in range(res):
# convert pixel to complex number at the corresponding position
z = complex(
map(x, 0, res, -2, 2),
map(y,
0, heigth,
prebake_div * id - 2, prebake_div * (id+1) - 2)
)
# applying the simple equation is handled by this function (see def simle_equation(c, ite=2):)
score = simple_equation(z, ite)
# color the pixel accordingly
array[y][x] = map(score, 0, ite, 0, 255)
# update from processes
if 100*y/heigth > progress:
progress += 1
print(f"Finished {progress}% of process {id}! " + ["Awesome!", "Great!", "Amazing!", f"Good job process number {id}!"][random.randint(0,3)]) # motivating processes (100% required)
print(f"!\nFINISHED PROCESS {id} LET'S GOOOOO!\n!")
if path != None:
cv2.imwrite(path+f"/output_res{res}-ite{ite}-id{id}.jpg", array)
queue.put((id, array))
except Exception as e:
print(f"!\nWHAT JUST HAPPENED LIKE I DON'T EVEN KNOW??\nProcess {id} explain yourself!\n\nProcess {id}:")
print(e)
print("\n!")
# input but if you don't enter anything, it will be default
def input_int_with_def(default=0, prompt=""):
var = input(prompt + f"(default: {default})> ")
if var == "":
return default
else:
return int(var)
if __name__ == "__main__":
try:
# inputs
res = int(input("resolution (px)> "))
ite = input_int_with_def(12, "iterations")
pro = input_int_with_def(4, "processes")
# create a queue to collect results from individual processes
queue = multiprocessing.Queue()
# start all processes
processes = []
for id in range(pro):
p = multiprocessing.Process(target=mandelbrot_process, args=(res, ite, pro, id, queue, path))
processes.append(p)
p.start()
# wait until they finish
for p in processes:
p.join()
# create an empty array to store results from individual processes
results = [None] * pro
# retrieve results from the queue and place them in the appropriate positions in the results array
while not queue.empty():
id, result = queue.get()
results[id] = result
# concatenate results from individual processes into one image
final_image = np.vstack(results)
# save and display the image
please = cv2.imwrite(path+f"/output_res{res}-ite{ite}.jpg", final_image)
if please:
message = "Finished generating and saved correctly :)"
else:
message = "Finished generating !BUT SAVED INCORRECTLY! :("
print(message)
cv2.imshow("Mandelbrot_Set", final_image)
except Exception as e:
print("something happened in main")
print(e)
finally:
cv2.waitKey(1)
我不知道是什么原因导致这种行为,但我信任你们这些善良的陌生人:)
我尝试通过放置打印语句(不包含在代码中)来调试代码,似乎所有进程都完成了工作,但代码的下一部分(例如等待它们完成)没有激活。
我还尝试生成具有不同分辨率和默认设置的迭代和过程图像。对于分辨率值 87 或以下 (?),程序按预期运行,而 88 或以上会导致上述问题。不知道为什么:S
您应该在此处使用
multiprocessing.Pool
(https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool.map)。只需对代码进行少量更改,我就可以成功运行它。 BTW complex
已经在 Python 中实现,无需您自己实现。命名函数/变量时还要避免使用内置名称(即 map
)。
这是代码:
import math
import multiprocessing
from functools import partial
import cv2
import numpy as np
path = "./img"
def simple_equation(c, ite=2):
"""
Mandelbrot set is created by recursively applying the simple equation
Z_(n+1) = Z_n^2 - c where c is the initial number and Z_0=0 until the
magnitude of the number becomes > 2
"""
z = complex(0)
i = 0
while i <= ite and abs(z) < 2:
z = z * z - c
i += 1
return i
def map_range(n, min1, max1, min2, max2):
"""
Maps a number n in the range <min1, max1> to the range <min2, max2> such
that the ratio of its distance from both ends of the interval remains the
same
"""
if math.isinf(n): # just in case
return max2
return (n - min1) / (max1 - min1) * (max2 - min2) + min2
def mandelbrot_process(id, res, ite, total, path=None):
"""
Function controlling one of the processes generating the Mandelbrot set
image
"""
heigth = res // total
prebake_div = 4 / total
array = np.zeros((heigth, res), dtype=np.uint8)
try:
progress = 0
# for each point in the image, check after how many iterations of the
# simple equation (if any) it has moved more than 2 units away from
# (0,0) and color it accordingly
for y in range(heigth):
for x in range(res):
# convert pixel to complex number at the corresponding position
z = complex(
map_range(x, 0, res, -2, 2),
map_range(
y,
0,
heigth,
prebake_div * id - 2,
prebake_div * (id + 1) - 2,
),
)
# applying the simple equation is handled by this function (see
# def simle_equation(c, ite=2):)
score = simple_equation(z, ite)
# color the pixel accordingly
array[y][x] = map_range(score, 0, ite, 0, 255)
# update from processes
if 100 * y / heigth > progress:
progress += 1
print(f"!\nFINISHED PROCESS {id} LET'S GOOOOO!\n!")
if path is not None:
cv2.imwrite(path + f"/output_res{res}-ite{ite}-id{id}.jpg", array)
return (id, array)
except Exception as e:
print(
f"!\nWHAT JUST HAPPENED LIKE I DON'T EVEN KNOW??\nProcess {id} "
f"explain yourself!\n\nProcess {id}:"
)
print(e)
print("\n!")
def input_int_with_def(default=0, prompt=""):
"""
input but if you don't enter anything, it will be default
"""
var = input(prompt + f"(default: {default})> ")
if var == "":
return default
else:
return int(var)
if __name__ == "__main__":
try:
# inputs
res = int(input("resolution (px)> "))
ite = input_int_with_def(12, "iterations")
pro = input_int_with_def(4, "processes")
with multiprocessing.Pool(pro) as pool:
ret = pool.map(
partial(mandelbrot_process, res=res, ite=ite, total=pro, path=path),
range(pro),
)
# create an empty array to store results from individual processes
results = [None] * pro
# retrieve results from the queue and place them in the appropriate
# positions in the results array
for id, result in ret:
results[id] = result
# concatenate results from individual processes into one image
final_image = np.vstack(results)
# save and display the image
please = cv2.imwrite(path + f"/output_res{res}-ite{ite}.jpg", final_image)
if please:
message = "Finished generating and saved correctly :)"
else:
message = "Finished generating !BUT SAVED INCORRECTLY! :("
print(message)
cv2.imshow("Mandelbrot_Set", final_image)
except Exception as e:
print("something happened in main")
print(e)
finally:
cv2.waitKey(1)