Qt开发:ImagePlay算子属性介绍

     在 ImagePlay 中,算子属性(Operator Properties) 是控制图像处理节点(IPProcessStep  或 IPProcess)行为的关键参数。这些属性允许用户动态调整算子的行为(如滤波器的半径、边缘检测的阈值等)

算子属性的核心作用

IPLProcessProperty 是一个抽象基类,用于定义图像处理算子(IPLProcess)的可配置属性

其核心功能包括:

  • 克隆与重置:支持深拷贝和恢复默认值。

  • 参数化控制:每个图像处理算子(如高斯模糊、Canny边缘检测)通过属性暴露可调参数,用户可通过 GUI(如滑块、输入框)修改这些参数,实时改变处理效果。

  • 数据流配置:部分属性可能影响输入/输出端口的行为(如启用/禁用某些端口)。

  • 序列化支持:属性值通常需要保存到项目文件(或从文件加载),支持流程的持久化。

class IPLSHARED_EXPORT IPLProcessProperty
{
public:
    struct SerializedData
    {
        std::string type;
        std::string widget;
        std::string widgetName;
        std::string value;
    };

    struct DeserialationFailed : public std::runtime_error
    { DeserialationFailed(): std::runtime_error("") {} };

    int position() const                  { return _position; }
    const char* name() const              { return _name; }
    const char* title() const             { return _title; }
    const char* description() const       { return _description; }
    IPLProcessWidgetType widget() const   { return _widget; }
    virtual const char* type() const = 0;
    virtual SerializedData serialize() const = 0;
    virtual void deserialize(const SerializedData &data) = 0;
    virtual IPLProcessProperty *clone() const = 0;
    virtual void resetValue() = 0;

protected:
    IPLProcessProperty(int position,
                       const char *name,
                       const char *title,
                       const char *description,
                       IPLProcess *process,
                       IPLProcessWidgetType widget = IPL_WIDGET_DEFAULT);

    int _position;                  //!< Position in GUI
    const char* _name;              //!< ID for GUI
    const char* _title;             //!< Short title for GUI
    const char* _description;       //!< Short help for GUI
    IPLProcess*  _process;
    IPLProcessWidgetType _widget;
};

关键成员解析

(1) 核心数据成员
成员类型说明
_positionint属性在 GUI 中的布局顺序(如面板中的从上到下索引)。
_nameconst char*属性的唯一标识符(程序内部使用,如 "kernel_size")。
_titleconst char*属性的显示名称(GUI 可见,如 "Blur Radius")。
_descriptionconst char*属性的详细描述(如工具提示文本)。
_processIPLProcess*指向所属算子的指针,用于属性变更时通知算子更新。
_widgetIPLProcessWidgetType指定 GUI 控件类型(如 IPL_WIDGET_SLIDERIPL_WIDGET_CHECKBOX)。
(2) 纯虚函数(需子类实现)
函数作用
type()返回属性类型名称(如 intfloat)。
serialize()将属性值转换为 SerializedData,用于保存到项目文件。
deserialize()从 SerializedData 加载属性值,可能抛出 DeserialationFailed 异常。
clone()深拷贝当前属性对象(多态复制,需子类实现)。
resetValue()重置属性为默认值。
(3) 异常类
struct DeserialationFailed : public std::runtime_error { ... };
  • 反序列化失败时抛出,例如文件数据格式不匹配。

(4) GUI 动态生成控件
// 伪代码:根据属性类型创建控件
IPLProcessProperty* prop = process->property("kernel_size");
if (prop->type() == "int" && prop->widget() == IPL_WIDGET_SLIDER) {
    QSlider* slider = new QSlider();
    slider->setRange(prop->as<IPLIntProperty>()->min(), 
                     prop->as<IPLIntProperty>()->max());
    slider->setValue(prop->as<IPLIntProperty>()->value());
}
(5) 序列化数据结构
struct SerializedData {
    std::string type;      // 属性类型(如 "int")
    std::string widget;    // 控件类型(如 "slider")
    std::string widgetName;// 控件名称(可选)
    std::string value;     // 属性值的字符串表示(如 "5")
};

设计模式与实现技巧

  • 工厂模式:子类(如 IPLIntPropertyIPLFloatProperty)通过 type() 和 clone() 支持动态创建和复制。

  • 观察者模式:属性值变化时,通过 _process 通知所属算子更新处理逻辑。

  • 策略模式:_widget 允许同一属性类型(如 int)对应不同 GUI 控件(滑块、输入框)。

在 ImagePlay 中,算子属性可能支持以下数据类型:

类型示例GUI 控件
整数 (int)模糊核大小、阈值滑块 (QSlider)
浮点数 (double)Sigma 值、透明度双精度滑块 (QDoubleSpinBox)
布尔值 (bool)启用/禁用功能复选框 (QCheckBox)
字符串 (string)文件路径、模式选择文本框 (QLineEdit)
枚举值 (enum)颜色空间转换类型下拉菜单 (QComboBox)
图像 (cv::Mat)掩模图像、模板特殊文件选择器

 属性UI控件 

class IPPropertyWidget : public QWidget
{
    Q_OBJECT
public:
    explicit IPPropertyWidget(IPLProcessProperty* processProperty, QWidget* parent=0) : QWidget(parent)
    {
        _processProperty = processProperty;
    }
    ~IPPropertyWidget()
    {
        _processProperty = NULL;
    }

    virtual void saveValue() = 0;
    virtual void resetValue() = 0;
    IPLProcessProperty* processProperty() { return _processProperty; }

signals:
    void changed();
private:
    IPLProcessProperty* _processProperty;

};

  IPPropertyWidget 是一个 抽象基类(继承自 QWidget),作为 属性控件 的通用接口,核心功能包括:

  • 桥梁作用:将 IPLProcessProperty(属性数据)与 Qt 控件(GUI 显示)连接。

  • 值同步:提供 saveValue() 和 resetValue() 方法,确保 GUI 控件值与属性数据双向同步。

  • 变更通知:通过 changed() 信号通知外部属性值已修改。

设计模式与实现逻辑

  • 策略模式
    每个 IPLProcessProperty 类型(如 IPLIntProperty)对应一个具体的 IPPropertyWidget 子类(如 IPPropertySliderInt),实现不同的控件交互方式。

  • 观察者模式
    changed() 信号允许外部对象(如主窗口)监听属性变更,触发实时处理。

工作流程

  1. 控件创建

    • 当用户打开属性面板时,根据 IPLProcessProperty 的类型和 widget() 标志创建对应的 IPPropertyWidget 子类实例。

    void IPProcessPropertiesWidget::init(IPProcessStep* processStep)
    {
        _processStep = processStep;
    
        // remove all children
        while (layout()->count() > 0)
        {
            QLayoutItem* item = layout()->takeAt(0);
            if(item  != NULL )
            {
                delete item->widget();
                delete item;
            }
        }
    //  delete layout();
    
        auto* processSettings = _processStep->process()->properties();
    
        if(processSettings->size() == 0)
        {
            addPropertyWidget("This step has no properties.", "", NULL);
        }
    
        // sort the properties by user set position
        std::vector<IPLProcessProperty*> orderedProperties;
        orderedProperties.reserve(processSettings->size());
    
        for (auto &entry: *processSettings)
            orderedProperties.push_back(entry.second.get());
    
        std::sort(orderedProperties.begin(), orderedProperties.end(), IPProcessPropertiesWidget::sortByPosition);
    
        // create all property widgets
        for (auto &property: orderedProperties)
        {
            // generate GUI based on the property type
    
            if (property->widget() == IPL_WIDGET_HIDDEN) {} //Don't process hidden widgets
            else if (property->widget() == IPL_WIDGET_LABEL) //Labels are type independent
            {
                QFormLayout* layout = (QFormLayout*) this->layout();
    
                QLabel* lblDescription = new QLabel(property->description());
                lblDescription->setWordWrap(true);
                lblDescription->setStyleSheet("color: #666; font-size: 10px");
                layout->addRow("", lblDescription);
    
                // add widget to list
                //_propertyWidgets.append(lblDescription);
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyInt*>(property))
            {
                IPPropertyWidget *widget = NULL;
                QString rawName, name;
                switch(p->widget())
                {
                case IPL_WIDGET_SLIDER:
                    widget = new IPPropertySliderInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_SLIDER_ODD:
                    widget = new IPPropertySliderIntOdd(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_SLIDER_EVEN:
                    widget = new IPPropertySliderIntEven(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_RADIOBUTTONS:
                    widget = new IPPropertyRadioInt(p, this);
                    rawName = property->title(); // name:value1|value2
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                case IPL_WIDGET_COMBOBOX:
                    widget = new IPPropertyCombobox(p, this);
                    rawName = property->title(); // name:value1|value2
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                case IPL_WIDGET_GROUP:
                    widget = new IPPropertyGroup(p, this);
                    rawName = property->title(); // name:value1|value2
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, "", widget);
    
                    // connect widget events to grid execution
                    connect((IPPropertyGroup*)widget, &IPPropertyGroup::groupChanged, this, &IPProcessPropertiesWidget::showPropertyGroup);
    
                    break;
    
                case IPL_WIDGET_BUTTON:
                    widget = new IPPropertyButtonInt(p, this);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                default: //IPL_WIDGET_SPINNER
                    IPPropertySpinnerInt* widget = new IPPropertySpinnerInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyUnsignedInt*>(property))
            {
                IPPropertyWidget *widget = NULL;
                QString rawName, name;
    
    
                switch(p->widget())
                {
                //TODO: Implement Sliders, Checkboxes etc.
    
                case IPL_WIDGET_CHECKBOXES:
                    widget = new IPPropertyCheckboxInt(p, this);
                    rawName = property->title(); // name:value1|value2
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                case IPL_WIDGET_BUTTON:
                    widget = new IPPropertyButtonUnsignedInt(p, this);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                default: //IPL_WIDGET_SPINNER
                    IPPropertySpinnerUnsignedInt* widget = new IPPropertySpinnerUnsignedInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyDouble*>(property))
            {
                IPPropertyWidget *widget = NULL;
                switch(p->widget())
                {
                default: //IPL_WIDGET_SLIDER
                    widget = new IPPropertySliderDouble(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyString*>(property))
            {
                IPPropertyWidget *widget = NULL;
                QString rawName, name;
                QString defaultDirectory =  _mainWindow->defaultImagePath();
                switch(p->widget())
                {
                case IPL_WIDGET_FILE_OPEN:
                    widget = new IPPropertyFileOpen(p, defaultDirectory, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_FILE_SAVE:
                    widget = new IPPropertyFileSave(p, this);
                    // name:value1|value2
                    rawName = property->title();
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                case IPL_WIDGET_FOLDER:
                    widget = new IPPropertyFolder(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                default: //IPL_WIDGET_TEXTFIELD
                    widget = new IPPropertyString(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyColor*>(property))
            {
                IPPropertyWidget *widget = NULL;
                IPLColorPickProvider *provider = dynamic_cast<IPLColorPickProvider*>(_mainWindow->imageViewer());
                switch(p->widget())
                {
                case IPL_WIDGET_COLOR_HSL:
                    widget = new IPPropertyColorHSL(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_COLOR_HSV:
                    widget = new IPPropertyColorHSV(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                default: //IPL_WIDGET_COLOR_RGB
                    widget = new IPPropertyColorRGB(p, this, provider);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
    
                //if (auto picker = dynamic_cast<IPLColorPickHandler*>(widget)) // connect to image viewer for color picking
                //    _mainWindow->imageViewer()->setColorPickHandler(picker);
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyBool*>(property))
            {
                IPPropertyWidget *widget = NULL;
                switch(p->widget())
                {
                case IPL_WIDGET_BUTTON:
                    widget = new IPPropertyButtonBool(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                default: //IPL_WIDGET_CHECKBOXES
                    widget = new IPPropertyCheckbox(p, property->title(), this);
                    addPropertyWidget("", property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyVectorInt*>(property))
            {
                IPPropertyWidget *widget = NULL;
                switch(p->widget())
                {
                case IPL_WIDGET_BINARY_MORPHOLOGY:
                    widget = new IPPropertyBinaryMorphologyInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_BINARY_MORPHOLOGY_TRISTATE:
                    widget = new IPPropertyBinaryMorphologyTristateInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                case IPL_WIDGET_GRAYSCALE_MORPHOLOGY:
                    widget = new IPPropertyGrayscaleMorphologyInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
    
                default: //IPL_WIDGET_KERNEL
                    widget = new IPPropertyKernelInt(p, this);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyVectorDouble*>(property))
            {
                IPPropertyWidget *widget = NULL;
                QString rawName, name;
                switch(p->widget())
                {
                case IPL_WIDGET_MATRIX:
                    widget = new IPPropertyMatrixDouble(p, this);
                    rawName = property->title(); // name:rows|cols
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, property->description(), widget);
                    break;
    
                default: //IPL_WIDGET_MATRIX
                    widget = new IPPropertyMatrixDouble(p, this);
                    rawName = property->title(); // name:rows|cols
                    name = rawName.split(":").at(0);
                    addPropertyWidget(name, property->description(), widget);
                    break;
                }
            }
    
            else if (auto p = dynamic_cast<IPLProcessPropertyPoint*>(property))
            {
                IPPropertyWidget *widget = NULL;
                IPLCoordinatePickProvider *provider = dynamic_cast<IPLCoordinatePickProvider*>(_mainWindow->imageViewer());
                switch(p->widget())
                {
                default: //IPL_WIDGET_POINT
                    widget = new IPPropertyPoint(p, this, provider);
                    addPropertyWidget(property->title(), property->description(), widget);
                    break;
                }
    
                //if (auto picker = dynamic_cast<IPLCoordinatePickHandler*>(widget)) // connect to image viewer for coordinate picking
                //    _mainWindow->imageViewer()->setCoordinatePickHandler(picker);
            }
        }
    }
  2. 用户交互

    • 用户操作控件(如拖动滑块)→ 触发 changed() 信号 → 主窗口调用 saveValue() 并更新处理结果。

  3. 数据持久化

    • 保存项目时,通过 _processProperty->serialize() 存储属性值。

属性控件呈现

///双击算子节点
/*!
 * \brief IPProcessStep::mouseDoubleClickEvent
 * \param event
 */
void IPProcessStep::mouseDoubleClickEvent(QGraphicsSceneMouseEvent*)
{
    _mainWindow->setActiveProcessStep(this);
}



void MainWindow::setActiveProcessStep(IPProcessStep* step)
{
    if(!step)
    {
        _activeProcessStep = NULL;
        _lastActiveProcessStep = NULL;
        return;
    }

    if(_allowChangeActiveProcessStep)
    {
        _activeProcessStep = step;

        if(_synchronizeViews)
        {
            _allowChangeActiveProcessStep = false;
            _imageViewer->setActiveStep(_activeProcessStep->stepID());
            _allowChangeActiveProcessStep = true;
        }

        if(_lastActiveProcessStep)
        {
            hideProcessSettings();
            _lastActiveProcessStep->setEditing(false);
        }

        // activate and show settings
        showProcessSettings(_activeProcessStep);
        _activeProcessStep->setEditing(true);

        // save last active step
        _lastActiveProcessStep = _activeProcessStep;
    }
}


///主窗口MainWindow中显示
void MainWindow::showProcessSettings(IPProcessStep* processStep)
{
    ui->dockSettings->setVisible(true);
    ui->dockProcesses->setVisible(false);

    // add description
    if(processStep->process()->description().length() > 0)
    {
        ui->lblProcessDescription->setText(QString::fromStdString(processStep->process()->description()));
        ui->lblProcessDescription->setVisible(true);
    }
    else
    {
        ui->lblProcessDescription->setVisible(false);
    }

    // change title
    QString title = QString::fromStdString(processStep->process()->title());
    ui->lblProcessSettings->setText(title);

    // hide help button because it is currently not used
    //ui->btnHelpPage->hide();

    // add inputs/outputs
    if(processStep->process()->inputs()->size() > 0 || processStep->process()->outputs()->size() > 0)
    {
        QString msgInputsOutputs;

        if(processStep->process()->inputs()->size() > 0)
        {
            msgInputsOutputs.append("<b>Inputs:</b>");
            for(int i=0; i < (int)processStep->process()->inputs()->size(); i++)
            {
                IPLProcessIO input = processStep->process()->inputs()->at(i);
                QString msgString("<br />%1: %2 (<i>%3</i>)");
                msgInputsOutputs.append(msgString
                                        .arg(input.index)
                                        .arg(QString::fromStdString(input.name))
                                        .arg(QString::fromStdString(dataTypeName(input.type))));
            }
        }

        if(processStep->process()->outputs()->size() > 0)
        {
            if(processStep->process()->inputs()->size() > 0)
                msgInputsOutputs.append("<br />");

            msgInputsOutputs.append("<b>Outputs:</b>");
            for(int i=0; i < (int)processStep->process()->outputs()->size(); i++)
            {
                IPLProcessIO output = processStep->process()->outputs()->at(i);
                QString msgString("<br />%1: %2 (<i>%3</i>)");
                msgInputsOutputs.append(msgString
                                        .arg(output.index)
                                        .arg(QString::fromStdString(output.name))
                                        .arg(QString::fromStdString(dataTypeName(output.type))));
            }
        }
        ui->lblProcessInputsOutputs->setText(msgInputsOutputs);
        ui->lblProcessInputsOutputs->setVisible(true);
    }
    else
    {
        ui->lblProcessInputsOutputs->setVisible(false);
    }

    ui->processPropertiesWidget->init(processStep);
    ui->processMessageWidget->init(processStep);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值