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

现在,问题在于后一个版本的失败率(“不成功触发器”的比例)要高得多——至少一个数量级。此外,这段代码最终也会陷入无限循环,无论它尝试重新触发相机多少次,它都没有看到任何帧返回。在后一种情况下,杀死应用程序并检查相机表明相机处于完美的工作状态并耐心等待下一次触发,因此它似乎不是相机问题。我得出的结论是,实际上这是某种系统资源问题或线程冲突,因此 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 线程中的互斥体上运行AcquireFramethat blocks 对我来说没有多大意义,除非你想用 GUI 响应来换取延迟,但我怀疑你是否关心这里的延迟,因为相机捕捉单帧并且你坚持处理它们首先是繁忙的 GUI 线程。

其次,Qt 不会做任何事情来阻止回调被另一个线程调用,除了另一个线程具有较低优先级并被较高优先级线程抢占,完全独占 CPU。

我只需从回调函数向 GUI 线程(或任何其他 QThread!)中的 QObject 发布一个事件。您可以从任何线程发布事件,这并不重要——重要的是接收者。QCoreApplication::postEvent毕竟是一个静态函数,它根本不检查当前线程。

在一个复杂的应用程序中,您可能希望将逻辑放在专用的控制器 QObject 中,而不是分散在 QWidget 派生类中。因此,您只需将事件发布到控制器实例。

请注意,将事件发布到空闲的 GUI 线程将与使用互斥锁完全相同——Qt 的事件循环使用互斥锁并在该互斥锁和来自操作系统的消息上休眠。美妙的是,Qt 已经为您完成了所有的等待,但等待是可中断的。发布的事件应该具有高优先级,以便它最终成为队列中的第一个事件并抢占所有其他事件。当您准备好获取帧时,但在触发它之前,您可能可以调用QCoreApplication::flush(). 就是这样。

将单独的图像处理器 QObject 放在专用的 QThread 中以利用多核机器应该没有问题。然后,您可以将图像处理成 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

请注意,超过 1 毫秒的结果可能是由于目前未安排 VMWare。

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 回答