c++ - 相机中的 Qt 视频帧损坏

标签 c++ qt video webcam

编辑:第一个答案解决了我的问题。除此之外,我必须将 ASI_BANDWIDTH_OVERLOAD 值设置为 0。

我正在使用 C++/Qt 5.7 编写一个 Linux 应用程序来跟踪望远镜中的星星。我使用相机(ZWO ASI 120MM,带有 SDK v0.3)并在单独线程的 while 循环中抓取其帧。然后将它们发送到 QOpenGlWidget 进行显示。我有以下问题:当鼠标位于 QOpenGlWidget 区域内时,显示的帧会损坏。尤其是当鼠标移动时。当我使用 50 毫秒的曝光时间时,这个问题最为严重,而当曝光时间较短时,该问题就会消失。当我向管道提供来自磁盘的交替图像时,问题就消失了。我认为这是相机线程和主线程之间的某种线程同步问题,但我无法解决它。 openastro软件中也出现同样的问题。以下是部分代码:

主窗口:

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();
}

抓取帧的例程:

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" << endl;
            mutex->unlock();
        }
    }
}

接收图像的槽位:

bool GLViewer::showImage(const QImage image)
{
    mutex->lock();
    mOrigImage = image;
    mRenderQtImg = mOrigImage;

    recalculatePosition();

    updateScene();

    mutex->unlock();
    return true;
}

以及设置图像的GL函数:

void GLViewer::renderImage()
{
    makeCurrent();
    glClear(GL_COLOR_BUFFER_BIT);

    if (!mRenderQtImg.isNull())
    {
        glLoadIdentity();
        glPushMatrix();
        {
            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();
    }
}

我从这里偷了这个代码:https://github.com/Myzhar/QtOpenCVViewerGl

最后,我的问题如下:

This looks awful.

最佳答案

图像生成器应该生成新图像并通过信号发出它们。由于QImage是隐式共享的,它会自动回收帧以避免新的分配。只有当生产者线程运行超过显示线程时,才会进行图像复制。

您可以使用零持续时间计时器运行捕获,并让事件循环调用它,而不是在 Camera 对象中使用显式循环。这样相机对象就可以处理事件,例如定时器、跨线程槽调用等。

不需要显式互斥体,也不需要阻塞连接。 Qt 的事件循环提供跨线程同步。最后,QtOpenCVViewerGl 项目在 CPU 上执行图像缩放,这实际上是一个如何不执行此操作的示例。您可以通过在四边形上绘制图像来免费获得图像缩放,尽管这也是固定管道时代的过时技术 - 但它工作得很好。

ASICamera 类大致如下所示:

// https://github.com/KubaO/stackoverflown/tree/master/questions/asi-astro-cam-39968889
#include <QtOpenGL>
#include <QOpenGLFunctions_2_0>
#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();
   }
};

由于我使用的是虚拟 ASI API 实现,因此上述内容就足够了。真正的 ASI 相机的代码需要设置适当的控制,例如曝光。

OpenGL 查看器也相当简单:

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();
   }
};

最后,我们将相机和观察器连接在一起。由于相机初始化可能需要一些时间,因此我们在相机线程中执行它。

UI 应发出控制相机的信号,例如打开它,开始/停止采集等,并具有提供来自相机的反馈(例如状态变化)的插槽。独立函数会将两个对象连接在一起,适当使用仿函数来使 UI 适应特定的相机。如果适配器代码很广泛,您可以使用辅助器 QObject 来实现,但通常一个函数就足够了(如下所示)。

class Thread : public QThread { public: ~Thread() { quit(); wait(); } };

// See http://stackoverflow.com/q/21646467/1329652
template <typename F>
static void postToThread(F && fun, QObject * obj = qApp) {
   QObject src;
   QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(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"

GitHub 项目包含一个非常基本的 ASI 相机 API 测试工具,并且已经完成:您可以运行它并查看实时渲染的测试视频。

关于c++ - 相机中的 Qt 视频帧损坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39968889/

相关文章:

c++ - 锁定函数层次结构

c++ - 指针的大小是多少?它具体取决于什么?

jquery - 没有播放器的视频自动播放(无闪光灯)

c++ - volatile 但不 protected 读取能否产生无限期的陈旧值? (在真实硬件上)

c++ - 在不关注程序窗口的情况下捕获键盘输入

Qt5.7 QML QtQuick;如何为桌面和 Android 构建可滚动和可编辑的 TextArea

c++ - 我无法在 QT 中编译我的项目

qt - 如何用qt向给定的串口写入数据?

c# - 在 C# 中使用 ffmpeg.exe 将 MPG 转换为 AVI

ruby-on-rails - Heroku 上的 ffmpeg : unrecognized option 'preset'