Qt开发:ImagePlay算子逻辑流程处理

      ImagePlay 是一个基于 Qt 框架开发的快速图像处理原型工具,它允许用户通过可视化的方式构建图像处理流程。丰富的处理节点:包含各种图像处理算法和计算机视觉功能,用户可以在ImagePlay中快速实验各种图像处理算法,无需从头编写代码‌。实时预览:即时查看处理结果。涉及的核心关键类: IPProcessGrid(IPProcessGridScene,IPProcessStep,IPProcessThread (IPL算子库中的IPLProcess, IPLImage))

class IPProcessGrid : public QGraphicsView, public IPLPropertyChangedEventHandler, public IPLOutputsChangedEventHandler
{
    Q_OBJECT
public:
    explicit                IPProcessGrid           (QWidget *parent = 0);
    void                    zoomIn                  ();
    void                    zoomOut                 ();
    void                    zoomBy                  (float scaleChange);
    void                    zoomTo                  (float scale);
    static bool             sortTreeDepthLessThan   (IPProcessStep* s1, IPProcessStep* s2);
    void                    buildQueue              ();
    int                     executeThread           (IPLProcess* process, IPLImage *image = NULL, int inputIndex = 0, bool useOpenCV = false);
    void                    propagateNeedsUpdate    (IPLProcess* process);
    void                    propagateResultReady    (IPLProcess *process, bool resultReady);
    void                    propertyChanged         (IPLProcess *);
    void                    outputsChanged          (IPLProcess *);
    void                    setSequenceIndex        (int index);
    void                    setSequenceRunning      (bool status)                           { _isSequenceRunning = status; }
    void                    setMainWindow           (MainWindow* mainWindow)                { _mainWindow = mainWindow; }
    void                    requestUpdate           ();
    MainWindow*             mainWindow              ()                                      { return _mainWindow; }
    IPProcessGridScene*     scene                   ()                                      { return _scene; }
    void                    stopExecution           ()                                      { _stopExecution = true; }
    bool                    isRunning               ()                                      { return _isRunning; }

signals:
    void                    sequenceChanged         (int index, int count);

public slots:
    void                    execute                 (bool forcedUpdate = false);
    void                    terminate               ();
    void                    updateProgress          (int);
    void                    sceneRectChanged        (const QRectF & rect);

private:
    void                    fitLargeSceneRect();

    IPProcessGridScene*     _scene;                 //!< Scene
    float                   _scale;                 //!< Scale for zooming
    MainWindow*             _mainWindow;            //!< MainWindow
    bool                    _isRunning;             //!< Is running
    bool                    _updateNeeded;
    IPProcessStep*          _currentStep;           //!< Currently active step, settings shown on the left side
    QList<IPProcessStep*>   _processList;           //!< Ordered process list
    int                     _sequenceCount;         //!< Image sequence count
    int                     _sequenceIndex;         //!< Current image sequence index
    int                     _lastSequenceIndex;     //!< Last image sequence index
    bool                    _isSequenceRunning;     //!< Is sequence running
    bool                    _lastProcessSuccess;    //!< Last process was successful
    bool                    _stopExecution;         //!< Used to stop the execution early
    bool                    _longProcess;           //!< Unmeasurable processes must update GUI regularly
    IPProcessThread*        _thread;                //!< Reference to the current thread

    // QWidget interface
protected:
    virtual void            wheelEvent              (QWheelEvent *)     override;
    virtual void            resizeEvent             (QResizeEvent *)    override;
    virtual void            keyPressEvent           (QKeyEvent *)       override;
    virtual void            keyReleaseEvent         (QKeyEvent *)       override;
    virtual void            showEvent               (QShowEvent *)      override;
};

类职责与功能

IPProcessGrid 继承自 QGraphicsView,是图像处理流程的核心控制器,主要职责包括:

  • 流程可视化:通过 IPProcessGridScene 管理处理节点(IPProcessStep)和连接线(IPProcessEdge)的图形渲染。

  • 流程调度:构建执行队列、管理多线程计算、处理图像序列。

  • 事件响应:处理属性变更、输出更新、用户交互(缩放、键盘事件)。

  • 状态管理:跟踪执行状态、序列索引、进度更新。

关键成员解析

(1) 核心数据成员
成员类型说明
_sceneIPProcessGridScene*管理所有图形项(节点、连线)的 Qt 场景对象。
_processListQList<IPProcessStep*>按拓扑排序的处理步骤列表,用于执行顺序控制。
_currentStepIPProcessStep*当前选中的节点(用于显示属性面板)。
_threadIPProcessThread*处理线程,负责实际执行 IPLProcess 的计算逻辑。
_isRunningbool标记流程是否正在执行。
_sequenceIndexint当前图像序列索引(支持多帧处理)。
_stopExecutionbool用户主动终止执行的标志。
(2) 重要方法
方法功能
execute()启动流程执行(可强制更新 forcedUpdate)。
buildQueue()拓扑排序节点,构建执行队列 _processList
propagateNeedsUpdate()递归标记下游节点需要更新(当上游节点数据变更时)。
zoomIn()/zoomOut()缩放视图,通过 _scale 控制缩放比例。
propertyChanged()响应属性变更事件(来自 IPLPropertyChangedEventHandler 接口)。
(3) 信号与槽
信号/槽触发场景
sequenceChanged()图像序列索引变化时通知 UI 更新。
updateProgress()线程执行进度更新时触发。
sceneRectChanged()场景尺寸变化时自动调整视图显示区域。

核心工作流程

流程执行(execute()

void IPProcessGrid::execute(bool forcedUpdate /* = false*/)
{
    // if no processes yet, then exit
    if(_scene->steps()->size() < 1)
    {
        return;
    }

    // if already running or nothing has changed, exit
    if(_isRunning || !_updateNeeded)
    {
        return;
    }
    // prevent user changes during execution
    _mainWindow->lockScene();
    _isRunning = true;
    _sequenceCount = 0;

    buildQueue();

    int totalDurationMs = 0;

    // execute the processes
    int counter = 0;
    int limit = 10000;
    bool blockFailLoop = false;

    QList<IPProcessStep*> afterProcessingList;

    QListIterator<IPProcessStep *> it(_processList);
    while (it.hasNext() && counter < limit)
    {
        if(_stopExecution)
            return;

        IPProcessStep* step = it.next();
        _currentStep = step;

        // make sure the progress bar gets filled
        updateProgress(1);

        // source processes don't have inputs
        if(step->process()->isSource())
        {
            // execute thread
            if(step->process()->updateNeeded() || forcedUpdate)
            {
                step->process()->resetMessages();
                step->process()->beforeProcessing();
                int durationMs = executeThread(step->process());
                if ( !_lastProcessSuccess ) blockFailLoop = true;

                // afterProcessing will be called later
                afterProcessingList.append(step);

                totalDurationMs += durationMs;
                step->setDuration(durationMs);

                // update error messages
                _mainWindow->updateProcessMessages();
            }
        }
        else
        {
            if(step->process()->updateNeeded() || forcedUpdate)
            {
                // execute process once for every input
                for(int i=0; i < step->edgesIn()->size(); i++)
                {
                    IPProcessEdge* edge = step->edgesIn()->at(i);
                    int indexFrom = edge->indexFrom();
                    int indexTo = edge->indexTo();
                    IPProcessStep* stepFrom = edge->from();

                    IPLImage* result = static_cast<IPLImage*>(stepFrom->process()->getResultData(indexFrom));

                    // invalid result, stopp the execution
                    if(!result)
                    {
                        QString msg("Invalid operation at step: ");
                        msg.append(QString::fromStdString(stepFrom->process()->title()));
                        _mainWindow->showMessage(msg, MainWindow::MESSAGE_ERROR);
                        break;
                    }

                    // execute thread
                    step->process()->resetMessages();
                    step->process()->beforeProcessing();
                    int durationMs = executeThread(step->process(), result, indexTo, mainWindow()->useOpenCV());
                    if ( !_lastProcessSuccess ) blockFailLoop = true;

                    // afterProcessing will be called later
                    afterProcessingList.append(step);

                    totalDurationMs += durationMs;
                    step->setDuration(durationMs);

                    // update error messages
                    _mainWindow->updateProcessMessages();
                }
            }
        }

        // make sure the progress bar gets filled
        updateProgress(100);

        counter++;
    }

    if(_stopExecution)
        return;

    // call afterProcessing of all steps which were executed this time
    // processes like the camera might request another execution
    QListIterator<IPProcessStep *> it2(_processList);
    while (it2.hasNext())
    {
        IPProcessStep* step = it2.next();
        step->process()->setUpdateNeeded(false);
    }
    QListIterator<IPProcessStep *> it3(_processList);
    while (it3.hasNext())
    {
        IPProcessStep* step = it3.next();
        step->updateThumbnail();
        step->process()->afterProcessing();
    }

    _updateNeeded = false;

    // check to see if any of these items changed while running,
    // set _updateNeeded to true if any still need it
    // this can happen if a slider is still being dragged after
    // a process is started
    // blockFailLoop prevents an infinite loop if a process is failing
    if ( !blockFailLoop ){
        QListIterator<IPProcessStep *> itp(_processList);
        while (itp.hasNext())
        {
            IPProcessStep* step = itp.next();
            if (step->process()->updateNeeded() )
            {
                _updateNeeded = true;
                break;
            }
        }
    }

    // update images
    _mainWindow->imageViewer()->updateImage();
    _mainWindow->imageViewer()->showProcessDuration(totalDurationMs);

    // update process graph
    _mainWindow->updateGraphicsView();
    _mainWindow->unlockScene();

    _isRunning = false;
    _currentStep = NULL;
}

方法职责
  • 流程执行中枢:协调整个图像处理流程的按序执行

  • 状态管理:处理执行状态、更新标记、错误控制

  • 性能统计:记录每个步骤耗时及总耗时

  • 线程调度:通过 executeThread() 实现异步计算

2. 执行阶段分解

(1) 前置检查
if(_scene->steps()->size() < 1) return;  // 空流程检查
if(_isRunning || !_updateNeeded) return; // 运行中/无需更新检查
  • 双重保护:防止空流程或重复执行

  • 脏标记机制_updateNeeded 避免无效计算

(2) 执行准备
_mainWindow->lockScene();  // 锁定UI防止误操作
_isRunning = true;         // 标记执行状态
buildQueue();              // 构建拓扑排序队列
  • 线程安全:通过 lockScene() 保证执行期间UI稳定

  • 拓扑排序:确保依赖关系正确的执行顺序

(3) 主执行循环
while (it.hasNext() && counter < limit) {
    IPProcessStep* step = it.next();
    // ... 处理源节点/普通节点 ...
}
  • 节点分类处理

    • 源节点:直接执行(如摄像头采集)

    executeThread(step->process());
    • 普通节点:获取上游数据后执行

    IPLImage* result = stepFrom->process()->getResultData(indexFrom);
    executeThread(step->process(), result, indexTo);
  • 错误处理

    if(!result) {
        _mainWindow->showMessage("Invalid operation...", MESSAGE_ERROR);
        break;
    }
(4) 后处理阶段
// 批量更新节点状态
step->process()->setUpdateNeeded(false); 
step->updateThumbnail();              // 更新缩略图
step->process()->afterProcessing();   // 触发后处理钩子
  • 状态清理:重置更新标记

  • 延迟更新:避免执行过程中的中间状态干扰

(5) 收尾工作
_mainWindow->imageViewer()->updateImage();  // 刷新结果
_mainWindow->showProcessDuration(totalDurationMs); // 显示耗时
_mainWindow->unlockScene();  // 解锁UI
  • 结果回显:更新所有关联视图

  • 资源释放:恢复系统可操作状态

3. 关键设计亮点

(1) 增量执行机制
  • 脏节点检测:仅执行 updateNeeded==true 的节点

  • 强制更新forcedUpdate 参数覆盖脏检查

(2) 执行安全保障
bool blockFailLoop = false;
if(!_lastProcessSuccess) blockFailLoop = true;
  • 失败阻断:防止错误节点导致无限循环

  • 进度反馈:通过 updateProgress() 保持UI响应

(3) 性能监控
int durationMs = executeThread(...);
totalDurationMs += durationMs;
step->setDuration(durationMs);
  • 精确计时:使用 QElapsedTimer 记录每个步骤耗时

  • 可视化统计:总耗时显示在状态栏

拓扑排序(buildQueue()

  • 核心目标:通过 广度优先搜索(BFS) 对处理节点(IPProcessStep)进行拓扑排序,生成按依赖关系排列的执行队列 _processList

  • 关键输出

    • 设置每个节点的 treeDepth(依赖深度)和 branchID(分支标识)。

    • 排序后的 _processList 用于后续流程执行。

//breath first search
void IPProcessGrid::buildQueue()
{
    qDebug() << "IPProcessGrid::buildQueue";
    QQueue<IPProcessStep*> tmpQueue;
    _processList.clear();

    // find source processes
    int branchID = 0;
    for(auto it = _scene->steps()->begin(); it < _scene->steps()->end(); ++it)
    {
        IPProcessStep* step = (IPProcessStep*) *it;
        IPLProcess* process = step->process();

        // attach property changed event handler
        process->registerPropertyChangedEventHandler(this);
        process->registerOutputsChangedEventHandler(this);

        if(process->isSource())
        {
            step->setTreeDepth(0);
            step->setBranchID(branchID++);
            _processList.push_back(step);
            tmpQueue.enqueue(step);
        }
    }

    // add all other process steps with BFS
    int counter = 0;
    int limit   = 100;
    while(!tmpQueue.isEmpty() && counter < limit)
    {
        IPProcessStep* step = tmpQueue.dequeue();

        for(auto it = step->edgesOut()->begin(); it < step->edgesOut()->end(); ++it)
        {
            IPProcessEdge* edge = (IPProcessEdge*) *it;
            IPProcessStep* nextStep = edge->to();

            // set depth
            nextStep->setTreeDepth(step->treeDepth()+1);

            // set branch ID
            nextStep->setBranchID(step->branchID());

            // add to queue and list
            tmpQueue.enqueue(nextStep);

            // unique
            if(!_processList.contains(nextStep))
            {
                _processList.push_back(nextStep);
            }
        }
    }

    // sort by depth
    qSort(_processList.begin(), _processList.end(), IPProcessGrid::sortTreeDepthLessThan);

    // et voila, we have the execution order

    // move the tabs in the right order
    _mainWindow->imageViewer()->sortTabs();
}
(1) 初始化阶段
QQueue<IPProcessStep*> tmpQueue;  // BFS临时队列
_processList.clear();             // 清空最终执行列表
  • 注册事件处理器:为所有节点绑定属性变更和输出变更的回调。

    process->registerPropertyChangedEventHandler(this);
    process->registerOutputsChangedEventHandler(this);
(2) 识别源节点
if(process->isSource()) {
    step->setTreeDepth(0);         // 源节点深度为0
    step->setBranchID(branchID++); // 分配分支ID
    _processList.push_back(step);  // 加入最终列表
    tmpQueue.enqueue(step);        // 加入BFS队列
}
  • 源节点定义:无输入依赖的节点(如图像加载节点)。

  • 分支标识branchID 用于区分并行子流程。

(3) BFS遍历
while(!tmpQueue.isEmpty() && counter < limit) {
    IPProcessStep* step = tmpQueue.dequeue();
    for(auto edge : step->edgesOut()) {
        IPProcessStep* nextStep = edge->to();
        nextStep->setTreeDepth(step->treeDepth() + 1); // 深度+1
        nextStep->setBranchID(step->branchID());       // 继承分支ID
        tmpQueue.enqueue(nextStep);
        if(!_processList.contains(nextStep)) {
            _processList.push_back(nextStep);           // 去重添加
        }
    }
    counter++;
}
  • 依赖深度:子节点的 treeDepth = 父节点深度 + 1。

  • 循环保护limit=100 防止异常依赖导致的无限循环。

(4) 按深度排序
qSort(_processList.begin(), _processList.end(), sortTreeDepthLessThan);
  • 排序规则sortTreeDepthLessThan 比较节点的 treeDepth,确保无依赖的节点先执行。

(5) 更新UI
_mainWindow->imageViewer()->sortTabs();
  • 同步调整图像预览标签页的顺序,与执行顺序一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值