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) 核心数据成员
成员 | 类型 | 说明 |
---|---|---|
_scene | IPProcessGridScene* | 管理所有图形项(节点、连线)的 Qt 场景对象。 |
_processList | QList<IPProcessStep*> | 按拓扑排序的处理步骤列表,用于执行顺序控制。 |
_currentStep | IPProcessStep* | 当前选中的节点(用于显示属性面板)。 |
_thread | IPProcessThread* | 处理线程,负责实际执行 IPLProcess 的计算逻辑。 |
_isRunning | bool | 标记流程是否正在执行。 |
_sequenceIndex | int | 当前图像序列索引(支持多帧处理)。 |
_stopExecution | bool | 用户主动终止执行的标志。 |
(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();
-
同步调整图像预览标签页的顺序,与执行顺序一致。