I have a simple Qt/C++ program which gathers a webcam image out of one of my LAN devices using a cv::VideoCapture object.
The application is being built using Qt Quick and has an Image QML Item which is being served with a picture every 500 milliseconds via a custom QQuickImageProvider implementation:
WebcamImageProvider::WebcamImageProvider()
: QQuickImageProvider(ImageType::Image)
{
connect(&_timer, &QTimer::timeout, this, &WebcamImageProvider::updateImage);
// refresh our picture every .5 seconds
_timer.start(500);
}
// overridden function from base class
QImage WebcamImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
Q_UNUSED(id)
Q_UNUSED(size)
Q_UNUSED(requestedSize)
return _img;
}
My main function looks like this:
// Qt Quick
#include <QQuickItem>
#include <QQuickWindow>
// QML + GUI
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
// webcam stuff
#include "webcamimageprovider.h"
/*
* Here we go
*/
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
// we're adding our self-made image provider instance here
WebcamImageProvider wip;
engine.addImageProvider("webcam", &wip);
engine.rootContext()->setContextProperty("webcamImageProvider", &wip);
engine.load(url);
return app.exec();
The code of my main.qml file should not be important here.
Inside my image provider class, I have provided the updateImage slot as follows:
void WebcamImageProvider::updateImage()
{
/*
* Webcam image grapping via https://raspberrypi.stackexchange.com/a/85510/132942
*/
if (!_cap.open(_videoUrl)) {
qDebug() << "Error opening stream.";
}
if (!_cap.read(_mat)) {
qDebug() << "No video capture or webcam error.";
}
// via https://stackoverflow.com/a/12312326/4217759
_img = QImage(
static_cast<uchar*>(_mat.data),
_mat.cols,
_mat.rows,
static_cast<int>(_mat.step),
QImage::Format_BGR888
);
emit imageChanged(_img);
}
My problem here is that when my device is not reachable via network, the application will freeze completely as it gets stuck in the _cap.open() function. Therefore, I am trying to outsource this function to a future so the image will be loaded asynchronously. Since I have literally zero idea of threading and futures, I randomly gave it a shot with using QFutures:
void WebcamImageProvider::updateImage()
{
/*
* Webcam image grapping via https://raspberrypi.stackexchange.com/a/85510/132942
*/
QFuture<void> future = QtConcurrent::run([&](){ _cap.open(_videoUrl); });
if (!future.isRunning()) {
qDebug() << "Error opening stream.";
}
if (future.isFinished()) {
if (!_cap.read(_mat)) {
qDebug() << "No video capture or webcam error.";
}
// via https://stackoverflow.com/a/12312326/4217759
_img = QImage(
static_cast<uchar*>(_mat.data),
_mat.cols,
_mat.rows,
static_cast<int>(_mat.step),
QImage::Format_BGR888
);
emit imageChanged(_img);
}
}
However, I won't get any image output from that.
Can someone help me on how to structure the code correctly so I can load my image asynchronously here?
Thanks a lot in advance.
1- you should know something , create setter for _img and emit changed in that ! because it is possible one day you need set Image in another functions and if you don't do this , you should duplicate emit fooChanged().
2- when you check these and is's not responsible for working in your method , you should throw an exception then handle them and if you wanted use qDebug().
3- my suggest is that (if I understood completely your job) create an thread that working in loop and always getting new images then create a queue worker and create another thread (worker) for process your scenario(here updateImage method).
Related
i am making a Tail Log application. It is executing perfectly but the output window is not showing any results.
Below is the code:
LogTail.cpp
#include "logtail.h"
#include <QApplication>
#include <QTextEdit>
#include<QDebug>
//start id=constructor
LogTail::LogTail(QString fn) {
//if (fn == QString()) {
// fn = "C:/Users/arpit.k/OneDrive - Accord Software & Systems Pvt. Ltd/Documents/QT/LogTail3/sample.log";
// }
connect (this, SIGNAL(readyReadStandardOutput()),
this, SLOT(logOutput())); /* When there is input ready, call this slot.*/
QStringList argv;
argv << "-f" << fn; /* tail -f filename */
start("tail", argv); /* Returns immediately, and now there is a child process running, "attached"
to this process. When this process exits, the child tail process will also terminate. */
}
LogTail::~LogTail() {
terminate(); /* Attempts to terminate this process. */
}
//end
//start id=logOutput
// tail sends its output to stdout.
void LogTail::logOutput() { /* Slot called whenever there is input to read. */
QByteArray bytes = readAllStandardOutput();
QStringList lines = QString(bytes).split("\n");
foreach (QString line, lines) {
//qDebug() << line;
emit logString(line);
}
}
//end
main.cpp
#include "logtail.h"
#include <QTextEdit>
#include <QApplication>
int main (int argc, char* argv[]) {
QApplication app(argc, argv);
QStringList al = app.arguments();
QTextEdit textEdit;
textEdit.setWindowTitle("Debug");
textEdit.setWindowTitle("logtail demo");
QString filename = "sample.log";
if (al.size() > 1) filename = al[1];
LogTail tail(filename); /* Create object, starts process too. */
tail.connect (&tail, SIGNAL(logString(const QString&)),
&textEdit, SLOT(append(const QString&)));
textEdit.show();
return app.exec();
}
Note: sample.log file contains some log data
Output window doesn't show any logs:
(https://i.stack.imgur.com/ENhqs.png)
I need it to display recent 10 Log lines!
Assuming you are subclassing from QProcess (you should post a complete minimal example in order to get an accurate answer).
You are starting the process in the constructor of LogTail, 'tail' will probably output some data or some events that will trigger readyReadStandardOutput before you have the chance to connect the signal in the main function.
readyReadStandardOutput will be triggered only once as you never cleared the input buffer (you never had the chance to read the available data).
So in order to make your example work you should call logOutput after you start the process.
Anyway I will suggest you to implement differently, I would call start from outside or at least outside the constructor, that way your object will be more scalable.
I have written a simple video player GUI code in QT. The GUI allows the user to browse the local files and select a video for playing in the GUI. The GUI also has options for 'play', 'pause' and 'stop' to apply to the video selected.
I want to add another button 'Capture', that captures the current frame of the video that is being played, and displays this captured image next to the video (The video should should get paused at this point).
I looked into the documentation of QT, specifically: this and this. But I am still not able to understand how to implement this in my case.
Kindly guide.
My code so far is as follows:
#include "qtwidgetsapplication4.h"
#include <iostream>
QtWidgetsApplication4::QtWidgetsApplication4(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
player = new QMediaPlayer(this);
vw = new QVideoWidget(this);
player->setVideoOutput(vw);
this->setCentralWidget(vw);
}
void QtWidgetsApplication4::on_actionOpen_triggered() {
QString filename = QFileDialog::getOpenFileName(this, "Open a File", "", "Video File (*.*)");
on_actionStop_triggered();
player->setSource(QUrl::fromLocalFile(filename));
on_actionPlay_triggered();
qDebug("Error Message in actionOpen");
qDebug()<<player->mediaStatus();
}
void QtWidgetsApplication4::on_actionPlay_triggered() {
player->play();
ui.statusBar->showMessage("Playing");
qDebug("Error Message in actionPlay");
qDebug() << player->mediaStatus();
}
void QtWidgetsApplication4::on_actionPause_triggered() {
player->pause();
ui.statusBar->showMessage("Paused...");
qDebug("Error Message in actionPause");
qDebug() << player->mediaStatus();
}
void QtWidgetsApplication4::on_actionStop_triggered() {
player->stop();
ui.statusBar->showMessage("Stopped");
qDebug("Error Message in actionStop");
qDebug() << player->mediaStatus();
}
You can use QVideoProbe to capture QVideoFrame. Instantiate and connect videoFrameProbed signal to your slot before pausing video. Convert QVideoFrame to QImage in this slot using frame data.
QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
QImage image(frame.bits(), frame.width(), frame.height(), imageFormat);
Take a look at player example for reference. It uses QVideoProbe to calculate histogram of current frame.
I am working on a Qt project with a team. I have two functions — one retrives the numerical coordinates of a place, the other downloads the map of the place — that I want to merge in one wrapper class, so that my teammates can call it easily.
#include <QCoreApplication>
#include <QFile>
#include <QHttpMultiPart>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <iostream>
class OpenStreetMapWrapper: public QObject{
Q_OBJECT
public:
OpenStreetMapWrapper(QObject *parent=nullptr):QObject(parent){
connect(&manager, &QNetworkAccessManager::finished, this, &OpenStreetMapWrapper::handle_finished);
}
void download(const std::string ®ion, const std::string &department, const QFile& outfile){
QNetworkRequest request;
QUrl url = QUrl(QString::fromStdString("https://download.openstreetmap.fr/extracts/europe/france/" + region + "/" + department + ".osm.pbf"));
request.setUrl(url);
request.setAttribute(QNetworkRequest::User, outfile.fileName());
manager.get(request);
}
void searchCSV(QFile& file, QFile& outfile){
QNetworkRequest request(QUrl("https://api-adresse.data.gouv.fr/search/csv/")); // Free API provided by the French government
request.setAttribute(QNetworkRequest::User, outfile.fileName());
QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart postpart;
postpart.setHeader(QNetworkRequest::ContentDispositionHeader,
QString("form-data; name=%1; filename=%2")
.arg("data", file.fileName()));
postpart.setBodyDevice(&file);
multipart->append(postpart);
file.setParent(multipart);
manager.post(request, multipart);
}
private:
QNetworkAccessManager manager;
void handle_finished(QNetworkReply *reply){
if(reply->error() == QNetworkReply::NoError){
QByteArray read = reply->readAll();
std::cout << read.toStdString() << std::endl; // For debugging
QString filename = reply->request().attribute(QNetworkRequest::User).toString();
QFile out(filename);
if(out.open(QIODevice::WriteOnly)){
out.write(read);
out.close();
}
}
else{
qDebug() << reply->error() << reply->errorString();
}
reply->deleteLater();
// QCoreApplication::quit(); This is done somewhere else?
}
};
#include <main.moc>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
OpenStreetMapWrapper A;
QFile file("./search.csv");
file.open(QIODevice::ReadWrite);
QFile outfile("./output.csv");
outfile.open(QIODevice::ReadWrite);
// Search
A.searchCSV(file, outfile); // 1st call works
A.searchCSV(file, outfile); // 2nd call -> makes both calls fail.
// Downloader
std::string region = "corse";
std::string department = "haute_corse";
return a.exec();
}
The problem with the code above, is that when for example searchCSV is called it displays the output as needed, but if it is called twice in the code, there is no output at all. After some debugging I think the problem is that the manager and handle_finished are not connected properly, because the execution never reaches there. Is there a simple way to solve this issue? Ideally, there is just one class instant, and any method can be called any number of times.
I don't know much about Qt, but it looks like you're trying to read from file twice and my guess is that when it reaches the end of the file after the first call to A.searchCSV it is done and you can't read from it anymore - unless you reposition the QFile to the beginning of the file.
Possible solution:
A.searchCSV(file, outfile);
file.unsetError(); // possibly needed
file.seek(0); // rewind to the start of the file
A.searchCSV(file, outfile);
The two QFile (input, output) are shared between two asynchronous calls (searchCSV) that might give an undefined behavior. The input file (stream) contents will be load and push only after the connection was made (like curl does).
You should:
Make searchCSV a blocking function (wait until handle_finished() done), input file pointer should be reinitialized before an other call.
OR: Use separated input/output QFile instances
Here is my code:
QString BoatProgramming::setDuration(QString path)
{
if (path.isNull()) { return ""; }
QMediaPlayer mp;
mp.setMedia(QUrl::fromLocalFile("/home/akiva/deleteme.ogg"));
qDebug() << mp.duration(); // Outputting a value of -1
m_Duration = QString::number(mp.duration());
emit durationChanged();
return m_Duration;
}
There is obviously an error somewhere, but beyond checking the filename, I am lamentably amiss as to what the problem is. Could it be simply that .ogg is not supported? Am I calling the function before the object is fully loaded into memory? Or is it something else?
Thanks.
QMediaPlayer::setMedia() performs loading asynchronously, from the docs:
This function returns immediately after recording the specified source of the media. It does not wait for the media to finish loading and does not check for errors. Listen for the mediaStatusChanged() and error() signals to be notified when the media is loaded and when an error occurs during loading.
This means that querying QMediaPlayer for duration() after calling setMedia() immediately may not work, since QMediaPlayer might not have loaded the media yet.
In order to guarantee that loading has finished before calling duration(), you have to listen for mediaStatusChanged() signal, and get duration() only when mediaStatus() returns QMediaPlayer::LoadedMedia. Here is a minimal example:
#include <QtWidgets>
#include <QtMultimedia>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QLabel label; //a label to display duration
QString fileName = QFileDialog::getOpenFileName(nullptr, "Open Media File");
QMediaPlayer mp;
mp.setMedia(QUrl::fromLocalFile(fileName));
QObject::connect(&mp, &QMediaPlayer::mediaStatusChanged,
[&](QMediaPlayer::MediaStatus status){
if(status == QMediaPlayer::LoadedMedia) //when loading is finished
{
//show duration in a label
qint64 duration= mp.duration();
label.setText(QString("Duration: %1 ms.\n\nThat is: %2")
.arg(duration)
.arg(QDateTime::fromTime_t(duration/1000).toUTC()
.toString("hh:mm:ss")));
label.show();
}
});
return app.exec();
}
I am trying to create a image-saving application using Qt. Now the stub
class ImageSaver:public QObject
{
int index;
QWebPage * main_Page;
QNetworkAccessManager * manager;
QNetworkReply * reply;
QString file_Name;
QSet<QString> image_Addresses;
QString web_Address;
Q_OBJECT
signals:
void image_Saved();
public slots:
void request_Image();
void on_Finished(bool status);
void got_Reply(QNetworkReply * reply);
public:
ImageSaver();
void start();
};
ImageSaver::ImageSaver()
{
index = 0;
manager = new QNetworkAccessManager;
reply = NULL;
connect(main_Page,SIGNAL(loadFinished(bool)),this,SLOT(on_Finished(bool)));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(got_Reply(QNetworkReply*)));
connect(this,SIGNAL(image_Saved()),this,SLOT(request_Image()));
}
void ImageSaver::start()
{
//loads the url
// In the end of the loading it will emit load_Finished(bool)
// So that signal will execute on_Finished(bool)
}
void ImageSaver::request_Image()
{
QString temp_Address = *(image_Addresses.begin()+index);
//makes a request to the server to give the image "temp_Address"
//When the server gives the reply signal finished(QNetworkReply*) will be emitted
// this in turn will call the got_Reply(QNetworkReply*)
}
void ImageSaver::on_Finished(bool status)
{
//collects all the images's url addresses, and pushes them in the list
//"image_Addresses"
//Then emits image_Saved();
//This signal will wake up the function request_Image()
}
void ImageSaver::got_Reply(QNetworkReply * reply)
{
//Image is extracted from the reply and got saved in the same name as in the page
//index got increased;
//emits the signal image_Saved();
//This signal will activate the function request_Image()
}
int main(int argc,char * argv[])
{
QApplication app(argc,argv);
ImageSaver a;
a.start();
return app.exec();
}
#include "main.moc"
In short First call is to "start".That calls "on_Finished" and there is no problem untill this. So all the image files's addresses got pushed in the list. Next is one by one request for image[i] made, and the reply image got saved. This thing is happening repeatedly. Here only I am getting problem. Crashes are appearing in this operation especially in saving the image.
My assumption is "signal-slot" is not like function call, thy are more or less like thread but operates on the same function( pointer). So when one signal requests for painter, which is already rendering something then the crash will appear.
Can anybody say the fact behind the crash and how to save all the images without crash?
EDIT:
Hi, This is the full code. Run this one, and click the message boxes contineously
#include <QApplication>
#include <QDir>
#include <QImage>
#include <QObject>
#include <QMessageBox>
#include <QPainter>
#include <QPixmap>
#include <QSet>
#include <QTimer>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QtWebKit/QWebElement>
#include <QtWebKit/QWebFrame>
#include <QtWebKit/QWebPage>
#include <QUrl>
class ImageSaver:public QObject
{
int index;
QWebPage * main_Page;
QNetworkAccessManager * manager;
QNetworkReply * reply;
QString file_Name;
QSet<QString> image_Addresses;
QString web_Address;
Q_OBJECT
signals:
void image_Saved();
public slots:
void request_Image();
void on_Finished(bool status);
void got_Reply(QNetworkReply * reply);
public:
ImageSaver();
void start();
protected:
//void show_Frame(QWebFrame * frame);
};
ImageSaver::ImageSaver()
{
index = 0;
this->main_Page = new QWebPage;
manager = new QNetworkAccessManager;
reply = NULL;
connect(main_Page,SIGNAL(loadFinished(bool)),this,SLOT(on_Finished(bool)));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(got_Reply(QNetworkReply*)));
connect(this,SIGNAL(image_Saved()),this,SLOT(request_Image()));
}
void ImageSaver::start()
{
web_Address = "yahoo.com";
QDir dir;
dir.mkdir(web_Address);
QUrl url = QUrl::fromUserInput(web_Address);
main_Page->mainFrame()->load(url);
}
void ImageSaver::request_Image()
{
QString temp_Address = *(image_Addresses.begin()+index);
int a = temp_Address.lastIndexOf("/");
file_Name = temp_Address.mid(a+1);
//Without the below message box, the program closes shortly
//This message box is slowing down that effect
QMessageBox hh;
hh.setText(file_Name);
hh.exec();
QNetworkRequest request= QNetworkRequest(QUrl(temp_Address));
request.setRawHeader("img","src");
manager->get(request);
}
void ImageSaver::on_Finished(bool status)
{
if(status)
{
QMessageBox mm;
mm.setText("Finished");
mm.exec();
QWebElementCollection temp_Collection= main_Page->mainFrame()->findAllElements("*");
for(int i=0;i<temp_Collection.count();++i)
{
QWebElement temp_Element = temp_Collection[i];
if(temp_Element.tagName().contains("img",Qt::CaseInsensitive) && temp_Element.attributeNames().contains("src",Qt::CaseInsensitive))
{
QString image_Web_Address = temp_Element.attribute("src");
if(!image_Addresses.contains(image_Web_Address))
image_Addresses.insert(image_Web_Address);
}
}
emit image_Saved();
QMessageBox kk;
kk.setText("Image is going to be saved");
kk.exec();
}
else
{
QMessageBox mm;
mm.setText("Not ready");
mm.exec();
}
QMessageBox mm;
mm.setText("Getting out of finished");
mm.exec();
}
void ImageSaver::got_Reply(QNetworkReply * reply)
{
QImage image;
if(image.load(static_cast<QIODevice *>(reply),0))
image.save(web_Address+QDir::separator()+file_Name,0);
++index;
emit image_Saved();
}
/*
void ImageSaver::show_Frame(QWebFrame * temp_Frame)
{
QImage image(temp_Frame->contentsSize(),QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setRenderHint(QPainter::TextAntialiasing,true);
painter.setRenderHint(QPainter::SmoothPixmapTransform,true);
temp_Frame->documentElement().render(&painter);
painter.end();
foreach(QWebFrame * temp_Frame0,temp_Frame->childFrames())
show_Frame(temp_Frame0);
}
*/
int main(int argc,char * argv[])
{
QApplication app(argc,argv);
ImageSaver a;
a.start();
return app.exec();
}
#include "main.moc"
This is the pro file
QT += webkit network
SOURCES += \
main.cpp
Your code crashes because you read beyond the boundaries of the image_Addresses set.
void ImageSaver::request_Image()
{
QString temp_Address = *(image_Addresses.begin()+index);
...
You increment index after every image received, but there isn't any check anywhere in the code whether index is still less than image_Addresses.size(), so it crashes once dereferencing image_Addresses.begin()+index for index == image_Addresses.size().
There may be many solutions to this problem. I think that you should have a look at The State Machine Framework. In easy situations you can just use boolean variable to check if you can go on. You should also think what to do when you're busy processing the image. You can queue request or just reject them. Also you can implement threading, so that new requests are served by new threads.
P.S. Signals are more like events than threads to me.
What's the error and why do you have a #include at the end?
FYI there is a QImage class you can use instead which includes saving and loading from a QIODevice* such as QNetworkReply for example. It's extremely rare to need to reinvent the wheel in the massive framework that is Qt.