Qt Designer & Runtime Qt библиотеки на службе OpenCV разработчика, или тащим IplImage на QLabel

Введение


Как и большинства разработчиков Qt, использующих библиотеку OpenCV, меня заинтересовала тема представления изображения, полученного из web-камеры, как компонент визуального проектирования интерфейса для Qt Designer.
Перерыв кучу информации в сети, заметил, что большинство статей повторяют друг друга, и найти «изюминку» слишком трудно. Думаю, мой обыт создания визуальной компоненты по представлению изобравжения библиотеки OpenCV для Qt Designer будет полезен. К тому же, поделюсь информацией, как разделить библиотеки времени проектирования дизайна и времени выполнения кода. Подобный подход весьма удачно зарекомендовал себя в RAD Delphi для операционных систем семейства Windows.

Замечания

  • Автор coffeesmoke не «разжёвывает» простые истины, методы и тонкости. Код понятен. Функции не превышают 20 строк.
  • Проект и пример использования реализован на Qt Creator для ОС Linux. Разработчики Windows-приложений должны установить настройки в файлох .pro соответственно своей ОС
  • Автор не пускается в дискуссии типа «а так было бы лучше». Нашли оптимальное решение, реализуйте.
  • Автор не отвечает на вопросы настроек Qt Creator и/или Qt Designer. Поисковые системы вам в помощь!
  • Пост несёт ознакомительный характер и служит обозначению направления движения ваших мыслей. Куда они пойдут, вам виднее.


Проектирование и выполнение


Выполнение

Оставим ненадолго библиотеку OpenCV работы с изображениями. Наша задача — универсальная runtime-библиотека (библиотека времени исполнения кода).
Возможно, то, что я сейчас покажу используется повсеместно. Однако, не встретил я на просторах сети по тематике построения плагинов под Qt Designer подобного подхода.
Задача состоит в том, чтобы не «тащить» библиотеку плагина Qt Designera в проект, а обойтись библиотекой времени исполнения. Очевидно, что runtime-библиотеку можно «обрамить» виджетом представления компонента на палитре Qt Dessigner

Дизайнер содержит простой компонент QLabel, способный передавать изображение свойством pixmap. Для наших целей этого достаточно. Обратимся к переводу документации Добавление модулей Qt Designer
и самому первоисточнику информации по ссылке Using Custom Widgets with Qt Designer.Самое полезное — информация о размещении пользовательских библиотек-плагинов! теперь нам известен путь назначения: $$QTDIR/plugin/designer
Оставим обвёртку нового компонента в стороне.Займёмся непосредственно компонентом runtime-библиотеки. Создадим динамически подгружаемую библиотеку с классом CQtOpenCVImage нашего нового виджета.
Заголовочный файл cqtopencvimage.h

#ifndef QTOPENCVIMAGE_H
#define QTOPENCVIMAGE_H


#include <QtDesigner/QDesignerExportWidget>

#include <QWidget>
#include <QUrl>
#include <QLabel>
#include "opencv2/opencv.hpp"
#include <QScopedPointer>

/*----------------------------------------------------------------------------*/

class CQtOpenCVImagePrivate;

/*----------------------------------------------------------------------------*/
class
    #if defined(QDESIGNER_EXPORT_WIDGETS)
      QDESIGNER_WIDGET_EXPORT
    #else
      Q_DECL_EXPORT
    #endif
CQtOpenCVImage
 : public QWidget
{
    Q_OBJECT

    Q_PROPERTY(QUrl           capture         READ getCapture         WRITE slot_setCapture)
    Q_PROPERTY(QString        text            READ getLabelText       WRITE slot_setLabelText)
    Q_PROPERTY(QPixmap        pixmap          READ getPixmap          WRITE slot_setPixmap)
    Q_PROPERTY(bool           scaledContents  READ hasScaledContents  WRITE slot_setScaledContents)
    Q_PROPERTY(bool           grayscale       READ isGrayscale        WRITE slot_setGrayscale)
    Q_PROPERTY(Qt::Alignment  alignment       READ getAlignment       WRITE setAlignment)

  public:
    explicit
    CQtOpenCVImage(QWidget* parent = 0, Qt::WindowFlags f = 0);

    virtual
    ~CQtOpenCVImage();

    QUrl&     getCapture ();
    QString   getLabelText () const;
    const
    QPixmap*  getPixmap () const;
    const
    QImage*   getImage () const;
    Qt::Alignment getAlignment() const;
    void      setAlignment(Qt::Alignment);
    const
    QLabel*   getQLabel () const;
    bool      hasScaledContents() const;
    bool      isGrayscale() const;

  public Q_SLOTS:
    void      slot_setCapture ( const QUrl& );
    void      slot_setLabelText ( const QString& );
    void      slot_setPixmap ( const QPixmap& );
    void      slot_setImage ( const QImage& );
    void      slot_setQLabel ( const QLabel& );
    void      slot_setGrayscale(bool);
    void      slot_setScaledContents(bool);

  Q_SIGNALS:
    void      signal_Grayscale(bool);
    void      signal_ImageChanged();
    void      signal_CaptureChanged();
    void      signal_PixmapChanged();
    void      signal_LabelChanged();
    void      signal_AlignmentChanged();

  private:
    Q_DISABLE_COPY(CQtOpenCVImage)
    Q_DECLARE_PRIVATE(CQtOpenCVImage)


QScopedPointer d_ptr;

};
/*----------------------------------------------------------------------------*/

#endif // QTOPENCVIMAGE_H


Обратите внимание на участки, выделенные специальным шрифтом
  • Включение заголовочного файла QtDesigner/QDesignerExportWidget гарантирует подключение необходимого макроса QDESIGNER_WIDGET_EXPORT при использовании в файле проекта директивы
    DEFINES += QDESIGNER_EXPORT_WIDGETS
  • Класс CQtOpenCVImagePrivate скрывает внутренние переменные и указатели из объявления основного класса CQtOpenCVImage. Мне кажется это правильным: не следует перегружать объявления ненужной информацией;
  • Объявление единственной приватной переменной-указателя
    QScopedPointer<CQtOpenCVImagePrivate> d_ptr;
    на структуру внутренней реализации алгоритмов и данных в рамках правил Qt. ИМХО, «хороший тон».

Наш класс CQtOpenCVImage предоставляет для Qt Designer свойства (Q_PROPERTY) визуального управления изображением. Методы записи свойст реализованы слотами void slot_**** (***).
И так, наследуемый от QWidget класс представления изображения, полученного из web(IP)-камеры при вызуальном проектировании даёт доступ к свойствам:
  • capture — URL устройства видеозахвата или номер web-камеры;
  • text — текстовая надпись. Аналог свойства text QLabel;
  • pixmap — указатель на объект типа QPixmap. Аналог свойства pixmap QLabel;
  • scaledContents — масштабируемость изображения. Аналог свойства scaledContents QLabel;
  • grayscale — булевый переключатель цветового режима вывода изображения, где значение true соответствует градациям серого, а false — цветное (RGB);
  • alignment — выравнивание содержимого text и pixmap. Аналог свойства alignment QLabel;


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

Перейдём к коду реализации классов (файл cqtopencvimage.cpp).

Рассмотрим скрытый класс CQtOpenCVImagePrivate, его атрибуты и методы.
class CQtOpenCVImagePrivate
{
    Q_DECLARE_PUBLIC(CQtOpenCVImage)

  public:
    CQtOpenCVImagePrivate(CQtOpenCVImage* owner);
    virtual
    ~CQtOpenCVImagePrivate();

    CQtOpenCVImage* q_ptr; // указатель на экземпляр основного класса

    QGridLayout*  f_grid_layout; // сетка выравнивания находящихся в ней компонент по всему периметру
    QLabel*       f_label;// экземпляр типа QLabel для вывода изображения

    QUrl          f_capture_path;// URL устройства видеозахвата или его номер 
    QImage*       p_qt_image; // Указател на экземпляр изображения типа QImage

    CvCapture*    p_capture; // Указател на экземпляр устройства видеозахвата в рамках описания OpenCV
    IplImage*     p_opencv_frame; // Указател на текущий фрем из p_capture
    uint          f_grayscaled:1; // признак представления изобравжения в градациях серого

    void init (); // инициализация данных
    void close (); // корекктное закрытие всех указателей

    void free_qt_image (); // освобождение памяти экземпляра p_qt_image
    void new_qt_image ();// распределение  памяти для экземпляра p_qt_image

    void free_capture (); // освобождение памяти экземпляра p_capture
    void new_capture (); // распределение  памяти для экземпляра p_capture
};

Перейдём к реализации методов.
/*----------------------------------------------------------------------------*/
void
CQtOpenCVImagePrivate::init ()
{
  Q_ASSERT(q_ptr);

  f_grid_layout = new QGridLayout(q_ptr);
  Q_ASSERT(f_grid_layout);

  f_label = new QLabel(q_ptr/*, Qt::WindowNoState*/);
  Q_ASSERT(f_label);

  f_grid_layout->addWidget (f_label);

  p_qt_image = 0,
  p_capture = 0,
  p_opencv_frame = 0,
  f_grayscaled = 0;
}

/*----------------------------------------------------------------------------*/
inline void
CQtOpenCVImagePrivate::close ()
{
  free_qt_image ();
  free_capture ();
}

/*----------------------------------------------------------------------------*/
CQtOpenCVImagePrivate::CQtOpenCVImagePrivate(CQtOpenCVImage* owner)
  : q_ptr(owner)
{
  init ();
}


/*----------------------------------------------------------------------------*/
CQtOpenCVImagePrivate::~CQtOpenCVImagePrivate ()
{
  close ();

  if(!(f_label->parent ()))
    delete f_label;

  if(!(f_grid_layout->parent ()))
    delete f_grid_layout;
}

/*----------------------------------------------------------------------------*/
inline void
CQtOpenCVImagePrivate::free_qt_image ()
{
  if(p_qt_image) {
    delete p_qt_image;
    p_qt_image = 0;
  }
}

/*----------------------------------------------------------------------------*/
inline void
CQtOpenCVImagePrivate::free_capture ()
{
  if(p_capture) {
    cvReleaseCapture(&p_capture);
    p_capture = 0;
  }
}

Код понятен и читаем. Вопросов не должно возникнуть. Параметр CQtOpenCVImage* owner конструтора представляет указател на объект CQtOpenCVImage, содержащий экземпляр скрытого класса как переменную CQtOpenCVImage::d_ptr.
Как говорил доктор, герой Леонида Броневого, из фильма «Формула любви», — «Так, я продолжу...».

Рассмотрим определение устройства захвата методом new_capture
/*----------------------------------------------------------------------------*/
void
CQtOpenCVImagePrivate::new_capture ()
{
  free_capture ();//освобождение устройства захвата

  bool b_ok;
  int i_index = f_capture_path.toString ().toInt (&b_ok); // определение номера камеры, если это номер, а не URL

/* информация к размышлению:
   перезахват устройства, если камера реализована как CGI для вебсервера, возвращающий по одному кадру
  if(b_ok) {
    p_capture = cvCreateCameraCapture(i_index);
    if(p_capture)
      if(!p_opencv_frame)
        p_opencv_frame = cvQueryFrame (p_capture);
  } else {
    while((p_capture =cvCaptureFromFile(f_capture_path.toString ().toStdString ().c_str ()))) {
      p_opencv_frame = cvQueryFrame (p_capture);
      new_qt_image ();
      cvWaitKey (1000);
    }
  }
*/
  // да, ну его, этот перезахват. Перезахватим по запросу методами OpenCV.
  p_capture =
      b_ok ?
        cvCreateCameraCapture(i_index) : 
        cvCaptureFromFile(f_capture_path.toString ().toStdString ().c_str ());

  p_opencv_frame = p_capture ? cvQueryFrame (p_capture) : 0; // получение фрейма методом из OpenCV

  new_qt_image (); // формирование экземпляра QImage
}

Для подробного знакомства с OpenCV читайте Оглавление

Формирование изображения типа QImage:
/*----------------------------------------------------------------------------*/
void
CQtOpenCVImagePrivate::new_qt_image ()
{
  if(!p_capture) return;

  free_qt_image (); // освободим указатель на изображение

  if(p_opencv_frame) {
  // создадим OpenCV экземпляр изображения согласно параметру оттенка серого
    IplImage *_tmp =
      f_grayscaled ?
        cvCreateImage(
          cvSize( p_opencv_frame->width, p_opencv_frame->height ),
          IPL_DEPTH_8U,
          1
        ) :
        cvCloneImage (p_opencv_frame)
    ;

    try
    {
      // пребразование цвета в RGB или оттенки серого
      cvCvtColor( p_opencv_frame, _tmp, f_grayscaled ? CV_RGB2GRAY : CV_BGR2RGB );

      // формирование изображения типа QImage
      p_qt_image = new QImage(
                 (const uchar*)(_tmp->imageData),
                 _tmp->width,
                 _tmp->height,
                 _tmp->widthStep,
                 (f_grayscaled ? QImage::Format_Indexed8 : QImage::Format_RGB888)
               );

      emit q_ptr->signal_ImageChanged (); // оповещение о готовности

      q_ptr->slot_setPixmap (QPixmap::fromImage (*p_qt_image)); // отрисовать изображение на QLabel

    } catch(...) {
     // упс... Дрова -- в исходное, пельмени разлепить!
      close ();
    }

    // не забываем прибрать за собой!
    cvReleaseImage(&_tmp);
  }
}



Думаю, с подводной частью айсберга всё понятно.

Реализация основного класса ещё проще. Даже, лень объяснять. Смотрите сами:
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
CQtOpenCVImage::CQtOpenCVImage(QWidget* parent, Qt::WindowFlags f)
  : QWidget(parent, f),
    d_ptr(new CQtOpenCVImagePrivate(this))
{
}

/*----------------------------------------------------------------------------*/
CQtOpenCVImage::~CQtOpenCVImage()
{
}

/*----------------------------------------------------------------------------*/
QUrl& CQtOpenCVImage::getCapture ()
{
  return (d_func ()->f_capture_path);
}

/*----------------------------------------------------------------------------*/
QString CQtOpenCVImage::getLabelText () const
{
  return (d_func ()->f_label->text ());
}

/*----------------------------------------------------------------------------*/
bool  CQtOpenCVImage::hasScaledContents() const
{
  return d_func ()->f_label->hasScaledContents ();
}

/*----------------------------------------------------------------------------*/
void CQtOpenCVImage::slot_setScaledContents(bool Value )
{
  d_func ()->f_label->setScaledContents (Value);
}

/*----------------------------------------------------------------------------*/
bool CQtOpenCVImage::isGrayscale() const
{
  return d_func ()->f_grayscaled;
}

/*----------------------------------------------------------------------------*/
void
CQtOpenCVImage::slot_setGrayscale( bool Value )
{
  if(d_func ()->f_grayscaled != Value)
  {
    d_func ()->f_grayscaled = Value;

    if(!(d_func ()->p_capture))
      d_func ()->new_capture ();
    else
      d_func ()->new_qt_image ();

    emit signal_Grayscale (d_func ()->f_grayscaled);
  }
}

/*----------------------------------------------------------------------------*/
void  CQtOpenCVImage::slot_setLabelText ( const QString& Value )
{
  d_func ()->f_label->setText (Value);
}

/*----------------------------------------------------------------------------*/
void  CQtOpenCVImage::slot_setCapture ( const QUrl& Value )
{
// Раскомментировать при необходимости исключения переинициализации захвата
//  if(getCapture ().toString () != Value.toString () || !d_func ()->p_opencv_frame)
//  {
    d_func ()->f_capture_path = Value.toString ().trimmed ();
    d_func ()->new_capture ();
    emit signal_CaptureChanged ();
//  }
}

/*----------------------------------------------------------------------------*/
const QPixmap*
CQtOpenCVImage::getPixmap () const
{
  return ((const QPixmap*)(d_func ()->f_label->pixmap ()));
}

/*----------------------------------------------------------------------------*/
void
CQtOpenCVImage::slot_setPixmap ( const QPixmap& Value )
{
  d_func ()->f_label->setPixmap (Value);
  emit signal_PixmapChanged ();
}

/*----------------------------------------------------------------------------*/
const QImage*
CQtOpenCVImage::getImage () const
{
  return(d_func ()->p_qt_image);
}

/*----------------------------------------------------------------------------*/
void
CQtOpenCVImage::slot_setImage ( const QImage& Value )
{
  d_func ()->free_qt_image ();
  d_func ()->p_qt_image = new QImage(Value);
  slot_setPixmap (QPixmap::fromImage (*(d_func ()->p_qt_image)));
  emit signal_ImageChanged ();
}

/*----------------------------------------------------------------------------*/
void
CQtOpenCVImage::slot_setQLabel ( const QLabel& Value)
{
  d_func ()->f_label->setText (Value.text ());
  emit signal_LabelChanged ();
}

/*----------------------------------------------------------------------------*/
Qt::Alignment
CQtOpenCVImage::getAlignment() const
{
  return(d_func ()->f_label->alignment ());
}

/*----------------------------------------------------------------------------*/
void
CQtOpenCVImage::setAlignment(Qt::Alignment Value)
{
  d_func ()->f_label->setAlignment (Value);
  emit signal_AlignmentChanged ();
}

/*----------------------------------------------------------------------------*/
const QLabel*
CQtOpenCVImage::getQLabel () const
{
  return ((const QLabel*)(d_func ()->f_label));
}


Формируем runtime-библиотеку. Опишем файл проекта images.pro:
TARGET = QtOpenCVImages
TARGET = $$qtLibraryTarget($$TARGET)
TEMPLATE = lib

CONFIG += debug_and_release

Не забываем включить:
DEFINES += QDESIGNER_EXPORT_WIDGETS

Определим путь, куда попадут файлы библиотек:

unix:!symbian {
    target.path = $$PWD/../../../../lib
    DESTDIR    = $$PWD/../../../../lib
    INSTALLS += target
}


Добавим файлы класса:
SOURCES += \
    cqtopencvimage.cpp

HEADERS += \
    cqtopencvimage.h


Определим пути заголовков файлов библиотек OpenCV и пути зависимостей проекта:
INCLUDEPATH += /usr/include/opencv2
DEPENDPATH += /usr/include/opencv2


Добавим библиотеку OpenCV (core и highgui) к нашему проекту:
#win32:CONFIG(release): LIBS += -L/usr/lib/ -lopencv_core
#else:win32:CONFIG(debug, debug|release): LIBS += -L/usr/lib/ -lopencv_cored
#else:
unix: LIBS += -L/usr/lib/ -lopencv_core

#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../../../../../usr/lib/release/ -lopencv_highgui
#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../../../../../usr/lib/debug/ -lopencv_highguid
#win32:CONFIG(release): LIBS += -L/usr/lib/release/ -lopencv_highgui
#else:symbian: LIBS += -lopencv_highgui
#else:
unix: LIBS += -L/usr/lib/ -lopencv_highgui


Уважаемые разработчики ОС Windows, я «соскочил» с иглы Microsoft давно и прошу прощения, что закомментировал пути библиотек для вашей ОС. Вы — народ неглупый, разберётесь.
*NIX-оиды, используйте команду ln -s, чтобы создать ссылки на ваши библиотеки из места расположения в каталог /usr/lib
Один раз создав ссылку, просто, пересобирайте проект, и всё будет работать нормально!

Итак, мы создали динамическую библиотеку Qt визуального компонента отображения рисунка, полученного из web-камеры средствами OpenCV.

Исходный код: Wu a La (в прямом смысле)

Проектирование

Настало время подключить нашу runtime-библиотеку к оболочке класса-плагина для Qt Designer.
Создадим проект библиотеки-плагина:
TARGET = QtOpenCVWidgets
TARGET = $$qtLibraryTarget($$TARGET)
TEMPLATE = lib

CONFIG += designer plugin release

DEFINES += QDESIGNER_EXPORT_WIDGETS

SOURCES += \
    qtopencvimageplugin.cpp \
    ../qtopencvwidgets.cpp

HEADERS +=\
    qtopencvimageplugin.h \
    ../qtopencvwidgets.h \
    ../runtime/images/cqtopencvimage.h

RESOURCES += \
    qtopencvimages.qrc

unix:!symbian {
     target.path = $$PWD/../../../lib
     DESTDIR    = $$PWD/../../../lib
    INSTALLS += target
}

INCLUDEPATH += /usr/include/opencv2
DEPENDPATH += /usr/include/opencv2

#win32:CONFIG(release, debug|release): LIBS += -L/usr/lib/ -lQtOpenCVImages
#else:win32:CONFIG(debug, debug|release): LIBS += -L/usr/lib -lQtOpenCVImages
#else:symbian: LIBS += -lQtOpenCVImages
#else:
unix: LIBS += -L/usr/lib -lQtOpenCVImages

INCLUDEPATH += $$PWD/../runtime/images
DEPENDPATH += $$PWD/../runtime/images


Похож на проект Runtime, не так ли? Рассмотрм некоторые отличия:
  • Изменилось название библиотеки: TARGET = QtOpenCVWidgets;
  • Параметр CONFIG += designer plugin release содержит тэги plugin и designer;
  • Плагин реализован в двух файлах по одному на заголовок и реализацию qtopencvimageplugin.* qtopencvwidgets.*<.i>;
    Всё так же присутствует зависимость от заголовков библиотеки OpenCV т.к. добавилась зависимость от включённого заголовка cqtopencvimage.h нашего виджета QtOpenCVImages;
    Включена динамическая runtime библиотека libQtOpenCVImages по символьной ссылке из /usr/lib/libQtOpenCVImages.so, указывающей на её реальное расположение в каталоге проекта

Создаём виджет-плагин Qt Designer по всем канонам Qt: Creating Custom Widgets for Qt Designer (надо же,coffeesmoke, как громко сказано!):
qtopencvimageplugin.h
#ifndef QTOPENCVIMAGEPLUGIN_H
#define QTOPENCVIMAGEPLUGIN_H

#include <QObject>
#include <QDesignerCustomWidgetInterface>

class QtOpenCVImagePlugin : public QObject, public QDesignerCustomWidgetInterface
{
    Q_OBJECT
    Q_INTERFACES(QDesignerCustomWidgetInterface)

  public:
    explicit QtOpenCVImagePlugin(QObject *parent = 0);

    QString     name() const;
    QString     includeFile() const;
    QString     group() const;
    QIcon       icon() const;
    QString     toolTip() const;
    QString     whatsThis() const;
    bool        isContainer() const;

    QWidget*  createWidget(QWidget *parent);
    void      initialize(QDesignerFormEditorInterface *core);
    bool      isInitialized() const;
    QString   domXml() const;

  signals:

  public slots:

  private:
    bool f_init;

};

#endif // QTOPENCVIMAGEPLUGIN_H

Основная идея: объявить как Q_INTERFACE, наследуясь от QDesignerCustomWidgetInterface и перегрузить методы класса-интерфеса дизайнера согласно своим требованиям.

qtopencvimageplugin.cpp
#include "qtopencvimageplugin.h"
#include "cqtopencvimage.h"

QtOpenCVImagePlugin::QtOpenCVImagePlugin(QObject *parent) :
  QObject(parent),
  f_init(false)
{
}

QString
QtOpenCVImagePlugin::name() const
{
  return "CQtOpenCVImage";// имя класса виджета
}

QString
QtOpenCVImagePlugin::includeFile() const
{
    return QLatin1String("cqtopencvimage.h"); // название включения в файл ui_*.h новой формы
}

QString
QtOpenCVImagePlugin::group() const
{
    return tr("OpenCV Widgets"); // название группы отображения на панели компонентов Qt Designer
}

QIcon
QtOpenCVImagePlugin::icon() const
{
  return QIcon(":QtOpenCVLogo.png"); // значок виджета
}

QString
QtOpenCVImagePlugin::toolTip() const
{
    return QString();
}

QString
QtOpenCVImagePlugin::whatsThis() const
{
    return QString();
}

bool
QtOpenCVImagePlugin::isContainer() const
{
    return false;
}

QWidget*
QtOpenCVImagePlugin::createWidget(QWidget *parent)
{
    return new CQtOpenCVImage(parent); // вот она, реализация экземпляра класса нашего виджета!
}

void
QtOpenCVImagePlugin::initialize(QDesignerFormEditorInterface *core)
{
// установка признака инициализации при помещении на форму
    if (f_init) return; 
    f_init = true;
}

bool
QtOpenCVImagePlugin::isInitialized() const
{
    return f_init;
}

QString
QtOpenCVImagePlugin::domXml() const
{
// основные параметры для дизайнера
    return "<ui language=\"c++\">\n"
               " <widget class=\"CQtOpenCVImage\" name=\"QtOpenCVImage\">\n"
               "  <property name=\"geometry\">\n"
               "   <rect>\n"
               "    <x>0</x>\n"
               "    <y>0</y>\n"
               "    <width>400</width>\n"
               "    <height>200</height>\n"
               "   </rect>\n"
               "  </property>\n"
               " </widget>\n"
               "</ui>";
}



Осталось совсем немного: создать общую обвёртку-контейнер для всех наших виджетов группы Qt <-> OpenCV
qtopencvwidgets.h
#ifndef QTOPENCVWIDGETS_H
#define QTOPENCVWIDGETS_H

#include <QObject>
#include <QtPlugin>
#include <QDesignerCustomWidgetCollectionInterface>

class QtOpenCVWidgets :
    public QObject,
    public QDesignerCustomWidgetCollectionInterface
{
    Q_OBJECT
    Q_INTERFACES(QDesignerCustomWidgetCollectionInterface)
public:
    explicit QtOpenCVWidgets(QObject *parent = 0);
    QList<QDesignerCustomWidgetInterface*> customWidgets() const { return f_plugins; }

private:
    QList<QDesignerCustomWidgetInterface *> f_plugins;
};

#endif // QTOPENCVWIDGETS_H


qtopencvwidgets.cpp
#include "qtopencvwidgets.h"
#include "images/qtopencvimageplugin.h"

QtOpenCVWidgets::QtOpenCVWidgets(QObject *parent) :
    QObject(parent)
{
    f_plugins << new QtOpenCVImagePlugin(this);
}

//Q_DECLARE_INTERFACE(QtOpenCVWidgets, "com.trolltech.Qt.Designer.QtOpenCV")

Q_EXPORT_PLUGIN2(qtopencvwidgetsplugin, QtOpenCVWidgets)

Интерес представляет конструктор, а именно: f_plugins << new QtOpenCVImagePlugin(this);. При последующем добавлении новых компонентов в коллекцию достаточно будет переписать конструктор, добавив ещё один оператор f_plugins << new <Очередной>Plugin(this);
Применение

Открываем Qt Designer и ищем на палитре компонентов наш СQtOpenCVImage. Пмещаем его на новую форму. Меняем capture на нужный URL 192.168.0.20:8080/image.jpg. В данном случае, это Java-аплет сервера камеры, предоставляющий текущий запрашиваемый кадр
Поставим «галочку» на свойстве grayscale
Исходный код: Если скачали ранее, не переходите по ней.

Выводы


  • Научились создавать библиотеку времени выполнения для реализации собственного класса на основе визуальных компонент Qt;
  • Научились обрамлять runtime библиотеку в плагин Qt Designer, сделав приложения независимыми от плагина дизайнера на время выполнения;

Приложения


Простой тестовый пример

Создадим стандартное Qt GUI приложение, откроем основную форму окна, поместим с неё наш компонент, создадим немного сервисных управлений, соберём и запустим.
Меняем «Адрес...» на значение 0 (встроенная видеокамера) и смотрим на изменения изображения.

Исходный код: Если скачали ранее, не переходите по ней.

Что дальше?


  • Оптимизация текущего проекта;
  • Описание проверенного визуального компонента поиска границ изображения методом Кэнни (Canny): CQtOpenCVCannyWidget на основе класса CQtOpenCVCanny;
  • Связь между QtOpenCVImage и QtOpenCVCanny

Всего доброго.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 13

    +1
    Ваш проект однозначно заслуживает внимание. Мой вам совет, залейте, его на gitorious или github.

    Мне очень понравилось. Спасибо. (ушел читать исходники)
      +1
      И да, ребята, поднимаем парню карму — дельный проект написал!
        +1
        Оптимизирую в следующем посте. Плюс, сегодня проверил метод Canny из известной cvCanny OpenCV как визувльный компонент на основе QLabel и QSpinBox. Выложу на днях.
        0
        Изображение статическое? Не преуменьшая заслуг автора — это фигня.
        Самое веселье начинается при чтении фреймов с камеры отдельным тредом, вот где задница. Qt серьезно вставляет палки в колеса, а OpenCV с только poll-доступом(на момент моего с ней общения) к камере только добавляет веселья.
          0
          Динамическое, если настроить capture. Например, в данном проекте удалить комментарии из
          void CQtOpenCVImage::slot_setCapture ( const QUrl& Value )

          Основная цель проекта — разделить designtime и runtime, а OpenCV — пример (нужно было по работе)
            0
            >>Например, в данном проекте удалить комментарии

            В упор не вижу, где здесь именно _цикл_ чтения.
            Возможно, я неоднозначно выразился, под «статическое» имелось ввиду, что оно не обновляется без запроса.
              0
              Ошибка вышла: нужно см. др. функцию. — void CQtOpenCVImagePrivate::new_capture (). Сменить комменты блоков.
                0
                Уважаемые коллеги, учитывая верные замечания, следует отделить устройство захвата от прорисовки изображения. Сейчас этим и займусь!
            +1
            Хм… уже скоро на дворе Qt5 и Quick 2.0, зачем на виджетах то да с кривым дизайнером?
              +2
              Ну… когда проект на десятки тысяч строк кода и написан на 4.x, а фича нужна уже вчера,
              то клиент врядли захочет слушать как космические корабли бороздят просторы большого театра
              что Qt5 и Quick 2.0 скоро на дворе.
                0
                С ужасом посмотрел на код кутима Оо
              0
              Раз уж используете макрос Q_DECLARE_PRIVATE «в рамках правил Qt», то почему бы заодно не использовать макрос Q_D?
                0
                … лениво :))) Использую d_func() :)) Отнеситесь к коду как шаблону :)

              Only users with full accounts can post comments. Log in, please.