Qt images vidéo de la caméra corrompues

EDIT: La première réponse a résolu mon problème. En dehors de cela, je devais définir la valeur ASI_BANDWIDTH_OVERLOAD sur 0.

Je programme une application Linux en C ++ / Qt 5.7 pour suivre les écanvass dans mon télescope. J’utilise une caméra (ZWO ASI 120MM avec SDK v0.3 correspondant) et récupère ses images dans une boucle while dans un fil séparé. Celles-ci sont ensuite émises sur un QOpenGlWidget pour être affichées. J’ai le problème suivant: lorsque la souris est dans la zone QOpenGlWidget, les images affichées sont corrompues. Surtout quand la souris est déplacée. Le problème est pire lorsque j’utilise un temps d’exposition de 50 ms et disparaît pour des temps d’exposition inférieurs. Lorsque je nourris le pipeline avec des images alternées à partir du disque, le problème disparaît. Je suppose qu’il s’agit d’une sorte de problème de synchronisation des threads entre le thread de la caméra et le thread principal, mais je ne pouvais pas le résoudre. Le même problème apparaît dans le logiciel openastro. Voici des parties du code:

Fenêtre principale:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ mutex = new QMutex; camThread = new QThread(this); camera = new Camera(nullptr, mutex); display = new GLViewer(this, mutex); setCentralWidget(display); cameraHandle = camera->getHandle(); connect(camThread, SIGNAL(started()), camera, SLOT(connect())); connect(camera, SIGNAL(exposureCompleted(const QImage)), display, SLOT(showImage(const QImage)), Qt::BlockingQueuedConnection ); camera->moveToThread(camThread); camThread->start(); } 

La routine qui saisit les frameworks:

 void Camera::captureFrame(){ while( cameraIsReady && capturing ){ mutex->lock(); error = ASIGetVideoData(camID, buffer, bufferSize, int(exposure*2*1e-3)+500); if(error == ASI_SUCCESS){ frame = QImage(buffer,width,height,QImage::Format_Indexed8).convertToFormat(QImage::Format_RGB32); //Indexed8 is for 8bit mutex->unlock(); emit exposureCompleted(frame); } else { cameraStream << "timeout" <unlock(); } } } 

La fente qui reçoit l’image:

 bool GLViewer::showImage(const QImage image) { mutex->lock(); mOrigImage = image; mRenderQtImg = mOrigImage; recalculatePosition(); updateScene(); mutex->unlock(); return true; } 

Et la fonction GL qui définit l’image:

 void GLViewer::renderImage() { makeCurrent(); glClear(GL_COLOR_BUFFER_BIT); if (!mRenderQtImg.isNull()) { glLoadIdentity(); glPushMasortingx(); { if (mResizedImg.width() <= 0) { if (mRenderWidth == mRenderQtImg.width() && mRenderHeight == mRenderQtImg.height()) mResizedImg = mRenderQtImg; else mResizedImg = mRenderQtImg.scaled(QSize(mRenderWidth, mRenderHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } glRasterPos2i(mRenderPosX, mRenderPosY); glPixelZoom(1, -1); glDrawPixels(mResizedImg.width(), mResizedImg.height(), GL_RGBA, GL_UNSIGNED_BYTE, mResizedImg.bits()); } glPopMatrix(); glFlush(); } } 

J’ai volé ce code à partir d’ici: https://github.com/Myzhar/QtOpenCVViewerGl

Et enfin, voici à quoi ressemble mon problème:

Cela semble affreux.

Le producteur d’images doit produire de nouvelles images et les émettre via un signal. Comme QImage est implicitement partagé, il recyclera automatiquement les frameworks pour éviter de nouvelles atsortingbutions. Ce n’est que lorsque le fil du producteur a dépassé le fil d’affichage que des copies d’images seront effectuées.

Au lieu d’utiliser une boucle explicite dans l’object Camera , vous pouvez exécuter la capture à l’aide d’un minuteur de durée nulle et de l’invoquer avec la boucle d’événement. De cette façon, l’object caméra peut traiter des événements, par exemple des timers, des invocations de fentes transversales, etc.

Il n’y a pas besoin de mutex explicites, ni de connexion bloquante. La boucle d’événement de Qt fournit une synchronisation inter-thread. Enfin, le projet QtOpenCVViewerGl effectue une mise à l’échelle de l’image sur le CPU et constitue vraiment un exemple de la façon dont il convient de ne pas le faire. Vous pouvez obtenir une mise à l’échelle d’image gratuitement en dessinant l’image sur un quad, même s’il s’agit également d’une technique dépassée datant des jours de référence, mais cela fonctionne très bien.

La classe ASICamera ressemblerait approximativement à ceci:

 // https://github.com/KubaO/stackoverflown/tree/master/questions/asi-astro-cam-39968889 #include  #include  #include "ASICamera2.h" class ASICamera : public QObject { Q_OBJECT ASI_ERROR_CODE m_error; ASI_CAMERA_INFO m_info; QImage m_frame{640, 480, QImage::Format_RGB888}; QTimer m_timer{this}; int m_exposure_ms = 0; inline int id() const { return m_info.CameraID; } void capture() { m_error = ASIGetVideoData(id(), m_frame.bits(), m_frame.byteCount(), m_exposure_ms*2 + 500); if (m_error == ASI_SUCCESS) emit newFrame(m_frame); else qDebug() << "capture error" << m_error; } public: explicit ASICamera(QObject * parent = nullptr) : QObject{parent} { connect(&m_timer, &QTimer::timeout, this, &ASICamera::capture); } ASI_ERROR_CODE error() const { return m_error; } bool open(int index) { m_error = ASIGetCameraProperty(&m_info, index); if (m_error != ASI_SUCCESS) return false; m_error = ASIOpenCamera(id()); if (m_error != ASI_SUCCESS) return false; m_error = ASIInitCamera(id()); if (m_error != ASI_SUCCESS) return false; m_error = ASISetROIFormat(id(), m_frame.width(), m_frame.height(), 1, ASI_IMG_RGB24); if (m_error != ASI_SUCCESS) return false; return true; } bool close() { m_error = ASICloseCamera(id()); return m_error == ASI_SUCCESS; } Q_SIGNAL void newFrame(const QImage &); QImage frame() const { return m_frame; } Q_SLOT bool start() { m_error = ASIStartVideoCapture(id()); if (m_error == ASI_SUCCESS) m_timer.start(0); return m_error == ASI_SUCCESS; } Q_SLOT bool stop() { m_error = ASIStopVideoCapture(id()); return m_error == ASI_SUCCESS; m_timer.stop(); } ~ASICamera() { stop(); close(); } }; 

Comme j'utilise une implémentation d'API ASI factice, ce qui précède est suffisant. Le code pour une vraie caméra ASI devrait définir les contrôles appropriés, tels que l'exposition.

Le visualiseur OpenGL est également assez simple:

 class GLViewer : public QOpenGLWidget, protected QOpenGLFunctions_2_0 { Q_OBJECT QImage m_image; void ck() { for(GLenum err; (err = glGetError()) != GL_NO_ERROR;) qDebug() << "gl error" << err; } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.2f, 0.2f, 0.25f, 1.f); } void resizeGL(int width, int height) override { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, width, height, 0, 0, 1); glMatrixMode(GL_MODELVIEW); update(); } // From http://stackoverflow.com/a/8774580/1329652 void paintGL() override { auto scaled = m_image.size().scaled(this->size(), Qt::KeepAspectRatio); GLuint texID; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glGenTextures(1, &texID); glEnable(GL_TEXTURE_RECTANGLE); glBindTexture(GL_TEXTURE_RECTANGLE, texID); glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGB, m_image.width(), m_image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, m_image.constBits()); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(0, 0); glTexCoord2f(m_image.width(), 0); glVertex2f(scaled.width(), 0); glTexCoord2f(m_image.width(), m_image.height()); glVertex2f(scaled.width(), scaled.height()); glTexCoord2f(0, m_image.height()); glVertex2f(0, scaled.height()); glEnd(); glDisable(GL_TEXTURE_RECTANGLE); glDeleteTextures(1, &texID); ck(); } public: GLViewer(QWidget * parent = nullptr) : QOpenGLWidget{parent} {} void setImage(const QImage & image) { Q_ASSERT(image.format() == QImage::Format_RGB888); m_image = image; update(); } }; 

Enfin, nous associons la caméra et le spectateur. Comme l'initialisation de la caméra peut prendre un certain temps, nous l'exécutons dans le fil de la caméra.

L’interface utilisateur doit émettre des signaux qui contrôlent la caméra, par exemple pour l’ouvrir, démarrer / arrêter l’acquisition, etc., et disposer de fentes qui fournissent un retour de la caméra (par exemple, des changements d’état). Une fonction autonome prend les deux objects et les relie ensemble, en utilisant les foncteurs appropriés pour adapter l'interface utilisateur à une caméra particulière. Si le code de l'adaptateur était volumineux, vous utiliseriez un QObject assistance, mais une fonction devrait normalement suffire (comme ci-dessous).

 class Thread : public QThread { public: ~Thread() { quit(); wait(); } }; // See http://stackoverflow.com/q/21646467/1329652 template  static void postToThread(F && fun, QObject * obj = qApp) { QObject src; QObject::connect(&src, &QObject::destroyed, obj, std::forward(fun), Qt::QueuedConnection); } int main(int argc, char ** argv) { QApplication app{argc, argv}; GLViewer viewer; viewer.setMinimumSize(200, 200); ASICamera camera; Thread thread; QObject::connect(&camera, &ASICamera::newFrame, &viewer, &GLViewer::setImage); QObject::connect(&thread, &QThread::destroyed, [&]{ camera.moveToThread(app.thread()); }); camera.moveToThread(&thread); thread.start(); postToThread([&]{ camera.open(0); camera.start(); }, &camera); viewer.show(); return app.exec(); } #include "main.moc" 

Le projet GitHub inclut un faisceau de test de base API de caméra ASI et est complet: vous pouvez l'exécuter et voir la vidéo de test restituée en temps réel.