Функция hist
в matplotlib.pyplot работает очень медленно, что, по-видимому, связано с выбранной мной структурой. Я создал в Tkinter переднюю панель, которая запускает цикл управления камерой. Чтобы обеспечить оперативность цикла управления, я создал класс ImageProcessor
, который собирает, обрабатывает и отображает изображения в cv2. Объект ImageProcessor
выполняется в своем собственном потоке. Это работает до тех пор, пока я не пытаюсь построить гистограмму изображения.
Поскольку Tkinter не является потокобезопасным, я использую Agg в качестве бэкэнда и рисую нарисованный холст pyplot.figure
с помощью cv2. Расчет гистограммы изображения с помощью pyplot.hist
занимает более 20 секунд. Самостоятельный расчет гистограммы занимает всего 0,5 секунды.
Как это проявляется? Нужно ли запускать Matplotlib из основного потока или этого достаточно, если с ним взаимодействует только один поток (как в моем случае)? Или в моем коде есть еще одно недоразумение?
import threading
import time
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from timeit import default_timer as timer
from datetime import timedelta
import queue
class ImageProcessor(threading.Thread):
def __init__(self):
matplotlib.use('Agg')
threading.Thread.__init__(self)
# initialize plot for histograms
self.hist_fig = plt.figure()
self.loop = True
self.continuous_acquisition_var = False
self.a = None
def run(self):
while self.loop:
self.a = np.random.uniform(low=0, high=16384, size=12320768).reshape((4096, 3008))
self.hist_fig.clf() # clear histogram plot
start = timer()
plt.hist(self.a.flatten(), bins=256, range=(0.0, 16384), fc='r', ec='r')
end = timer()
print(timedelta(seconds=end - start))
def stop(self):
self.loop = False
def ctl_loop(command):
ctl_loop_var = True
img_proc = ImageProcessor()
img_proc.daemon = True
img_proc.start()
while ctl_loop_var: # main loop
while not command.empty():
q_element = command.get()
task = q_element[0]
data = q_element[1]
func = getattr(img_proc, task)
func(data)
if task == "stop":
ctl_loop_var = False
if __name__ == '__main__':
cmd_queue = queue.Queue()
ctl = threading.Thread(target=ctl_loop, args=(cmd_queue, ))
ctl.daemon = True
ctl.start()
time.sleep(40)
cmd_queue.put(('stop', ''))
Если я правильно понимаю, гистограмму можно рассчитать с помощью numpy в отдельном потоке, а результат можно представить в виде гистограммы. Или даже в нескольких потоках, каждый из которых имеет часть входных данных, а затем суммируется. Если вместо точной гистограммы требуется только приближение, можно использовать подходящее подмножество данных, что приведет к визуально почти идентичной гистограмме.
@MichaelButscher спасибо за этот анализ. Что бы вы тогда предложили? Я пытался улучшить ситуацию при обновлении до numpy 1.26.4, но это не помогло.
@JohanC, конечно, вы могли бы просто уменьшить объем данных. Но это не объясняет разницу в поведении. Если я запускаю код расчета гистограммы автономно, он работает намного быстрее.
К сожалению, я смог сузить его только до цикла for, но не дальше. Кто-то, обладающий большими знаниями о внутренних компонентах numpy, может рассказать больше. В настоящее время моя единственная идея — вместо этого попробовать многопроцессорность, используя, если возможно, общую память. Это должно быть как минимум быстрее, чем нынешний подход.
Решение простое и не имеет ничего общего с plt.hist
. Просто добавьте строку time.sleep(0.01)
в основной цикл. Причина в том, что многопоточность — это не то же самое, что многопроцессорность. Все потоки используют один и тот же процесс (ЦП), то есть одновременно может выполняться только один поток. В вашем случае основной поток (цикл while ctl_loop_var
) как можно быстрее проверяет, истинно ли ctl_loop_var
, не позволяя другому потоку что-либо делать. Поэтому убедитесь, что вы не создаете ненужную нагрузку на процессор. Это относится и к многопроцессорной обработке, хотя влияние может быть менее заметным.
def ctl_loop(command):
ctl_loop_var = True
img_proc = ImageProcessor()
img_proc.daemon = True
img_proc.start()
while ctl_loop_var: # main loop
while not command.empty():
q_element = command.get()
task = q_element[0]
data = q_element[1]
if task == "stop":
img_proc.stop()
ctl_loop_var = False
else:
func = getattr(img_proc, task)
func(data)
time.sleep(.01) # give the other thread time to process
Кроме того, код также исправляет две ошибки в исходном коде:
ImageProcessor.stop
принимает только один аргументImageProcessor.stop
не останавливал поток должным образом, когда основной поток перегружен.Я также заметил, что ваша реализация с plt.hist(..., bins=256, range=(0.0, 16384))
примерно в 7 раз быстрее, чем с plt.hist(..., bins=list_of_bins)
! Не меняйте его ;).
спасибо за ваш анализ. Перегрузка процессора непроверенным циклом while — довольно глупая ошибка. Я должен был увидеть это сам... Спасибо за вашу поддержку.
Если вы хотите довести скорость до предела. Попробуйте рассчитать гистограмму с помощью numba
: numba.pydata.org/numba-examples/examples/density_estimation/…
Я мог отследить его до этого звонка от
np.histogram
. Так что, похоже, это не связано с matplotlib.