Светодиодные полосы с Teensy и Python - PullRequest
0 голосов
/ 21 февраля 2019

В настоящее время я работаю над проектом по управлению светодиодными лентами, которые подключены к плате 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 узких места:

  1. Запись в последовательный порт (self.port.write в методе send).Может быть, это нельзя ускорить, и это приемлемо.

Но что более важно:

Доступ к массиву изображений (image [led, pin, order [channel]] в методе image2data).Когда я изменяю строку, например:

, если 0x80 >> bit & 255:

, код работает в 6-7 раз быстрее (~ 80 FPS).Между прочим, order [channel] используется для преобразования цвета из BGR в GRB.

Короче говоря: чтение цвета из массива изображений происходит очень медленно.Как я могу ускорить преобразование массива изображения в байтовый массив в методе image2data?

Когда вы дошли до этого момента, спасибо за ваше терпение :-) Извините за длинный пост,но это сложный проект, и мне его нелегко объяснить.Я был бы очень признателен за вашу помощь, может быть, кто-то еще может извлечь выгоду из этого.

Заранее спасибо, Al

Ответы [ 2 ]

0 голосов
/ 25 февраля 2019

Спасибо за ваш ответ и ваши улучшения.Я реализую их позже, но, думаю, они не увеличат частоту кадров до требуемых 60 FPS.

Код отправляет 3 x 180 x 8 из-за платы Teensy.Светодиоды подключены к плате с помощью кабеля Ethernet, который имеет 8 контактов, и все 8 контактов должны быть адресованы, в противном случае полосы показывают странные результаты.С другой стороны, в более поздней конфигурации мне нужно более 4 полос, поэтому в настоящее время я не хочу отправлять данные на 8 полос вместо 4. И я не думаю, что код будет работать значительно быстрее.

Как я отмечал в своем первом посте, кажется, что этот кусок кода очень медленный, и я не понимаю, почему: image [led, pin, order [channel]]

Вот код из Processingскетч, который работает как минимум в 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.Я все еще надеюсь, что у кого-нибудь есть идея, в чем проблема с доступом к пикселю массива numpy.

0 голосов
/ 23 февраля 2019

Эта вторая горячая точка может быть слегка улучшена путем поднятия 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. Есть ли вероятность того, что два внутренних цикла необходимо инвертировать?

...