0

私はQtアプリケーションを使用して産業用カメラを制御しています。特に、特定の時間(たとえば、さまざまな照明設定が設定されている場合)にカメラをトリガーし、フレームが返されるまで待つ必要があります。最も単純なケースでは、次のコードがうまく機能します。

void AcquireFrame()
{
    // Runs in the main GUI thread:
    camera -> m_mutex.lock();
    camera -> frameHasArrived = false;
    camera -> m_mutex.unlock();
    camera -> triggerImageAcquisition();

    forever
    {
        camera -> m_mutex.lock()
        bool isReady = camera -> frameHasArrived;
        camera -> m_mutex.unlock()
        if (isReady)
        {
            return;
        }
        else
        {
            Sleep(10);
        }
    }
}

void callback(camera *)
{
    // Called by the camera driver from a separate OS thread - not a Qt thread - 
    // when a frame is ready:
    camera -> m_mutex.lock();
    camera -> frameHasArrived = true;
    camera -> m_mutex.unlock();
}

...そしてほとんどの場合、これは完全にうまく機能します。ただし、これは現実の世界であるため、カメラがトリガーを受信できない場合や、コンピューターがフレームを正常に受信できない場合があり、上記のコードは無限ループに入ります。

当然のことですが、タイムアウトを設定することです。そのため、フレームが特定の時間内に受信されない場合は、画像の取得を再試行できます。改訂されたコードは次のようになります。

void AcquireFrame()
{
    camera -> m_mutex.lock();
    camera -> frameHasArrived = false;
    camera -> m_mutex.unlock();
    camera -> triggerImageAcquisition();

    QTime timeout;
    timeout.start();
    forever
    {
        timeout.restart();

fetch:  camera -> m_mutex.lock()
        bool isReady = camera -> frameHasArrived;
        camera -> m_mutex.unlock()
        if (isReady)
        {
            return;
        }
        else if (timeout.elapsed() > CAM_TIMEOUT) 
        { 
            // Assume the first trigger failed, so try again:
            camera -> triggerImageAcquisition();
            continue;
        }
        else
        {
            Sleep(10);
            goto fetch;
        }
    }
}

さて、問題は、この後者のバージョンでは、失敗率(「失敗したトリガー」の割合)がはるかに高く、少なくとも1桁高いことです。さらに、このコードも最終的には無限ループに陥ります。カメラを再トリガーしようとしても、フレームが戻ってくることはありません。後者の状況では、アプリケーションを強制終了してカメラをチェックすると、カメラが完全に機能し、次のトリガーを辛抱強く待っていることがわかります。したがって、カメラの問題ではないようです。実際、これはある種のシステムリソースの問題またはスレッドの競合であるため、Qtのイベントループではカメラのコールバックを適切なタイミングで呼び出すことができないという結論に達しました。

これは可能性がありますか、そして実際にこれを行うためのより良い方法はありますか?


6月6日の更新:

以下の方法を採用して以来、問題は発生していません(カメラオブジェクトに追加のメンバー、つまり「m_condition」と呼ばれるQWaitConditionを指定しました)。

void AcquireFrame()
{
    bool frameReceived;

    forever
    {
        camera -> triggerImageAcquisition();

        camera -> m_mutex.lock();
        frameReceived = camera -> m_condition.wait(&camera->m_mutex, CAM_TIMEOUT);

        if (frameReceived)
        {
            // We received a frame from the camera, so can return:
            camera -> m_mutex.unlock();
            return;
        }

        // If we got to here, then the wait condition must have timed out. We need to
        // unlock the mutex, go back to the beginning of the 'forever' loop and try 
        // again:
        camera -> m_mutex.unlock();
    }
}

void callback (camera *)
{
    // Called by the camera driver from a separate OS thread -
    // not a QThread - when a frame is ready:
    camera -> m_condition.wakeOne();
}

これには、フレームを受信するかタイムアウトが発生するまでメインスレッドを一時停止する効果がありますが、Sleep()を削除し、Qtイベントループは全体を通して完全に制御されたままになります。古い方法がなぜこれほど多くの問題を引き起こしたのかはまだわかりませんが、システムリソースの制限があるのではないかと思いますが、この新しいアプローチはより軽量で、確かにうまく機能しているようです。

4

2 に答える 2

1

GUI スレッドのミューテックスでそのブロックを実行するAcquireFrameことは、GUI の応答性とレイテンシをトレードオフしたい場合を除き、私にはあまり意味がありませんが、カメラが単一のフレームをスナップし、それらをそもそもビジーな GUI スレッド。

第二に、コールバックが他のスレッドから呼び出されるのを防ぐために Qt が行うことはありません。他のスレッドの優先度が低く、CPU を完全に独占している優先度の高いスレッドによってプリエンプトされることを除きます。

コールバック関数から GUI スレッド (またはその他の QThread!) の QObject にイベントをポストするだけです。どのスレッドからでもイベントを投稿できますが、問題ではありません。重要なのはレシーバーです。QCoreApplication::postEvent結局のところ、静的関数であり、現在のスレッドをまったくチェックしません。

複雑なアプリケーションでは、専用のコントローラー QObject にロジックを配置し、QWidget 派生クラスに分散させたくない場合があります。したがって、イベントをコントローラー インスタンスにポストするだけです。

アイドル状態の GUI スレッドにイベントを投稿することは、ミューテックスを使用するのとまったく同じように機能することに注意してください。Qt のイベント ループはミューテックスを使用し、そのミューテックスと OS からのメッセージでスリープします。すばらしいことに、Qt はすでにすべての待機を行っていますが、待機は中断可能です。ポストされたイベントは、キューの最初のイベントになり、他のすべてのイベントより優先されるように、高い優先度を持つ必要があります。フレームを取得する準備ができたら、それをトリガーする前に、おそらく を呼び出すことができますQCoreApplication::flush()。それはそれについてです。

マルチコア マシンを活用するために、専用の QThread に別のイメージ プロセッサ QObject を配置しても問題はありません。次に、画像を QImage に処理し、別のイベントを使用して、または単純にシグナルスロット接続を介して、その画像を GUI スレッドに転送できます。フレームを取得したが、処理を開始したばかりの場合は、おそらく GUI スレッドに通知することもできます。そうすれば、何かが起こっていることがユーザーにとってより明白になります。画像処理に時間がかかる場合は、プログレス バーにマップされる定期的な更新を送信することもできます。

ベンチマークの結果 (リリース ビルドを使用) は興味深いものですが、Qt のイベント キューがミューテックスによって内部的に保護されており、イベント ループがそのミューテックスで待機しているという事実と一致しています。ああ、結果は Mac と Windows XP プラットフォーム間で移植可能のようです。

裸の待機条件を使用するのは最速の方法ではありませんが、裸の投稿されたイベントを使用するとさらに遅くなります。最も速い方法は、キューに入れられたシグナルスロット接続を使用することです。その場合、イベントを同じスレッドにポストするコスト (これが のFrameProcessorEvents::tick()役割です) は無視できるように見えます。

マック

warming caches...
benchmarking...
wait condition latency: avg=45us, max=152us, min=8us, n=1001
queued signal connection latency: avg=25us, max=82us, min=10us, n=1000
queued event latency: avg=71us, max=205us, min=14us, n=1000

VMWare Fusion の下の Windows XP

現時点で VMWare がスケジュールされていないため、結果が 1 ミリ秒を超える可能性があることに注意してください。

warming caches...
benchmarking...
wait condition latency: avg=93us, max=783us, min=8us, n=1000
queued signal connection latency: avg=46us, max=1799us, min=0us, n=1000
queued event latency: avg=117us, max=989us, min=18us, n=1001

以下はベンチマークコードです。

#include <cstdio>
#include <limits>
#include <QtCore>

QTextStream out(stdout);

class TimedBase : public QObject
{
public:
    TimedBase(QObject * parent = 0) : QObject(parent) { reset(); }
    friend QTextStream & operator<<(QTextStream & str, const TimedBase & tb) {
        return str << "avg=" << tb.avg() << "us, max=" << tb.usMax << "us, min="
                   << tb.usMin << "us, n=" << tb.n;
    }
    void reset() { usMax = 0; n = 0; usMin = std::numeric_limits<quint32>::max(); usSum = 0; }
protected:
    quint64 n, usMax, usMin, usSum;
    quint64 avg() const { return (n) ? usSum/n : 0; }
    void tock() {
        const quint64 t = elapsed.nsecsElapsed() / 1000;
        usSum += t;
        if (t > usMax) usMax = t;
        if (t < usMin) usMin = t;
        n ++;
    }
    QElapsedTimer elapsed;
};

class FrameProcessorEvents : public TimedBase
{
    Q_OBJECT
public:
    FrameProcessorEvents(QObject * parent = 0) : TimedBase(parent) {}
public slots: // can be invoked either from object thread or from the caller thread
    void tick() {
        elapsed.start();
        QCoreApplication::postEvent(this, new QEvent(QEvent::User), 1000);
    }
protected:
    void customEvent(QEvent * ev) { if (ev->type() == QEvent::User) tock(); }
};

class FrameProcessorWait : public TimedBase
{
    Q_OBJECT
public:
    FrameProcessorWait(QObject * parent = 0) : TimedBase(parent) {}
    void start() {
        QTimer::singleShot(0, this, SLOT(spinner()));
    }
public: // not a slot since it must be always invoked in the caller thread
    void tick() { elapsed.start(); wc.wakeAll(); }
protected:
    QMutex mutex;
    QWaitCondition wc;
protected slots:
    void spinner() {
        forever {
            QMutexLocker lock(&mutex);
            if (wc.wait(&mutex, 1000)) {
                tock();
            } else {
                return;
            }
        }
    }
};

FrameProcessorEvents * fpe;
FrameProcessorWait * fpw;

static const int avgCount = 1000;
static const int period = 5;

class FrameSender : public QObject
{
    Q_OBJECT
public:
    FrameSender(QObject * parent = 0) : QObject(parent), n(0), N(1) {
        QTimer::singleShot(0, this, SLOT(start()));
    }
protected slots:
    void start() {
        out << (N ? "warming caches..." : "benchmarking...") << endl;
        // fire off a bunch of wait ticks
        n = avgCount;
        timer.disconnect();
        connect(&timer, SIGNAL(timeout()), SLOT(waitTick()));
        fpw->reset();
        fpw->start();
        timer.start(period);
    }
    void waitTick() {
        fpw->tick();
        if (!n--) {
            if (!N) { out << "wait condition latency: " << *fpw << endl; }
            // fire off a bunch of signal+event ticks
            n = avgCount;
            fpe->reset();
            timer.disconnect();
            connect(&timer, SIGNAL(timeout()), fpe, SLOT(tick()));
            connect(&timer, SIGNAL(timeout()), SLOT(signalTick()));
        }
    }
    void signalTick() {
        if (!n--) {
            if (!N) { out << "queued signal connection latency: " << *fpe << endl; }
            // fire off a bunch of event-only ticks
            n = avgCount;
            fpe->reset();
            timer.disconnect();
            connect(&timer, SIGNAL(timeout()), SLOT(eventTick()));
        }
    }
    void eventTick() {
        fpe->tick();
        if (!n--) {
            if (!N) { out << "queued event latency: " << *fpe << endl; }
            if (!N--) {
                qApp->exit();
            } else {
                start();
            }
        }
    }

protected:
    QTimer timer;
    int n, N;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QThread eThread;
    QThread wThread;
    eThread.start(QThread::TimeCriticalPriority);
    wThread.start(QThread::TimeCriticalPriority);
    fpw = new FrameProcessorWait();
    fpe = new FrameProcessorEvents();
    fpw->moveToThread(&eThread);
    fpe->moveToThread(&wThread);
    FrameSender s;
    a.exec();
    eThread.exit();
    wThread.exit();
    eThread.wait();
    wThread.wait();
    return 0;
}

#include "main.moc"
于 2012-06-01T06:09:34.483 に答える
0

トリガー状態を検出してカメラを発射するのにどれくらいの作業が必要ですか?
それが比較的安価な場合-トリガーイベントをブロックしてカメラを起動するだけの別のスレッドがあります。次に、コールバック関数から送信されたQtシグナルによってメインスレッドに通知します。

于 2012-05-22T15:50:50.937 に答える