Monday 27 May 2013

Working with Video Using OpenCV and QT - Part 2

This tutorial was written because of a request from a previous tutorial. In this tutorial, we improve upon the work done in that tutorial by adding a track-bar and display duration of the video. Also a few errors, I discovered will also be corrected in this tutorial. So let get cracking.

Add widgets to GUI

This is an extension to a previous tutorial so I will only point out changes or additions to the previous work so as not to repeat myself. Open the mainwindow.ui file, this file could be edited manually but for this tutorial we would use the designer.
  • Add a horizontal track-bar to the GUI, this would be used to the adjust the position in the video.  
  • Add two labels to the GUI; place one on the left and another on the right of the horizontal track-bar ( or however suits your needs) the left one would show the current time into the video whereas the right  one would be used to show the total time of the video.  The GUI should now look similar to the image above.

Player Class Definition

Now we add a few function definitions to Player class header file -player.h.
#ifndef PLAYER_H
#ifndef PLAYER_H
#define PLAYER_H
#include <QMutex>
#include <QThread>
#include <QImage>
#include <QWaitCondition>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
class Player : public QThread
{    Q_OBJECT
 private:
    ....
    VideoCapture *capture;
    Mat RGBframe;
    QImage img;
 signals:
     ......
 protected:
     ......
 public:
    .......
    //set video properties 
    void setCurrentFrame( int frameNumber);
    

    //Get video properties
    double getFrameRate();
    double getCurrentFrame();
    double getNumberOfFrames();
};

#endif // VIDEOPLAYER_H
The class definition is still simple and straightforward. We add a few public setter and getter functions to enable us grab some important video parameters. Also I change the capture variable to a pointer I discovered that it was not possible to reload a new video once one has been loaded so to correct this I initialize a new VideoCapture instance when a new video is loaded. So the method used to access the VideoCapture instance members must be changed from the "." to the "->" notation.

Player Class Implementation

Here is the constructor for the Player class.
bool Player::loadVideo(string filename) {
    capture  =  new cv::VideoCapture(filename);

    if (capture->isOpened())
    {
        frameRate = (int) capture->get(CV_CAP_PROP_FPS);
        return true;
    }
    else
        return false;
}

void Player::run()
{
    int delay = (1000/frameRate);
    while(!stop){
        if (!capture->read(frame))
        {
            stop = true;
        }
        .....
    }
}
In the loadVideo() method, we use the instance of the VideoCapture class to load the video and set the frame rate. As you should already know the VideoCapture class is from the OpenCV library
double Player::getCurrentFrame(){

    return capture->get(CV_CAP_PROP_POS_FRAMES);
}

double Player::getNumberOfFrames(){

    return capture->get(CV_CAP_PROP_FRAME_COUNT);
}

double Player::getFrameRate(){
    return frameRate;
}

void Player::setCurrentFrame( int frameNumber )
{
    capture->set(CV_CAP_PROP_POS_FRAMES, frameNumber);
}
Here are the getter and setter function to access the video information. I ran into a glitch, with ffmpeg where the capture->get(CV_CAP_PROP_FRAME_COUNT) function returned the wrong frame count. The solution might be to update my ffmpeg. I would update the post once the solution is confirmed.
Player::~Player()
{
    mutex.lock();
    stop = true;
    capture.release();
    delete capture;
    condition.wakeOne();
    mutex.unlock();
    wait();
}
Here is the rest of the Player class, in the destructor we release the VideoCapture object, also we allocated memory with the new keyword it must be freed.

MainWindow Class Definition

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>
#include <player.h>
#include <QTime>

namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    .......
    
private slots:
    .........
    QString getFormattedTime(int timeInSeconds);

    void on_horizontalSlider_sliderPressed();

    void on_horizontalSlider_sliderReleased();

    void on_horizontalSlider_sliderMoved(int position);
private:
    ........
};
#endif // MAINWINDOW_H
Here is the class definition for the MainWindow class, we include the 3 event slots for the new horizontal slider and a update some other functions

Mainwindow class implementation

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    .......

    ui->pushButton_2->setEnabled(false);
    ui->horizontalSlider->setEnabled(false);
}
We disable the play button and the horizontal slider; these would be enabled once a video is loaded.
void MainWindow::updatePlayerUI(QImage img)
{
    if (!img.isNull())
    {
        ui->label->setAlignment(Qt::AlignCenter);
        ui->label->setPixmap(QPixmap::fromImage(img).scaled(ui->label->size(),
                                           Qt::KeepAspectRatio, Qt::FastTransformation));
        ui->horizontalSlider->setValue(myPlayer->getCurrentFrame());
        ui->label_2->setText( getFormattedTime( (int)myPlayer->getCurrentFrame()/(int)myPlayer->getFrameRate()) );
    }
}
The updatePlayerUI slot receives a QImage and resizes it to fit the label (keeping the aspect ratio) which will be used to display. It displays the image by setting the label pixmap. We also update the horizontal slider position and the label that displays the elapsed time. We calculate the duration of the video but dividing the total number of frames by the frame rate.
void MainWindow::on_pushButton_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this,
                                          tr("Open Video"), ".",
                                          tr("Video Files (*.avi *.mpg *.mp4)"));
    QFileInfo name = filename;

    if (!filename.isEmpty()){
        if (!myPlayer->loadVideo(filename.toAscii().data()))
        {
            QMessageBox msgBox;
            msgBox.setText("The selected video could not be opened!");
            msgBox.exec();
        }
        else{
            this->setWindowTitle(name.fileName());
            ui->pushButton_2->setEnabled(true);
            ui->horizontalSlider->setEnabled(true);
            ui->horizontalSlider->setMaximum(myPlayer->getNumberOfFrames());
            ui->label_3->setText( getFormattedTime( (int)myPlayer->getNumberOfFrames()/(int)myPlayer->getFrameRate()) );
        }
    }
}

QString MainWindow::getFormattedTime(int timeInSeconds){

    int seconds = (int) (timeInSeconds) % 60 ;
    int minutes = (int) ((timeInSeconds / 60) % 60);
    int hours   = (int) ((timeInSeconds / (60*60)) % 24);

    QTime t(hours, minutes, seconds);
    if (hours == 0 )
        return t.toString("mm:ss");
    else
        return t.toString("h:mm:ss");
}

void MainWindow::on_horizontalSlider_sliderPressed()
{
    myPlayer->Stop();
}

void MainWindow::on_horizontalSlider_sliderReleased()
{
    myPlayer->Play();
}

void MainWindow::on_horizontalSlider_sliderMoved(int position)
{
    myPlayer->setCurrentFrame(position);
    ui->label_2->setText( getFormattedTime( position/(int)myPlayer->getFrameRate()) );
}

This is the remaining part of the MainWindow Class, the getFormattedTime function takes the time in seconds and formats it for display, the rest is self explanatory. You can Download the full code Here.

Final words...

This is just a simple tutorial to help anyone get started with videos in OpenCV and QT. Please let me know if this was helpful and ask questions and give suggestions(if any) in the comments. Happy Coding!

21 comments:

  1. Thanks Sthephen it's useful for me.

    I have a question, please give me a hint: Im updating 3 qlabels from a single video source,

    the first one is the original video,
    the second one is the video in the hsv color space, the third one (where the problem is) Im updating a mask for segmenting by colors.
    This last one sometimes just gets partially updated. You can see this pics :

    minute 0:00 everything look well

    http://postimg.org/image/8bckwb29b/

    after a few seconds :

    http://postimg.org/image/ah6vqt5pr/

    Im doing all image processing inside Player's run function :


    void Player::run()
    {
    ...
    ...
    ...

    if (frame.channels()== 3){

    cv::cvtColor(frame, RGBframe, CV_BGR2RGB);

    cvtColor( frame, hsv, CV_BGR2HSV );

    img = QImage((const unsigned char*)(RGBframe.data),
    RGBframe.cols,RGBframe.rows,QImage::Format_RGB888);


    emit originalImage(img);


    imgHSV = QImage((const unsigned char*)(hsv.data),
    hsv.cols,hsv.rows,QImage::Format_RGB888);


    emit processedImageHSV(imgHSV);

    Mat bw;

    inRange(hsv, Scalar(101, 110, 112), Scalar(118 , 255, 255), bw);

    imgBW = QImage((uchar*) bw.data, bw.cols, bw.rows, bw.step, QImage::Format_Indexed8);



    emit lastProcesedImage(imgBW);


    }

    }

    Im managing 3 signals from this thread, connected to 3 differents slots that update 3 qlabels

    //code

    {
    ...
    ...
    videoPrincipal = new Player();

    QObject::connect(videoPrincipal, SIGNAL(originalImage(QImage)),
    this, SLOT(updatePlayerQuad1(QImage))); //updates a label

    QObject::connect(videoPrincipal, SIGNAL(processedImageHSV(QImage)),
    this, SLOT(updatePlayerQuad2(QImage)));
    //updates a label

    QObject::connect(videoPrincipal, SIGNAL(lastProcesedImage(QImage)),
    this, SLOT(updatePlayerPrincipal(QImage)));
    //updates a label
    ...
    ...
    }

    Thanks in advance. :)

    ReplyDelete
  2. Thank you. This was very useful for a project that I'm currently working on.

    ReplyDelete
  3. Hi,

    I am working on a project on a micro-controller board, "Adaptive vehicle tracking". Where Cars and Buses in a Video has to be identified. Can you help me in this regard....how to identify a car or truck in a video using opencv and Qt.

    In a traffic video, we have to anlayze how many cars, truck or buses are available using their shape and update in a file...so please help me..or provide any tutorial to k.sundeep2007@gmail.com

    ReplyDelete
  4. Hello! I've just started OpenCV and Qt few days ago and encounter with your tutorial. It is very helpful for a beginner like me. Because the code is not much but still good enough for the relevant application. Thank you very much. Good work!

    ReplyDelete
  5. hello! i'm afraid that i'm not working your project.
    current my error is The selected video could not be opened!
    i want your solution. good luck

    ReplyDelete
  6. Thanks Stephen Nneji

    ReplyDelete
  7. whenever i try to load a message
    i get the following message "the selected video could not be opened"

    any idea ?

    ReplyDelete
    Replies
    1. have a look at this http://stackoverflow.com/questions/11481566/linux-opencv-videocapture-not-opening-the-video-file

      Delete
  8. Starting /home/ibx/build-video-Desktop_Qt_5_5_0_GCC_32bit-Debug/video...
    The program has unexpectedly finished.
    /home/ibx/build-video-Desktop_Qt_5_5_0_GCC_32bit-Debug/video crashed

    ReplyDelete
  9. while running this code........i get fallowing errors plz help urgently........
    Starting /home/ibx/build-video-Desktop_Qt_5_5_0_GCC_32bit-Debug/video...
    The program has unexpectedly finished.
    /home/ibx/build-video-Desktop_Qt_5_5_0_GCC_32bit-Debug/video crashed

    ReplyDelete
    Replies
    1. There are a number of problems that can cause this error. You have to use the debugger to find out which line is causing the crash. You can watch this video to see how to debug using qt creator
      https://www.youtube.com/watch?v=B7UsWtyhXh4

      Delete
  10. Hi !
    Thanks for this tutorial but I am having trouble building it :

    Linking CXX executable calibration
    Undefined symbols for architecture x86_64:
    "Player::processedImage(QImage const&)", referenced from:
    Player::run() in player.cpp.o
    "vtable for Player", referenced from:
    Player::Player(QObject*) in player.cpp.o
    Player::~Player() in player.cpp.o
    NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    make[2]: *** [src/tools/calibration] Error 1
    make[1]: *** [src/tools/CMakeFiles/calibration.dir/all] Error 2
    make: *** [all] Error 2


    Thank you for your help,

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Hi Stephen Nneji! 'nanosleep' was not declared in this scope

    ReplyDelete
  13. hi stephen really this is useful to me..but am getting Error in QTime t like :

    /home/user/QT -programs/OpencvQT/mainwindow.cpp:70: error: variable 'QTime t' has initializer but incomplete type

    i followed the instruction what you given there. i combined this slider with Previous tutorial code but its not working for me..am New to this QT and Opencv please help me.

    ReplyDelete
    Replies
    1. Check that you have included QTime in your mainwindow.h

      Delete
    2. ya thank you stephen its working now..here am trying to add Two push buttons with Next and previous. if i pressed Next button it should have to go Next frame and if i pressed Previous button it should go previous frame.so for this what i need to do please help me regarding this.am new to opencv and QT..

      Delete
  14. Your code project not found. Can you send code for me to my email? My email: hungdocong82@gmail.com. Thanks a lot.

    ReplyDelete
    Replies
    1. https://www.dropbox.com/s/cyfagcwdxfgc4ge/ExtendedPlayer.tar.bz2?dl=0

      Delete
  15. Awesome ! Exactly what I needed ! Thank you

    ReplyDelete
  16. How i can load video run in separate thread?
    myPlayer->loadVideo(filename.toAscii().data()

    than i help me i tray run in with RTSP address.

    ReplyDelete