2016-10-11 145 views
2

编辑:第一个答案解决了我的问题。除此之外,我必须将ASI_BANDWIDTH_OVERLOAD值设置为0.从相机Qt视频帧损坏

我正在使用C++/Qt 5.7编写Linux应用程序来跟踪我望远镜中的恒星。我使用相机(ZWO ASI 120MM,根据SDK v0.3),并在一个单独的线程中在一个while循环中抓取帧。然后这些被发送到一个QOpenGlWidget被显示。我有以下问题:当鼠标位于QOpenGlWidget区域内时,显示的帧被损坏。特别是当鼠标移动时。当我使用50ms的曝光时间时,问题最严重,并且曝光时间较短时消失。当我通过磁盘交替传输图像给管道时,问题就消失了。我认为这是摄像头线程和主线程之间的某种线程同步问题,但我无法解决它。 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.

回答

2

图像生成器应该产生新的图像,并通过一个信号发射它们。由于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测试工具,是完整:你可以运行它,看看实时渲染的测试视频。

+0

感谢您发表这样一个详细的答案!这比我期待的要多得多:) – MonsterMax