Category Archives: OpenCV

Ambilight своими руками: Часть 5 – Tuning

После того, как всё заработало в предыдущем посте, осталось сделать финальную настройку, чтобы светодиоды более точно передавали изображение на экране. Для начала посмотрим на картинку, на основе которой мы выставляем цвета, с помощью простого скрипта, который делает чтение буфера из EasyCap и сохраняет его в файл. Как обычно мой любимый vim screenshot.py:

import numpy as np
import cv2
import sys

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print "Failed to initialize"
    exit(0)

ret, frame = cap.read()

if not ret:
    print "Failed to read frame"
    exit(0)

cv2.imwrite(sys.argv[1],frame)

Единственная новшество тут – это запись в файл с помощью функции cv2.imwrite, которая сама определяет формат сохранения в зависимости от расширения файла. То есть, если туда передать .jpg – то будет JPEG, а если .png то PNG.

sys.argv[1] – это чтение параметра из командной строки. Нулевой элемент – это имя скрипта. Таким образом, объединив эти две вещи, скрипт будет сохранять в формат файла в зависимости от параметра который мы передадим. Проверьте переключатель формата на устройстве конвертации из HDMI в аналоговый видеосигнал. У меня он установлен в NTSC поэтому делаю первый снимок с соответствующим именем:

sudo python screenshot.py /run/ntsc.jpg

ntsc_defНа снимке экрана видно, что слева и справа здоровенная чёрная рамка, поменьше снизу и маленькая сверху. Именно из-за этой рамки цвет светодиодов так сильно отличается. В дополнение к этому картинка обрезана слева и справа. Посмотрим, что будет если попробовать PAL. Переведите переключатель на конвертере в другое положение и повторите процедуру, чтобы получить второй снимок и решить, какой из режимов предпочтительнее.

sudo python screenshot.py /run/pal.jpeg

pal_defТут также здоровенная рамка, да ещё и картинка сильнее обрезана справа и снизу. Так что мой выбор очевиден – NTSC. Если у вас такая же ситуация, то переведите переключатель обратно.

Теперь нам нужно модифицировать наш скрипт управления лентой, чтобы он не учитывал чёрные пиксели рамки, а работал только с той частью, которая показывает картинку с устройства. Для этого нужно померить количество пикселей с каждой стороны в любом графическом редакторе, например mspaint-е. После этого заведём четыре переменные, которые и будут определять начало значимых для нас пикселей на картинке. Для этого откроем наш скрипт и добавим новые переменные. vim run.py:

import numpy as np
import time
import cv2
import sys
from neopixel import *

LED_PIN = 18
LED_HEIGHT = 15
LED_WIDTH = 25
LED_FREQ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 255
LED_INVERT = False

xborderl = 23
xborderr = 21
ybordert = 2
yborderb = 9

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

strip = Adafruit_NeoPixel(LED_HEIGHT*2+LED_WIDTH*2,LED_PIN,LED_FREQ,\
                                LED_DMA,LED_INVERT,LED_BRIGHTNESS)
strip.begin()

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
    print "unable to initialize!"
    exit(0)

ret,frame1 = cap.read()
ret,frame2 = cap.read()

while(cap.isOpened()):
    ret,frame1 = cap.read(frame1)
    ret,frame2 = cap.read(frame2)
    img = frame2[ybordert:frame2.shape[0]-yborderb,\
                        xborderl:frame2.shape[1]-xborderr]

Последняя строчка определяет как раз границы, по-которым мы будем обрезать. Теперь нам нужно уже resize-ить не всю картинку, а только значимую область:

    rsz1 = cv2.resize(img, (LED_HEIGHT, LED_WIDTH))

а всё остальное остаётся по старому:

    for i in range(LED_WIDTH):
        BGR = rsz1[LED_WIDTH-i-1,0]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[0,i]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_WIDTH, color)
    for i in range(LED_WIDTH):
        BGR = rsz1[i,LED_HEIGHT-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[LED_WIDTH-1,LED_HEIGHT-i-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH*2, color)
    strip.show()

Пришло время попробовать. В youtube полно видео для тестирования ambilight. Мне понравилось вот это (не забудьте включить полный экран):

Вот как это выглядит у меня:

Так же нашёл прикольное видео с Laser show – решил попробовать свой билд на нём:

В общем видно, что ещё есть над чем работать. Задержка заметна, видно пропуски, да и ленту надо чуть переклеить. Думаю, что на новогодних каникулах разберусь

Ambilight своими руками: Часть 4 – всё вместе

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

Для этого можно модифицировать скрипт, который мы написали для тестирования.

cp test.py run.py
vim run.py

теперь помимо тех библиотек, которые мы уже подключили нам понадобится ещё time и neopixel.

import numpy as np
import time
import cv2
import sys
from neopixel import *

Далее нужно инициализировать библиотеку для работы со светодиодами, предварительно объявив несколько переменных, которые будут описывать нашу ленту и способ доступа к ней:

LED_PIN = 18
LED_HEIGHT = 15
LED_WIDTH = 25
LED_FREQ = 800000 
LED_DMA = 5
LED_BRIGHTNESS = 255
LED_INVERT = False

strip = Adafruit_NeoPixel(LED_HEIGHT*2+LED_WIDTH*2,LED_PIN,LED_FREQ,\
                                LED_DMA,LED_INVERT,LED_BRIGHTNESS)
strip.begin()

теперь инициализируем видеозахват используя OpenCV, этот код уже был написан в прошлый раз:

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
    print "unable to initialize!"
    exit(0)

создаем переменные с которыми будем работать и читаем кадр из устройства

ret,frame1 = cap.read()

теперь нам нужен цикл который будет все время вычитывать кадры и управлять лентой:

while(cap.isOpened()):
    ret,frame1 = cap.read(frame1)

EasyCap выдает картинку 720×480 пикселей на основе, которой нам надо выставлять цвет для светодиодов. Самый простой способ, который я вижу это просто сжать эту самую картинку до размера количества светодиодов, то есть из 720×480 должно получиться 25×15. К счастью в OpenCV это делается одной строчкой:

 rsz1 = cv2.resize(frame1, (LED_HEIGHT, LED_WIDTH))

а дальше всё просто, каждый светодиод – это просто пиксель на краю полученной картинки. Прочитав цвет каждого пикселя мы и выставим их для светодиодов.

    for i in range(LED_WIDTH):
        BGR = rsz1[LED_WIDTH-i-1,0]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i, color)

Тут следует сказать о двух особенностях.

  1. У меня лента начинается в нижнем левом углу телевизора и идёт по часовой стрелки если смотреть на экран. Соответственно мне нужны пиксели с левой стороны картинки то есть X=0, а Y=высота картинки – индекс, так как начало картинки находится в верхнем левом углу
  2. В Картинках, представленных как массивы numpy, сначала идут Y координаты, а потом X

Дальше нужно повторить всё тоже самое только для верха, правого бока и низа:

    for i in range(LED_HEIGHT):
        BGR = rsz1[0,i]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_WIDTH, color)
    for i in range(LED_WIDTH):
        BGR = rsz1[i,LED_HEIGHT-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[LED_WIDTH-1,LED_HEIGHT-i-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT*2+LED_WIDTH, color)

Единственное, на что нужно обратить внимание – это то, что каждый раз вызывая setPixelColor вы должны сдвигать index светодиода для которого вы выставляете цвет. В первом цикле у нас 15 светодиодов, соответственно во втором цикле мы уже выставляем цвет для 15+i-го светодиода, а в 3ем цикле для 15+25+i-го и т.д..

Теперь, когда цвет для всех светодиодов задан осталось только отправить эти данные на ленту:

    strip.show()

Вот и всё – скрипт готов, пришло время запускать.

sudo python run.py

светодиоды зажгутся и если вы запустите кино или просто свернёте все окна то увидите, что некоторые (или все) светодиоды начнут неприятно моргать:

Первое, что можно сделать, чтобы избавиться от этого – это разогнать Raspberry Pi 2 используя стандартную утилиту raspi-config.

sudo raspi-config

выберите overclocking

raspiconfigзатем выберите preset pi2 – 1000MHz

raspiconfig-pi2presetПерезагрузитесь и попробуйте ещё раз. Должно стать лучше, но не идеально. Причина тому, видимо в том, что мы читаем только один кадр, в то время, как EasyCap предосталвяет buffer из двух. Чтобы проверить это можно воспользоваться всё тем же v4l2-ctl и опцией –all:

v4l2allв голову приходит только один фикс – читать два раза. Для этого сделаем небольшое изменение для нашего скрипта vim run.py на строчке 30:

ret,frame1 = cap.read()
ret,frame2 = cap.read()

и на строчках 34-37

    ret,frame1 = cap.read(frame1)
    ret,frame2 = cap.read(frame2)

    rsz1 = cv2.resize(frame2, (LED_WIDTH, LED_HEIGHT))

и попробуем ещё раз

sudo python ryn.py

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

Ambilight своими руками: Часть 3 – софтверная

Если вы прочитали мой предыдущий пост, все подключили и у вас тоже железо, что и у меня – не спешите радоваться, всё только начинается. Теперь нужно всё настроить.

Обновите ваш Raspbian

sudo apt-get update
sudo apt-get upgrade

Для начала убедитесь, что ваш EasyCap девайс работает. В этом вам помогут команды lsusb и dmesg | tail:

lsusb
dmesg | tail

вывод должен выглядеть примерно так:

lsusbdmesgТут видно, что появился новый USB девайс 1b71:3002, что это USBTV007 от fushicai. Дальше нам понадобятся утилиты Video4linux. С помощью v4l2-ctl –list-devices найдите свой EasyCap. В моем случае это просто /dev/video0.

v4l2ctllistОтлично, запомните (или запишите) результат v4l2-ctl – тот что выглядит как /dev/video0. Теперь используя, например, mplayer можно посмотреть картинку предварительно запустив Иксы:

startx
sudo apt-get -y install mplayer
mplayer tv:// -tv device=/dev/video0 -hardframedrop

вместо /dev/video0 может быть что-то другое – смотри вывод v4l2-ctl –list-devices.

Тут вас ждёт первая засада: чёрный или зелёный экран.

20151119_205807Проверьте кабели и побалуйтесь с переключателем PAL-NTSC:

20151103_220807Или же вы можете указать PAL или NTSC в командной строчке для mplayer

mplayer tv:// -tv device=/dev/video0:norm=NTSC -hardframedrop

20151119_210935Python+OpenCV

Cледующий шаг – это поставить и настроить Python+OpenCV. Python – это скриптовый язык программирования, подробнее о нём можете прочитать например в Wikipedia. Скорее всего Python уже установлен на Raspberry Pi, тем не менее имеет смысл проверить версию, так как я тестировал только с версией 2.7.3.

Далее вам нужно поставить две python библиотеки. Это numpy и OpenCV. Они так же доступны через apt-get.

sudo apt-get install python-numpy
sudo apt-get install python-opencv

Numpy – это библиотека для научных вычислений, а OpenCV – это библиотека машинного зрения. Для того, чтобы проверить, что всё установлено и работает как надо, можно написать простенький скрипт на Python-е. Я люблю vim, но вы можете использовать любой другой текстовый редактор, например nano test.py.

Импортируем библиотеки numpy, OpenCV и sys для последующего расширения и работы с системными функциями.

import numpy as np
import cv2
import sys

Эта часть инициализирует видеозахват для устройства /dev/video0. Если ваш EasyCap имеет другой номер – используйте его вместо 0. И проверяем, что инициализация прошла успешно.

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
  exit(0)

Читаем кадр из устройства и проверяем, что чтение прошло успешно.

ret,frame = cap.read()

if (not ret):
  exit(0)

И сохраняем в файл.

cv2.imwrite('/run/test.jpeg',frame)

Запускаем наш Python скрипт, чтобы посмотреть как оно работает:

sudo python test.py

Для того, чтобы посмотреть картинки в консоли, я использую fbi.

sudo apt-get install fbi
sudo fbi /run/test.jpeg

20151119_211614

_rpi_ws281x

Следующий шаг – это начать работу со светодиодной лентой. На просторах интернета есть разные реализации. Наиболее часто попадающаяся это rpi_ws281x, в частности инструкия на сайте Adafruit ссылается на неё, однако мне так и не удалось её поставить и заставить работать отдельно на Raspberry Pi 2, в то время как на Raspberry Pi B+ всё работает. Спасибо ребятам из pimoroni, за их волшебный unicorn_hat, который решил все проблемы  и простому shell скрипту, который устанавливает все необходимое. Просто качаете и запускаете.

Сначала ставим саму библиотеку, как описано здесь:

sudo apt-get install build-essential python-dev git scons swig
git clone https://github.com/jgarff/rpi_ws281x.git
cd rpi_ws281x/
scons

После того как библиотека скомпилирована и установлена, нужно её немножко подправить и для этого то и понадобится скрипт от ребят из pimoroni.

wget get.pimoroni.com/unicornhat
chmod +x unicornhat
./unicornhat
sudo pip install unicornhat

Чтобы проверить работоспособность можно запустить один из exmaple-ов.

20151119_222410А в следующей серии вы узнаете как скрестить OpenCV и Adafruit_NeoPixel чтобы управлять светодиодами в зависимости от изображения на экране