Светодиодные полосы с Teensy и Python
В настоящее время я работаю над проектом по управлению светодиодными лентами, которые подключены к плате Teensy 3.2, которая подключена к ПК с Windows. Технически он основан на этом проекте: https://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Есть также проект, реализованный в vvvv: https://vvvv.org/contribution/realtime-led-control-with-teensy3.xoctows2811
Пока оба работают нормально. Я пытаюсь перенести программу movie2serial (касательно проекта на pjrc.com) на Python.
Итак, я нашел этот проект: https://github.com/agwn/movie2serial_py
Это не работало из коробки, но с несколькими модификациями я заставил это бежать. Вот мой код класса, который получает изображение, преобразует его в байтовый массив и отправляет его на последовательный порт:
import serial
import numpy as np
class Teensy:
def __init__(self, port='COM3', baudrate=115200, stripes=4, leds=180):
self.stripes = stripes
self.leds = leds
self.connected = True
try:
self.port = serial.Serial(port, baudrate)
except:
self.connected = False
def close(self):
if not self.connected:
return
self.black_out()
self.port.close()
def send(self, image):
data = list(self.image2data(image))
data.insert(0, 0x00)
data.insert(0, 0x00)
data.insert(0, ord('*'))
if not self.connected:
return
self.port.write(''.join(chr(b) for b in data).encode())
def black_out(self):
self.send(np.zeros((self.leds,self.stripes,3), np.uint8))
def image2data(self, image):
buffer = np.zeros((8*self.leds*3), np.uint8)
byte_count = 0
order = [1,2,0]
for led in range(self.leds):
for channel in range(3):
for bit in range(8):
bits_out = 0
for pin in range(self.stripes):
if 0x80 >> bit & image[led,pin,order[channel]]:
bits_out |= 1 << pin
buffer[byte_count] = bits_out
byte_count += 1
return buffer
Работает, но медленно (~ 13 FPS на моем компьютере).
Чтобы объяснить код: я создаю простую анимацию с помощью cv2 и отправляю изображение (numpy ndarray с 4 x 180 пикселей, потому что у меня есть 4 светодиодные полосы по 180 светодиодов в каждой) в метод send экземпляра Teensy. Метод send отправляет изображение в метод image2data для преобразования изображения в байтовый массив, помещает несколько байтов в начало и отправляет все это в Teensy.
В этом коде 2 узких места:
- Запись в последовательный порт (self.port.write в методе send). Может быть, это нельзя ускорить, и это приемлемо.
Но что более важно:
Доступ к массиву изображений (image[led,pin,order[channel]] в методе image2data). Когда я изменяю строку, например:
если 0x80 >> бит & 255:
код работает в 6-7 раз быстрее (~ 80 FPS). Кстати, order [channel] используется для преобразования цвета из BGR в GRB.
Короче говоря: чтение цвета из массива изображений происходит очень медленно. Как я могу ускорить преобразование массива изображения в байтовый массив в методе image2data?
Когда вы дойдете до этого момента, спасибо за ваше терпение:-) Я извиняюсь за длинный пост, но это сложный проект и не так легко объяснить для меня. Буду очень признателен за вашу помощь, может быть, кто-то еще может извлечь выгоду из этого
Заранее спасибо, Ал
2 ответа
Спасибо за ваш ответ и ваши улучшения. Я реализую их позже, но, думаю, они не увеличат частоту кадров до необходимых 60 кадров в секунду.
Код отправляет 3 х 180 х 8 из-за доски Teensy. Светодиоды подключены к плате с помощью кабеля Ethernet, который имеет 8 контактов, и все 8 контактов должны быть адресованы, в противном случае полосы показывают странные результаты. С другой стороны, в более поздней конфигурации мне нужно более 4 полос, поэтому в настоящее время я не хочу отправлять данные на 8 полос вместо 4. И я не думаю, что код будет работать значительно быстрее.
Как я отмечал в своем первом посте, кажется, что этот фрагмент кода очень медленный, и я не понимаю, почему: image[led,pin,order[channel]]
Вот код из эскиза обработки, который работает как минимум в 10 раз быстрее, чем скрипт Python:
void image2data(PImage image, byte[] data, boolean layout) {
int offset = 3;
int x, y, xbegin, xend, xinc, mask;
int linesPerPin = image.height / 8;
int pixel[] = new int[8];
for (y = 0; y < linesPerPin; y++) {
if ((y & 1) == (layout ? 0 : 1)) {
xbegin = 0;
xend = image.width;
xinc = 1;
} else {
xbegin = image.width - 1;
xend = -1;
xinc = -1;
}
for (x = xbegin; x != xend; x += xinc) {
for (int i=0; i < 8; i++) {
pixel[i] = image.pixels[x + (y + linesPerPin * i) * image.width];
pixel[i] = colorWiring(pixel[i]);
}
for (mask = 0x800000; mask != 0; mask >>= 1) {
byte b = 0;
for (int i=0; i < 8; i++) {
if ((pixel[i] & mask) != 0) b |= (1 << i);
}
data[offset++] = b;
}
}
}
}
Я не могу поверить, что Python намного медленнее, чем Java. Я все еще надеюсь, что у кого-нибудь есть идея, в чем проблема с доступом к пикселю массива.
Эта вторая горячая точка может быть немного улучшена путем подъема order[channel]
вне этого внутреннего цикла (сохраняя channel_index = order[channel]
внутри петли order
), а затем писать
if 0x80 >> bit & image[led,pin,channel_index]:
это будет небольшое улучшение. Это также похоже на подъем 0x80 >> bit
Уровень выше может сохранить это, будучи излишне рассчитанным 8 раз. Сохранить как mask
и ты бы
if mask & image[led,pin,channel_index]:
Вместе они могут стоить несколько FPS.
Но, глядя на ваш код, что-то с тем, как эти циклы вложены, выглядит неправильно. для 180 x 4 RGB светодиодов я бы ожидал, что вам нужно будет отправить 180 x 4 x 3 байта в Teensy. Но код отправляет 3 x 180 x 8. Есть ли вероятность того, что два внутренних цикла необходимо инвертировать?