Как улучшить пользовательский опыт, заранее подготовив следующее изображение для созданного зрителя?

Я создал программу Tkinter, которая отображает изображения, обработанные с помощью OpenCV. Графический интерфейс Tkinter имеет кнопки для выбора папки, просмотра следующего изображения и просмотра предыдущего изображения, а также холст, на котором отображаются эти изображения. И когда человек с помощью переключателя проверяет, какой тип изображения это изображение, запись сохраняется.

Вот как это выглядит для пользователей:

  1. Когда человек нажимает кнопку просмотра следующего изображения,
  2. Загрузите изображение,
  3. Процесс с OpenCV (ЦП: 0,8 секунды),
  4. Покажите изображение на холсте.
  5. Проверьте изображение и отметьте переключатель (Человек: 1 секунда)
  6. Перейти к 1

В течение 1 секунды, пока человек 5 принимает решение, я хочу начать обработку и подготовку следующего изображения 3, а затем немедленно отобразить его, когда я нажму кнопку. Я думаю, что это значительно улучшит пользовательский опыт.

Как я могу это сделать? Я был бы признателен, если бы вы могли сказать мне хотя бы одно ключевое слово о том, какие технологии мне следует изучить.


Я изменил программу на основе ответа Ахмеда АЕКа. Это сработало хорошо и, очевидно, немного улучшило пользовательский опыт, заранее подготовив следующее изображение. Однако часть Canvas.draw(), рисующая заранее подготовленное изображение в Tkinter, сама по себе отнимает время. Так что я не смог далеко продвинуться в улучшении его до уровня, на котором нет абсолютно никаких задержек. Это моя вина, что я заранее не досконально проанализировал время на каждый шаг.

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

Я думаю, вам захочется использовать «предварительную загрузку» в Google или что-то подобное.

Mark Setchell 07.06.2024 13:34
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
1
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

вам следует использовать многопоточность, т. е. переносить работу на другой поток, чтобы основной поток не блокировался, наиболее надежным способом является использование Threadpool следующим образом.

from multiprocessing.pool import ThreadPool

class ImageRepository:
    def __init__(self):
        self.next_image_future = None
        self.threadpool = ThreadPool(1)  # 1 worker, we don't need more

    def start_image_processing(self):
        # initialize and start the first task
        self.next_image_future = self.threadpool.apply_async(self.load_next_image)

    def get_next_image(self):
        if self.next_image_future is not None:
            # get the image
            new_image = self.next_image_future.get()

            # submit task for the next one in future 
            self.next_image_future = \
                self.threadpool.apply_async(self.load_next_image)

            return new_image

    def load_next_image(self):
        print("loading and processing image")
        return 5  # new image object

repo = ImageRepository()
repo.start_image_processing()  # start grabbing the first image

# get next image and initiate work for the one after it
new_image = repo.get_next_image()  

и поэтому каждый раз, когда вы получаете изображение, вы будете назначать задачу другому потоку для загрузки и обработки следующего.

Я думаю, вы могли бы создать очень элегантное решение, используя Redis, который представляет собой молниеносный сервер со структурой данных в памяти, идеально подходящий для кэширования и предварительной выборки. У него есть клиенты для C/C++, Python, Ruby, bash, Java и так далее. В вашем случае я бы использовал его для хранения двух структур данных:

  • очередь изображений, требующих обработки — это будет Redis LIST
  • хранилище обработанных изображений — на языке Redis это будут STRINGS. Но пусть это вас не смущает: вы можете использовать их для хранения строк, целых чисел, чисел с плавающей запятой, JSON, HTML-страниц, изображений и т. д. Каждый из них может иметь размер до 512 МБ и может хранить двоичные данные, такие как изображения.

Основная идея состоит в том, чтобы позволить приложению Tk помещать список имен изображений в рабочую очередь при запуске приложения и, например, всякий раз, когда пользователь меняет каталог. А затем забирайте обработанное изображение из Redis всякий раз, когда ему нужно его отобразить.

Затем вы запускаете рабочий процесс, который просто ожидает появления имен изображений в очереди, обрабатывает их и сохраняет результат в быстром кэше Redis, готовом для использования приложением Tk.

Это имеет некоторые преимущества:

  • масштабируемость — если вам нужна более высокая производительность, вы можете просто добавить больше рабочих процессов, код не изменится.
  • развязка — обработка изображений полностью отделена от приложения Tk, что является преимуществом, поскольку Python плохо масштабируется с помощью GIL.
  • простота - обработанные изображения могут храниться в Redis с TTL «Время жизни», что означает, что они автоматически удаляются через 10 или 5 минут, без необходимости использования кода и без заполнения оперативной памяти. Кроме того, если изображения просматривают несколько пользователей, кэширование и обработка не будут повторяться. Кроме того, если пользователь перейдет назад или вперед, все ранее обработанные изображения все равно останутся там, а не только следующее, отдельное изображение.

Таким образом, код рабочего процесса обработки изображений будет таким, и вы можете просто запустить несколько одинаковых рабочих процессов, если хотите более высокую пропускную способность:

connect to Redis
while True:
    # Wait for image, popping next from queue
    image = BRPOP from work queue (implemented as Redis LIST)

    ... do processing with OpenCV

    # Store processed result as Redis STRING with expiry in 10 minutes
    SET imagename processedImage EX 600

БРПОП описан здесь.

СЕТ описан здесь.


Код внутри Tk для помещения одного или нескольких имен файлов в очередь заданий будет выглядеть следующим образом:

connect to Redis

LPUSH one or more filenames to work queue in Redis

LPUSH описан здесь.

А если вы хотите отобразить обработанное изображение, проверьте его существование в Redis и загрузите его, если оно существует. Если его не существует в Redis, повторите запрос — возможно, с помощью RPUSH, чтобы он получил более высокий приоритет, или обработайте его самостоятельно и сохраните результат в Redis.

GET processed image
if image is NULL:
    re-request as above
    wait till ready

GET описан здесь.


Поскольку Redis имеет клиент bash (т. е. командной строки), он также упрощает отладку. Вы можете проверить длину рабочей очереди с вашего терминала. Вы можете помещать элементы в рабочую очередь и удалять элементы из рабочей очереди. Вы можете увидеть, было ли обработано изображение и т. д., находясь вне приложения, без необходимости его изменения.

Примеры:

Предположим, вы вызываете свою рабочую очередь WQUEUE и хотите узнать ее длину, просто используя терминал:

redis-cli LLEN WQUEUE

Или вы хотите перечислить все элементы в очереди:

redis-cli LRANGE WQUEUE 0 -1

Или вы хотите поместить имя файла изображения londonbridge.jpg в рабочую очередь:

redis-cli LPUSH WQUEUE londonbridge.jpg

Или вы хотите узнать, было ли обработано изображение londonbridge.jpg, и если да, то извлечь его в файл:

redis-cli GET londonbridge.jpg > processedImage.jpg

Или вы хотите увидеть названия всех обработанных изображений:

redis-cli KEYS '*'

Обратите внимание, что Redis подключен к сети, поэтому вы можете запустить Redis на одном компьютере и подключиться к нему с любого другого компьютера, указав его имя хоста или IP-адрес:

# Get length of work queue on host 192.168.0.10
redis-cli -h 192.168.0.10 LLEN WQUEUE

Другие вопросы по теме