Захват видео: python + gstreamer и немного v4l2-ctl
snegovick — Втр, 03/05/2011 - 21:28
В процессе прикручивания своего нового алгоритма зрения для робота столкнулся с необходимостью определиться, как же я буду граббить кадры. Сразу скажу, что решение принято в пользу gstreamer, в этой работе постараюсь показать, почему так и как это сделать.
Почему gstreamer
На самом деле можно привести много доводов в его пользу:
- gstreamer стабилен
- позволяет конструировать конвейеры с различными источниками вроде файлов (filesrc), видео девайсов (v4l2src) и вообще любых, на которые хватит фантазии
- позволяет втыкать между собственно обработчиком и источником кадров различные фильтры и преобразователи
При всех этих плюсах gstreamer конечно же медленнее и, возможно, более избыточен в сравнении с другими решениями.
Поэтому, будем считать этот выбор обоснованным субъективными пристрастиями автора.
Когда я искал ссылку на документацию по конвейерам (чуть ниже она всё таки есть), то нашел вот это http://processors.wiki.ti.com/index.php/Example_GStreamer_Pipelines и считаю, что одной этой ссылкой можно перевесить почти все минусы gstreamer разом.
Как это сделать
В сети как всегда есть огромное множество хороших примеров написания грабберов камеры с помощью gstreamer, которые меня не устраивают, поэтому будем писать велосипед по-своему.
Основная причина, по которой меня не устраивают грабберы в стиле этого, заключается в том, что мне не нравится идея смешивать логику программы с колбэком, а также в том, что я уже написал граббер на си с помощью элемента appsink, который в данный момент вошел в gst-plugins-base.
Как следует из описания, после запуска конвейера на воспроизведение, достаточно вызывать в цикле метод gst_app_sink_pull_buffer(), который будет возвращать буфер.
В своем проекте я решил оформить граббер в виде класса с методом get_image для получения кадра в формате opencv (ввиду того, что дальше это планируется обрабатывать с помощью opencv).
Элемент appsink имеет несколько интересующих нас настроек. Так, в частности, он позволяет дропать (опция drop) кадры по заполнению очереди, длина которой тоже настраивается (max-buffers). В данном случае мне интересно работать с актуальными кадрами, но период между кадрами может превышать допустимый, поэтому решено было выставить drop=true и max-buffers=1.
В питоне для работы с gstreamer'ом требуются модули gst и pygst (pygst по-видимому лишь для настройки требуемой версии и путей).
import pygst pygst.require("0.10") import gst
Конструирование элемента осуществляется почти как и в си либо методом element_factory_make либо, в случае задания параметров в строке описания элемента, методом parse_launch:
self.appsink = gst.parse_launch("appsink drop=true max-buffers=1")
Кроме appsink я использовал еще ряд элементов : v4l2src для захвата камеры, ffmpegcolorspace и capsfilter для преобразования видео из YUV формата в RGB.
cf = gst.parse_launch("capsfilter caps=\"video/x-raw-rgb,width="+str(width)+ \",height="+str(height)+",bpp=24,red_mask=255, green_mask=65280, blue_mask=16711680, endianness=4321\"") ff = gst.element_factory_make("ffmpegcolorspace", "converter") src = gst.parse_launch("v4l2src device="+device)
После того как все необходимые элементы инициализированы, нужно собрать из них конвейер:
self.pipe = gst.Pipeline(name="ecvpipe") self.pipe.add(src) self.pipe.add(ff) self.pipe.add(cf) self.pipe.add(self.appsink)
Закидывать элементы в конвейер можно в произвольном порядке, последовательность определяется на этапе линковки:
src.link(ff) ff.link(cf) cf.link(self.appsink)
После линковки конвейер запускается, и можно дергать кадры:
self.pipe.set_state(gst.STATE_PLAYING)
На следующем этапе меня ждал неприятный сюрприз: объекты, возвращаемые после parse_launch, не имели нужных методов. Например, self.appsink не имел метода pull_buffer.
Затратив определенные усилия на поиск решения, я, таки, его нашел:
data = self.appsink.emit("pull-buffer")
Преобразование кадра к формату opencv выполняется достаточно просто: нужно создать пустой заголовок, а затем добавить к нему данные. Заголовок можно создать один раз, а затем лишь менять данные.
self.imcur = cv.CreateImageHeader((self.imagewidth, self.imageheight), 8, 3) cv.SetData(self.imcur, data[:], self.imagewidth*3)
Документация по opencv достаточно подробна, вот, например, описание CreateImageHeader.
Стоит отметить, что передавать в SetData следует именно копию исходного массива, а не сам буфер.
В результате у меня получился такой класс:
import pygst pygst.require("0.10") import gst import cv class camerasrc(): def __init__(self, width, height, device): self.appsink = gst.parse_launch("appsink drop=true max-buffers=1") cf = gst.parse_launch("capsfilter caps=\"video/x-raw-rgb,width="+str(width)+",height="+str(height)+",bpp=24,red_mask=255, green_mask=65280, blue_mask=16711680, endianness=4321\"") ff = gst.element_factory_make("ffmpegcolorspace", "converter") src = gst.parse_launch("v4l2src device="+device) self.pipe = gst.Pipeline(name="ecvpipe") self.pipe.add(src) self.pipe.add(ff) self.pipe.add(cf) self.pipe.add(self.appsink) src.link(ff) ff.link(cf) cf.link(self.appsink) self.pipe.set_state(gst.STATE_PLAYING) self.imagewidth = width self.imageheight = height self.imcur = cv.CreateImageHeader((self.imagewidth, self.imageheight), 8, 3) def get_image(self): data = self.appsink.emit("pull-buffer") cv.SetData(self.imcur, data[:], self.imagewidth*3) return self.imcur
Проблема медленного захвата
Мне достаточно много приходится работать с дешевыми вебкамерами, и все они страдают болезнью под названием 1/6 дюймовый cmos сенсор и слабый dsp. Эта болезнь проявляется в том, что при недостаточной освещенности (а это значит, что почти при любой освещенности для устройств подобного типа) камера выдает кадры медленнее. Долгое время я думал, что проблема в АРУ, однако, по-видимому это не так. Я не могу ручаться, что мое решение этой проблемы будет работать со всеми камерами, и даже, что оно будет работать со всеми UVC камерами, потому что, насколько мне известно, некоторые камеры имеют просто отвратную скорость обработки кадра в любом случае. Однако, если установить у камеры фиксированную выдержку, то проблема со скоростью может исчезнуть. Для logitech c200 это делается следующим образом (v4l2-ctl в debian находится в пакете v4l-utils):
v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto_priority=0
Конечно, при отключении авто приоритета картинка немного пострадает, но после этой операции я увидел действительно законные 30 кадров в секунду при любом свете.

Отправить комментарий