illumium.org

Главная › Блоги › Блог snegovick

Захват видео: python + gstreamer и немного v4l2-ctl

snegovick — Втр, 03/05/2011 - 21:28

В процессе прикручивания своего нового алгоритма зрения для робота столкнулся с необходимостью определиться, как же я буду граббить кадры. Сразу скажу, что решение принято в пользу gstreamer, в этой работе постараюсь показать, почему так и как это сделать.

Почему gstreamer

На самом деле можно привести много доводов в его пользу:

  1. gstreamer стабилен
  2. позволяет конструировать конвейеры с различными источниками вроде файлов (filesrc), видео девайсов (v4l2src) и вообще любых, на которые хватит фантазии
  3. позволяет втыкать между собственно обработчиком и источником кадров различные фильтры и преобразователи

При всех этих плюсах 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 кадров в секунду при любом свете.

  • gstreamer
  • logitech c200
  • opencv
  • python
  • v4l2
  • Блог пользователя - snegovick

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

Содержимое этого поля является приватным и не будет отображаться публично.
  • Доступные HTML теги: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".

Подробнее о форматировании

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
  _____   _  __  __  ____           _     
|___ | | | \ \/ / | ___| ___ | |__
/ / | | \ / |___ \ / __| | '_ \
/ / | | / \ ___) | | (__ | |_) |
/_/ |_| /_/\_\ |____/ \___| |_.__/
Введите код, изображенный в стиле ASCII-арт.
RSS-материал

Навигация

  • Подшивки
  • Фотоальбомы

Работает на Drupal, система с открытым исходным кодом.

(L) 2010, Illumium.Org. All rights reversed ^_~