Как быстрее всего нарисовать круги в python и сохранить как png? - PullRequest
2 голосов
/ 27 мая 2020

Фон

Оптимизирую проект.

Профилируя код, я обнаружил, что 50% времени тратится на функцию, в которой набор кругов (разные радиусы, цвета и местоположения) рисуются в выбранном секторе фиксированного размера (белый холст), если их центральная часть находится в пределах сектора. В зависимости от использования функция сохраняет фигуру как png и возвращает путь или возвращает изображение как массив numpy.

build-in method matplotlib._png.write_png из savefig является самым дорогим, но есть также некоторые накладные расходы на создание фигур, et c.

Обычно код используется с многопроцессорным / параллельным программированием .

Пример вывода

Image with circles.

Код

import matplotlib.pyplot as plt
import cv2
import os

    def get_top_view(sector, circles, file_path, save_image_flag):

        # get the sector bounds. 
        x_low, y_low, x_high, y_high = get_sector_bounds(sector)

        # init figure
        fig, ax = plt.subplots()
        ax.set_xlim(y_low, y_high)
        ax.set_ylim(x_low, x_high)
        ax.set_yticklabels([])
        ax.set_xticklabels([])
        ax.set_yticks([])
        ax.set_xticks([])
        ax.set_aspect('equal')
        ax.axis('off')

        # c is a circle object with all relevant data (center coordinates,
        # radius, RGB color tuple)
        for c in circles:
           if x_low <= c.x_coord <= x_high and y_low <= c.y_coord <= y_high:
                    shape = plt.Circle((c.x_coord, c.y_coord), c.radius, color=c.color)
                    shape_plot = ax.add_artist(shape)
                    shapes.append(shape_plot)
        plt.gca().invert_yaxis()
        if save_image_flag:
            plt.savefig(file_path + '_cc.png', bbox_inches='tight', pad_inches=0.02)
            plt.close()
            return file_path
        else:
            ax.margins(0)
            fig.tight_layout()
            fig.canvas.draw()
            image_from_plot = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
            image_from_plot = image_from_plot.reshape(
                                fig.canvas.get_width_height()[::-1] + (3,))
            image_from_plot = image_from_plot[:, 13:-14]
            resized = cv2.resize(image_from_plot, (499, 391))
            cropped = resized[78:-78]
            plt.close()
            return cropped

Вопросы

Есть проблема в том, что версия массива и изображение png немного отличаются. Я думаю, это связано с DPI изображения. Я хочу это исправить и думаю о разных вариантах, которые могут ускорить эту функцию.

  1. Ускорьте процесс и сохраните matplotlib, аналогично этому примеру из Github .
  2. Избавьтесь от matplotlib и нарисуйте его с помощью Pillow, например например:

    from PIL import Image, ImageDraw
    
    def get_top_view(sector, circles, file_path, save_image_flag):
    
        # get the sector bounds. 
        x_low, y_low, x_high, y_high = get_sector_bounds(sector)
    
        im = Image.new('RGB', (499, 235), (255, 255, 255))
        draw = ImageDraw.Draw(im)
    
        # there needs to be some rescaling that the corrdinates match which
        # I don't account for at the moment. 
        for c in circles:
               if x_low <= c.x_coord <= x_high and y_low <= c.y_coord <= y_high:
                     draw.ellipse((c.x_coord - c.radius, c.y_coord - c.radius,
                                   c.x_coord + c.radius, c.y_coord + c.radius), 
                                   fill=c.color)
    
        if save_image_flag:
            im.save(file_path + '.png')
            return file_path
    
        else:
            image_as_array = convert_to_array()  # have to think about how I'll do that
            return image_as_array
    
  3. Другой подход, который быстрее (и в чем-то удобнее) ...

Я был бы рад за любые отзывы по двум вопросам.

1 Ответ

0 голосов
/ 28 мая 2020

Я поделюсь своими выводами здесь. Я вызываю функцию 2200 раз для каждого типа вывода в рамках симуляции. Все вычисления являются последовательными, поскольку код многопроцессорной обработки не является частью примера.

Есть 220 секторов с 200 кругами, случайно распределенных между секторами. Моделирование длится 10 шагов, на которых обновляются радиусы окружностей и рисуются новые фигуры. Следовательно, 2200 вызовов функции.

  1. К сгенерировать и сохранить изображения png потребовалось 293483 мс ранее.
  2. К сгенерировать numpy массивы раньше требовалось 92715мс .

Ускорение с помощью Matplotlib

  1. Генерация и сохранение изображений (save_image_flag = True) теперь занимает 126485 мс .
  2. Генерация numpy массивов теперь занимает 57029 мс .

    import matplotlib.pyplot as plt
    import os
    
    def get_top_view(sector, circles, file_path, save_image_flag):
    
        # get the sector bounds. 
        x_low, y_low, x_high, y_high = get_sector_bounds(sector)
    
        # init figure
        fig, ax = plt.subplots()
        ax.set_xlim(y_low, y_high)
        ax.set_ylim(x_low, x_high)
        # These were unnecessary 
        # ax.set_yticklabels([])
        # ax.set_xticklabels([])
        # ax.set_yticks([])
        # ax.set_xticks([])
        ax.set_aspect('equal')
        ax.axis('off')
    
        # c is a circle object with all relevant data (center coordinates,
        # radius, RGB color tuple)
        for c in circles:
           if x_low <= c.x_coord <= x_high and y_low <= c.y_coord <= y_high:
                    shape = plt.Circle((c.x_coord, c.y_coord), c.radius, color=c.color)
                    shape_plot = ax.add_artist(shape)
                    shapes.append(shape_plot)
        plt.gca().invert_yaxis()
        # I added this to get the discrepancies between the generated image and 
        # the numpy array, as bbox_inches='tight' only applies to the saved image.
        bbox0 = fig.get_tightbbox(fig.canvas.get_renderer()).padded(0.02)
        if save_image_flag:
            plt.savefig(file_path + '_cc.png', bbox_inches=bbox0)
            plt.close()
            return file_path
        else:
            buf = io.BytesIO()
            fig.savefig(buf, format="rgba", dpi=100, bbox_inches=bbox0)
            buf.seek(0)
            img = np.reshape(np.frombuffer(buf.getvalue(), dtype=np.uint8), 
                             newshape=(235, 499, -1))
            img = img[..., :3]
            buf.close()
            plt.close()
            return image
    

Ускорение без Matplotlib

Ведутся исследования ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...