Qt + OpenCV. Новое устройство GigE интерфейса доступа к сетевым видеокамерам как CvCapture

в 9:18, , рубрики: c++, opencv, qt, Qt Software, обработка изображений, Программирование, метки: , , ,

Как говорил Генрих VIII очередной жене, — «Я вас долго не задержу...»
Если вас, уважаемый читатель, угораздило приобрести видеокамеру, поддержка которой не обеспечена библиотекой OpenCV, а методы работы оной с изображениями ой как нужны, не следует расстраиваться.
Сперва изучим, что нам преподнесли, или что сами, несведующие, купили.

  1. Интерфейс GigE поддерживается SDK, лежащей в свободном доступе или поставленной совместно с товаром. Первый плюс!
  2. Документация более-менее осмыслена. Снова повезло!
  3. Примеры есть! Надо же… Плюс!

Итак, я стал «счастливчиком» по плюсикам этого списка, заполучив камеру Smartek Giganetix GC1921M
Методы SDK работают, но как-то так… Код частично закрыт. Уровень программиста стал понятен из фрагмента кода

...
if (m_selectedDevice->IsConnected()){
			m_disconnectAct->setEnabled(true);
			m_fwUpdateAct->setEnabled(true);
}
...

Если у вас возник вопрос, — «А что тут такого?», — я не смогу отправить вас на машине времени в советский вуз, где за это с вас снимут балл на экзамене. :)
Да, и ладно. Нам-то нужно, всего лишь, подключиться, принять поток и отключиться. Благо, примеры — на месте.

Варианты реализаций собственного устройства видеозахвата.

Спасибо разработчикам библиотеки OpenCV за код, распрпостраняемый под BSD лицензией.
Речь идёт о версии 2.4.2.
Изучим часть исходников в каталоге modules/highgui/src. Как видим, всё несложно. Можно, просто скопировав похожий модуль, например cap_pvapi.cpp, под собственное устройство cap_giganetix.cpp сделать надлежащие правки, внести изменения (новый метод и элементы перечислений enum) в код precomp.hpp и соответствующие файлы директив для cmake и запустить сборку.
Это — первый метод. Основной минус — всё меняется от версии к версии, и пересборки новых релизов неизбежны.
Существует и более стабильный, если так можно назвать, вариант — реализация собственного CvCaptute для нового устройства, совместимого со структурой из OpenCV. Плюс в том, что разработчик помещает всю эту байду все свои зазработки в отдельную библиотеку, которая по требованиям зависимостей подгрузит и SDK на новое устройство, и OpenCV.
Минус, как всегда, в наличии «кота в мешке» — реализации на стороне разработчиков OpenCV скрытой структуры CvCapture.
Рассмотрим последний вариант.

Внешняя библиотека устройства видеозахвата типа CvCapture.

Вначале было слово обеспечим совместимость с макросами, значениями перечислений исходных и заголовочных файлов OpenCV.
macros.hpp

#ifndef MACROS_HPP
#define MACROS_HPP

#define QTGIG_HEARTBEAT_TIME (12000.0)
#define QTGIG_MAX_WAIT_TIME (2.0)
#define QTGIG_IMG_WAIT_TIME (3.0)

#define CV_CAP_GIGANETIX 1300

// GigE additional for highgui_c.h
//enum {
#define CV_CAP_PROP_GIGA_FRAME_SENS_WIDTH   40
#define CV_CAP_PROP_GIGA_FRAME_SENS_HEIGH   41
#define CV_CAP_PROP_GIGA_FRAME_WIDTH_MAX    42
#define CV_CAP_PROP_GIGA_FRAME_HEIGH_MAX    43
#define CV_CAP_PROP_GIGA_FRAME_OFFSET_X     44
#define CV_CAP_PROP_GIGA_FRAME_OFFSET_Y     45
//};


//precomp.hpp double
#define __BEGIN__ __CV_BEGIN__
#define __END__  __CV_END__
#define EXIT __CV_EXIT__

#endif // MACROS_HPP

Участок

//precomp.hpp double
#define __BEGIN__ __CV_BEGIN__
#define __END__  __CV_END__
#define EXIT __CV_EXIT__

приведёт код к правилам разработки модулей OpenCV. В чужой монастырь со своим уставом не лезь!
А так, мы просто добавили некоторые свойства для нашего устройства.
Создадим методы-оболочки для функций SDK нашей камеры. Это позволит минимизировать изменения в нашем коде при возможной правке разработчиками SDK собственного. Думаю, это — хороший стиль.

gige_wrapper.h


#ifndef GIGE_WRAPPER_H
#define GIGE_WRAPPER_H

/**
  module GIGA_WRAPPER
  brief Smartek Giganetix Cameras wrapper
*/

#include <GigEVisionSDK.h>

namespace gigew {

/*----------------------------------------------------------------------------*/
/**
  internal
  fn bool gigew::wrprInitGigEVisionAPI();
  brief Wrapper to GigEVisionAPI function gige::InitGigEVisionAPI ()
  return true - success
  See a gigew::wrprExitGigEVisionAPI

*/
bool
wrprInitGigEVisionAPI();

/*----------------------------------------------------------------------------*/
/**
  internal
  fn void gigew::wrprExitGigEVisionAPI()
  brief Wrapper to GigEVisionAPI function gige::ExitGigEVisionAPI ()
  return true -- success
  See a gigew::wrprInitGigEVisionAPI

*/
bool
wrprExitGigEVisionAPI();

// и так далее...

} //namespace gigew

#endif // GIGE_WRAPPER_H

И, собственно, само наше устройство (cap_giganetix.h)

#ifndef CAP_GIGANENIX_H
#define CAP_GIGANENIX_H

#include <opencv2/highgui/highgui_c.h>
#include "GigEVisionSDK.h"
#include "../../common/macros.hpp"
#include <QObject>

#ifdef HAVE_GIGE_API

  #if !defined WIN32 && !defined _WIN32 && !defined _LINUX
  #define _LINUX
  #endif

  #if defined(_x64) || defined (__x86_64) || defined (_M_X64)
  #define _x64 1
  #elif defined(_x86) || defined(__i386) || defined (_M_IX86)
  #define _x86 1
  #endif

/*----------------------------------------------------------------------------*/
/**
  internal
  struct CvCapture
  brief Copy OpenCV CvCapture internal release.
*/
struct CvCapture
{
    virtual ~CvCapture() {}
    virtual double getProperty(int) { return 0; }
    virtual bool setProperty(int, double) { return 0; }
    virtual bool grabFrame() { return true; }
    virtual IplImage* retrieveFrame(int) { return 0; }
    virtual int getCaptureDomain() { return CV_CAP_ANY; } // Return the type of the capture object: CV_CAP_VFW, etc...
};

/*----------------------------------------------------------------------------*/
/**
  internal
  class CvCaptureCAM_Giganetix
  brief Capturing video from camera via Smartec Giganetix GigEVisualSDK
*/

class Q_DECL_EXPORT CvCaptureCAM_Giganetix : public CvCapture
{
  public:
    CvCaptureCAM_Giganetix();
    virtual ~CvCaptureCAM_Giganetix();

    virtual bool open( int index );
    virtual void close();
    virtual double getProperty(int);
    virtual bool setProperty(int, double);
    virtual bool grabFrame();
    virtual IplImage* retrieveFrame(int);
    virtual int getCaptureDomain()
    {
        return CV_CAP_GIGANETIX;
    }

    bool  start ();
    bool  stop ();

  protected:

    void  init ();
    void  grabImage ();

    gige::IGigEVisionAPI  m_api;
    bool                  m_api_on;
    gige::IDevice         m_device;
    bool                  m_active;

    IplImage* m_raw_image;
    UINT32    m_rawImagePixelType;
    bool      m_monocrome;

};



/*----------------------------------------------------------------------------*/
Q_DECL_EXPORT CvCapture* cvCreateCameraCapture_Giganetix( int index );

/*----------------------------------------------------------------------------*/
#endif

#endif // CQTGIGEVISIONCAPTURE_H

Обратите внимание на то, что объявление struct CvCapture взято из исходных кодов OpenCV (каталог modules/highgui/src, файл precomp.hpp). Вот оно, узкое место библиотеки!

Пишем реализацию класса и метода cvCreateCameraCapture_Giganetix.
Архив проекта заберите по ссылке
Приведу лишь код метода open:

...
/*----------------------------------------------------------------------------*/
bool
CvCaptureCAM_Giganetix::open( int index )
{
  bool b_ret = m_api_on;

  CV_FUNCNAME("CvCaptureCAM_Giganetix::open");
  __BEGIN__;

  if(b_ret)
    b_ret = m_api.IsValid ();

  if(b_ret )
  {
    m_api->FindAllDevices (QTGIG_MAX_WAIT_TIME);

    //TODO - serch device as DevicesList member
    gige::DevicesList DevicesList = m_api->GetAllDevices ();

    m_device = 0;
    b_ret = false;

    for (int i = 0; i < (int) DevicesList.size() && !b_ret; i++)
    {
      if((b_ret = i == index))
      {
        m_device = DevicesList[i];
        b_ret = m_device->Connect ();

        if(b_ret)
        {
          b_ret =
                m_device->SetStringNodeValue("AcquisitionStatusSelector", "AcquisitionActive")
                &&
                m_device->SetStringNodeValue ("TriggerMode", "Off")
                &&
                m_device->SetStringNodeValue ("AcquisitionMode", "Continuous")
                &&
                m_device->SetIntegerNodeValue ("AcquisitionFrameCount", 20)
                ;
        }
      }
    } // for
  }

  if(!b_ret)
  {
    CV_ERROR(CV_StsError, "Giganetix: Error cannot find cameran");
    close ();
  } else {
    start ();
  }

  __END__;

  return b_ret;
}
...

Показано, как использовать макросы согласно стилю программирования от OpenCV.

Файл проекта оформляем как динамически подключаемую библиотеку c обращением к SDK и OpenCV:

#-----------------------------------------------------------
TARGET = QtGigEVisionCapture
TEMPLATE = lib
#CONFIG += release
#-----------------------------------------------------------
DEFINES += QTGIGEVISION_LIBRARY 
  HAVE_GIGE_API

#-----------------------------------------------------------
SOURCES += 
    ../../common/QtGigEVision_global.cpp 
    cap_giganetix.cpp 
    gige_wrapper.cpp

HEADERS +=
    ../../common/QtGigEvision_global.h 
    ../../common/macros.hpp 
    cap_giganetix.h 
    gige_wrapper.h

#-----------------------------------------------------------
unix:!symbian: target.path = /usr/lib
INSTALLS += target

#-----------------------------------------------------------
unix:!macx:!symbian: LIBS += -L/usr/local/lib/ -lGigEVisionSDK

INCLUDEPATH += /usr/local/include/GigEVisionSDK/gige_cpp 
  /usr/local/include/GigEVisionSDK/gige_c
DEPENDPATH += /usr/local/include/GigEVisionSDK/gige_cpp 
  /usr/local/include/GigEVisionSDK/gige_c

#-----------------------------------------------------------
unix: LIBS += -L/usr/lib/ -lopencv_core -lopencv_highgui

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

Под привилегиями cуперпользователя в каталоге /usr/lib размещаем ссылки на новую библиотеку и… приступаем к тестированию.

Тестовый пример.

Просто до безобразия, как два…
Одним словом, как-то так:

#include <QtGui/QApplication>
#include "../../common/macros.hpp"
#include <stdio.h>
#include "cap_giganetix.h"

void print_properties (CvCaptureCAM_Giganetix* cap)
{
  if(cap) {
      printf("Device found.n");
      printf("Sensor Width  = %d.n", (int)cap->getProperty (CV_CAP_PROP_GIGA_FRAME_SENS_WIDTH));
      printf("Sensor Height = %d.n", (int)cap->getProperty (CV_CAP_PROP_GIGA_FRAME_SENS_HEIGH));
      printf("Offset X      = %d.n", (int)cap->getProperty (CV_CAP_PROP_GIGA_FRAME_OFFSET_X));
      printf("Offset Y      = %d.n", (int)cap->getProperty (CV_CAP_PROP_GIGA_FRAME_OFFSET_Y));
      printf("Width         = %d.n", (int)cap->getProperty (CV_CAP_PROP_FRAME_WIDTH));
      printf("Height        = %d.n", (int)cap->getProperty (CV_CAP_PROP_FRAME_HEIGHT));
      printf("Frame Count   = %d.n", (int)cap->getProperty (CV_CAP_PROP_FRAME_COUNT));
      printf("Gain          = %d.n", (int)cap->getProperty (CV_CAP_PROP_GAIN));
  }
}

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);

  CvCapture* capture = cvCreateCameraCapture_Giganetix(0);

  CvCaptureCAM_Giganetix* cap = (CvCaptureCAM_Giganetix*)capture;
  int i_width, i_height, i_offX, i_offY;

  if(cap) {
    i_width = (int)capture->getProperty (CV_CAP_PROP_FRAME_WIDTH);
    i_height = (int)capture->getProperty (CV_CAP_PROP_FRAME_HEIGHT);
    i_offX =  (int)capture->getProperty (CV_CAP_PROP_GIGA_FRAME_OFFSET_X);
    i_offY =  (int)capture->getProperty (CV_CAP_PROP_GIGA_FRAME_OFFSET_Y);
    printf("-------------------------n");
    print_properties (cap);

    printf("-------------------------n");

...


    INT64 i = 0;
    cvNamedWindow("Frame",0);
    while(1) {
      if(i == 1000) {
        printf("------ Reset to original -------n");
        (void)capture->setProperty (CV_CAP_PROP_FRAME_WIDTH, i_width);
        (void)cap->setProperty (CV_CAP_PROP_FRAME_HEIGHT, i_height);
        (void)capture->setProperty (CV_CAP_PROP_GIGA_FRAME_OFFSET_X, i_offX);
        (void)cap->setProperty (CV_CAP_PROP_GIGA_FRAME_OFFSET_Y, i_offY);
        print_properties (cap);
      }
      i++;
      IplImage* frame = cvQueryFrame (capture);
      if(frame)
        cvShowImage ("Frame",frame);

      if((cvWaitKey (3) == 27)) break;
    }
    cvDestroyWindow("Frame");


    cvReleaseCapture(&capture);
  }

  return 0;//a.exec();
}

Рензультат — что-то размывчатое (:)):
image

Спросите, зачем я использую два указателя capture и cap на ...? Не спросите? Очень хорошо!

С уважением.
Успехов!

Автор: coffeesmoke


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js