2016-10-11 3 views
2

편집 : 첫 번째 대답이 내 문제를 해결했습니다. 그 외에도 ASI_BANDWIDTH_OVERLOAD 값을 0으로 설정해야했습니다.카메라의 Qt 비디오 프레임이 손상되었습니다

내 망원경의 별을 추적하기 위해 C++/Qt 5.7에서 Linux 응용 프로그램을 프로그래밍하고 있습니다. 카메라 (SDK v0.3에 따른 ZWO ASI 120MM)를 사용하고 별도의 스레드에서 while 루프의 프레임을 가져옵니다. 그런 다음 QOpenGlWidget에 출력되어 표시됩니다. 다음과 같은 문제가 있습니다 : 마우스가 QOpenGlWidget 영역 안에 있으면 표시된 프레임이 손상됩니다. 특히 마우스를 움직일 때. 문제는 50ms의 노출 시간을 사용하고 노출 시간이 낮을 때 사라지는 경우 최악입니다. 디스크에서 이미지를 번갈아 파이프 라인에 공급하면 문제가 사라집니다. 나는 이것이 카메라 스레드와 메인 스레드 사이의 일종의 스레드 동기화 문제라고 생각하지만 해결할 수 없었다. 같은 문제가 openastro 소프트웨어에 나타납니다. 여기서, 코드의 일부는 :

MainWindow를 :

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 함수 :

내가 여기에서이 코드를 훔친 : https://github.com/Myzhar/QtOpenCVViewerGl

그리고 마지막으로, 여기 내 문제가 모습입니다 :

This looks awful.

답변

2

이미지의 생산자는 새로운 이미지를 생성 및 신호를 통해를 방출한다. QImage은 암시 적으로 공유되므로 새로운 할당을 피하기 위해 프레임을 자동으로 재활용합니다. 생산자 쓰레드가 디스플레이 쓰레드를 out-run 할 때만 이미지 사본이 만들어집니다.

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

관련 문제