Python:使用多重处理生成图像冻结

问题描述 投票:0回答:1

在下面的代码中,我尝试使用多重处理生成 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

python numpy opencv image-processing multiprocessing
1个回答
0
投票

您应该在此处使用

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)
© www.soinside.com 2019 - 2024. All rights reserved.