sábado, 19 de mayo de 2012

Integrating OpenCV in Qt GUI applications

OpenCV is, hands down, the best Computer Vision library out there. If you want to create multi-platform computer vision applications, OpenCV is the way to go. The thing is, it doesn't provide you with the tools to create rich UIs, other than the "basic" controls which will make your app look like it never left the research environment it was created on.

The best multi-platform UI library is Qt. Wouldn't it be nice to use QT to create the UIs and OpenCV to crunch all the data? The problem is, neither Qt nor OpenCV provide tools for smooth interaction. How do you display an OpenCV image in a Qt widget? Not possible, at least not in an easy way.

Let's see how we can fix this. The idea is to have a Qt QWidget class (the base class for all the UI elements in Qt) which is able to display OpenCV images. Qt has the ability to display objects of the type QImage via the QPainter::drawImage() method. Of course, a QImage is no OpenCV image...

Luckily the QImage class has support for in-memory construction, that is, you can assign a QImage to an existing image stored in the memory. But, unluckily, QImage only supports a set of image formats which are not OpenCV's defaults. By default, OpenCV stores color images in memory using the BGR byte ordering instead of RGB which is the format supported by QImage. Also, if you want to display grayscale images, QImage doesn't support regular, 8-bit grayscale images.

For these reasons, some conversion needs to be done prior to displaying, so we can assign a OpenCV image buffer to a QImage.

With this in mind, let's build our QWidget class that displays OpenCV images. It will store internally a QImage which will point to a converted OpenCV image, The QWidget should be able to update the image, so we can display video on the widget itself.

Here's the code. Store in cvimagewidget.h.

#pragma once
#include <QWidget>
#include <QImage>
#include <QPainter>
#include <opencv2/opencv.hpp>

class CVImageWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CVImageWidget(QWidget *parent = 0) : QWidget(parent) {}

    QSize sizeHint() const { return _qimage.size(); }
    QSize minimumSizeHint() const { return _qimage.size(); }

public slots:
    
    void showImage(const cv::Mat& image) {
        // Convert the image to the RGB888 format
        switch (image.type()) {
        case CV_8UC1:
            cvtColor(image, _tmp, CV_GRAY2RGB);
            break;
        case CV_8UC3:
            cvtColor(image, _tmp, CV_BGR2RGB);
            break;
        }

        // QImage needs the data to be stored continuously in memory
        assert(_tmp.isContinuous());
        // Assign OpenCV's image buffer to the QImage. Note that the bytesPerLine parameter
        // (http://qt-project.org/doc/qt-4.8/qimage.html#QImage-6) is 3*width because each pixel
        // has three bytes.
        _qimage = QImage(_tmp.data, _tmp.cols, _tmp.rows, _tmp.cols*3, QImage::Format_RGB888);

        this->setFixedSize(image.cols, image.rows);

        repaint();
    }

protected:
    void paintEvent(QPaintEvent* /*event*/) {
        // Display the image
        QPainter painter(this);
        painter.drawImage(QPoint(0,0), _qimage);
        painter.end();
    }
    
    QImage _qimage;
    cv::Mat _tmp;
};


Now let's try this baby. Put this on main.cpp:

#include <cvimagewidget.h>

#include <QDialog>
#include <QApplication>
#include <QMainWindow>

int main(int argc, char** argv) {
    QApplication app(argc, argv);
    QMainWindow window;
    
    // Create the image widget
    CVImageWidget* imageWidget = new CVImageWidget();
    window.setCentralWidget(imageWidget);
    
    // Load an image
    cv::Mat image = cv::imread("somepicture.jpg", true);
    imageWidget->showImage(image);
    
    window.show();
    
    return app.exec();
}

Compile using qmake and you should see the image in all its glory inside a Qt widget. Also note that if you perform several calls to CVImageWidget::showImage(image), the widget will be refreshed on each call, so you can easily display video frames in the widget in real time.

10 comentarios:

  1. code is perfectly explained, written in easy to understand way, with nice introduction and what is most interesting - compiles out of the box and works just fine (opencv 2.4.0 and qt 5.1.0)

    THANKS A LOT!

    ResponderEliminar
  2. I think I love you.
    Way better than the label method and way simpler than the GL one.

    ResponderEliminar
  3. Hey I am encountering the following error : "collect2: ld returned 1 exit status". How to resolve this?

    pro file contents:

    HEADERS += cvimagewidget.h
    INCLUDEPATH += F:\\OpenCV\\include \

    LIBS += -L \
    F:\\OpenCV\\lib \
    -lopencv_core244d \
    \ \
    -lopencv_highgui244d \
    \ \
    -lopencv_imgproc244d
    SOURCES += main.cpp

    ResponderEliminar
  4. @Shiuli Das: I found that adding a empty virtual destructor did not hurt, and then you can also look at http://stackoverflow.com/a/3087312.

    ResponderEliminar
  5. good tutorial! make sure you include the opencv_imgproc lib in your .pro file.

    ResponderEliminar
  6. I have a question. I use layout->addWidget(imageWidget).
    but I'm encountering the error :
    mainwindow.obj:-1: error: LNK2019: unresolved external symbol "public: __cdecl cvImageWidget::cvImageWidget(class QWidget *)" (??0cvImageWidget@@QEAA@PEAVQWidget@@@Z) referenced in function "public: __cdecl MainWindow::MainWindow(class QWidget *)" (??0MainWindow@@QEAA@PEAVQWidget@@@Z)

    mainwindow.obj:-1: error: LNK2019: unresolved external symbol "public: void __cdecl cvImageWidget::showImage(class cv::Mat const &)" (?showImage@cvImageWidget@@QEAAXAEBVMat@cv@@@Z) referenced in function "public: __cdecl MainWindow::MainWindow(class QWidget *)" (??0MainWindow@@QEAA@PEAVQWidget@@@Z)

    debug\QtNavi.exe:-1: error: LNK1120: 2 unresolved externals

    why did it/

    ResponderEliminar
  7. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  8. Thank you so much ♥
    ( from morocco )

    ResponderEliminar
  9. NJ's first sports betting app to launch in New Jersey
    The NJ Online Gaming Control 문경 출장샵 Board 김천 출장샵 has approved a new online sportsbook for New Jersey 보령 출장안마 customers to launch 여주 출장마사지 on July 서울특별 출장샵 1. The app, named Gopher NY, is a

    ResponderEliminar