1

I have an app that has to pull frames from video, transform one a little, transform one a lot, and simultaneously display them in GUI. In a worker thread, there's an OpenCV loop:

while(1) {
    cv::VideoCapture kalibrowanyPlik; 
    kalibrowanyPlik.open(kalibracja.toStdString()); //open file from url
    int maxFrames = kalibrowanyPlik.get(CV_CAP_PROP_FRAME_COUNT);
    for(int i=0; i<maxFrames; i++) //I thought it crashed when finished reading the first time around
    {
        cv::Mat frame;
        cv::Mat gray;
        cv::Mat color;

        kalibrowanyPlik.read(frame);
        cv::cvtColor(frame, gray, CV_BGR2GRAY);
        cv::cvtColor(frame, color, CV_BGR2RGB);

        QImage image((uchar*)color.data, color.cols, color.rows,QImage::Format_RGB888);
        QImage processedImage((uchar*)gray.data, gray.cols, gray.rows,QImage::Format_Indexed8);

        emit progressChanged(image, processedImage);
        QThread::msleep(50);
    }
}

And this is how frames are placed in GUI

void MainWindow::onProgressChagned(QImage image, QImage processedImage) {
    QPixmap processed = QPixmap::fromImage(processedImage);
    processed = processed.scaledToHeight(379);
    ui->labelHsv->clear();
    ui->labelHsv->setPixmap(processed);

    QPixmap original = QPixmap::fromImage(image); //debug points SIGSEGV here
    original = original.scaledToHeight(379);
    ui->labelKalibracja->clear();
    ui->labelKalibracja->setPixmap(original);
}

The RGB image always crashes, grayscale image never crashes (tested). Why is the RGB image crashing?

edit: I've just discovered that if I change msleep(50) to msleep(100) it executes perfectly. But I don't want that. I need at least 25 frames per second, 10 is not acceptable... why would that cause a SIGSEGV

Petersaber
  • 851
  • 1
  • 12
  • 29
  • Impossible to say from what you show us. But the only reasonable location where in this code a segfault can happen is the (uchar*)color.data pointer in QImage image(...). Most likely color.cols, color.rows don't agree with the memory size behind color.data. But this is only a speculation. – Greenflow May 08 '15 at 10:59
  • @Greenflow I don't know what more to show you, there is literally nothing else (yet) that does anything with these images edit: added more info – Petersaber May 08 '15 at 11:02
  • Really no much more than I can tell you. Try to comment out **QImage image((uchar*)color.data, color.cols, color.rows,QImage::Format_RGB888);** Does it still crash? You said it crashes only when color is involved. This functions is the only one I see, which uses pointers. So maybe QImage::Format_RGB888 is wrong. Probably your color format is in truth smaller and QImage tries to access data beyond color.data. – Greenflow May 08 '15 at 11:07
  • RGB888 must be wrong. I've replaced original with a jpg file and set msleep back to 50, and it loops nicely. edit: RGB888 is the only one that doesn't crash the app on frame 1 – Petersaber May 08 '15 at 11:11
  • Frames from video are usually not RGB, or BGR... They are usually in a yuv format. – Greenflow May 08 '15 at 11:17

2 Answers2

2

Standard issue. Problem is memory management! See my other answer. In comments there is a good link.

So in your code QImage doesn't copy and doesn't take ownership of memory of matrix. And later on when matrix is destroyed and QImage tries access this memory (QImage is copied by creating shallow copy) you have a segfault.


Here is a code form this link (I've tweak it a bit), for some reason this site has some administration issues (some quota exceeded), that is why I'm pasting it here.
inline QImage  cvMatToQImage( const cv::Mat &inMat )
   {
      switch ( inMat.type() )
      {
         // 8-bit, 4 channel
         case CV_8UC4:
         {
            QImage image( inMat.data, inMat.cols, inMat.rows, inMat.step, QImage::Format_RGB32 );

            QImage copy(image);
            copy.bits(); //enforce deep copy
            return copy;
         }

         // 8-bit, 3 channel
         case CV_8UC3:
         {
            QImage image( inMat.data, inMat.cols, inMat.rows, inMat.step, QImage::Format_RGB888 );

            return image.rgbSwapped();
         }

         // 8-bit, 1 channel
         case CV_8UC1:
         {
            static QVector<QRgb>  sColorTable;

            // only create our color table once
            if ( sColorTable.isEmpty() )
            {
               for ( int i = 0; i < 256; ++i )
                  sColorTable.push_back( qRgb( i, i, i ) );
            }

            QImage image( inMat.data, inMat.cols, inMat.rows, inMat.step, QImage::Format_Indexed8 );
            image.setColorTable( sColorTable );

            QImage copy(image);
            copy.bits(); //enforce deep copy
            return copy;
         }

         default:
            qWarning() << "ASM::cvMatToQImage() - cv::Mat image type not handled in switch:" << inMat.type();
            break;
      }

      return QImage();
   }

Your code should utilize this functions like that:

while(1) {
    cv::VideoCapture kalibrowanyPlik; 
    kalibrowanyPlik.open(kalibracja.toStdString()); //open file from url
    int maxFrames = kalibrowanyPlik.get(CV_CAP_PROP_FRAME_COUNT);
    for(int i=0; i<maxFrames; i++) //I thought it crashed when finished reading the first time around
    {
        cv::Mat frame;
        cv::Mat gray;

        kalibrowanyPlik.read(frame);
        cv::cvtColor(frame, gray, CV_BGR2GRAY);

        QImage image(cvMatToQImage(frame));
        QImage processedImage(cvMatToQImage(gray));

        emit progressChanged(image, processedImage);
        QThread::msleep(10); // this is bad see comments below
    }
}

Use of msleep is in 95% cases bad! Remove this loop and create slot which will be invoked by signal from QTimer.

Community
  • 1
  • 1
Marek R
  • 32,568
  • 6
  • 55
  • 140
  • It still crashes when msleep() is set lower than 100 – Petersaber May 08 '15 at 12:07
  • do it as described in links! Playing around with sleep as synchronization is very bad idea and definitely will not help here. Just copy paste conversion methods and use them. – Marek R May 08 '15 at 12:39
  • The updated version worked like a charm. Thank you! And the suggestion about QTimer might solve my biggest problem yet – Petersaber May 11 '15 at 06:20
0

Another solution will be to use a timer :

void ??::timerEvent(QTimerEvent*){

 if(kalibrowanssky.isOpened()) 
        cv::Mat frame;
        cv::Mat gray;
        cv::Mat color;

        kalibrowanyPlik.read(frame);
        cv::cvtColor(frame, gray, CV_BGR2GRAY);
        cv::cvtColor(frame, color, CV_BGR2RGB);

        ui->labelHsv->setPixmap(QPixmap::fromImage(Mat2QImage(color)));
        ui->labelKalibracja->setPixmap(QPixmap::fromImage(Mat2QImage(gray)));
}

In your main :

cv::VideoCapture kalibrowanyPlik; 
startTimer(1000/25); // 25 frames by second

And the function Mat2QImage (I found it here : how to convert an opencv cv::Mat to qimage) :

QImage ??::Mat2QImage(cv::Mat const& src) {

     cv::Mat temp; 
     cvtColor(src, temp,CV_BGR2RGB); 
     QImage dest((const uchar *) temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);
     dest.bits();

     return dest;
}
Community
  • 1
  • 1
Stalyon
  • 538
  • 5
  • 20