Memory leak using opencv : VideoCapture - c++

I have developed an application with Qt Creator 2.4.1 (Qt 4.8.4) and OpenCV 2.4.2 that reads images from a folder and displays them.
It uses cv::VideoCapture and QGraphicsScene/QGraphicsView. It runs well, however I encounter a memory leak : if I look at the consumed memory in task manager, memory goes up each time a new image is read and end up crashing.
My main window was created with Qt Designer, it's a class that inherits QMainWindow. There is a QGraphicsView view_src on it and also a push button : buttonStart
Here is a sample of code : Class declaration :
using namespace std;
using namespace cv;
namespace Ui {
class FenetrePrinc;
}
class FenetrePrinc : public QMainWindow {
Q_OBJECT
public:
explicit FenetrePrinc(QWidget *parent = 0);
~FenetrePrinc();
public slots:
virtual void start();
virtual void tick();
virtual void stop_timer();
private:
Ui::FenetrePrinc *ui;
QString filename;
QGraphicsScene *scene_src;
QGraphicsItem *img_src;
VideoCapture sequence;
Mat src;
};
Class definition :
FenetrePrinc::FenetrePrinc(QWidget *parent) : QMainWindow(parent), ui(new Ui::FenetrePrinc){
ui->setupUi(this);
scene_src = new QGraphicsScene();
timer = new QTimer(this);
img_src = scene_src->addPixmap(QPixmap("vide.jpg"));
ui->view_src->setScene(scene_src);
connect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(start()));
}
FenetrePrinc::~FenetrePrinc(){
delete scene_src;
delete img_src;
delete ui;
}
void FenetrePrinc::start(){
if(src.empty())
sequence.open(filename.toStdString());
connect(timer, SIGNAL(timeout()), this, SLOT(tick()));
timer->start(1000/24); //24 frames per second
disconnect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(start()));
connect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(stop_timer()));
}
void FenetrePrinc::tick(){
sequence >> src;
if(src.empty())
{
sequence.release();
stop_timer();
return;
}
scene_src->removeItem(img_src);
img_src = scene_src->addPixmap(convert16uc1(src));
src.release();
}
void FenetrePrinc::stop_timer(){
timer->stop();
disconnect(timer, SIGNAL(timeout()), this, SLOT(tick()));
disconnect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(stop_timer()));
connect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(start()));
}
I don't understand why does memory usage goes up and up each time an image is read, I do release the image each time it is read, and release sequence once finished. But maybe I missed something ?
EDIT : The function QPixmap convert16uc1(Mat img) is the cause of the memory leak. I have to use this function because I am working with 16bits grayscale images, which Qt cannot read. I open images and perform image processing with OpenCV and display the images with Qt.
The code of the function is the following :
QPixmap FenetrePrinc::convert16uc1(const cv::Mat& source)
{
quint16* pSource = (quint16*) source.data;
int pixelCounts = source.cols * source.rows;
QImage dest(source.cols, source.rows, QImage::Format_RGB32);
char* pDest = (char*) dest.bits();
for (int i = 0; i < pixelCounts; i++)
{
quint8 value = (quint8) ((*(pSource)) >> 8);
*(pDest++) = value; // B
*(pDest++) = value; // G
*(pDest++) = value; // R
*(pDest++) = 0; // Alpha
pSource++;
}
return QPixmap::fromImage(dest);
}

Most probably it is convert16uc1.
If you can't post convert16uc1 here, try saving the image temporarily in opencv using imwrite and loading the image in Qt. If the the memleak disappears. analyze convert16uc1.
Or don't call convert16uc1(src) but call addPixmap using some other constant image previously loaded in Qt.

I found what causes the problem and how to fix it, reading this thread.
From the Qt documentation:
void QGraphicsScene::removeItem ( QGraphicsItem * item )
Removes the item item and all its children from the scene. The ownership of item is passed on to the caller (i.e., QGraphicsScene will no longer delete item when destroyed).
See also addItem().
Once QGraphicsScene::removeItem(QGraphicsItem *item)` has been called, QGraphicsScene will no longer delete the item whenn destroyed.
Fix : call delete img_src after removeItem(img_src) : in function FenetrePrinc::tick() :
void FenetrePrinc::tick(){
sequence >> src;
if(src.empty())
{
sequence.release();
stop_timer();
return;
}
scene_src->removeItem(img_src);
delete img_src;
img_src = scene_src->addPixmap(convert16uc1(src));
src.release();
}

Related

Recreate QMovie upon resizing the widget

What is the 'proper' way to recreate a QMovie widget when it gets resized?
class Gif : public QPushButton
{
Q_OBJECT
public:
QMovie* movie = nullptr;
QTimer *timer = new QTimer(this);
int widget_width = 0;
int widget_height = 0;
Gif(QWidget* parent = 0) : QPushButton(parent) { }
void paintEvent(QPaintEvent* p)
{
// Check if the widget has been resized, if so
// delete the QLabel/QMovie and recreate them.
if (widget_width)
{
if (widget_width != width())
{
QLabel* label = this->findChild<QLabel*>("label");
label->deleteLater();
movie->deleteLater();
movie = nullptr;
}
}
// Load the gif into the QMovie/QLabel.
if (!movie)
{
auto StyleSheet = styleSheet();
QVector<QString> matches;
QRegularExpression re(R"(image:\s*url\((.*)\);)");
QRegularExpressionMatch m = re.match(StyleSheet);
for (int i = 0; i <= m.lastCapturedIndex(); i++)
matches.append(m.captured(i));
movie = new QMovie(matches[1]);
if (!movie->isValid())
qDebug() << "failed to create the QMovie.";
QLabel* label = new QLabel(this);
label->setObjectName("label");
widget_width = width();
widget_height = height();
label->setGeometry(0, 0, widget_width, widget_height);
label->setMovie(movie);
label->show();
movie->setScaledSize(QSize().scaled(widget_width, widget_height, Qt::IgnoreAspectRatio));
movie->setSpeed(150);
movie->start();
}
// Pause for 2 seconds after getting into the
// last frame.
if (movie->currentFrameNumber() == (movie->frameCount() -1))
{
movie->stop();
connect(timer, SIGNAL(timeout()), this, SLOT(resume()));
timer->start(2000);
}
}
public slots:
void resume()
{
movie->start();
}
};
Is it 'safe' to call deleteLater() and then assign movie to nullptr? won't cause any mem leak / UB?
if (widget_width != width())
{
QLabel* label = this->findChild<QLabel*>("label");
label->deleteLater();
movie->deleteLater();
movie = nullptr;
}
Also, is there any alternative to QMovie for playing a gif in a gui? it uses a lot of CPU when the gif is medium/high size
Sorry I can not offer the answer to your main question.
With your second question, "a lot of CPU usage" when playing QMovie. I found a method to set the CacheMode of QMovie. It will help to reduce the CPU usage of QMovie playing gif.
movie.setCacheMode(QMovie.CacheMode.CacheAll)

QPainting QPixmap using clipping to gain performance

I'm trying to create a simple Image Viewer in Qt with zooming supported.
To display an image file I load it into a QImage and create a QPixmap.
class NN: public QWidget{
Q_OBJECT
Q_DISABLE_COPY(NN)
public:
NN(QWidget* parent = nullptr) : QWidget(parent){
}
const QPixmap& pixmap() const
{
return m_pixmap;
}
void setPixmap(const QPixmap& px)
{
m_pixmap = px;
update();
}
protected:
void paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, false);
style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, m_pixmap.scaled(rect().size()));
}
private:
QPixmap m_pixmap;
};
(This Widget is part of a ScrollArea)
This works fine, but when I try to load large images and zoom in, the performance starts to decrease (lag).
I thought of applying a clip to the drawItemPixmap() method, but I am not quite sure how and whether it would help increasing the performance.
My question is whether the clipping idea would work, and if so how. If not, maybe there is another way to gain performance?
When m_pixmap and/or rect() are very large, the bulk of your slowdown is likely coming from here:
m_pixmap.scaled(rect().size())
Here you are you are asking Qt to create a new QPixmap object the same size as rect(), which is a potentially very expensive operation; and passing that QPixmap object into the call to drawItemPixmap() which will draw just a small portion of the pixmap, after which the QPixmap object will get discarded, and the whole procedure will have to be done again the next time you want to redraw your object.
Needless to say, that can be very inefficient.
A more efficient approach would be to call QPainter::drawPixmap(const QRect & target, const Pixmap & pixmap, const QRect & source), like this:
painter.drawPixmap(rect(), m_pixmap, srcRect);
... and drawPixmap() will draw a scaled pixmap of size rect() (i.e. just the size of your widget) by rescaling the content of m_pixmap that is inside srcRect; much more efficient than rescaling the entire m_pixmap image.
You'll need to calculate the correct left/top/width/height values for srcRect, of course, but that should be straightforward with a little bit of algebra. (Basically just figure out what portion of the pixmap should currently be visible based on your widget's current zoom/pan state)
As I pointed out in this answer, it is better to use QGraphicsView for image scaling so I will translate the code to C++:
#include <QtWidgets>
class ImageViewer: public QGraphicsView{
public:
ImageViewer(QWidget *parent=nullptr):QGraphicsView(parent){
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// setAlignment(Qt::AlignLeft | Qt::AlignTop);
setAlignment(Qt::AlignCenter);
setBackgroundRole(QPalette::Dark);
QGraphicsScene *scene = new QGraphicsScene(this);
setScene(scene);
pixmapItem = new QGraphicsPixmapItem;
scene->addItem(pixmapItem);
}
bool setPixmap(const QPixmap & pixmap){
if(pixmap.isNull())
return false;
pixmapItem->setPixmap(pixmap);
return true;
}
void zoom(qreal f){
scale(f, f);
}
void zoomIn(){
zoom(factor);
}
void zoomOut(){
zoom(1.0 / factor);
}
void resetZoom(){
resetTransform();
}
void fitToWindow(){
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
private:
qreal factor = 2.0;
QGraphicsPixmapItem * pixmapItem;
};
class MainWindow: public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent=nullptr):QMainWindow(parent),
view(new ImageViewer)
{
setCentralWidget(view);
createActions();
createMenus();
resize(640, 480);
}
private Q_SLOTS:
void open(){
QStringList l;
for(const QByteArray & ba: QImageReader::supportedImageFormats()){
l << ("*." + QString::fromUtf8(ba));
}
QString filter = QString("Image Files(%1)").arg(l.join(" "));
QString fileName = QFileDialog::getOpenFileName(
this,
tr("Open Image"),
QDir::currentPath(),
filter
);
if(!fileMenu->isEmpty()){
bool loaded = view->setPixmap(QPixmap(fileName));
fitToWindowAct->setEnabled(loaded);
updateActions();
}
}
void fitToWindow(){
if(fitToWindowAct->isChecked())
view->fitToWindow();
else
view->resetZoom();
updateActions();
}
void about(){
QMessageBox::about(this, "ImageViewer", "ImageViewer");
}
private:
void createActions(){
openAct = new QAction("&Open...", this);
openAct->setShortcut(QKeySequence("Ctrl+O"));
connect(openAct, &QAction::triggered, this, &MainWindow::open);
exitAct = new QAction("E&xit", this);
exitAct->setShortcut(QKeySequence("Ctrl+Q"));
connect(exitAct, &QAction::triggered, this, &MainWindow::close);
zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
zoomInAct->setShortcut(QKeySequence("Ctrl++"));
zoomInAct->setEnabled(false);
connect(zoomInAct, &QAction::triggered, view, &ImageViewer::zoomIn);
zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
zoomOutAct->setShortcut(QKeySequence("Ctrl+-"));
zoomOutAct->setEnabled(false);
connect(zoomOutAct, &QAction::triggered, view, &ImageViewer::zoomOut);
normalSizeAct = new QAction(tr("&Normal Size"), this);
normalSizeAct->setShortcut(QKeySequence("Ctrl+S"));
normalSizeAct->setEnabled(false);
connect(normalSizeAct, &QAction::triggered, view, &ImageViewer::resetZoom);
fitToWindowAct = new QAction(tr("&Fit to Window"), this);
fitToWindowAct->setShortcut(QKeySequence("Ctrl+F"));
fitToWindowAct->setEnabled(false);
fitToWindowAct->setCheckable(true);
connect(fitToWindowAct, &QAction::triggered, this, &MainWindow::fitToWindow);
aboutAct = new QAction(tr("&About"), this);
connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
aboutQtAct = new QAction(tr("About &Qt"), this);
connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);
}
void createMenus(){
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
fileMenu->addSeparator();
fileMenu->addAction(exitAct);
viewMenu = new QMenu(tr("&View"), this);
viewMenu->addAction(zoomInAct);
viewMenu->addAction(zoomOutAct);
viewMenu->addAction(normalSizeAct);
viewMenu->addSeparator();
viewMenu->addAction(fitToWindowAct);
helpMenu = new QMenu(tr("&Help"), this);
helpMenu->addAction(aboutAct);
helpMenu->addAction(aboutQtAct);
menuBar()->addMenu(fileMenu);
menuBar()->addMenu(viewMenu);
menuBar()->addMenu(helpMenu);
}
void updateActions(){
zoomInAct->setEnabled(not fitToWindowAct->isChecked());
zoomOutAct->setEnabled(not fitToWindowAct->isChecked());
normalSizeAct->setEnabled(not fitToWindowAct->isChecked());
}
ImageViewer *view;
QAction *openAct;
QAction *exitAct;
QAction *zoomInAct;
QAction *zoomOutAct;
QAction *normalSizeAct;
QAction *fitToWindowAct;
QAction *aboutAct;
QAction *aboutQtAct;
QMenu *fileMenu;
QMenu *viewMenu;
QMenu *helpMenu;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"

Receiving Black frames only from embedded camera using openCV and Qt

I am trying to do a simple camera application in QT using openCV, but i have problem with black frames.
The point is, when i run my application on desktop with USB camera, everything seems to work, i am getting frames, and everything is showing in QGraphicsView.
The problem starts when i run my app in laptop with embedded camera. While my app is running, on QGraphicsView i can see only black frames..
Here is a sample of my code:
First of all that's my custom QGraphicsView:
class CustomGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
CustomGraphicsView(QWidget *parent);
~CustomGraphicsView();
void mouseMoveEvent(QMouseEvent *event);
QGraphicsPixmapItem *pItem;
QGraphicsScene *pScene;
QThread *p_worker;
QTimer *p_timer;
CustomThread *p_MyCustomThread;
public slots:
void StartThread();
void GetFrame(QPixmap);
};
CustomGraphicsView::CustomGraphicsView(QWidget *parent): QGraphicsView(parent)
{
pScene = new QGraphicsScene(this);
setScene(pScene);
pItem = new QGraphicsPixmapItem();
pItem->setPixmap(pixmap);
pScene->addItem(pItem);
}
void CustomGraphicsView::StartThread()
{
p_worker = new QThread();
p_MyCustomThread = new CustomThread();
p_MyCustomThread->moveToThread(p_worker);
connect(p_worker, SIGNAL(started()), p_MyCustomThread,SLOT(StartCamera()));
connect(p_MyCustomThread, SIGNAL(SendImage(QPixmap)), this, SLOT(GetFrame(QPixmap)));
p_worker->start();
}
void CustomGraphicsView::GetFrame(QPixmap pix)
{
pItem->setPixmap(pix);
pScene->update();
}
And here is my class which receive frames from camera, convert it to QPixmap and send to QGraphicsView with SIGNAL/SLOT.
class CustomThread : public QObject
{
Q_OBJECT
public:
CustomThread();
VideoCapture cam;
Mat frame;
void RunTaking();
public slots:
void StartCamera();
signals:
void SendImage(QPixmap);
};
void CustomThread::RunTaking()
{
while(1)
{
cam >> frame;
cvtColor(frame, frame, CV_BGR2RGB);
QImage image = QImage((uchar*) frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
QPixmap pix = QPixmap::fromImage(image);
emit SendImage(pix);
}
}
void CustomThread::StartCamera()
{
if(cam.open(0))
{
RunTaking();
}
}
I was trying to do same thing with labels, with no custom classes, but nothing was working.
Maybe i am doing something wrong? If someone know the solution, i will be grateful for tips.
Thanks in advance for any answer!

Qt splashscreen won't close

I have a QSplashScreen made that runs through a bunch of images to resemble a gif and closes when the main window opens. This works fine on windows, but when I run it on mac it gets funky. Instead of closing when it's gone through all the pictures like it should it starts going through the images in revers order when clicked.
Here is header (splashscreen.h):
class SplashScreen : public QObject
{
Q_OBJECT
public:
explicit SplashScreen(QObject *parent = 0);
private:
QString filename0;
QString filename1;
QString filename;
int frameNum;
Qt::WindowFlags flags;
private slots:
void showFrame(void);
};
and here is implementation (splashscreen.cpp):
SplashScreen::SplashScreen(QObject *parent) :
QObject(parent)
{
QTimer *timer = new QTimer;
timer->singleShot(0, this, SLOT(showFrame()));
frameNum = 0;
}
void SplashScreen::showFrame(void)
{
QSplashScreen *splash = new QSplashScreen;
QTimer *timer = new QTimer;
frameNum++;
QString filename0 = ""; //first and second half of file path
QString filename1 = "";
splash->showMessage("message here", Qt::AlignBottom, Qt::black);
filename = filename0 + QString::number(frameNum) +filename1; // the number for the file is added here
splash->setPixmap(QPixmap(filename)); // then shown in the splashscreen
splash->show();
if (frameNum < 90)
{
timer->singleShot(75, this, SLOT(showFrame()));
}
else if (frameNum == 90)
{
splash->close();
flags |= Qt::WindowStaysOnBottomHint;
return;
}
}
and here is main file (main.cpp):
int main(int argc, char *argv[])
{
Application app(argc, argv);
SplashScreen *splash = new SplashScreen;
QSplashScreen *splashy = new QSplashScreen;
View view; //main window
QTimer::singleShot(10000, splashy, SLOT(close()));
splashy->hide();
QTimer::singleShot(10000, &view, SLOT(show()));
return app.exec();
}
I've got several different ways to close the splash screen but none of them seem to be working. Is this a bug in macs or is there something I can fix in my code?
There are created 90 different QSplashScreen objects. Only the 90th object is closed.
So, it is the main reason for observed behavior.
If you create a new splash screen QSplashScreen *splash = new QSplashScreen; for each frame then the previous screen should be closed and deleted. It is possible to store QSplashScreen *splash as a class member. Otherwise there is a memory leak.
You may consider to use only one instance of QSplashScreen splash as a private SplashScreen class member. The rest of the code may be unchanged (after replacement splash-> by splash.). It will be automatically deleted with deletion of SplashScreen.
Other issues
QTimer should not be instantiated each time to use its static member function. Each call of showFrame() and SplashScreen() creates a new QTimer object that is never deleted and never used.
The splashy also does not make any sense in main(). All three lines related to splashy may be deleted. Actual splash screens are triggered by new SplashScreen. By the way, it is also a leak. In that case it makes sense to instantiate it directly on the main() function stack: SplashScreen splash;
It looks that the private member SplashScreen::flags is not used.

How to use the threads to create the images thumbnail

I'm use QTreeView to get the images path, then I'm use QListView to display the images that in specific path as thumbnail.
The problem in the period, create and display the thumbnail images.
The previous process, take a long time to done, depend on the number of images.
And for that reason I decided to use the threads, maybe helps to prevent the hung up which occur in application and increase the speed of create and display the thumbnail images.
void mainWidget::on_treeView_clicked(const QModelIndex &index){
filesModel->clear();
QFileSystemModel *sysModel = qobject_cast<QFileSystemModel*>(ui->treeView->model());
QDir dir(sysModel->filePath(ui->treeView->currentIndex()));
QFileInfoList filesList = dir.entryInfoList(QStringList() << "*.jpg" << "*.jpeg" << "*.tif" << "*.png" << "*.gif" << "*.bmp" ,QDir::Files);
int filesCount = filesList.size();
for(int i=0;i<filesCount;i++){
QPixmap originalImage(filesList[i].filePath());
if(!originalImage.isNull()){
QPixmap scaledImage = originalImage.scaled(150, 120);
filesModel->setItem(i, new QStandardItem(QIcon(scaledImage), filesList[i].baseName()));
}
}
}
How can I use threads with the previous code ?
I believe that a simple and correct approach is using QtConcurrent as the following:
Note: If you are using Qt 5 you will need to add QT += concurrent to the .pro file.
Header:
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
#include <QtConcurrent>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void UpdateItem(int, QImage);
private slots:
void on_treeView_clicked(const QModelIndex &);
void List(QFileInfoList filesList, QSize size);
void setThumbs(int index, QImage img);
private:
Ui::MainWindow *ui;
QFileSystemModel *model;
QStandardItemModel *filesmodel;
QFuture<void> thread;
bool running;
};
CPP file:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QThreadPool::globalInstance()->setMaxThreadCount(1);
model = new QFileSystemModel(this);
model->setRootPath("\\");
filesmodel = new QStandardItemModel(this);
ui->treeView->setModel(model);
ui->listView->setModel(filesmodel);
connect(this, SIGNAL(UpdateItem(int,QImage)), SLOT(setThumbs(int,QImage)));
ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
running = false;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_treeView_clicked(const QModelIndex&)
{
filesmodel->clear();
running = false;
thread.waitForFinished();
QDir dir(model->filePath(ui->treeView->currentIndex()));
QFileInfoList filesList = dir.entryInfoList(QStringList() << "*.jpg" << "*.jpeg" << "*.tif" << "*.png" << "*.gif" << "*.bmp", QDir::Files);
int filesCount = filesList.size();
QPixmap placeholder = QPixmap(ui->listView->iconSize());
placeholder.fill(Qt::gray);
for (int i = 0; i < filesCount; i++)
filesmodel->setItem(i, new QStandardItem(QIcon(placeholder), filesList[i].baseName()));
running = true;
thread = QtConcurrent::run(this, &MainWindow::List, filesList, ui->listView->iconSize());
}
void MainWindow::List(QFileInfoList filesList, QSize size)
{
int filesCount = filesList.size();
for (int i = 0; running && i < filesCount; i++)
{
QImage originalImage(filesList[i].filePath());
if (!originalImage.isNull())
{
QImage scaledImage = originalImage.scaled(size);
if (!running) return;
emit UpdateItem(i, scaledImage);
}
}
}
void MainWindow::setThumbs(int index, QImage img)
{
QIcon icon = QIcon(QPixmap::fromImage(img));
QStandardItem *item = filesmodel->item(index);
filesmodel->setItem(index, new QStandardItem(icon, item->text()));
}
You don't have to use threads to keep your application responsive in this case. Use QCoreApplication::processEvents() in the loop to keep the application responsive. QCoreApplication::processEvents() will process all the events in the event queue of the thread which calls it.
This is an older thread, but still comes up in Google. Check out the answer to this related question. I found the use of QIdentityProxyModel to be a bit more elegant, as it allowed QFileSystemModel to be used as the list view's model.