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!
Related
I try to create a green circle which every 5 seconds disappears.
Actually, I have the green circle created with the QPainter method. I tried QTimer and others methods but I can't find the good solution.
I overrided the paintEvent function like this :
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
Qt::BrushStyle style = Qt::SolidPattern;
QBrush brush(Qt::green, style);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(brush);
painter.drawEllipse(525, 5, 50, 50);
}
MainWindow::MainWindow() : QWidget()
{
QTimer *ledtimer = new QTimer(this);
connect(ledtimer, SIGNAL(timeout()), this, SLOT(run_led()));
ledtimer->start(5000);
}
I tried to do something like this, but when i'm using run_led, it tells that painter is already removed (i tried in MainWindow class).
I understand the signal function and the timer, I used it in another files, so some tips would be appreciated. Am I supposed to use timers to make circles wink ?
Define a flag boolean that changes every 5 seconds and in paint use a brush as global variable
void MainWindow::paintEvent(QPaintEvent *)
{
....
QBrush brush(myBrush, style);
...
}
and in slot (run_led)
void MainWindow::run_led()
{
c != true;
if(c)
{
myBrush=Qt::green;
}
else
{
myBrush=Qt::gray;
}
}
Assuming your MainWindowinherits QMainWindow
MainWindow::paintEvent(QPaintEvent *) is a function that tells the systems to render your window.
So I let you guess what goes wrong when you override it like this.
But you can put the drawing in a QWidget made for this : QGraphicsView which displays the content of QGraphicsScene .
You should create a slot to do what you want, like this :
void MainWindow::on_led_timer_timeout(){
/*
Do stuff the the QGraphicsScene or QGraphicsView
*/
}
And then connect the correct signal of your QTimer to it :
connect(ledtimer, &QTimer::timeout, this, &MainWindow::on_led_timer_timeout);
class QSimpleLed : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
public:
using QWidget::QWidget;
void setColor(const QColor& c) {
if (m_color != m) {
m_color = m;
update();
}
}
QColor color() const;
void paintEvent(QPaintEvent *) override;
private:
QColor m_color;
}
Implementation above should be obvious.
int main(int argc, char* argv[])
{
QApplication app{argc, argv};
QSimpleLed led;
auto animation = new QPropertyAnimation(&led, "color");
animation->setStartValue(Qt::red);
animation->setEndValue(Qt::green);
animation->setLoopCount(-1);
animation->setDuration(5000);
animation->start();
led.show();
return app.exec();
}
I am trying to create a round button by subclassing and setting the region mask so that I can reuse it in my project. I know we can override paintEvent method and draw a circle to show it as a round button. But the problem with this approach is that if user clicks outside the circle (but within button rect) it will be treated as a button click. This problem we don't see when set the region mask.
I tried to set the region by calling setmask method inside resizeEvent/paintEvent. In either of case, button will be blank. I am trying to figure out the place inside the subclass to set the region mask.
RoundAnimatingButton.h ->
#include <QPushButton>
namespace Ui {
class CRoundAnimatingBtn;
}
class CRoundAnimatingBtn : public QPushButton
{
Q_OBJECT
public:
explicit CRoundAnimatingBtn(QWidget *parent = nullptr);
~CRoundAnimatingBtn();
void StartAnimation(QColor r);
void StopAnimation();
public slots:
void timerEvent(QTimerEvent *e);
private:
Ui::CRoundAnimatingBtn *ui;
bool m_Spinning;
// QWidget interface
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent * e) override;
};
#endif // ROUNDANIMATINGBTN_H
RoundAnimatingButton.cpp
CRoundAnimatingBtn::CRoundAnimatingBtn(QWidget *parent)
: QPushButton (parent)
, ui(new Ui::CRoundAnimatingBtn)
, m_Spinning(false)
{
ui->setupUi(this);
}
CRoundAnimatingBtn::~CRoundAnimatingBtn()
{
delete ui;
}
void CRoundAnimatingBtn::paintEvent(QPaintEvent *e)
{
QPushButton::paintEvent(e);
if(m_Spinning)
{
// Animating code
}
}
void CRoundAnimatingBtn::StartAnimation(QColor r)
{
m_Spinning=true;
startTimer(5);
}
void CRoundAnimatingBtn::StopAnimation()
{
m_Spinning=false;
this->update();
}
void CRoundAnimatingBtn::timerEvent(QTimerEvent *e)
{
if(m_Spinning)
this->update();
else
killTimer(e->timerId());
}
void CRoundAnimatingBtn::DrawRing()
{
}
void CRoundAnimatingBtn::resizeEvent(QResizeEvent *event)
{
// -----------------------------------
// This code didn't work
// -----------------------------------
QRect rect = this->geometry();
QRegion region(rect, QRegion::Ellipse);
qDebug() << "PaintEvent Reound button - " << region.boundingRect().size();
this->setMask(region);
// ----------------------------------
// ------------------------------------
// This code worked
// -------------------------------------
int side = qMin(width(), height());
QRegion maskedRegion(width() / 2 - side / 2, height() / 2 - side / 2, side,
side, QRegion::Ellipse);
setMask(maskedRegion);
}
Qt doc. provides a sample for “non-rectangular” widgets – Shaped Clock Example.
(Un-)Fortunately, I remembered this not before I got my own sample running.
I started in Qt doc. with
void QWidget::setMask(const QBitmap &bitmap)
Causes only the pixels of the widget for which bitmap has a corresponding 1 bit to be visible. If the region includes pixels outside the rect() of the widget, window system controls in that area may or may not be visible, depending on the platform.
Note that this effect can be slow if the region is particularly complex.
The following code shows how an image with an alpha channel can be used to generate a mask for a widget:
QLabel topLevelLabel;
QPixmap pixmap(":/images/tux.png");
topLevelLabel.setPixmap(pixmap);
topLevelLabel.setMask(pixmap.mask());
The label shown by this code is masked using the image it contains, giving the appearance that an irregularly-shaped image is being drawn directly onto the screen.
Masked widgets receive mouse events only on their visible portions.
See also mask(), clearMask(), windowOpacity(), and Shaped Clock Example.
(When reading this, I still missed the link to example.)
At first, I prepared a suitable pixmap for my purpose – dialog-error.png:
for which I converted an SVG from one of my applications.
I tried to apply it to a QPushButton as icon and as mask. This looked very strange. I'm not quite sure what exactly was the problem:
- using the resp. QPushButton as toplevel widget (i.e. main window)
- the fact that QPushButtons icon rendering and the mask may not match concerning position or size.
Without digging deeper, I changed the code and fixed both issues in next try:
making a derived button (like described by OP)
using the button as non-toplevel widget.
This worked soon. I added some code to make the effect more obvious:
a mouse press event handler for main window to show whether shape is considered correctly
a signal handler to show whether clicks on button (in shape) are received correctly.
So, I came to the following sample – testQPushButtonMask.cc:
#include <QtWidgets>
class MainWindow: public QWidget {
public:
explicit MainWindow(QWidget *pQParent = nullptr):
QWidget(pQParent)
{ }
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
protected:
virtual void mousePressEvent(QMouseEvent *pQEvent) override;
};
void MainWindow::mousePressEvent(QMouseEvent *pQEvent)
{
qDebug() << "MainWindow::mousePressEvent:" << pQEvent->pos();
QWidget::mousePressEvent(pQEvent);
}
class RoundButton: public QPushButton {
private:
QPixmap _qPixmap;
public:
RoundButton(const QPixmap &qPixmap, QWidget *pQParent = nullptr):
QPushButton(pQParent),
_qPixmap(qPixmap)
{
setMask(_qPixmap.mask());
}
virtual ~RoundButton() = default;
RoundButton(const RoundButton&) = delete;
RoundButton& operator=(const RoundButton&) = delete;
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
QSize RoundButton::sizeHint() const { return _qPixmap.size(); }
void RoundButton::paintEvent(QPaintEvent*)
{
QPainter qPainter(this);
const int xy = isDown() * -2;
qPainter.drawPixmap(xy, xy, _qPixmap);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QPixmap qPixmap("./dialog-error.png");
// setup GUI
MainWindow qWin;
qWin.setWindowTitle(QString::fromUtf8("QPushButton with Mask"));
QVBoxLayout qVBox;
RoundButton qBtn(qPixmap);
qVBox.addWidget(&qBtn);
qWin.setLayout(&qVBox);
qWin.show();
// install signal handlers
QObject::connect(&qBtn, &RoundButton::clicked,
[](bool) { qDebug() << "RoundButton::clicked()"; });
// runtime loop
return app.exec();
}
The corresponding Qt project file testQPushButtonMask.pro
SOURCES = testQPushButtonMask.cc
QT += widgets
Compiled and tested on cygwin64:
$ qmake-qt5 testQPushButtonMask.pro
$ make && ./testQPushButtonMask
Qt Version: 5.9.4
MainWindow::mousePressEvent: QPoint(23,22)
MainWindow::mousePressEvent: QPoint(62,24)
MainWindow::mousePressEvent: QPoint(62,61)
MainWindow::mousePressEvent: QPoint(22,60)
RoundButton::clicked()
Concerning the output:
I clicked into the four corners of button.
I clicked on the center of button.
I have created an Image Viewer application that opens and saves an image and load the image on QLabel,then I created a ScrollArea to scroll for large images, in my second step I am trying to draw a selection rectangle to select a specific sub area , the steps I have taken to paint the selection rectangle as follows:
1- I used an PaintEvent to paint the rectangle.
2- I used MouseEvent to select the sub area.
The problem is, when I run my code, I can draw the rectangle on the QWidget but not on the ScrollArea.
Here is my code:
in imageviewer.h
class ImageViewer : public QWidget{
Q_OBJECT
public:
explicit ImageViewer(QWidget *parent = 0);
~ImageViewer();
private:
Ui::ImageViewer *ui;
private slots:
void on_openButton_pressed();
void on_saveButton_pressed();
private:
QPixmap image;
QImage *imageObject;
bool selectionStarted;
QRect selectionRect;
QMenu contextMenu;
protected:
void paintEvent(QPaintEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
};
this is imageviewer.cpp
ImageViewer::ImageViewer(QWidget *parent) :
QWidget(parent),
ui(new Ui::ImageViewer)
{
ui->setupUi(this);
ui->scrollArea->setWidget(ui->imageLabel);
}
open & save functions:
void ImageViewer::on_openButton_pressed()
{
QString imagePath = QFileDialog::getOpenFileName(this, tr("Open File") , "" ,
tr("JPEG (*.jpg *.jpeg);;PNG (*.png);;BMP (*.bmp)"));
imageObject = new QImage();
imageObject->load(imagePath);
image = QPixmap::fromImage(*imageObject);
ui->imageLabel->setPixmap(image);
ui->imageLabel->adjustSize();
}
void ImageViewer::on_saveButton_pressed()
{
QString imagePath = QFileDialog::getSaveFileName(this, tr("Save File") , "" ,
tr("JPEG (*.jpg *.jpeg);;PNG (*.png);;BMP (*.bmp)"));
*imageObject = image.toImage();
imageObject->save(imagePath);
}
paint & mouse event functions:
void ImageViewer::paintEvent(QPaintEvent *e){
QWidget::paintEvent(e);
QPainter painter(this);
painter.setPen(QPen(QBrush(QColor(0,0,0,180)),1,Qt::DashLine));
painter.setBrush(QBrush(QColor(255,255,255,120)));
painter.drawRect(selectionRect);
}
void ImageViewer::mousePressEvent(QMouseEvent *e){
if(e->button() == Qt::RightButton){
if(selectionRect.contains(e->pos()))
contextMenu.exec(this->mapToGlobal(e->pos()));
}
else{
selectionStarted = true;
selectionRect.setTopLeft(e->pos());
selectionRect.setBottomRight(e->pos());
}
}
void ImageViewer::mouseMoveEvent(QMouseEvent *e){
if(selectionStarted){
selectionRect.setBottomRight(e->pos());
repaint();
}
}
void ImageViewer::mouseReleaseEvent(QMouseEvent *e){
selectionStarted = false;
}
and this is a screenshot for my application
How can I draw a selection rectangle on QScrollArea?
You need QRubberBand which supposed to be directly applied to ui->imageLabel widget you use to display an image you want to scroll. Whether or not you should overload QLabel for ui->imageLabel is a question of implementation. Please see the example for mouse events which, of course better be applied to your label image class.
I understand from the way the code looks you used Qt Designer? That complicates overriding that label class but you can either not use .ui file or use event filter for trapping all the events for the given object.
What you currently have can be finally polished enough to be good for actual use, of course. It just makes much more effort for you. You can avoid overloading paintEvent etc. and the key is to apply the right approach directly where it needs to be applied.
// the example applied to authors code as requested
class MyImageLabel : public QLabel
{
public:
explicit MyImageLabel(QWidget *parent = 0) : QLabel(parent), rubberBand(0) {}
private:
QRubberBand* rubberBand;
QPoint origin;
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event)
};
void MyImageLabel::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void MyImageLabel::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
}
void MyImageLabel::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand->hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
ImageViewer::ImageViewer(QWidget *parent) :
QWidget(parent),
ui(new Ui::ImageViewer)
{
ui->setupUi(this);
// resolve this better than:
delete ui->imageLabel; // you already have the QLabel object here?
ui->imageLabel = new MyImageLabel;
ui->scrollArea->setWidget(ui->imageLabel);
}
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();
}
I need to display a button on top of an image. Something similar to
The background is a QPixmap/QImage and the button is a QPushbutton. I need to be able to dynamically change the image - so I am not sure if a stylesheet would be suitable for the task. I tried this, but could not get it to work.
Any solutions?
Subclass QWidget and implement paintEent where you can paint your image at the background. Set and change background image by stylesheet also possible.
Add layout with button to this widget.
There are something like this:
class WidgetWithButton
: public QWidget
{
Q_OBJECT
QImage m_bgImage;
public:
WidgetWithButton(QWidget* aParent)
: QWidget(aParent)
{
QHBoxLayout* l = new QHBoxLayout(this);
QPushButton* myButton = new QPushButton(tr("Close"));
l->addWidget( myButton, 0, Qt::AlignCenter );
}
void setImage(const QImage& aImage)
{
m_image = aImage;
update();
}
protected:
virtual void paintEvent(QPaintEvent* aPainEvent)
{
if (m_image.isValid())
{
QPainter painter(this);
painter.drawImage(rect(), m_image);
}
else
QWidget::paintEvent(aPainEvent);
}
};