☰
OpenCV contrib viz 模块自定义 3D Widget 开发指南:基于 WidgetAccessor 与 VTK 创建专属可视化控件
2026/9/25 3:30:54 网站建设 项目流程
  • 计算机视觉
  • 图像处理
  • 机器学习

【免费下载链接】opencv_contrib

项目地址:https://gitcode.com/gh_mirrors/ope/opencv_contrib
点击查看免费下载

本篇技术指南以 opencv_contrib 仓库 viz 模块官方教程 为核心,讲解如何通过继承viz::Widget3D、利用viz::WidgetAccessor桥接 VTK 渲染管线,创建属于你自己的 3D 可视化控件(Widget),并将其显示在Viz3d可视化窗口中。读完本文,你将掌握自定义 Widget 的四步通用套路——定义子类、构建 VTK 几何管线、注入 Actor、设置颜色并展示,同时能读懂 viz 模块内置控件(如 WLine、WSphere、WCloud)的底层实现模式,具备扩展任意自定义几何体的能力。

前置背景:viz 模块与 VTK 的关系

opencv_contrib 的modules/viz是 OpenCV 的三维可视化模块,它本身并不实现渲染引擎,而是封装了 VTK(Visualization Toolkit)来负责真实的三维场景渲染。这一点从构建系统可以明确看到:modules/viz/CMakeLists.txt 第 1~3 行直接声明if(NOT HAVE_VTK) ocv_module_disable(viz) endif()——即系统找不到 VTK 时,viz 模块会被整体禁用;后续链接阶段还会把VTK_LIBRARIES作为模块依赖(第 38~46 行)。因此,使用本教程的前提是:

  • 系统已安装 VTK 开发库;
  • 编译 OpenCV 时通过 CMake 配置检测到 VTK(HAVE_VTK为真);
  • 若以静态库方式构建 OpenCV,仓库还在 CMake 中做了与 VTK 内置依赖(zlib/jpeg/png/tiff)的冲突规避处理(第 7~24 行)。

理解了"渲染最终由 VTK 完成"这一事实,下面要介绍的自定义 Widget 机制就顺理成章了:既然底层场景是 VTK 的渲染器(Renderer)与 Actor,那么让用户自定义几何体的最直接方式,就是允许用户构造一个 VTKvtkProp/vtkActor并塞进 OpenCV 的 Widget 外壳中——这正是WidgetAccessor存在的意义。

Widget 体系与 WidgetAccessor 的桥梁作用

在深入代码之前,先理清 viz 模块中与 Widget 相关的类层次(定义见 modules/viz/include/opencv2/viz/widgets.hpp):

  • viz::Widget:所有控件的基类,支持从 PLY 文件创建(Widget::fromPlyFile)、渲染属性设置(setRenderingProperty/getRenderingProperty)以及控件类型互转(cast<_W>);
  • viz::Widget3D:三维控件基类,在 Widget 之上增加了位姿控制(setPose/updatePose/getPose)、几何变换(applyTransform)与颜色设置(setColor);
  • viz::Widget2D:二维控件基类(如文字、图像覆盖层),只有颜色设置接口;
  • viz::WidgetAccessor:定义在 modules/viz/include/opencv2/viz/widget_accessor.hpp,它是对外暴露的"存取器",仅有getProp(const Widget&)与setProp(Widget&, vtkSmartPointer<vtkProp>)两个静态方法,负责在 Widget 与底层 VTK Prop 之间建立关联。

WidgetAccessor的实现非常精简,见 modules/viz/src/widget.cpp:

vtkSmartPointer<vtkProp> cv::viz::WidgetAccessor::getProp(const Widget& widget) { return widget.impl_->prop; } void cv::viz::WidgetAccessor::setProp(Widget& widget, vtkSmartPointer<vtkProp> prop) { widget.impl_->prop = prop; }

即每个Widget内部通过Impl(不透明实现指针)持有一个vtkSmartPointer<vtkProp>。setProp就是把这个内部指针赋值成你构造好的 VTK Actor;而getProp供 viz 内部取回该 Prop 交给 VTK 渲染器。在 modules/viz/src/vizimpl.cpp 中可以看到Viz3d::VizImpl::showWidget的实现:它先vtkProp3D::SafeDownCast(WidgetAccessor::getProp(widget))取出 Actor,应用位姿矩阵,再renderer_->AddActor(...)加入渲染器,并把 Prop 记录到widget_actor_map_中。也就是说,只要你在自定义 Widget 里正确调用了setProp,viz 就能自动完成后续的显示工作。

核心步骤:自定义 3D Widget 的完整流程

原教程给出了创建自定义控件的一套固定流程,本文将其展开为可复现的完整代码(完整程序见 modules/viz/samples/creating_widgets.cpp)。

第一步:扩展 Widget3D 定义新控件类

class WTriangle : public viz::Widget3D { public: WTriangle(const Point3f &pt1, const Point3f &pt2, const Point3f &pt3, const viz::Color & color = viz::Color::white()); };

要点:

  • 自定义类必须继承viz::Widget3D(三维控件)或viz::Widget2D(二维控件);
  • 构造函数接受三角形的三个三维顶点Point3f,颜色参数带默认值viz::Color::white();
  • 几何数据在构造函数内构建,这是自定义控件与内置控件保持一致的设计——内置的WLine、WSphere、WCube等同样是在构造函数中完成几何构造。

第二步:在构造函数内构建 VTK 几何管线并注入 Actor

这一步是核心中的核心,共分四个小环节:几何点、拓扑连接、PolyData 数据对象、Mapper 与 Actor。

WTriangle::WTriangle(const Point3f &pt1, const Point3f &pt2, const Point3f &pt3, const viz::Color & color) { // 1) 创建三个顶点 vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New(); points->InsertNextPoint(pt1.x, pt1.y, pt1.z); points->InsertNextPoint(pt2.x, pt2.y, pt2.z); points->InsertNextPoint(pt3.x, pt3.y, pt3.z); // 2) 定义三角形拓扑:按索引 0、1、2 连接三个点 vtkSmartPointer<vtkTriangle> triangle = vtkSmartPointer<vtkTriangle>::New(); triangle->GetPointIds()->SetId(0,0); triangle->GetPointIds()->SetId(1,1); triangle->GetPointIds()->SetId(2,2); vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New(); cells->InsertNextCell(triangle); // 3) 打包为 vtkPolyData:几何 + 拓扑 vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(points); polyData->SetPolys(cells); // 4) 创建 Mapper 与 Actor vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); #if VTK_MAJOR_VERSION <= 5 mapper->SetInput(polyData); // 老版本 VTK 接口 #else mapper->SetInputData(polyData); // VTK 6 及以上接口 #endif vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); // 5) 将 Actor 存入 Widget —— 这一步是自定义控件的关键 viz::WidgetAccessor::setProp(*this, actor); // 6) 设置颜色 —— 必须放在 setProp 之后 setColor(color); }

这段代码逐层解释如下:

  1. 顶点数据:vtkPoints依次插入三个点,顺序对应调用者传入的pt1、pt2、pt3;
  2. 拓扑关系:vtkTriangle通过GetPointIds()->SetId(...)声明三角形由第 0、1、2 号顶点构成,再通过vtkCellArray把该单元注册进单元列表;
  3. 数据对象:vtkPolyData同时持有几何(SetPoints)与拓扑(SetPolys),这是 VTK 渲染管线的输入数据单元;
  4. Mapper 与 Actor:vtkPolyDataMapper负责把 PolyData 转换为可绘制的图元,vtkActor是场景中真正被渲染器处理的实体,两者通过actor->SetMapper(mapper)绑定。注意代码里对VTK_MAJOR_VERSION <= 5做了分支处理——VTK 6 之后SetInput更名为SetInputData,示例代码对两代 VTK 都做了兼容;
  5. 注入 Actor:viz::WidgetAccessor::setProp(*this, actor)把 Actor 存入 Widget 内部指针,这样Viz3d::showWidget才能通过getProp取回并交给 VTK 渲染器(对应 modules/viz/src/vizimpl.cpp 中的取用逻辑);
  6. 设置颜色:setColor(color)必须放在setProp之后,因为Widget3D::setColor的实现(modules/viz/src/widget.cpp)内部要通过vtkActor::SafeDownCast(WidgetAccessor::getProp(*this))拿到 Actor 才能修改属性;若在setProp之前调用,Actor 还是空指针,会触发CV_Assert断言失败。

从setColor的源码可以看到颜色是如何被应用的:先关闭 Mapper 的标量着色(ScalarVisibilityOff(),确保不使用顶点标量色),再对 Actor 属性设置SetColor与SetEdgeColor(同时决定面片与边线颜色),最后actor->Modified()通知 VTK 重绘。

第三步:构造控件并显示在窗口中

int main() { help(); /// 创建可视化窗口 viz::Viz3d myWindow("Creating Widgets"); /// 创建一个三角形控件 WTriangle tw(Point3f(0.0,0.0,0.0), Point3f(1.0,1.0,1.0), Point3f(0.0,1.0,0.0), viz::Color::red()); /// 将控件显示到窗口中 myWindow.showWidget("TRIANGLE", tw); /// 启动事件循环 myWindow.spin(); return 0; }

三个环节的说明:

  • 窗口创建:viz::Viz3d myWindow("Creating Widgets")创建名为 "Creating Widgets" 的三维可视化窗口(Viz3d类定义于 modules/viz/include/opencv2/viz/viz3d.hpp);
  • 控件展示:myWindow.showWidget("TRIANGLE", tw)以字符串 ID "TRIANGLE" 注册该控件。showWidget的完整签名是void showWidget(const String &id, const Widget &widget, const Affine3d &pose = Affine3d::Identity()),第三个参数允许你直接在展示时指定初始位姿(默认单位变换)。ID 用于后续getWidget/removeWidget/setWidgetPose等窗口级操作;
  • 事件循环:myWindow.spin()启动渲染与交互事件循环,窗口保持打开并响应用户视角操作,直到用户关闭窗口。

示例中三个顶点取(0,0,0)、(1,1,1)、(0,1,0),颜色为红色viz::Color::red(),最终在窗口中渲染出一个红色三角形。

结果展示

程序运行后,窗口中会显示一个红色三角形,如下图所示。用户可以通过鼠标自由旋转、缩放视角来观察该三角形在三维空间中的位置与朝向。

源码印证:内置控件也遵循同一套模式

"定义子类 → 构建 VTK 管线 → setProp → setColor"并不是教程的孤例,而是整个 viz 模块所有内置控件统一遵循的范式。可以从源码中找出大量同类实现:

  • 几何体类(modules/viz/src/shapes.cpp):WLine(第 60~67 行)、WSphere(第 91~98 行)、WArrow(第 172~179 行)、WCube(第 332~339 行)等控件全部以WidgetAccessor::setProp(*this, actor)收尾;
  • 点云类(modules/viz/src/clouds.cpp):WCloud(第 91 行)、WPaintedCloud(第 159、201 行)、WMesh(第 492 行)同样如此;
  • 复合控件(modules/viz/src/shapes.cpp):WCloudCollection::addCloud(第 242 行)与WWidgetMerger::addWidget(第 497 行)还会用vtkActor::SafeDownCast(WidgetAccessor::getProp(widget))取出已有控件的 Actor 做进一步组合,这印证了getProp的另一个用途——读取并修改已有控件的底层 VTK 对象。

值得注意的差异是:内置几何控件构造后并未显式调用setColor,而是在构造函数通过actor->GetProperty()设置颜色等属性;而本教程的自定义控件把setColor作为对外接口,这恰好演示了如何复用Widget3D::setColor这一现成能力。另外,内置控件还会做一些渲染细节设置(如BackfaceCullingOn()背面剔除、SetInterpolationToFlat()平面着色),如果你需要类似效果,可以在自己的构造器中通过vtkActor的 Property 直接配置。

进阶:自定义控件的渲染属性与后续操作

控件创建并显示后,还可以通过以下接口进一步控制外观与行为:

  • 渲染属性:Widget::setRenderingProperty(int property, double value)支持POINT_SIZE、OPACITY、LINE_WIDTH、FONT_SIZE、REPRESENTATION(点/线框/面,取值为REPRESENTATION_POINTS、REPRESENTATION_WIREFRAME、REPRESENTATION_SURFACE)、SHADING(SHADING_FLAT/SHADING_GOURAUD/SHADING_PHONG)、AMBIENT、LIGHTING等(枚举定义于 modules/viz/include/opencv2/viz/widgets.hpp,实现于 modules/viz/src/widget.cpp)。其实现同样依赖getProp取出vtkActor后逐项映射到 VTK 属性;
  • 位姿控制:Widget3D::setPose/updatePose/getPose以及Viz3d::setWidgetPose/updateWidgetPose/getWidgetPose可以随时移动、旋转自定义控件;
  • 颜色调整:Widget3D::setColor与Widget2D::setColor分别支持三维、二维控件改色,viz::Color预置了丰富的颜色工厂方法(red()、green()、blue()、white()、orange()等,见 modules/viz/include/opencv2/viz/types.hpp);
  • 窗口级操作:Viz3d::removeWidget("TRIANGLE")移除控件、getWidget("TRIANGLE")取回控件(控件是隐式共享的,修改返回对象会立即反映到窗口)、spinOnce单步渲染、saveScreenshot保存截图等接口可参考 modules/viz/include/opencv2/viz/viz3d.hpp。

注意事项与适用前提

  • 编译开关:示例源码(modules/viz/samples/creating_widgets.cpp)用#ifndef USE_VTK做了兜底——若未定义USE_VTK宏,程序只打印提示并退出。这意味着该示例"需要直接与 VTK 一同编译"(教程原文说明),在 OpenCV 构建系统中由ocv_add_samples(modules/viz/CMakeLists.txt)统一处理;
  • 头文件:自定义代码需要同时包含<opencv2/viz.hpp>与<opencv2/viz/widget_accessor.hpp>,以及所需 VTK 头文件(vtkPoints.h、vtkTriangle.h、vtkCellArray.h、vtkPolyData.h、vtkPolyDataMapper.h、vtkActor.h等);
  • 类型匹配:setProp期望vtkSmartPointer<vtkProp>类型的参数,vtkActor是其子类可直接传入;反过来取用时应按 modules/viz/include/opencv2/viz/widget_accessor.hpp 的说明用vtkActor::SafeDownCast(viz::WidgetAccessor::getProp(widget))向下转型,因为getProp返回的是基类指针;
  • API 状态:viz 模块头文件(modules/viz/include/opencv2/viz/viz3d.hpp)保留了 beta 声明,提示该模块 API 仍处不稳定阶段、不保证二进制兼容,升级 OpenCV/VTK 版本时需留意接口变化;
  • 依赖环境:所有自定义控件功能都建立在系统具备 VTK 且 viz 模块成功编译的前提下,若构建时未检测到 VTK(HAVE_VTK为假),viz 模块及本示例都不会被编译。

扩展阅读

  • 教程同系列:@prev_tutorial{tutorial_transformations}(三维变换)、@next_tutorial{tutorial_histo3D}(直方图三维可视化)与 creating_widgets.markdown 构成 viz 模块教程链;
  • viz 模块其余教程位于 modules/viz/tutorials,涵盖轨迹可视化、SLAM 场景重建等应用;
  • 更多示例程序见 modules/viz/samples,其中不少展示了WidgetAccessor与自定义控件的实际用法。

通过本指南,你已经掌握了 viz 模块自定义控件从类定义、VTK 管线构建、Actor 注入到窗口展示的完整链路——这正是把任意自定义三维几何(三角形、网格、点云乃至复合体)接入 OpenCV 可视化场景的标准方法。

  • 计算机视觉
  • 图像处理
  • 机器学习

【免费下载链接】opencv_contrib

项目地址:https://gitcode.com/gh_mirrors/ope/opencv_contrib
点击查看免费下载

相关推荐

上一篇:AgentScope 2.0完整指南:如何构建生产级多智能体系统?
下一篇:大麦抢票自动化:ticket-purchase 三步跑通实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询