From 02ebc4368c34df1904f1a6f3b5eef96a496aaf9f Mon Sep 17 00:00:00 2001 From: Anatoly Baksheev Date: Sun, 19 Jan 2014 03:16:06 +0400 Subject: [PATCH] Viz initial backport, compilation fixes, moved viz/viz.hpp header according to 2.4 style --- CMakeLists.txt | 5 + cmake/OpenCVDetectVTK.cmake | 21 + doc/tutorials/images/viz.jpg | Bin 0 -> 31804 bytes doc/tutorials/tutorials.rst | 16 + .../viz/creating_widgets/creating_widgets.rst | 159 +++ .../creating_widgets/images/red_triangle.png | Bin 0 -> 10489 bytes .../viz/launching_viz/images/window_demo.png | Bin 0 -> 7519 bytes .../viz/launching_viz/launching_viz.rst | 118 ++ .../images/facedetect.jpg | Bin 0 -> 17902 bytes .../images/image_effects.png | Bin 0 -> 29966 bytes .../viz/table_of_content_viz/images/intro.png | Bin 0 -> 2584 bytes .../table_of_content_viz.rst | 94 ++ .../images/camera_view_point.png | Bin 0 -> 18439 bytes .../images/global_view_point.png | Bin 0 -> 13767 bytes .../viz/transformations/transformations.rst | 202 +++ .../viz/widget_pose/images/widgetpose.png | Bin 0 -> 40892 bytes doc/tutorials/viz/widget_pose/widget_pose.rst | 162 +++ modules/core/include/opencv2/core/affine.hpp | 509 ++++++++ modules/core/include/opencv2/core/core.hpp | 1 + modules/viz/CMakeLists.txt | 11 + modules/viz/doc/images/cpw1.png | Bin 0 -> 4321 bytes modules/viz/doc/images/cpw2.png | Bin 0 -> 3548 bytes modules/viz/doc/images/cpw3.png | Bin 0 -> 17724 bytes modules/viz/doc/images/cube_widget.png | Bin 0 -> 6507 bytes modules/viz/doc/viz.rst | 9 + modules/viz/doc/viz3d.rst | 637 ++++++++++ modules/viz/doc/widget.rst | 1019 +++++++++++++++ modules/viz/include/opencv2/viz/types.hpp | 236 ++++ modules/viz/include/opencv2/viz/viz3d.hpp | 131 ++ modules/viz/include/opencv2/viz/vizcore.hpp | 127 ++ .../include/opencv2/viz/widget_accessor.hpp | 69 ++ modules/viz/include/opencv2/viz/widgets.hpp | 396 ++++++ modules/viz/src/clouds.cpp | 441 +++++++ modules/viz/src/interactor_style.cpp | 639 ++++++++++ modules/viz/src/interactor_style.hpp | 119 ++ modules/viz/src/precomp.hpp | 324 +++++ modules/viz/src/shapes.cpp | 1088 +++++++++++++++++ modules/viz/src/types.cpp | 206 ++++ modules/viz/src/viz3d.cpp | 148 +++ modules/viz/src/vizcore.cpp | 312 +++++ modules/viz/src/vizimpl.cpp | 542 ++++++++ modules/viz/src/vizimpl.hpp | 138 +++ modules/viz/src/vtk/vtkCloudMatSink.cpp | 158 +++ modules/viz/src/vtk/vtkCloudMatSink.h | 79 ++ modules/viz/src/vtk/vtkCloudMatSource.cpp | 286 +++++ modules/viz/src/vtk/vtkCloudMatSource.h | 96 ++ modules/viz/src/vtk/vtkImageMatSource.cpp | 143 +++ modules/viz/src/vtk/vtkImageMatSource.h | 82 ++ modules/viz/src/vtk/vtkOBJWriter.cpp | 241 ++++ modules/viz/src/vtk/vtkOBJWriter.h | 79 ++ modules/viz/src/vtk/vtkTrajectorySource.cpp | 110 ++ modules/viz/src/vtk/vtkTrajectorySource.h | 84 ++ modules/viz/src/vtk/vtkXYZWriter.cpp | 93 ++ modules/viz/src/vtk/vtkXYZWriter.h | 78 ++ modules/viz/src/widget.cpp | 327 +++++ modules/viz/test/test_main.cpp | 3 + modules/viz/test/test_precomp.cpp | 24 + modules/viz/test/test_precomp.hpp | 104 ++ modules/viz/test/test_tutorial2.cpp | 54 + modules/viz/test/test_tutorial3.cpp | 64 + modules/viz/test/test_viz3d.cpp | 64 + modules/viz/test/tests_simple.cpp | 407 ++++++ 62 files changed, 10455 insertions(+) create mode 100644 cmake/OpenCVDetectVTK.cmake create mode 100644 doc/tutorials/images/viz.jpg create mode 100644 doc/tutorials/viz/creating_widgets/creating_widgets.rst create mode 100644 doc/tutorials/viz/creating_widgets/images/red_triangle.png create mode 100644 doc/tutorials/viz/launching_viz/images/window_demo.png create mode 100644 doc/tutorials/viz/launching_viz/launching_viz.rst create mode 100644 doc/tutorials/viz/table_of_content_viz/images/facedetect.jpg create mode 100644 doc/tutorials/viz/table_of_content_viz/images/image_effects.png create mode 100644 doc/tutorials/viz/table_of_content_viz/images/intro.png create mode 100644 doc/tutorials/viz/table_of_content_viz/table_of_content_viz.rst create mode 100644 doc/tutorials/viz/transformations/images/camera_view_point.png create mode 100644 doc/tutorials/viz/transformations/images/global_view_point.png create mode 100644 doc/tutorials/viz/transformations/transformations.rst create mode 100644 doc/tutorials/viz/widget_pose/images/widgetpose.png create mode 100644 doc/tutorials/viz/widget_pose/widget_pose.rst create mode 100644 modules/core/include/opencv2/core/affine.hpp create mode 100644 modules/viz/CMakeLists.txt create mode 100644 modules/viz/doc/images/cpw1.png create mode 100644 modules/viz/doc/images/cpw2.png create mode 100644 modules/viz/doc/images/cpw3.png create mode 100644 modules/viz/doc/images/cube_widget.png create mode 100644 modules/viz/doc/viz.rst create mode 100644 modules/viz/doc/viz3d.rst create mode 100644 modules/viz/doc/widget.rst create mode 100644 modules/viz/include/opencv2/viz/types.hpp create mode 100644 modules/viz/include/opencv2/viz/viz3d.hpp create mode 100644 modules/viz/include/opencv2/viz/vizcore.hpp create mode 100644 modules/viz/include/opencv2/viz/widget_accessor.hpp create mode 100644 modules/viz/include/opencv2/viz/widgets.hpp create mode 100644 modules/viz/src/clouds.cpp create mode 100644 modules/viz/src/interactor_style.cpp create mode 100644 modules/viz/src/interactor_style.hpp create mode 100644 modules/viz/src/precomp.hpp create mode 100644 modules/viz/src/shapes.cpp create mode 100644 modules/viz/src/types.cpp create mode 100644 modules/viz/src/viz3d.cpp create mode 100644 modules/viz/src/vizcore.cpp create mode 100644 modules/viz/src/vizimpl.cpp create mode 100644 modules/viz/src/vizimpl.hpp create mode 100644 modules/viz/src/vtk/vtkCloudMatSink.cpp create mode 100644 modules/viz/src/vtk/vtkCloudMatSink.h create mode 100644 modules/viz/src/vtk/vtkCloudMatSource.cpp create mode 100644 modules/viz/src/vtk/vtkCloudMatSource.h create mode 100644 modules/viz/src/vtk/vtkImageMatSource.cpp create mode 100644 modules/viz/src/vtk/vtkImageMatSource.h create mode 100644 modules/viz/src/vtk/vtkOBJWriter.cpp create mode 100644 modules/viz/src/vtk/vtkOBJWriter.h create mode 100644 modules/viz/src/vtk/vtkTrajectorySource.cpp create mode 100644 modules/viz/src/vtk/vtkTrajectorySource.h create mode 100644 modules/viz/src/vtk/vtkXYZWriter.cpp create mode 100644 modules/viz/src/vtk/vtkXYZWriter.h create mode 100644 modules/viz/src/widget.cpp create mode 100644 modules/viz/test/test_main.cpp create mode 100644 modules/viz/test/test_precomp.cpp create mode 100644 modules/viz/test/test_precomp.hpp create mode 100644 modules/viz/test/test_tutorial2.cpp create mode 100644 modules/viz/test/test_tutorial3.cpp create mode 100644 modules/viz/test/test_viz3d.cpp create mode 100644 modules/viz/test/tests_simple.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d7db8fd13..aa4a2e28f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,7 @@ OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O" ON IF IOS) OCV_OPTION(WITH_CARBON "Use Carbon for UI instead of Cocoa" OFF IF APPLE ) OCV_OPTION(WITH_CUDA "Include NVidia Cuda Runtime support" ON IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT IOS) ) +OCV_OPTION(WITH_VTK "Include VTK library support (and build opencv_viz module eiher)" ON IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_CUFFT "Include NVidia Cuda Fast Fourier Transform (FFT) library support" ON IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT IOS) ) OCV_OPTION(WITH_CUBLAS "Include NVidia Cuda Basic Linear Algebra Subprograms (BLAS) library support" OFF IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT IOS) ) OCV_OPTION(WITH_NVCUVID "Include NVidia Video Decoding library support" OFF IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT ANDROID AND NOT IOS AND NOT APPLE) ) @@ -471,6 +472,9 @@ if(WITH_OPENCL) include(cmake/OpenCVDetectOpenCL.cmake) endif() +# --- VTK support --- +include(cmake/OpenCVDetectVTK.cmake) + # ---------------------------------------------------------------------------- # Add CUDA libraries (needed for apps/tools, samples) # ---------------------------------------------------------------------------- @@ -705,6 +709,7 @@ else() endif() status(" OpenGL support:" HAVE_OPENGL THEN "YES (${OPENGL_LIBRARIES})" ELSE NO) +status(" VTK support:" HAVE_VTK THEN "YES (ver ${VTK_VERSION})" ELSE NO) # ========================== MEDIA IO ========================== status("") diff --git a/cmake/OpenCVDetectVTK.cmake b/cmake/OpenCVDetectVTK.cmake new file mode 100644 index 000000000..ef9aa8043 --- /dev/null +++ b/cmake/OpenCVDetectVTK.cmake @@ -0,0 +1,21 @@ +if(NOT WITH_VTK OR ANDROID OR IOS) + return() +endif() + +find_package(VTK 6.0 QUIET COMPONENTS vtkRenderingCore vtkInteractionWidgets vtkInteractionStyle vtkIOLegacy vtkIOPLY vtkRenderingFreeType vtkRenderingLOD vtkFiltersTexture vtkIOExport NO_MODULE) + +if(NOT DEFINED VTK_FOUND OR NOT VTK_FOUND) + find_package(VTK 5.10 QUIET COMPONENTS vtkCommon vtkFiltering vtkRendering vtkWidgets vtkImaging NO_MODULE) +endif() + +if(NOT DEFINED VTK_FOUND OR NOT VTK_FOUND) + find_package(VTK 5.8 QUIET COMPONENTS vtkCommon vtkFiltering vtkRendering vtkWidgets vtkImaging NO_MODULE) +endif() + +if(VTK_FOUND) + set(HAVE_VTK ON) + message(STATUS "Found VTK ver. ${VTK_VERSION} (usefile: ${VTK_USE_FILE})") +else() + set(HAVE_VTK OFF) + message(STATUS "VTK is not found. Please set -DVTK_DIR in CMake to VTK build directory, or set $VTK_DIR enviroment variable to VTK install subdirectory with VTKConfig.cmake file (for windows)") +endif() diff --git a/doc/tutorials/images/viz.jpg b/doc/tutorials/images/viz.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ac8f3ed8d641d2332755e8c9196517f1438b616 GIT binary patch literal 31804 zcmcF~Wl$VZ)9x;dC0KBmun-a~!F3^o;OV% zR5Y|S|62+0-=i4l7`T|2xHLrgL^S{7@wXj-j|n^gZh?UG02F*62p{;j3qS<`0)S}$ zy#2oe9SA~2!$86OC)Ile0D?d$|6E7I1Yu!fy!dxF%0DSS!E1Cv4h(uR6=Nb!2VY|F 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 Za{J9|C-wgpr~fA2NBO^g0R8Xc{{oslWW)df literal 0 HcmV?d00001 diff --git a/doc/tutorials/tutorials.rst b/doc/tutorials/tutorials.rst index cbc51c195..54cc91acb 100644 --- a/doc/tutorials/tutorials.rst +++ b/doc/tutorials/tutorials.rst @@ -186,6 +186,21 @@ As always, we would be happy to hear your comments and receive your contribution :width: 80pt :alt: gpu icon +* :ref:`Table-Of-Content-Viz` + + .. tabularcolumns:: m{100pt} m{300pt} + .. cssclass:: toctableopencv + + =========== ======================================================= + |Viz| These tutorials show how to use Viz module effectively. + + =========== ======================================================= + + .. |Viz| image:: images/viz.jpg + :height: 80pt + :width: 80pt + :alt: viz icon + * :ref:`Table-Of-Content-General` .. tabularcolumns:: m{100pt} m{300pt} @@ -221,4 +236,5 @@ As always, we would be happy to hear your comments and receive your contribution gpu/table_of_content_gpu/table_of_content_gpu contrib/table_of_content_contrib/table_of_content_contrib ios/table_of_content_ios/table_of_content_ios + viz/table_of_content_viz/table_of_content_viz general/table_of_content_general/table_of_content_general diff --git a/doc/tutorials/viz/creating_widgets/creating_widgets.rst b/doc/tutorials/viz/creating_widgets/creating_widgets.rst new file mode 100644 index 000000000..8858035c3 --- /dev/null +++ b/doc/tutorials/viz/creating_widgets/creating_widgets.rst @@ -0,0 +1,159 @@ +.. _creating_widgets: + +Creating Widgets +**************** + +Goal +==== + +In this tutorial you will learn how to + +.. container:: enumeratevisibleitemswithsquare + + * Create your own widgets using WidgetAccessor and VTK. + * Show your widget in the visualization window. + +Code +==== + +You can download the code from :download:`here <../../../../samples/cpp/tutorial_code/viz/creating_widgets.cpp>`. + +.. code-block:: cpp + + #include + #include + #include + + #include + #include + #include + #include + #include + #include + #include + #include + + using namespace cv; + using namespace std; + + /** + * @class WTriangle + * @brief Defining our own 3D Triangle widget + */ + class WTriangle : public viz::Widget3D + { + public: + WTriangle(const Point3f &pt1, const Point3f &pt2, const Point3f &pt3, const viz::Color & color = viz::Color::white()); + }; + + /** + * @function WTriangle::WTriangle + */ + WTriangle::WTriangle(const Point3f &pt1, const Point3f &pt2, const Point3f &pt3, const viz::Color & color) + { + // Create a triangle + vtkSmartPointer points = vtkSmartPointer::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); + + vtkSmartPointer triangle = vtkSmartPointer::New(); + triangle->GetPointIds()->SetId(0,0); + triangle->GetPointIds()->SetId(1,1); + triangle->GetPointIds()->SetId(2,2); + + vtkSmartPointer cells = vtkSmartPointer::New(); + cells->InsertNextCell(triangle); + + // Create a polydata object + vtkSmartPointer polyData = vtkSmartPointer::New(); + + // Add the geometry and topology to the polydata + polyData->SetPoints(points); + polyData->SetPolys(cells); + + // Create mapper and actor + vtkSmartPointer mapper = vtkSmartPointer::New(); + #if VTK_MAJOR_VERSION <= 5 + mapper->SetInput(polyData); + #else + mapper->SetInputData(polyData); + #endif + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + // Store this actor in the widget in order that visualizer can access it + viz::WidgetAccessor::setProp(*this, actor); + + // Set the color of the widget. This has to be called after WidgetAccessor. + setColor(color); + } + + /** + * @function main + */ + int main() + { + /// Create a window + viz::Viz3d myWindow("Creating Widgets"); + + /// Create a triangle widget + 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()); + + /// Show widget in the visualizer window + myWindow.showWidget("TRIANGLE", tw); + + /// Start event loop + myWindow.spin(); + + return 0; + } + +Explanation +=========== + +Here is the general structure of the program: + +* Extend Widget3D class to create a new 3D widget. + +.. code-block:: cpp + + class WTriangle : public viz::Widget3D + { + public: + WTriangle(const Point3f &pt1, const Point3f &pt2, const Point3f &pt3, const viz::Color & color = viz::Color::white()); + }; + +* Assign a VTK actor to the widget. + +.. code-block:: cpp + + // Store this actor in the widget in order that visualizer can access it + viz::WidgetAccessor::setProp(*this, actor); + +* Set color of the widget. + +.. code-block:: cpp + + // Set the color of the widget. This has to be called after WidgetAccessor. + setColor(color); + +* Construct a triangle widget and display it in the window. + +.. code-block:: cpp + + /// Create a triangle widget + 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()); + + /// Show widget in the visualizer window + myWindow.showWidget("TRIANGLE", tw); + +Results +======= + +Here is the result of the program. + +.. image:: images/red_triangle.png + :alt: Creating Widgets + :align: center diff --git a/doc/tutorials/viz/creating_widgets/images/red_triangle.png b/doc/tutorials/viz/creating_widgets/images/red_triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..7da6ad0602a974e00695f9c0aa698c5dc474b2be GIT binary patch literal 10489 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 Y2MLWci`MvfP>e97x{g}mO`E6x2cG-67XSbN literal 0 HcmV?d00001 diff --git a/doc/tutorials/viz/launching_viz/images/window_demo.png b/doc/tutorials/viz/launching_viz/images/window_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..b853fe29da9b03ce7e606dae6aa743e3822df2df GIT binary patch literal 7519 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 A?*IS* literal 0 HcmV?d00001 diff --git a/doc/tutorials/viz/launching_viz/launching_viz.rst b/doc/tutorials/viz/launching_viz/launching_viz.rst new file mode 100644 index 000000000..a3dd5d93c --- /dev/null +++ b/doc/tutorials/viz/launching_viz/launching_viz.rst @@ -0,0 +1,118 @@ +.. _launching_viz: + +Launching Viz +************* + +Goal +==== + +In this tutorial you will learn how to + +.. container:: enumeratevisibleitemswithsquare + + * Open a visualization window. + * Access a window by its name. + * Start event loop. + * Start event loop for a given amount of time. + +Code +==== + +You can download the code from :download:`here <../../../../samples/cpp/tutorial_code/viz/launching_viz.cpp>`. + +.. code-block:: cpp + + #include + #include + + using namespace cv; + using namespace std; + + /** + * @function main + */ + int main() + { + /// Create a window + viz::Viz3d myWindow("Viz Demo"); + + /// Start event loop + myWindow.spin(); + + /// Event loop is over when pressed q, Q, e, E + cout << "First event loop is over" << endl; + + /// Access window via its name + viz::Viz3d sameWindow = viz::getWindowByName("Viz Demo"); + + /// Start event loop + sameWindow.spin(); + + /// Event loop is over when pressed q, Q, e, E + cout << "Second event loop is over" << endl; + + /// Event loop is over when pressed q, Q, e, E + /// Start event loop once for 1 millisecond + sameWindow.spinOnce(1, true); + while(!sameWindow.wasStopped()) + { + /// Interact with window + + /// Event loop for 1 millisecond + sameWindow.spinOnce(1, true); + } + + /// Once more event loop is stopped + cout << "Last event loop is over" << endl; + return 0; + } + +Explanation +=========== + +Here is the general structure of the program: + +* Create a window. + +.. code-block:: cpp + + /// Create a window + viz::Viz3d myWindow("Viz Demo"); + +* Start event loop. This event loop will run until user terminates it by pressing **e**, **E**, **q**, **Q**. + +.. code-block:: cpp + + /// Start event loop + myWindow.spin(); + +* Access same window via its name. Since windows are implicitly shared, **sameWindow** is exactly the same with **myWindow**. If the name does not exist, a new window is created. + +.. code-block:: cpp + + /// Access window via its name + viz::Viz3d sameWindow = viz::get("Viz Demo"); + +* Start a controlled event loop. Once it starts, **wasStopped** is set to false. Inside the while loop, in each iteration, **spinOnce** is called to prevent event loop from completely stopping. Inside the while loop, user can execute other statements including those which interact with the window. + +.. code-block:: cpp + + /// Event loop is over when pressed q, Q, e, E + /// Start event loop once for 1 millisecond + sameWindow.spinOnce(1, true); + while(!sameWindow.wasStopped()) + { + /// Interact with window + + /// Event loop for 1 millisecond + sameWindow.spinOnce(1, true); + } + +Results +======= + +Here is the result of the program. + +.. image:: images/window_demo.png + :alt: Launching Viz + :align: center diff --git a/doc/tutorials/viz/table_of_content_viz/images/facedetect.jpg b/doc/tutorials/viz/table_of_content_viz/images/facedetect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..788b7d8262b75bc416edc75495663d592fa1d4e2 GIT binary patch literal 17902 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

x5ax<_qW0y#dAIh~p33!OG=L*8bsD9JfGY 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 Syng=>03dFf8`QyFV*UrqR@d7A literal 0 HcmV?d00001 diff --git a/doc/tutorials/viz/table_of_content_viz/table_of_content_viz.rst b/doc/tutorials/viz/table_of_content_viz/table_of_content_viz.rst new file mode 100644 index 000000000..c3d08fe17 --- /dev/null +++ b/doc/tutorials/viz/table_of_content_viz/table_of_content_viz.rst @@ -0,0 +1,94 @@ +.. _Table-Of-Content-Viz: + +**OpenCV Viz** +----------------------------------------------------------- + +.. include:: ../../definitions/tocDefinitions.rst + ++ + .. tabularcolumns:: m{100pt} m{300pt} + .. cssclass:: toctableopencv + + ================== =============================================================================== + |VizLaunchingViz| **Title:** :ref:`launching_viz` + + *Compatibility:* > OpenCV 3.0.0 + + *Author:* Ozan Tonkal + + You will learn how to launch a viz window. + + ================== =============================================================================== + + .. |VizLaunchingViz| image:: ../launching_viz/images/window_demo.png + :height: 120pt + :width: 90pt + ++ + .. tabularcolumns:: m{100pt} m{300pt} + .. cssclass:: toctableopencv + + ================ ============================================================================ + |WidgetPose| **Title:** :ref:`widget_pose` + + *Compatibility:* > OpenCV 3.0.0 + + *Author:* Ozan Tonkal + + You will learn how to change pose of a widget. + + ================ ============================================================================ + + .. |WidgetPose| image:: ../widget_pose/images/widgetpose.png + :height: 90pt + :width: 90pt + ++ + .. tabularcolumns:: m{100pt} m{300pt} + .. cssclass:: toctableopencv + + ================== ============================================================================ + |Transformations| **Title:** :ref:`transformations` + + *Compatibility:* > OpenCV 3.0.0 + + *Author:* Ozan Tonkal + + You will learn how to transform between global and camera frames. + + ================== ============================================================================ + + .. |Transformations| image:: ../transformations/images/global_view_point.png + :height: 120pt + :width: 90pt + ++ + .. tabularcolumns:: m{100pt} m{300pt} + .. cssclass:: toctableopencv + + ================== ============================================================================ + |CreatingWidgets| **Title:** :ref:`creating_widgets` + + *Compatibility:* > OpenCV 3.0.0 + + *Author:* Ozan Tonkal + + You will learn how to create your own widgets. + + ================== ============================================================================ + + .. |CreatingWidgets| image:: ../creating_widgets/images/red_triangle.png + :height: 120pt + :width: 90pt + +.. raw:: latex + + \pagebreak + +.. toctree:: + :hidden: + + ../launching_viz/launching_viz + ../widget_pose/widget_pose + ../transformations/transformations + ../creating_widgets/creating_widgets diff --git a/doc/tutorials/viz/transformations/images/camera_view_point.png b/doc/tutorials/viz/transformations/images/camera_view_point.png new file mode 100644 index 0000000000000000000000000000000000000000..e2ac5b0f0de84aa88561e44b635c0b73fd68dab0 GIT binary patch literal 18439 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/viz/transformations/images/global_view_point.png b/doc/tutorials/viz/transformations/images/global_view_point.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6de2c1a1fa36ad56ad6ffb679eb20d5bf77609 GIT binary patch literal 13767 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 QpoJpyQt?HJgi-MS0}6qlQ~&?~ literal 0 HcmV?d00001 diff --git a/doc/tutorials/viz/transformations/transformations.rst b/doc/tutorials/viz/transformations/transformations.rst new file mode 100644 index 000000000..d1f2d0c2e --- /dev/null +++ b/doc/tutorials/viz/transformations/transformations.rst @@ -0,0 +1,202 @@ +.. _transformations: + +Transformations +*************** + +Goal +==== + +In this tutorial you will learn how to + +.. container:: enumeratevisibleitemswithsquare + + * How to use makeTransformToGlobal to compute pose + * How to use makeCameraPose and Viz3d::setViewerPose + * How to visualize camera position by axes and by viewing frustum + +Code +==== + +You can download the code from :download:`here <../../../../samples/cpp/tutorial_code/viz/transformations.cpp>`. + +.. code-block:: cpp + + #include + #include + #include + + using namespace cv; + using namespace std; + + /** + * @function cvcloud_load + * @brief load bunny.ply + */ + Mat cvcloud_load() + { + Mat cloud(1, 1889, CV_32FC3); + ifstream ifs("bunny.ply"); + + string str; + for(size_t i = 0; i < 12; ++i) + getline(ifs, str); + + Point3f* data = cloud.ptr(); + float dummy1, dummy2; + for(size_t i = 0; i < 1889; ++i) + ifs >> data[i].x >> data[i].y >> data[i].z >> dummy1 >> dummy2; + + cloud *= 5.0f; + return cloud; + } + + /** + * @function main + */ + int main(int argn, char **argv) + { + if (argn < 2) + { + cout << "Usage: " << endl << "./transformations [ G | C ]" << endl; + return 1; + } + + bool camera_pov = (argv[1][0] == 'C'); + + /// Create a window + viz::Viz3d myWindow("Coordinate Frame"); + + /// Add coordinate axes + myWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem()); + + /// Let's assume camera has the following properties + Point3f cam_pos(3.0f,3.0f,3.0f), cam_focal_point(3.0f,3.0f,2.0f), cam_y_dir(-1.0f,0.0f,0.0f); + + /// We can get the pose of the cam using makeCameraPose + Affine3f cam_pose = viz::makeCameraPose(cam_pos, cam_focal_point, cam_y_dir); + + /// We can get the transformation matrix from camera coordinate system to global using + /// - makeTransformToGlobal. We need the axes of the camera + Affine3f transform = viz::makeTransformToGlobal(Vec3f(0.0f,-1.0f,0.0f), Vec3f(-1.0f,0.0f,0.0f), Vec3f(0.0f,0.0f,-1.0f), cam_pos); + + /// Create a cloud widget. + Mat bunny_cloud = cvcloud_load(); + viz::WCloud cloud_widget(bunny_cloud, viz::Color::green()); + + /// Pose of the widget in camera frame + Affine3f cloud_pose = Affine3f().translate(Vec3f(0.0f,0.0f,3.0f)); + /// Pose of the widget in global frame + Affine3f cloud_pose_global = transform * cloud_pose; + + /// Visualize camera frame + if (!camera_pov) + { + viz::WCameraPosition cpw(0.5); // Coordinate axes + viz::WCameraPosition cpw_frustum(Vec2f(0.889484, 0.523599)); // Camera frustum + myWindow.showWidget("CPW", cpw, cam_pose); + myWindow.showWidget("CPW_FRUSTUM", cpw_frustum, cam_pose); + } + + /// Visualize widget + myWindow.showWidget("bunny", cloud_widget, cloud_pose_global); + + /// Set the viewer pose to that of camera + if (camera_pov) + myWindow.setViewerPose(cam_pose); + + /// Start event loop. + myWindow.spin(); + + return 0; + } + + +Explanation +=========== + +Here is the general structure of the program: + +* Create a visualization window. + +.. code-block:: cpp + + /// Create a window + viz::Viz3d myWindow("Transformations"); + +* Get camera pose from camera position, camera focal point and y direction. + +.. code-block:: cpp + + /// Let's assume camera has the following properties + Point3f cam_pos(3.0f,3.0f,3.0f), cam_focal_point(3.0f,3.0f,2.0f), cam_y_dir(-1.0f,0.0f,0.0f); + + /// We can get the pose of the cam using makeCameraPose + Affine3f cam_pose = viz::makeCameraPose(cam_pos, cam_focal_point, cam_y_dir); + +* Obtain transform matrix knowing the axes of camera coordinate system. + +.. code-block:: cpp + + /// We can get the transformation matrix from camera coordinate system to global using + /// - makeTransformToGlobal. We need the axes of the camera + Affine3f transform = viz::makeTransformToGlobal(Vec3f(0.0f,-1.0f,0.0f), Vec3f(-1.0f,0.0f,0.0f), Vec3f(0.0f,0.0f,-1.0f), cam_pos); + +* Create a cloud widget from bunny.ply file + +.. code-block:: cpp + + /// Create a cloud widget. + Mat bunny_cloud = cvcloud_load(); + viz::WCloud cloud_widget(bunny_cloud, viz::Color::green()); + +* Given the pose in camera coordinate system, estimate the global pose. + +.. code-block:: cpp + + /// Pose of the widget in camera frame + Affine3f cloud_pose = Affine3f().translate(Vec3f(0.0f,0.0f,3.0f)); + /// Pose of the widget in global frame + Affine3f cloud_pose_global = transform * cloud_pose; + +* If the view point is set to be global, visualize camera coordinate frame and viewing frustum. + +.. code-block:: cpp + + /// Visualize camera frame + if (!camera_pov) + { + viz::WCameraPosition cpw(0.5); // Coordinate axes + viz::WCameraPosition cpw_frustum(Vec2f(0.889484, 0.523599)); // Camera frustum + myWindow.showWidget("CPW", cpw, cam_pose); + myWindow.showWidget("CPW_FRUSTUM", cpw_frustum, cam_pose); + } + +* Visualize the cloud widget with the estimated global pose + +.. code-block:: cpp + + /// Visualize widget + myWindow.showWidget("bunny", cloud_widget, cloud_pose_global); + +* If the view point is set to be camera's, set viewer pose to **cam_pose**. + +.. code-block:: cpp + + /// Set the viewer pose to that of camera + if (camera_pov) + myWindow.setViewerPose(cam_pose); + +Results +======= + +#. Here is the result from the camera point of view. + + .. image:: images/camera_view_point.png + :alt: Camera Viewpoint + :align: center + +#. Here is the result from global point of view. + + .. image:: images/global_view_point.png + :alt: Global Viewpoint + :align: center diff --git a/doc/tutorials/viz/widget_pose/images/widgetpose.png b/doc/tutorials/viz/widget_pose/images/widgetpose.png new file mode 100644 index 0000000000000000000000000000000000000000..ef8a5937f9c3e18507acdefc758102919c2efdb8 GIT binary patch literal 40892 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 LUA9izJp6wE?|M7Z literal 0 HcmV?d00001 diff --git a/doc/tutorials/viz/widget_pose/widget_pose.rst b/doc/tutorials/viz/widget_pose/widget_pose.rst new file mode 100644 index 000000000..a4466bded --- /dev/null +++ b/doc/tutorials/viz/widget_pose/widget_pose.rst @@ -0,0 +1,162 @@ +.. _widget_pose: + +Pose of a widget +**************** + +Goal +==== + +In this tutorial you will learn how to + +.. container:: enumeratevisibleitemswithsquare + + * Add widgets to the visualization window + * Use Affine3 to set pose of a widget + * Rotating and translating a widget along an axis + +Code +==== + +You can download the code from :download:`here <../../../../samples/cpp/tutorial_code/viz/widget_pose.cpp>`. + +.. code-block:: cpp + + #include + #include + #include + + using namespace cv; + using namespace std; + + /** + * @function main + */ + int main() + { + /// Create a window + viz::Viz3d myWindow("Coordinate Frame"); + + /// Add coordinate axes + myWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem()); + + /// Add line to represent (1,1,1) axis + viz::WLine axis(Point3f(-1.0f,-1.0f,-1.0f), Point3f(1.0f,1.0f,1.0f)); + axis.setRenderingProperty(viz::LINE_WIDTH, 4.0); + myWindow.showWidget("Line Widget", axis); + + /// Construct a cube widget + viz::WCube cube_widget(Point3f(0.5,0.5,0.0), Point3f(0.0,0.0,-0.5), true, viz::Color::blue()); + cube_widget.setRenderingProperty(viz::LINE_WIDTH, 4.0); + + /// Display widget (update if already displayed) + myWindow.showWidget("Cube Widget", cube_widget); + + /// Rodrigues vector + Mat rot_vec = Mat::zeros(1,3,CV_32F); + float translation_phase = 0.0, translation = 0.0; + while(!myWindow.wasStopped()) + { + /* Rotation using rodrigues */ + /// Rotate around (1,1,1) + rot_vec.at(0,0) += CV_PI * 0.01f; + rot_vec.at(0,1) += CV_PI * 0.01f; + rot_vec.at(0,2) += CV_PI * 0.01f; + + /// Shift on (1,1,1) + translation_phase += CV_PI * 0.01f; + translation = sin(translation_phase); + + Mat rot_mat; + Rodrigues(rot_vec, rot_mat); + + /// Construct pose + Affine3f pose(rot_mat, Vec3f(translation, translation, translation)); + + myWindow.setWidgetPose("Cube Widget", pose); + + myWindow.spinOnce(1, true); + } + + return 0; + } + +Explanation +=========== + +Here is the general structure of the program: + +* Create a visualization window. + +.. code-block:: cpp + + /// Create a window + viz::Viz3d myWindow("Coordinate Frame"); + +* Show coordinate axes in the window using CoordinateSystemWidget. + +.. code-block:: cpp + + /// Add coordinate axes + myWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem()); + +* Display a line representing the axis (1,1,1). + +.. code-block:: cpp + + /// Add line to represent (1,1,1) axis + viz::WLine axis(Point3f(-1.0f,-1.0f,-1.0f), Point3f(1.0f,1.0f,1.0f)); + axis.setRenderingProperty(viz::LINE_WIDTH, 4.0); + myWindow.showWidget("Line Widget", axis); + +* Construct a cube. + +.. code-block:: cpp + + /// Construct a cube widget + viz::WCube cube_widget(Point3f(0.5,0.5,0.0), Point3f(0.0,0.0,-0.5), true, viz::Color::blue()); + cube_widget.setRenderingProperty(viz::LINE_WIDTH, 4.0); + myWindow.showWidget("Cube Widget", cube_widget); + +* Create rotation matrix from rodrigues vector + +.. code-block:: cpp + + /// Rotate around (1,1,1) + rot_vec.at(0,0) += CV_PI * 0.01f; + rot_vec.at(0,1) += CV_PI * 0.01f; + rot_vec.at(0,2) += CV_PI * 0.01f; + + ... + + Mat rot_mat; + Rodrigues(rot_vec, rot_mat); + +* Use Affine3f to set pose of the cube. + +.. code-block:: cpp + + /// Construct pose + Affine3f pose(rot_mat, Vec3f(translation, translation, translation)); + myWindow.setWidgetPose("Cube Widget", pose); + +* Animate the rotation using wasStopped and spinOnce + +.. code-block:: cpp + + while(!myWindow.wasStopped()) + { + ... + + myWindow.spinOnce(1, true); + } + +Results +======= + +Here is the result of the program. + +.. raw:: html + +

diff --git a/modules/core/include/opencv2/core/affine.hpp b/modules/core/include/opencv2/core/affine.hpp new file mode 100644 index 000000000..827d044b8 --- /dev/null +++ b/modules/core/include/opencv2/core/affine.hpp @@ -0,0 +1,509 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef __OPENCV_CORE_AFFINE3_HPP__ +#define __OPENCV_CORE_AFFINE3_HPP__ + +#ifdef __cplusplus + +#include + +namespace cv +{ + template + class Affine3 + { + public: + typedef T float_type; + typedef Matx Mat3; + typedef Matx Mat4; + typedef Vec Vec3; + + Affine3(); + + //Augmented affine matrix + Affine3(const Mat4& affine); + + //Rotation matrix + Affine3(const Mat3& R, const Vec3& t = Vec3::all(0)); + + //Rodrigues vector + Affine3(const Vec3& rvec, const Vec3& t = Vec3::all(0)); + + //Combines all contructors above. Supports 4x4, 4x3, 3x3, 1x3, 3x1 sizes of data matrix + explicit Affine3(const Mat& data, const Vec3& t = Vec3::all(0)); + + //From 16th element array + explicit Affine3(const float_type* vals); + + static Affine3 Identity(); + + //Rotation matrix + void rotation(const Mat3& R); + + //Rodrigues vector + void rotation(const Vec3& rvec); + + //Combines rotation methods above. Suports 3x3, 1x3, 3x1 sizes of data matrix; + void rotation(const Mat& data); + + void linear(const Mat3& L); + void translation(const Vec3& t); + + Mat3 rotation() const; + Mat3 linear() const; + Vec3 translation() const; + + //Rodrigues vector + Vec3 rvec() const; + + Affine3 inv(int method = cv::DECOMP_SVD) const; + + // a.rotate(R) is equivalent to Affine(R, 0) * a; + Affine3 rotate(const Mat3& R) const; + + // a.rotate(R) is equivalent to Affine(rvec, 0) * a; + Affine3 rotate(const Vec3& rvec) const; + + // a.translate(t) is equivalent to Affine(E, t) * a; + Affine3 translate(const Vec3& t) const; + + // a.concatenate(affine) is equivalent to affine * a; + Affine3 concatenate(const Affine3& affine) const; + + template operator Affine3() const; + + template Affine3 cast() const; + + Mat4 matrix; + +#if defined EIGEN_WORLD_VERSION && defined EIGEN_GEOMETRY_MODULE_H + Affine3(const Eigen::Transform& affine); + Affine3(const Eigen::Transform& affine); + operator Eigen::Transform() const; + operator Eigen::Transform() const; +#endif + }; + + template static + Affine3 operator*(const Affine3& affine1, const Affine3& affine2); + + template static + V operator*(const Affine3& affine, const V& vector); + + typedef Affine3 Affine3f; + typedef Affine3 Affine3d; + + static Vec3f operator*(const Affine3f& affine, const Vec3f& vector); + static Vec3d operator*(const Affine3d& affine, const Vec3d& vector); + + template class DataType< Affine3<_Tp> > + { + public: + typedef Affine3<_Tp> value_type; + typedef Affine3::work_type> work_type; + typedef _Tp channel_type; + + enum { generic_type = 0, + depth = DataType::depth, + channels = 16, + fmt = DataType::fmt + ((channels - 1) << 8), + type = CV_MAKETYPE(depth, channels) + }; + + typedef Vec vec_type; + }; +} + + +/////////////////////////////////////////////////////////////////////////////////// +/// Implementaiton + +template inline +cv::Affine3::Affine3() + : matrix(Mat4::eye()) +{} + +template inline +cv::Affine3::Affine3(const Mat4& affine) + : matrix(affine) +{} + +template inline +cv::Affine3::Affine3(const Mat3& R, const Vec3& t) +{ + rotation(R); + translation(t); + matrix.val[12] = matrix.val[13] = matrix.val[14] = 0; + matrix.val[15] = 1; +} + +template inline +cv::Affine3::Affine3(const Vec3& _rvec, const Vec3& t) +{ + rotation(_rvec); + translation(t); + matrix.val[12] = matrix.val[13] = matrix.val[14] = 0; + matrix.val[15] = 1; +} + +template inline +cv::Affine3::Affine3(const cv::Mat& data, const Vec3& t) +{ + CV_Assert(data.type() == cv::DataType::type); + + if (data.cols == 4 && data.rows == 4) + { + data.copyTo(matrix); + return; + } + else if (data.cols == 4 && data.rows == 3) + { + rotation(data(Rect(0, 0, 3, 3))); + translation(data(Rect(3, 0, 1, 3))); + return; + } + + rotation(data); + translation(t); + matrix.val[12] = matrix.val[13] = matrix.val[14] = 0; + matrix.val[15] = 1; +} + +template inline +cv::Affine3::Affine3(const float_type* vals) : matrix(vals) +{} + +template inline +cv::Affine3 cv::Affine3::Identity() +{ + return Affine3(cv::Affine3::Mat4::eye()); +} + +template inline +void cv::Affine3::rotation(const Mat3& R) +{ + linear(R); +} + +template inline +void cv::Affine3::rotation(const Vec3& _rvec) +{ + double rx = _rvec[0], ry = _rvec[1], rz = _rvec[2]; + double theta = std::sqrt(rx*rx + ry*ry + rz*rz); + + if (theta < DBL_EPSILON) + rotation(Mat3::eye()); + else + { + const double I[] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; + + double c = std::cos(theta); + double s = std::sin(theta); + double c1 = 1. - c; + double itheta = theta ? 1./theta : 0.; + + rx *= itheta; ry *= itheta; rz *= itheta; + + double rrt[] = { rx*rx, rx*ry, rx*rz, rx*ry, ry*ry, ry*rz, rx*rz, ry*rz, rz*rz }; + double _r_x_[] = { 0, -rz, ry, rz, 0, -rx, -ry, rx, 0 }; + Mat3 R; + + // R = cos(theta)*I + (1 - cos(theta))*r*rT + sin(theta)*[r_x] + // where [r_x] is [0 -rz ry; rz 0 -rx; -ry rx 0] + for(int k = 0; k < 9; ++k) + R.val[k] = static_cast(c*I[k] + c1*rrt[k] + s*_r_x_[k]); + + rotation(R); + } +} + +//Combines rotation methods above. Suports 3x3, 1x3, 3x1 sizes of data matrix; +template inline +void cv::Affine3::rotation(const cv::Mat& data) +{ + CV_Assert(data.type() == cv::DataType::type); + + if (data.cols == 3 && data.rows == 3) + { + Mat3 R; + data.copyTo(R); + rotation(R); + } + else if ((data.cols == 3 && data.rows == 1) || (data.cols == 1 && data.rows == 3)) + { + Vec3 _rvec; + data.reshape(1, 3).copyTo(_rvec); + rotation(_rvec); + } + else + CV_Assert(!"Input marix can be 3x3, 1x3 or 3x1"); +} + +template inline +void cv::Affine3::linear(const Mat3& L) +{ + matrix.val[0] = L.val[0]; matrix.val[1] = L.val[1]; matrix.val[ 2] = L.val[2]; + matrix.val[4] = L.val[3]; matrix.val[5] = L.val[4]; matrix.val[ 6] = L.val[5]; + matrix.val[8] = L.val[6]; matrix.val[9] = L.val[7]; matrix.val[10] = L.val[8]; +} + +template inline +void cv::Affine3::translation(const Vec3& t) +{ + matrix.val[3] = t[0]; matrix.val[7] = t[1]; matrix.val[11] = t[2]; +} + +template inline +typename cv::Affine3::Mat3 cv::Affine3::rotation() const +{ + return linear(); +} + +template inline +typename cv::Affine3::Mat3 cv::Affine3::linear() const +{ + typename cv::Affine3::Mat3 R; + R.val[0] = matrix.val[0]; R.val[1] = matrix.val[1]; R.val[2] = matrix.val[ 2]; + R.val[3] = matrix.val[4]; R.val[4] = matrix.val[5]; R.val[5] = matrix.val[ 6]; + R.val[6] = matrix.val[8]; R.val[7] = matrix.val[9]; R.val[8] = matrix.val[10]; + return R; +} + +template inline +typename cv::Affine3::Vec3 cv::Affine3::translation() const +{ + return Vec3(matrix.val[3], matrix.val[7], matrix.val[11]); +} + +template inline +typename cv::Affine3::Vec3 cv::Affine3::rvec() const +{ + cv::Vec3d w; + cv::Matx33d u, vt, R = rotation(); + cv::SVD::compute(R, w, u, vt, cv::SVD::FULL_UV + cv::SVD::MODIFY_A); + R = u * vt; + + double rx = R.val[7] - R.val[5]; + double ry = R.val[2] - R.val[6]; + double rz = R.val[3] - R.val[1]; + + double s = std::sqrt((rx*rx + ry*ry + rz*rz)*0.25); + double c = (R.val[0] + R.val[4] + R.val[8] - 1) * 0.5; + c = c > 1.0 ? 1.0 : c < -1.0 ? -1.0 : c; + double theta = acos(c); + + if( s < 1e-5 ) + { + if( c > 0 ) + rx = ry = rz = 0; + else + { + double t; + t = (R.val[0] + 1) * 0.5; + rx = std::sqrt(std::max(t, 0.0)); + t = (R.val[4] + 1) * 0.5; + ry = std::sqrt(std::max(t, 0.0)) * (R.val[1] < 0 ? -1.0 : 1.0); + t = (R.val[8] + 1) * 0.5; + rz = std::sqrt(std::max(t, 0.0)) * (R.val[2] < 0 ? -1.0 : 1.0); + + if( fabs(rx) < fabs(ry) && fabs(rx) < fabs(rz) && (R.val[5] > 0) != (ry*rz > 0) ) + rz = -rz; + theta /= std::sqrt(rx*rx + ry*ry + rz*rz); + rx *= theta; + ry *= theta; + rz *= theta; + } + } + else + { + double vth = 1/(2*s); + vth *= theta; + rx *= vth; ry *= vth; rz *= vth; + } + + return cv::Vec3d(rx, ry, rz); +} + +template inline +cv::Affine3 cv::Affine3::inv(int method) const +{ + return matrix.inv(method); +} + +template inline +cv::Affine3 cv::Affine3::rotate(const Mat3& R) const +{ + Mat3 Lc = linear(); + Vec3 tc = translation(); + Mat4 result; + result.val[12] = result.val[13] = result.val[14] = 0; + result.val[15] = 1; + + for(int j = 0; j < 3; ++j) + { + for(int i = 0; i < 3; ++i) + { + float_type value = 0; + for(int k = 0; k < 3; ++k) + value += R(j, k) * Lc(k, i); + result(j, i) = value; + } + + result(j, 3) = R.row(j).dot(tc.t()); + } + return result; +} + +template inline +cv::Affine3 cv::Affine3::rotate(const Vec3& _rvec) const +{ + return rotate(Affine3f(_rvec).rotation()); +} + +template inline +cv::Affine3 cv::Affine3::translate(const Vec3& t) const +{ + Mat4 m = matrix; + m.val[ 3] += t[0]; + m.val[ 7] += t[1]; + m.val[11] += t[2]; + return m; +} + +template inline +cv::Affine3 cv::Affine3::concatenate(const Affine3& affine) const +{ + return (*this).rotate(affine.rotation()).translate(affine.translation()); +} + +template template inline +cv::Affine3::operator Affine3() const +{ + return Affine3(matrix); +} + +template template inline +cv::Affine3 cv::Affine3::cast() const +{ + return Affine3(matrix); +} + +template inline +cv::Affine3 cv::operator*(const cv::Affine3& affine1, const cv::Affine3& affine2) +{ + return affine2.concatenate(affine1); +} + +template inline +V cv::operator*(const cv::Affine3& affine, const V& v) +{ + const typename Affine3::Mat4& m = affine.matrix; + + V r; + r.x = m.val[0] * v.x + m.val[1] * v.y + m.val[ 2] * v.z + m.val[ 3]; + r.y = m.val[4] * v.x + m.val[5] * v.y + m.val[ 6] * v.z + m.val[ 7]; + r.z = m.val[8] * v.x + m.val[9] * v.y + m.val[10] * v.z + m.val[11]; + return r; +} + +static inline +cv::Vec3f cv::operator*(const cv::Affine3f& affine, const cv::Vec3f& v) +{ + const cv::Matx44f& m = affine.matrix; + cv::Vec3f r; + r.val[0] = m.val[0] * v[0] + m.val[1] * v[1] + m.val[ 2] * v[2] + m.val[ 3]; + r.val[1] = m.val[4] * v[0] + m.val[5] * v[1] + m.val[ 6] * v[2] + m.val[ 7]; + r.val[2] = m.val[8] * v[0] + m.val[9] * v[1] + m.val[10] * v[2] + m.val[11]; + return r; +} + +static inline +cv::Vec3d cv::operator*(const cv::Affine3d& affine, const cv::Vec3d& v) +{ + const cv::Matx44d& m = affine.matrix; + cv::Vec3d r; + r.val[0] = m.val[0] * v[0] + m.val[1] * v[1] + m.val[ 2] * v[2] + m.val[ 3]; + r.val[1] = m.val[4] * v[0] + m.val[5] * v[1] + m.val[ 6] * v[2] + m.val[ 7]; + r.val[2] = m.val[8] * v[0] + m.val[9] * v[1] + m.val[10] * v[2] + m.val[11]; + return r; +} + + + +#if defined EIGEN_WORLD_VERSION && defined EIGEN_GEOMETRY_MODULE_H + +template inline +cv::Affine3::Affine3(const Eigen::Transform& affine) +{ + cv::Mat(4, 4, cv::DataType::type, affine.matrix().data()).copyTo(matrix); +} + +template inline +cv::Affine3::Affine3(const Eigen::Transform& affine) +{ + Eigen::Transform a = affine; + cv::Mat(4, 4, cv::DataType::type, a.matrix().data()).copyTo(matrix); +} + +template inline +cv::Affine3::operator Eigen::Transform() const +{ + Eigen::Transform r; + cv::Mat hdr(4, 4, cv::DataType::type, r.matrix().data()); + cv::Mat(matrix, false).copyTo(hdr); + return r; +} + +template inline +cv::Affine3::operator Eigen::Transform() const +{ + return this->operator Eigen::Transform(); +} + +#endif /* defined EIGEN_WORLD_VERSION && defined EIGEN_GEOMETRY_MODULE_H */ + + +#endif /* __cplusplus */ + +#endif /* __OPENCV_CORE_AFFINE3_HPP__ */ diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index cafba0f8f..2ecb70c71 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -892,6 +892,7 @@ public: typedef Point_ Point2i; typedef Point2i Point; typedef Size_ Size2i; +typedef Size_ Size2d; typedef Size2i Size; typedef Rect_ Rect; typedef Point_ Point2f; diff --git a/modules/viz/CMakeLists.txt b/modules/viz/CMakeLists.txt new file mode 100644 index 000000000..7ccd07921 --- /dev/null +++ b/modules/viz/CMakeLists.txt @@ -0,0 +1,11 @@ +if(NOT WITH_VTK OR NOT DEFINED HAVE_VTK OR NOT HAVE_VTK) + ocv_module_disable(viz) +endif() + +include(${VTK_USE_FILE}) +set(the_description "Viz") +ocv_define_module(viz opencv_core ${VTK_LIBRARIES}) + +if(APPLE AND BUILD_opencv_viz) + target_link_libraries(opencv_viz "-framework Cocoa") +endif() diff --git a/modules/viz/doc/images/cpw1.png b/modules/viz/doc/images/cpw1.png new file mode 100644 index 0000000000000000000000000000000000000000..985b1eeaffbed1e207128eaa8167cde04d3dcf04 GIT binary patch literal 4321 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 literal 0 HcmV?d00001 diff --git a/modules/viz/doc/images/cpw2.png b/modules/viz/doc/images/cpw2.png new file mode 100644 index 0000000000000000000000000000000000000000..5733a6af6579373d6b9512b70cd5ada26b62db3d GIT binary patch literal 3548 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 literal 0 HcmV?d00001 diff --git a/modules/viz/doc/images/cpw3.png b/modules/viz/doc/images/cpw3.png new file mode 100644 index 0000000000000000000000000000000000000000..585b836b66ddfdbfa34219972f9c766eccb5f9d4 GIT binary patch literal 17724 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

gB=@dpOOp^?a9R9TDL$j)- zMXOuI1HZR%h@Qv%*6b@~Dzl3CHF6(_6hN@Pn5ESv@|UTNF@KR5V80 zeqQbSQC<-?VBq<9-g?orw&{7rquy$+mTtD{C~19`EHgAs3SMqCYfQ?quZO7#yGC)7v?)rzgR4l89MG2c zc&Yb1DNMd>9g-_Y4kOg4EhZj_ktSHNGYy~%Vy^kmRIhj(d z&buf!6zpinUc1qYuI4r+@U!IR$M)Kd32JEdzh=ywF6X~bnW6`%e<6b`H(9T}zuXz) zD$q|T>}Yg@sd+_BeP|SoT4NLA{cZCBrG4h@vc;=W_NJne>CAocLr%)#wx;ti_o!yLg0JF`UWOn$p=}DJU z%?I;CSdrj-t=YEw;}JYjK{1#Ka$D_L1^jzffBZ!U&v+(cb=>@Xeij+s!k#krUdGj$ zm~9$2TcjPL-o06qQP`G>{3q+!`5_L;+oS$}qLg~$)X%bVZYCX>?4((H@i|`c63jom zh0V&tZxPB-v*-&m#~Ca22gdsPL?smnHGF&@_j3x&VJLc+jL{3HsN%zr@Fq_AlKJ@UVEZVh9RXW1!)b9 zM0~m{XqT=B2mZ%z|p7*2`oMvA$r?AU+-ujC=p<7XAng1 z0YpJV{s-pzEytS|-Bvp|SBW%@Xzg%6YYLKysE)mfq^%z~+Xgx>|Ag-#$hu4VDlxy4 zl0Ds+=hN=vTCF*nrDc9wh?kUwEXw`kkhv!1+5eUnzS>kBu2e_5v#hXFF-FQzg2xO5?Aa> z@y*024CT%xeU`U0?%yyqxL+!2dhpHTl1Ga0)olCFQwY1HE5iR;{qAp%7Xk>( z-T>_vs2fT9Xtqhm?<_y^+maaLL@?GH7(+n zLK35fg@W_1t*yL6RH$WcGRxSadQE>|m`qki|=%l-| zbE8`l1FP|9LdDo|Of5Mb9ab1`2m-E4Al4$W;Zh4Ffu@GWasBRM01_{|@8`SqTGPy{ zor$C&uh|P*0aWFO0Mx%hBmSjv(3<8=V*gSLKBC9&6wAX&1OrCpCdcxa*8Gk&q9%X^k`+j=Go`4zw znStr%je*87etcKBikbN<5?9~R{$EpQaDLS8AkOBhC3vQFJem40lvDBIaj2dq>wtOl z{Sc$jV^KK?TO$7*hV|7Ri4z}zz#kx^9DMiFLRZ~ee4*`mc?Mfu-Z6hI#X1`s8^e;~ zl5Ue0GB4HKjjNgj{L0|vgu&Q%p})P&zK8UGc|TypNB51%$#Nk%Hk~wA=r|b{h=`2V z;25HjNe?5>MB>M83Jf+m`xeH}dc&t)V~c-y-QC^MwH@a{;k)tAPoyvyj>P@U7mGg3 zkot$1Q&1p`j_-9XpnwhNfOiMNk$9iCX*8RL6RgIU!6dYgXV(#6js0(OzFL;f0fA8j z`~$6by4QzChBKx+_eBt{P#IGrfBvG;&$XtB=0_Qpf~}d2T(ccBaQ!hSTN1%Zq!W|9 zx;j8v_!v4bl}3ykKJfW|#5XnuWb`wk5Jd002{v*bB<7Ltxl4C_KBVz!i`Y zD~RDe01)zX>gzqaq_m`j{i0!x->nzn#pss7TNW_ELA) z=yfcK{ZZ!Uj~QKc^*__j2U^dA+aFz)78V8N(uDg<>$>9tro(eYj^Ae48eYffQvs|; zQJfLiMyOB-yipo_GV+=@_RKiw58wA2648hh7R$xb;;c5e8v~Hq-_l;nA^MaW4P=>! zdapawqz!eemsatf%rZP%un*2@uLw#8b8J3O?W6KcL#1TXA|7yRIMCJKn&(K3Ch31s z$*ZYBZ6_$nGWcF%U|;|p(rE5mXlp*8ch6ur7&_Q%inz_S^)d+x^@DJ94?ypSDTbe) zPu<{d2Z?I3+&97swmYoqH$}0ee<^In!HO{LuK%6yMeB1CORLNuz~{Yk!|#wqF&kERi-7H7e#Are81hHLx(zjBAQJTqTiH_0#w2 z&3~+M=bU%jjhWcuO=@E2QyJZAHt2hH z_jZ@9S#pKcgS~8;9mGD~i1l)?YcR<%l>uM}o*#~gg!s{7`3i$6d+sKJ^c}#G&UpNS zWvP~+7OP4?yvQ-TdpXLs{korv4niA`_<+ZcE25c;rLVSI){k3IElxwV2HUQms&TJB zXg5>mTywcfillM;^L;w6qq!!gkbq&vSrC0Q^4;SU5kpdc3Dt;va( z7G`QCF`mv5pi--vrv&1Z>0}1Q<6k9TT&v9o<^UabG2@>H-Ix8W*~p2#H&}IAU9NN{ z6R@^$qHJ96s#W;(WGPYdFNp%E~y{pobm8+Xu`fYy|))|lWAFQ`J@;z?{gy4G|18t9lC^irJ=lN14I5;?6&kGi&B>lU?2`<;m z0w!pZoJ1#y^}r72S&53=f7OqlEO>eipdg=*Yv-*;HHJ`8JeVMB?a2&~gUc#vwSYP^ zaJgu*`k|;{r2*#Pz-zU^90%8B1`F7Rgri+W>M{=Z&8N_zJ)jL)z_H9+yt+RUFXFcv z$+r1C&NS5%v%lLDu)3)DGWlJc|&2a@6SVgyf6FlmX?+WT{kM_s$n?(=AL{bzD{2< zq$RS^+7KXKZ)>VJQkmATtBH#(jTnRIdN0R*p8_dUh&X+Wjy%$N*(WqRZkydDWn~`k zH$x)ouj5CPnN<5Hz-CW~0XQ@mfdJ7@v@+XyQGJ(hx4HYn8fSvla1dSz9g1R3;D#RT z>m~b;vVp>!;@qB2_&ZePkb3rmwq;Nd&Uc`&rMOSBZ{mg6fIi{g&2w|tLuLFgIaPF%LG0^Q%W9c4W(igsOG`9vD0y97 zAQ?#On}-+PdcQqNX!4MLfU}v=omHNhT7OD5`ZX1a7}F_pSizcB+TN+BC}*=pFD(L< zK-US0V_`GQucN3lYu?hD*%!IXNINFJyc&_3PAqG0V3~dYXLAurAW+Nhhk!{H!yYy`zrJ z>XlFeY}sb=#N&R+Ivx^QI?f9!dHguEolZv?j#1}3xgq#*=7}HH_*uFeTBym%PXDYH zKF%BF%%tI*6N8@nQ0$U%AQ1kF^XH#Hm&O*rBwyFw?*b9Nqoh zf1A*LLX&GP>CAnFBAh={Rz~^HjzatAl*q(%u8@dfS}+P^-_x2c-wolz_!vFABY0|8 zYJ|8mMc>_>l724aFLl2hMEfR&#$e0@wFsQPSuSJ_k{FWz8J>FRw3DxAs&X+;9iGqg z&aJAD5I8i`QiZzEhWV~QX;m~HS5sl3@h4D1A5F1s;i7vXvK$}u!YOFNCH|d<674)r zI9;rx7}p=Y774G@@UI)`4mJeq9040hY z%o~1ffcgLe z!xJEC0f}JP$ulf7;dMHe|GuT6t^kS~Iq}n8cHGvV;N4Fq?c4DwbH^BBrnSb?YNawj zx!Z61uaLGeAQv=4HotUm0j=qUx*|BcZ8g`ek5Q`Xl;#qjstY2lpyo{vsdEi~YwPtk znjgYtj&!Uho7_!?Vw|(sCotCTFn*EW2a;(FQyY1Msh=qM6$$e*Xlerc-R0%3ub*p_ zwSd9qbUfYG#u-k4h^&8+?LAml786`QQPB1W0?zJ!rqn?c4?l?vGQ$BMEZKn>wz^H*KC4-dS}OsrTI>_0kb?EYPxrza6%dQyJfXm|ZOH|0Z1 zto=RSCYY6+?0VJtQe)xy#CJayZ? z3<6@Ve*sXYRse6y?%|3Ubwmc#s_yQs&>jr`I$5ki%Uw54o#&468hRl04n`9LAQ2=x zX(Q)?*lhY|#PUm^X=unPKzp7p3^ym824u)hO01m9VAu~j9PK>X!+?3s>1lfZZ&k3v z*ov*|jTQ@(cQ@MYE+e-9;zDx6(SQaf)CB1c(pr0dCTj_9nIQ_TAvxZTc5|zy}FAE`sA;{-0fGQb2`(RO64H zRMRXNzf$!%&7d@JD(EkE-XEUo)HVhUwhK*g>u zdvCM|ooNNlDcHn&{e*ae=V-o5qiyiLgn8}XP4Oi^2&$&P_4vN;5kzA%2&%Hi{5Y;7 zsFWV5Hg?Ttsk2uNw*howgg>Sr`uIqWaOBGfJdShecY8~Zc@0;Y6@E9EpvOSVAPk4vlDvRT>XL{} zfGwVW5EcMcaBv43$+|x3zNPwZbDHwTEg2r9SM?fBeKYN940`b+$~+(Uk1UosG~=Q1 z!J*^pGR$|`ZfP8b5u7_$-Cv*At{0n~nn_y#w+wP|ap8dEzT9PB_xG^;VAgBfOEBqw z?|}fR7Z+CM=GB+RGCNxo6J;;L#J2~p5TlL>aeI5GKde&*aSq(8?o*8K73Jt1(4hm; z+;lDO{IZrqzWV2#Pz!lQt&EG}Ee?+-3P|*+v)!A>?hhJhec?_m_c%BczLya>(utCt zPO}-}3w5{TyrRy|OwOa*yE`01-?z1`{z9V`b1cI7l|g@Cdhvf6JT51Q?^BM>N){V) za<p4yo>a;{xIoJbE34^K@ikkJEx+ta!u(j6+3e5i2vA#syx5)m!1H+hH2 z-S34~OjgVEE3;cW)-5a89MBT)=@-u({1&l8ofz~iz~>+z{u{2Xu7OTzt6#)%WYzUMyAh4pVd5=^lE{oSixQL{^O zW1(df`^kt`yz`tALr#{nnRAUyHX{YNf@5EJ*n3};5hqrul_HkD(aG&w{z`KHr+mz| zon>p1)A;S%zGHMdd_sOnIkkxcgqx&ny@V{MY^`|~)9Z##G z31g~T7s0L8=}dE^-5VhQt_4Ke8fW!|g(67}MFo0oi~3_!P(hsdqA42*?iC znyk_81_Ebie`UE5$A776`{ldZ5Qn<~C0V0>AEe`582MB>TQs9qK+F{MZTZ@xb2x<_ zKtcYDDEWD{#L$p#9Lp0Q%op{afT5e=`un1K4Mc z@IyU;&Q7%My8;Nvx90yo7hwMp7?w^klx6Ynh)QSQ$K1VfFH^;_l0ExsNs2K^XBWA z?k6!VK$R9+%Ir3+*)XB8ZaGfz92^|1qyWf+{e;IZ)<1n}V%iDx^5U5LVP46w7Legm z4MpLlw~3_jyq(wAQ8Q&r^&8aw7gjY6fND~f=V3kr9Z(ay@CV;$1s8=&(RBYkq{$D7 zu*)eem3VZ#Xk41LA@F(DpYs4Z9cyBd-xCdiq5~Lb=B?nqO4bjpVO@5hRHAS60t|I@ z%zkfhWrt!7zclI4Y|l5R47`L6|QI7@^S<|mOvw$ZQZVKMvTCE z#VIKqc#z|7uR%w|b_;1r>L&KvJ%Hg#+5))1`et`PLk$2CxO_ex*L}(8ShibTJRvMV zS#BFrr9YNLV{qejwpeL66lHB?WfUPo9IG?>;~n7mM2TaGghDhP>}rYLPs^(}+g+X!3y?NBKcDw@M!tZ}snl*~T~opfAz8}1{_*;M#$7A$#R@n!adP>bLp@&& z7#{4|y7lWNZ++gXGT`{u{onV#uSlBj4IBx406K%9f$cEk3*O3qpHAyfpE5_+0V zPZG&G`ugveO!nK%-`fj^Uxd!{ZaXa%Kf}1}K!>2( z;q$iN?--x6a9-vh*7hA30Yyf@5G-D6`9-WMGdL?Yb}lfzUkfM&6)*+=em?*IfZd+K zcg`H=%rn+4ptBkJ(tDn2yDYT$ z-T&2B-p{i9g+K80jsD}0^Y{Hs1FnUh`Yt63t50!3OH1o%4>QewNjyF zQvmR^f(Z*>giPX8JiL8!vEUTl)SgaZ54ICkGmbFKIw`YB_T`Dsw{G21khHttVm{|f z*$Jg3lYN%0TJ`GVarx=bCwSQLI{+8x=C|j#R2a={YisM3Hg~!!Iqxv%RVU!e%?U?2 zZq?{*T6Q{(Ik@ET&3k-1TY%dtJ6%?-Uj6vvjuY$E4&7o~aP{qj37dgqRu?lubV}-f zJZxtU&=oHTp5?6Ptha=Bjzb7L-9Y=9qyO1!m5)w)^J=p%@ca`7Pgg&ebxsLQ0QrLz AnE(I) literal 0 HcmV?d00001 diff --git a/modules/viz/doc/images/cube_widget.png b/modules/viz/doc/images/cube_widget.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe56811aedb6fc57e77097e6b2840cdd6bb64e4 GIT binary patch literal 6507 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 V6?X0&0K6`tFw`^Ct<-jn{U22ce2xGB literal 0 HcmV?d00001 diff --git a/modules/viz/doc/viz.rst b/modules/viz/doc/viz.rst new file mode 100644 index 000000000..1d8c743ad --- /dev/null +++ b/modules/viz/doc/viz.rst @@ -0,0 +1,9 @@ +*********************** +viz. 3D Visualizer +*********************** + +.. toctree:: + :maxdepth: 2 + + viz3d.rst + widget.rst diff --git a/modules/viz/doc/viz3d.rst b/modules/viz/doc/viz3d.rst new file mode 100644 index 000000000..d0e24ae8e --- /dev/null +++ b/modules/viz/doc/viz3d.rst @@ -0,0 +1,637 @@ +Viz +=== + +.. highlight:: cpp + +This section describes 3D visualization window as well as classes and methods +that are used to interact with it. + +3D visualization window (see :ocv:class:`Viz3d`) is used to display widgets (see :ocv:class:`Widget`), and it provides +several methods to interact with scene and widgets. + +viz::makeTransformToGlobal +-------------------------- +Takes coordinate frame data and builds transform to global coordinate frame. + +.. ocv:function:: Affine3d viz::makeTransformToGlobal(const Vec3f& axis_x, const Vec3f& axis_y, const Vec3f& axis_z, const Vec3f& origin = Vec3f::all(0)) + + :param axis_x: X axis vector in global coordinate frame. + :param axis_y: Y axis vector in global coordinate frame. + :param axis_z: Z axis vector in global coordinate frame. + :param origin: Origin of the coordinate frame in global coordinate frame. + +This function returns affine transform that describes transformation between global coordinate frame and a given coordinate frame. + +viz::makeCameraPose +------------------- +Constructs camera pose from position, focal_point and up_vector (see gluLookAt() for more infromation). + +.. ocv:function:: Affine3d makeCameraPose(const Vec3f& position, const Vec3f& focal_point, const Vec3f& y_dir) + + :param position: Position of the camera in global coordinate frame. + :param focal_point: Focal point of the camera in global coordinate frame. + :param y_dir: Up vector of the camera in global coordinate frame. + +This function returns pose of the camera in global coordinate frame. + +viz::getWindowByName +-------------------- +Retrieves a window by its name. + +.. ocv:function:: Viz3d getWindowByName(const String &window_name) + + :param window_name: Name of the window that is to be retrieved. + +This function returns a :ocv:class:`Viz3d` object with the given name. + +.. note:: If the window with that name already exists, that window is returned. Otherwise, new window is created with the given name, and it is returned. + +.. note:: Window names are automatically prefixed by "Viz - " if it is not done by the user. + + .. code-block:: cpp + + /// window and window_2 are the same windows. + viz::Viz3d window = viz::getWindowByName("myWindow"); + viz::Viz3d window_2 = viz::getWindowByName("Viz - myWindow"); + +viz::isNan +---------- +Checks **float/double** value for nan. + + .. ocv:function:: bool isNan(float x) + + .. ocv:function:: bool isNan(double x) + + :param x: return true if nan. + +Checks **vector** for nan. + + .. ocv:function:: bool isNan(const Vec<_Tp, cn>& v) + + :param v: return true if **any** of the elements of the vector is *nan*. + +Checks **point** for nan + + .. ocv:function:: bool isNan(const Point3_<_Tp>& p) + + :param p: return true if **any** of the elements of the point is *nan*. + +viz::Viz3d +---------- +.. ocv:class:: Viz3d + +The Viz3d class represents a 3D visualizer window. This class is implicitly shared. :: + + class CV_EXPORTS Viz3d + { + public: + typedef cv::Ptr Ptr; + typedef void (*KeyboardCallback)(const KeyboardEvent&, void*); + typedef void (*MouseCallback)(const MouseEvent&, void*); + + Viz3d(const String& window_name = String()); + Viz3d(const Viz3d&); + Viz3d& operator=(const Viz3d&); + ~Viz3d(); + + void showWidget(const String &id, const Widget &widget, const Affine3d &pose = Affine3d::Identity()); + void removeWidget(const String &id); + Widget getWidget(const String &id) const; + void removeAllWidgets(); + + void setWidgetPose(const String &id, const Affine3d &pose); + void updateWidgetPose(const String &id, const Affine3d &pose); + Affine3d getWidgetPose(const String &id) const; + + void showImage(InputArray image, const Size& window_size = Size(-1, -1)); + + void setCamera(const Camera &camera); + Camera getCamera() const; + Affine3d getViewerPose(); + void setViewerPose(const Affine3d &pose); + + void resetCameraViewpoint (const String &id); + void resetCamera(); + + void convertToWindowCoordinates(const Point3d &pt, Point3d &window_coord); + void converTo3DRay(const Point3d &window_coord, Point3d &origin, Vec3d &direction); + + Size getWindowSize() const; + void setWindowSize(const Size &window_size); + String getWindowName() const; + void saveScreenshot (const String &file); + void setWindowPosition (int x, int y); + void setFullScreen (bool mode); + void setBackgroundColor(const Color& color = Color::black()); + + void spin(); + void spinOnce(int time = 1, bool force_redraw = false); + bool wasStopped() const; + + void registerKeyboardCallback(KeyboardCallback callback, void* cookie = 0); + void registerMouseCallback(MouseCallback callback, void* cookie = 0); + + void setRenderingProperty(const String &id, int property, double value); + double getRenderingProperty(const String &id, int property); + + + void setRepresentation(int representation); + private: + /* hidden */ + }; + +viz::Viz3d::Viz3d +----------------- +The constructors. + +.. ocv:function:: Viz3d::Viz3d(const String& window_name = String()) + + :param window_name: Name of the window. + +viz::Viz3d::showWidget +---------------------- +Shows a widget in the window. + +.. ocv:function:: void Viz3d::showWidget(const String &id, const Widget &widget, const Affine3d &pose = Affine3d::Identity()) + + :param id: A unique id for the widget. + :param widget: The widget to be displayed in the window. + :param pose: Pose of the widget. + +viz::Viz3d::removeWidget +------------------------ +Removes a widget from the window. + +.. ocv:function:: void removeWidget(const String &id) + + :param id: The id of the widget that will be removed. + +viz::Viz3d::getWidget +--------------------- +Retrieves a widget from the window. A widget is implicitly shared; +that is, if the returned widget is modified, the changes will be +immediately visible in the window. + +.. ocv:function:: Widget getWidget(const String &id) const + + :param id: The id of the widget that will be returned. + +viz::Viz3d::removeAllWidgets +---------------------------- +Removes all widgets from the window. + +.. ocv:function:: void removeAllWidgets() + +viz::Viz3d::showImage +--------------------- +Removed all widgets and displays image scaled to whole window area. + +.. ocv:function:: void showImage(InputArray image, const Size& window_size = Size(-1, -1)) + + :param image: Image to be displayed. + :param size: Size of Viz3d window. Default value means no change. + +viz::Viz3d::setWidgetPose +------------------------- +Sets pose of a widget in the window. + +.. ocv:function:: void setWidgetPose(const String &id, const Affine3d &pose) + + :param id: The id of the widget whose pose will be set. + :param pose: The new pose of the widget. + +viz::Viz3d::updateWidgetPose +---------------------------- +Updates pose of a widget in the window by pre-multiplying its current pose. + +.. ocv:function:: void updateWidgetPose(const String &id, const Affine3d &pose) + + :param id: The id of the widget whose pose will be updated. + :param pose: The pose that the current pose of the widget will be pre-multiplied by. + +viz::Viz3d::getWidgetPose +------------------------- +Returns the current pose of a widget in the window. + +.. ocv:function:: Affine3d getWidgetPose(const String &id) const + + :param id: The id of the widget whose pose will be returned. + +viz::Viz3d::setCamera +--------------------- +Sets the intrinsic parameters of the viewer using Camera. + +.. ocv:function:: void setCamera(const Camera &camera) + + :param camera: Camera object wrapping intrinsinc parameters. + +viz::Viz3d::getCamera +--------------------- +Returns a camera object that contains intrinsic parameters of the current viewer. + +.. ocv:function:: Camera getCamera() const + +viz::Viz3d::getViewerPose +------------------------- +Returns the current pose of the viewer. + +..ocv:function:: Affine3d getViewerPose() + +viz::Viz3d::setViewerPose +------------------------- +Sets pose of the viewer. + +.. ocv:function:: void setViewerPose(const Affine3d &pose) + + :param pose: The new pose of the viewer. + +viz::Viz3d::resetCameraViewpoint +-------------------------------- +Resets camera viewpoint to a 3D widget in the scene. + +.. ocv:function:: void resetCameraViewpoint (const String &id) + + :param pose: Id of a 3D widget. + +viz::Viz3d::resetCamera +----------------------- +Resets camera. + +.. ocv:function:: void resetCamera() + +viz::Viz3d::convertToWindowCoordinates +-------------------------------------- +Transforms a point in world coordinate system to window coordinate system. + +.. ocv:function:: void convertToWindowCoordinates(const Point3d &pt, Point3d &window_coord) + + :param pt: Point in world coordinate system. + :param window_coord: Output point in window coordinate system. + +viz::Viz3d::converTo3DRay +------------------------- +Transforms a point in window coordinate system to a 3D ray in world coordinate system. + +.. ocv:function:: void converTo3DRay(const Point3d &window_coord, Point3d &origin, Vec3d &direction) + + :param window_coord: Point in window coordinate system. + :param origin: Output origin of the ray. + :param direction: Output direction of the ray. + +viz::Viz3d::getWindowSize +------------------------- +Returns the current size of the window. + +.. ocv:function:: Size getWindowSize() const + +viz::Viz3d::setWindowSize +------------------------- +Sets the size of the window. + +.. ocv:function:: void setWindowSize(const Size &window_size) + + :param window_size: New size of the window. + +viz::Viz3d::getWindowName +------------------------- +Returns the name of the window which has been set in the constructor. + +.. ocv:function:: String getWindowName() const + +viz::Viz3d::saveScreenshot +-------------------------- +Saves screenshot of the current scene. + +.. ocv:function:: void saveScreenshot(const String &file) + + :param file: Name of the file. + +viz::Viz3d::setWindowPosition +----------------------------- +Sets the position of the window in the screen. + +.. ocv:function:: void setWindowPosition(int x, int y) + + :param x: x coordinate of the window + :param y: y coordinate of the window + +viz::Viz3d::setFullScreen +------------------------- +Sets or unsets full-screen rendering mode. + +.. ocv:function:: void setFullScreen(bool mode) + + :param mode: If true, window will use full-screen mode. + +viz::Viz3d::setBackgroundColor +------------------------------ +Sets background color. + +.. ocv:function:: void setBackgroundColor(const Color& color = Color::black()) + +viz::Viz3d::spin +---------------- +The window renders and starts the event loop. + +.. ocv:function:: void spin() + +viz::Viz3d::spinOnce +-------------------- +Starts the event loop for a given time. + +.. ocv:function:: void spinOnce(int time = 1, bool force_redraw = false) + + :param time: Amount of time in milliseconds for the event loop to keep running. + :param force_draw: If true, window renders. + +viz::Viz3d::wasStopped +---------------------- +Returns whether the event loop has been stopped. + +.. ocv:function:: bool wasStopped() + +viz::Viz3d::registerKeyboardCallback +------------------------------------ +Sets keyboard handler. + +.. ocv:function:: void registerKeyboardCallback(KeyboardCallback callback, void* cookie = 0) + + :param callback: Keyboard callback ``(void (*KeyboardCallbackFunction(const KeyboardEvent&, void*))``. + :param cookie: The optional parameter passed to the callback. + +viz::Viz3d::registerMouseCallback +--------------------------------- +Sets mouse handler. + +.. ocv:function:: void registerMouseCallback(MouseCallback callback, void* cookie = 0) + + :param callback: Mouse callback ``(void (*MouseCallback)(const MouseEvent&, void*))``. + :param cookie: The optional parameter passed to the callback. + +viz::Viz3d::setRenderingProperty +-------------------------------- +Sets rendering property of a widget. + +.. ocv:function:: void setRenderingProperty(const String &id, int property, double value) + + :param id: Id of the widget. + :param property: Property that will be modified. + :param value: The new value of the property. + + **Rendering property** can be one of the following: + + * **POINT_SIZE** + * **OPACITY** + * **LINE_WIDTH** + * **FONT_SIZE** + * **REPRESENTATION**: Expected values are + * **REPRESENTATION_POINTS** + * **REPRESENTATION_WIREFRAME** + * **REPRESENTATION_SURFACE** + * **IMMEDIATE_RENDERING**: + * Turn on immediate rendering by setting the value to ``1``. + * Turn off immediate rendering by setting the value to ``0``. + * **SHADING**: Expected values are + * **SHADING_FLAT** + * **SHADING_GOURAUD** + * **SHADING_PHONG** + +viz::Viz3d::getRenderingProperty +-------------------------------- +Returns rendering property of a widget. + +.. ocv:function:: double getRenderingProperty(const String &id, int property) + + :param id: Id of the widget. + :param property: Property. + + **Rendering property** can be one of the following: + + * **POINT_SIZE** + * **OPACITY** + * **LINE_WIDTH** + * **FONT_SIZE** + * **REPRESENTATION**: Expected values are + * **REPRESENTATION_POINTS** + * **REPRESENTATION_WIREFRAME** + * **REPRESENTATION_SURFACE** + * **IMMEDIATE_RENDERING**: + * Turn on immediate rendering by setting the value to ``1``. + * Turn off immediate rendering by setting the value to ``0``. + * **SHADING**: Expected values are + * **SHADING_FLAT** + * **SHADING_GOURAUD** + * **SHADING_PHONG** + +viz::Viz3d::setRepresentation +----------------------------- +Sets geometry representation of the widgets to surface, wireframe or points. + +.. ocv:function:: void setRepresentation(int representation) + + :param representation: Geometry representation which can be one of the following: + + * **REPRESENTATION_POINTS** + * **REPRESENTATION_WIREFRAME** + * **REPRESENTATION_SURFACE** + +viz::Color +---------- +.. ocv:class:: Color + +This class a represents BGR color. :: + + class CV_EXPORTS Color : public Scalar + { + public: + Color(); + Color(double gray); + Color(double blue, double green, double red); + + Color(const Scalar& color); + + static Color black(); + static Color blue(); + static Color green(); + static Color cyan(); + + static Color red(); + static Color magenta(); + static Color yellow(); + static Color white(); + + static Color gray(); + }; + +viz::Mesh +----------- +.. ocv:class:: Mesh + +This class wraps mesh attributes, and it can load a mesh from a ``ply`` file. :: + + class CV_EXPORTS Mesh + { + public: + + Mat cloud, colors, normals; + + //! Raw integer list of the form: (n,id1,id2,...,idn, n,id1,id2,...,idn, ...) + //! where n is the number of points in the poligon, and id is a zero-offset index into an associated cloud. + Mat polygons; + + //! Loads mesh from a given ply file + static Mesh load(const String& file); + }; + +viz::Mesh::load +--------------------- +Loads a mesh from a ``ply`` file. + +.. ocv:function:: static Mesh load(const String& file) + + :param file: File name (for no only PLY is supported) + + +viz::KeyboardEvent +------------------ +.. ocv:class:: KeyboardEvent + +This class represents a keyboard event. :: + + class CV_EXPORTS KeyboardEvent + { + public: + enum { ALT = 1, CTRL = 2, SHIFT = 4 }; + enum Action { KEY_UP = 0, KEY_DOWN = 1 }; + + KeyboardEvent(Action action, const String& symbol, unsigned char code, int modifiers); + + Action action; + String symbol; + unsigned char code; + int modifiers; + }; + +viz::KeyboardEvent::KeyboardEvent +--------------------------------- +Constructs a KeyboardEvent. + +.. ocv:function:: KeyboardEvent (Action action, const String& symbol, unsigned char code, Modifiers modifiers) + + :param action: Signals if key is pressed or released. + :param symbol: Name of the key. + :param code: Code of the key. + :param modifiers: Signals if ``alt``, ``ctrl`` or ``shift`` are pressed or their combination. + + +viz::MouseEvent +--------------- +.. ocv:class:: MouseEvent + +This class represents a mouse event. :: + + class CV_EXPORTS MouseEvent + { + public: + enum Type { MouseMove = 1, MouseButtonPress, MouseButtonRelease, MouseScrollDown, MouseScrollUp, MouseDblClick } ; + enum MouseButton { NoButton = 0, LeftButton, MiddleButton, RightButton, VScroll } ; + + MouseEvent(const Type& type, const MouseButton& button, const Point& pointer, int modifiers); + + Type type; + MouseButton button; + Point pointer; + int modifiers; + }; + +viz::MouseEvent::MouseEvent +--------------------------- +Constructs a MouseEvent. + +.. ocv:function:: MouseEvent (const Type& type, const MouseButton& button, const Point& p, Modifiers modifiers) + + :param type: Type of the event. This can be **MouseMove**, **MouseButtonPress**, **MouseButtonRelease**, **MouseScrollDown**, **MouseScrollUp**, **MouseDblClick**. + :param button: Mouse button. This can be **NoButton**, **LeftButton**, **MiddleButton**, **RightButton**, **VScroll**. + :param p: Position of the event. + :param modifiers: Signals if ``alt``, ``ctrl`` or ``shift`` are pressed or their combination. + +viz::Camera +----------- +.. ocv:class:: Camera + +This class wraps intrinsic parameters of a camera. It provides several constructors +that can extract the intrinsic parameters from ``field of view``, ``intrinsic matrix`` and +``projection matrix``. :: + + class CV_EXPORTS Camera + { + public: + Camera(double f_x, double f_y, double c_x, double c_y, const Size &window_size); + Camera(const Vec2d &fov, const Size &window_size); + Camera(const Matx33d &K, const Size &window_size); + Camera(const Matx44d &proj, const Size &window_size); + + inline const Vec2d & getClip() const; + inline void setClip(const Vec2d &clip); + + inline const Size & getWindowSize() const; + void setWindowSize(const Size &window_size); + + inline const Vec2d & getFov() const; + inline void setFov(const Vec2d & fov); + + inline const Vec2d & getPrincipalPoint() const; + inline const Vec2d & getFocalLength() const; + + void computeProjectionMatrix(Matx44d &proj) const; + + static Camera KinectCamera(const Size &window_size); + + private: + /* hidden */ + }; + +viz::Camera::Camera +------------------- +Constructs a Camera. + +.. ocv:function:: Camera(double f_x, double f_y, double c_x, double c_y, const Size &window_size) + + :param f_x: Horizontal focal length. + :param f_y: Vertical focal length. + :param c_x: x coordinate of the principal point. + :param c_y: y coordinate of the principal point. + :param window_size: Size of the window. This together with focal length and principal point determines the field of view. + +.. ocv:function:: Camera(const Vec2d &fov, const Size &window_size) + + :param fov: Field of view (horizontal, vertical) + :param window_size: Size of the window. + + Principal point is at the center of the window by default. + +.. ocv:function:: Camera(const Matx33d &K, const Size &window_size) + + :param K: Intrinsic matrix of the camera. + :param window_size: Size of the window. This together with intrinsic matrix determines the field of view. + +.. ocv:function:: Camera(const Matx44d &proj, const Size &window_size) + + :param proj: Projection matrix of the camera. + :param window_size: Size of the window. This together with projection matrix determines the field of view. + +viz::Camera::computeProjectionMatrix +------------------------------------ +Computes projection matrix using intrinsic parameters of the camera. + +.. ocv:function:: void computeProjectionMatrix(Matx44d &proj) const + + :param proj: Output projection matrix. + +viz::Camera::KinectCamera +------------------------- +Creates a Kinect Camera. + +.. ocv:function:: static Camera KinectCamera(const Size &window_size) + + :param window_size: Size of the window. This together with intrinsic matrix of a Kinect Camera determines the field of view. diff --git a/modules/viz/doc/widget.rst b/modules/viz/doc/widget.rst new file mode 100644 index 000000000..008e0e68a --- /dev/null +++ b/modules/viz/doc/widget.rst @@ -0,0 +1,1019 @@ +Widget +====== + +.. highlight:: cpp + +In this section, the widget framework is explained. Widgets represent +2D or 3D objects, varying from simple ones such as lines to complex one such as +point clouds and meshes. + +Widgets are **implicitly shared**. Therefore, one can add a widget to the scene, +and modify the widget without re-adding the widget. + +.. code-block:: cpp + + ... + /// Create a cloud widget + viz::WCloud cw(cloud, viz::Color::red()); + /// Display it in a window + myWindow.showWidget("CloudWidget1", cw); + /// Modify it, and it will be modified in the window. + cw.setColor(viz::Color::yellow()); + ... + +viz::Widget +----------- +.. ocv:class:: Widget + +Base class of all widgets. Widget is implicitly shared. :: + + class CV_EXPORTS Widget + { + public: + Widget(); + Widget(const Widget& other); + Widget& operator=(const Widget& other); + ~Widget(); + + //! Create a widget directly from ply file + static Widget fromPlyFile(const String &file_name); + + //! Rendering properties of this particular widget + void setRenderingProperty(int property, double value); + double getRenderingProperty(int property) const; + + //! Casting between widgets + template _W cast(); + private: + /* hidden */ + }; + +viz::Widget::fromPlyFile +------------------------ +Creates a widget from ply file. + +.. ocv:function:: static Widget fromPlyFile(const String &file_name) + + :param file_name: Ply file name. + +viz::Widget::setRenderingProperty +--------------------------------- +Sets rendering property of the widget. + +.. ocv:function:: void setRenderingProperty(int property, double value) + + :param property: Property that will be modified. + :param value: The new value of the property. + + **Rendering property** can be one of the following: + + * **POINT_SIZE** + * **OPACITY** + * **LINE_WIDTH** + * **FONT_SIZE** + * **REPRESENTATION**: Expected values are + * **REPRESENTATION_POINTS** + * **REPRESENTATION_WIREFRAME** + * **REPRESENTATION_SURFACE** + * **IMMEDIATE_RENDERING**: + * Turn on immediate rendering by setting the value to ``1``. + * Turn off immediate rendering by setting the value to ``0``. + * **SHADING**: Expected values are + * **SHADING_FLAT** + * **SHADING_GOURAUD** + * **SHADING_PHONG** + +viz::Widget::getRenderingProperty +--------------------------------- +Returns rendering property of the widget. + +.. ocv:function:: double getRenderingProperty(int property) const + + :param property: Property. + + **Rendering property** can be one of the following: + + * **POINT_SIZE** + * **OPACITY** + * **LINE_WIDTH** + * **FONT_SIZE** + * **REPRESENTATION**: Expected values are + * **REPRESENTATION_POINTS** + * **REPRESENTATION_WIREFRAME** + * **REPRESENTATION_SURFACE** + * **IMMEDIATE_RENDERING**: + * Turn on immediate rendering by setting the value to ``1``. + * Turn off immediate rendering by setting the value to ``0``. + * **SHADING**: Expected values are + * **SHADING_FLAT** + * **SHADING_GOURAUD** + * **SHADING_PHONG** + +viz::Widget::cast +----------------- +Casts a widget to another. + +.. ocv:function:: template _W cast() + +.. code-block:: cpp + + // Create a sphere widget + viz::WSphere sw(Point3f(0.0f,0.0f,0.0f), 0.5f); + // Cast sphere widget to cloud widget + viz::WCloud cw = sw.cast(); + +.. note:: 3D Widgets can only be cast to 3D Widgets. 2D Widgets can only be cast to 2D Widgets. + +viz::WidgetAccessor +------------------- +.. ocv:class:: WidgetAccessor + +This class is for users who want to develop their own widgets using VTK library API. :: + + struct CV_EXPORTS WidgetAccessor + { + static vtkSmartPointer getProp(const Widget &widget); + static void setProp(Widget &widget, vtkSmartPointer prop); + }; + +viz::WidgetAccessor::getProp +---------------------------- +Returns ``vtkProp`` of a given widget. + +.. ocv:function:: static vtkSmartPointer getProp(const Widget &widget) + + :param widget: Widget whose ``vtkProp`` is to be returned. + +.. note:: vtkProp has to be down cast appropriately to be modified. + + .. code-block:: cpp + + vtkActor * actor = vtkActor::SafeDownCast(viz::WidgetAccessor::getProp(widget)); + +viz::WidgetAccessor::setProp +---------------------------- +Sets ``vtkProp`` of a given widget. + +.. ocv:function:: static void setProp(Widget &widget, vtkSmartPointer prop) + + :param widget: Widget whose ``vtkProp`` is to be set. + :param prop: A ``vtkProp``. + +viz::Widget3D +------------- +.. ocv:class:: Widget3D + +Base class of all 3D widgets. :: + + class CV_EXPORTS Widget3D : public Widget + { + public: + Widget3D() {} + + //! widget position manipulation, i.e. place where it is rendered. + void setPose(const Affine3d &pose); + void updatePose(const Affine3d &pose); + Affine3d getPose() const; + + //! updates internal widget data, i.e. points, normals, etc. + void applyTransform(const Affine3d &transform); + + void setColor(const Color &color); + + }; + +viz::Widget3D::setPose +---------------------- +Sets pose of the widget. + +.. ocv:function:: void setPose(const Affine3d &pose) + + :param pose: The new pose of the widget. + +viz::Widget3D::updateWidgetPose +------------------------------- +Updates pose of the widget by pre-multiplying its current pose. + +.. ocv:function:: void updateWidgetPose(const Affine3d &pose) + + :param pose: The pose that the current pose of the widget will be pre-multiplied by. + +viz::Widget3D::getPose +---------------------- +Returns the current pose of the widget. + +.. ocv:function:: Affine3d getWidgetPose() const + + +viz::Widget3D::applyTransform +------------------------------- +Transforms internal widget data (i.e. points, normals) using the given transform. + +.. ocv:function:: void applyTransform(const Affine3d &transform) + + :param transform: Specified transformation to apply. + +viz::Widget3D::setColor +----------------------- +Sets the color of the widget. + +.. ocv:function:: void setColor(const Color &color) + + :param color: color of type :ocv:class:`Color` + +viz::Widget2D +------------- +.. ocv:class:: Widget2D + +Base class of all 2D widgets. :: + + class CV_EXPORTS Widget2D : public Widget + { + public: + Widget2D() {} + + void setColor(const Color &color); + }; + +viz::Widget2D::setColor +----------------------- +Sets the color of the widget. + +.. ocv:function:: void setColor(const Color &color) + + :param color: color of type :ocv:class:`Color` + +viz::WLine +---------- +.. ocv:class:: WLine + +This 3D Widget defines a finite line. :: + + class CV_EXPORTS WLine : public Widget3D + { + public: + WLine(const Point3f &pt1, const Point3f &pt2, const Color &color = Color::white()); + }; + +viz::WLine::WLine +----------------- +Constructs a WLine. + +.. ocv:function:: WLine(const Point3f &pt1, const Point3f &pt2, const Color &color = Color::white()) + + :param pt1: Start point of the line. + :param pt2: End point of the line. + :param color: :ocv:class:`Color` of the line. + +viz::WPlane +----------- +.. ocv:class:: WPlane + +This 3D Widget defines a finite plane. :: + + class CV_EXPORTS WPlane : public Widget3D + { + public: + //! created default plane with center point at origin and normal oriented along z-axis + WPlane(const Size2d& size = Size2d(1.0, 1.0), const Color &color = Color::white()); + + //! repositioned plane + WPlane(const Point3d& center, const Vec3d& normal, const Vec3d& new_plane_yaxis,const Size2d& size = Size2d(1.0, 1.0), const Color &color = Color::white()); + }; + +viz::WPlane::WPlane +------------------- +Constructs a default plane with center point at origin and normal oriented along z-axis. + +.. ocv:function:: WPlane(const Size2d& size = Size2d(1.0, 1.0), const Color &color = Color::white()) + + :param size: Size of the plane + :param color: :ocv:class:`Color` of the plane. + +viz::WPlane::WPlane +------------------- +Constructs a repositioned plane + +.. ocv:function:: WPlane(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis,const Size2d& size = Size2d(1.0, 1.0), const Color &color = Color::white()) + + :param center: Center of the plane + :param normal: Plane normal orientation + :param new_yaxis: Up-vector. New orientation of plane y-axis. + :param color: :ocv:class:`Color` of the plane. + +viz::WSphere +------------ +.. ocv:class:: WSphere + +This 3D Widget defines a sphere. :: + + class CV_EXPORTS WSphere : public Widget3D + { + public: + WSphere(const cv::Point3f ¢er, double radius, int sphere_resolution = 10, const Color &color = Color::white()) + }; + +viz::WSphere::WSphere +--------------------- +Constructs a WSphere. + +.. ocv:function:: WSphere(const cv::Point3f ¢er, double radius, int sphere_resolution = 10, const Color &color = Color::white()) + + :param center: Center of the sphere. + :param radius: Radius of the sphere. + :param sphere_resolution: Resolution of the sphere. + :param color: :ocv:class:`Color` of the sphere. + +viz::WArrow +---------------- +.. ocv:class:: WArrow + +This 3D Widget defines an arrow. :: + + class CV_EXPORTS WArrow : public Widget3D + { + public: + WArrow(const Point3f& pt1, const Point3f& pt2, double thickness = 0.03, const Color &color = Color::white()); + }; + +viz::WArrow::WArrow +----------------------------- +Constructs an WArrow. + +.. ocv:function:: WArrow(const Point3f& pt1, const Point3f& pt2, double thickness = 0.03, const Color &color = Color::white()) + + :param pt1: Start point of the arrow. + :param pt2: End point of the arrow. + :param thickness: Thickness of the arrow. Thickness of arrow head is also adjusted accordingly. + :param color: :ocv:class:`Color` of the arrow. + +Arrow head is located at the end point of the arrow. + +viz::WCircle +----------------- +.. ocv:class:: WCircle + +This 3D Widget defines a circle. :: + + class CV_EXPORTS WCircle : public Widget3D + { + public: + //! creates default planar circle centred at origin with plane normal along z-axis + WCircle(double radius, double thickness = 0.01, const Color &color = Color::white()); + + //! creates repositioned circle + WCircle(double radius, const Point3d& center, const Vec3d& normal, double thickness = 0.01, const Color &color = Color::white()); + }; + +viz::WCircle::WCircle +------------------------------- +Constructs default planar circle centred at origin with plane normal along z-axis + +.. ocv:function:: WCircle(double radius, double thickness = 0.01, const Color &color = Color::white()) + + :param radius: Radius of the circle. + :param thickness: Thickness of the circle. + :param color: :ocv:class:`Color` of the circle. + +viz::WCircle::WCircle +------------------------------- +Constructs repositioned planar circle. + +.. ocv:function:: WCircle(double radius, const Point3d& center, const Vec3d& normal, double thickness = 0.01, const Color &color = Color::white()) + + :param radius: Radius of the circle. + :param center: Center of the circle. + :param normal: Normal of the plane in which the circle lies. + :param thickness: Thickness of the circle. + :param color: :ocv:class:`Color` of the circle. + +viz::WCone +------------------------------- +.. ocv:class:: WCone + +This 3D Widget defines a cone. :: + + class CV_EXPORTS WCone : public Widget3D + { + public: + //! create default cone, oriented along x-axis with center of its base located at origin + WCone(double lenght, double radius, int resolution = 6.0, const Color &color = Color::white()); + + //! creates repositioned cone + WCone(double radius, const Point3d& center, const Point3d& tip, int resolution = 6.0, const Color &color = Color::white()); + }; + +viz::WCone::WCone +------------------------------- +Constructs default cone oriented along x-axis with center of its base located at origin + +.. ocv:function:: WCone(double length, double radius, int resolution = 6.0, const Color &color = Color::white()) + + :param length: Length of the cone. + :param radius: Radius of the cone. + :param resolution: Resolution of the cone. + :param color: :ocv:class:`Color` of the cone. + +viz::WCone::WCone +------------------------------- +Constructs repositioned planar cone. + +.. ocv:function:: WCone(double radius, const Point3d& center, const Point3d& tip, int resolution = 6.0, const Color &color = Color::white()) + + :param radius: Radius of the cone. + :param center: Center of the cone base. + :param tip: Tip of the cone. + :param resolution: Resolution of the cone. + :param color: :ocv:class:`Color` of the cone. + +viz::WCylinder +-------------- +.. ocv:class:: WCylinder + +This 3D Widget defines a cylinder. :: + + class CV_EXPORTS WCylinder : public Widget3D + { + public: + WCylinder(const Point3d& axis_point1, const Point3d& axis_point2, double radius, int numsides = 30, const Color &color = Color::white()); + }; + +viz::WCylinder::WCylinder +----------------------------------- +Constructs a WCylinder. + +.. ocv:function:: WCylinder(const Point3f& pt_on_axis, const Point3f& axis_direction, double radius, int numsides = 30, const Color &color = Color::white()) + + :param axis_point1: A point1 on the axis of the cylinder. + :param axis_point2: A point2 on the axis of the cylinder. + :param radius: Radius of the cylinder. + :param numsides: Resolution of the cylinder. + :param color: :ocv:class:`Color` of the cylinder. + +viz::WCube +---------- +.. ocv:class:: WCube + +This 3D Widget defines a cube. :: + + class CV_EXPORTS WCube : public Widget3D + { + public: + WCube(const Point3f& pt_min, const Point3f& pt_max, bool wire_frame = true, const Color &color = Color::white()); + }; + +viz::WCube::WCube +--------------------------- +Constructs a WCube. + +.. ocv:function:: WCube(const Point3f& pt_min, const Point3f& pt_max, bool wire_frame = true, const Color &color = Color::white()) + + :param pt_min: Specifies minimum point of the bounding box. + :param pt_max: Specifies maximum point of the bounding box. + :param wire_frame: If true, cube is represented as wireframe. + :param color: :ocv:class:`Color` of the cube. + +.. image:: images/cube_widget.png + :alt: Cube Widget + :align: center + +viz::WCoordinateSystem +---------------------- +.. ocv:class:: WCoordinateSystem + +This 3D Widget represents a coordinate system. :: + + class CV_EXPORTS WCoordinateSystem : public Widget3D + { + public: + WCoordinateSystem(double scale = 1.0); + }; + +viz::WCoordinateSystem::WCoordinateSystem +--------------------------------------------------- +Constructs a WCoordinateSystem. + +.. ocv:function:: WCoordinateSystem(double scale = 1.0) + + :param scale: Determines the size of the axes. + +viz::WPolyLine +-------------- +.. ocv:class:: WPolyLine + +This 3D Widget defines a poly line. :: + + class CV_EXPORTS WPolyLine : public Widget3D + { + public: + WPolyLine(InputArray points, const Color &color = Color::white()); + }; + +viz::WPolyLine::WPolyLine +----------------------------------- +Constructs a WPolyLine. + +.. ocv:function:: WPolyLine(InputArray points, const Color &color = Color::white()) + + :param points: Point set. + :param color: :ocv:class:`Color` of the poly line. + +viz::WGrid +---------- +.. ocv:class:: WGrid + +This 3D Widget defines a grid. :: + + class CV_EXPORTS WGrid : public Widget3D + { + public: + //! Creates grid at the origin and normal oriented along z-axis + WGrid(const Vec2i &cells = Vec2i::all(10), const Vec2d &cells_spacing = Vec2d::all(1.0), const Color &color = Color::white()); + + //! Creates repositioned grid + WGrid(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis, + const Vec2i &cells = Vec2i::all(10), const Vec2d &cells_spacing = Vec2d::all(1.0), const Color &color = Color::white()); + }; + +viz::WGrid::WGrid +--------------------------- +Constructs a WGrid. + +.. ocv:function:: WGrid(const Vec2i &cells = Vec2i::all(10), const Vec2d &cells_spacing = Vec2d::all(1.0), const Color &color = Color::white()) + + :param cells: Number of cell columns and rows, respectively. + :param cells_spacing: Size of each cell, respectively. + :param color: :ocv:class:`Color` of the grid. + +.. ocv:function: WGrid(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis, Vec2i &cells, const Vec2d &cells_spacing, const Color &color; + + :param center: Center of the grid + :param normal: Grid normal orientation + :param new_yaxis: Up-vector. New orientation of grid y-axis. + :param cells: Number of cell columns and rows, respectively. + :param cells_spacing: Size of each cell, respectively. + :param color: :ocv:class:`Color` of the grid.. + +viz::WText3D +------------ +.. ocv:class:: WText3D + +This 3D Widget represents 3D text. The text always faces the camera. :: + + class CV_EXPORTS WText3D : public Widget3D + { + public: + WText3D(const String &text, const Point3f &position, double text_scale = 1.0, bool face_camera = true, const Color &color = Color::white()); + + void setText(const String &text); + String getText() const; + }; + +viz::WText3D::WText3D +------------------------------- +Constructs a WText3D. + +.. ocv:function:: WText3D(const String &text, const Point3f &position, double text_scale = 1.0, bool face_camera = true, const Color &color = Color::white()) + + :param text: Text content of the widget. + :param position: Position of the text. + :param text_scale: Size of the text. + :param face_camera: If true, text always faces the camera. + :param color: :ocv:class:`Color` of the text. + +viz::WText3D::setText +--------------------- +Sets the text content of the widget. + +.. ocv:function:: void setText(const String &text) + + :param text: Text content of the widget. + +viz::WText3D::getText +--------------------- +Returns the current text content of the widget. + +.. ocv:function:: String getText() const + +viz::WText +---------- +.. ocv:class:: WText + +This 2D Widget represents text overlay. :: + + class CV_EXPORTS WText : public Widget2D + { + public: + WText(const String &text, const Point2i &pos, int font_size = 10, const Color &color = Color::white()); + + void setText(const String &text); + String getText() const; + }; + +viz::WText::WText +----------------- +Constructs a WText. + +.. ocv:function:: WText(const String &text, const Point2i &pos, int font_size = 10, const Color &color = Color::white()) + + :param text: Text content of the widget. + :param pos: Position of the text. + :param font_size: Font size. + :param color: :ocv:class:`Color` of the text. + +viz::WText::setText +------------------- +Sets the text content of the widget. + +.. ocv:function:: void setText(const String &text) + + :param text: Text content of the widget. + +viz::WText::getText +------------------- +Returns the current text content of the widget. + +.. ocv:function:: String getText() const + +viz::WImageOverlay +------------------ +.. ocv:class:: WImageOverlay + +This 2D Widget represents an image overlay. :: + + class CV_EXPORTS WImageOverlay : public Widget2D + { + public: + WImageOverlay(InputArray image, const Rect &rect); + + void setImage(InputArray image); + }; + +viz::WImageOverlay::WImageOverlay +--------------------------------- +Constructs an WImageOverlay. + +.. ocv:function:: WImageOverlay(InputArray image, const Rect &rect) + + :param image: BGR or Gray-Scale image. + :param rect: Image is scaled and positioned based on rect. + +viz::WImageOverlay::setImage +---------------------------- +Sets the image content of the widget. + +.. ocv:function:: void setImage(InputArray image) + + :param image: BGR or Gray-Scale image. + +viz::WImage3D +------------- +.. ocv:class:: WImage3D + +This 3D Widget represents an image in 3D space. :: + + class CV_EXPORTS WImage3D : public Widget3D + { + public: + //! Creates 3D image at the origin + WImage3D(InputArray image, const Size2d &size); + //! Creates 3D image at a given position, pointing in the direction of the normal, and having the up_vector orientation + WImage3D(InputArray image, const Size2d &size, const Vec3d &position, const Vec3d &normal, const Vec3d &up_vector); + + void setImage(InputArray image); + }; + +viz::WImage3D::WImage3D +----------------------- +Constructs an WImage3D. + +.. ocv:function:: WImage3D(InputArray image, const Size2d &size) + + :param image: BGR or Gray-Scale image. + :param size: Size of the image. + +.. ocv:function:: WImage3D(InputArray image, const Size2d &size, const Vec3d &position, const Vec3d &normal, const Vec3d &up_vector) + + :param position: Position of the image. + :param normal: Normal of the plane that represents the image. + :param up_vector: Determines orientation of the image. + :param image: BGR or Gray-Scale image. + :param size: Size of the image. + +viz::WImage3D::setImage +----------------------- +Sets the image content of the widget. + +.. ocv:function:: void setImage(InputArray image) + + :param image: BGR or Gray-Scale image. + +viz::WCameraPosition +-------------------- +.. ocv:class:: WCameraPosition + +This 3D Widget represents camera position in a scene by its axes or viewing frustum. :: + + class CV_EXPORTS WCameraPosition : public Widget3D + { + public: + //! Creates camera coordinate frame (axes) at the origin + WCameraPosition(double scale = 1.0); + //! Creates frustum based on the intrinsic marix K at the origin + WCameraPosition(const Matx33d &K, double scale = 1.0, const Color &color = Color::white()); + //! Creates frustum based on the field of view at the origin + WCameraPosition(const Vec2d &fov, double scale = 1.0, const Color &color = Color::white()); + //! Creates frustum and display given image at the far plane + WCameraPosition(const Matx33d &K, InputArray image, double scale = 1.0, const Color &color = Color::white()); + //! Creates frustum and display given image at the far plane + WCameraPosition(const Vec2d &fov, InputArray image, double scale = 1.0, const Color &color = Color::white()); + }; + +viz::WCameraPosition::WCameraPosition +------------------------------------- +Constructs a WCameraPosition. + +- **Display camera coordinate frame.** + + .. ocv:function:: WCameraPosition(double scale = 1.0) + + Creates camera coordinate frame at the origin. + + .. image:: images/cpw1.png + :alt: Camera coordinate frame + :align: center + +- **Display the viewing frustum.** + + .. ocv:function:: WCameraPosition(const Matx33d &K, double scale = 1.0, const Color &color = Color::white()) + + :param K: Intrinsic matrix of the camera. + :param scale: Scale of the frustum. + :param color: :ocv:class:`Color` of the frustum. + + Creates viewing frustum of the camera based on its intrinsic matrix K. + + .. ocv:function:: WCameraPosition(const Vec2d &fov, double scale = 1.0, const Color &color = Color::white()) + + :param fov: Field of view of the camera (horizontal, vertical). + :param scale: Scale of the frustum. + :param color: :ocv:class:`Color` of the frustum. + + Creates viewing frustum of the camera based on its field of view fov. + + .. image:: images/cpw2.png + :alt: Camera viewing frustum + :align: center + +- **Display image on the far plane of the viewing frustum.** + + .. ocv:function:: WCameraPosition(const Matx33d &K, InputArray image, double scale = 1.0, const Color &color = Color::white()) + + :param K: Intrinsic matrix of the camera. + :param img: BGR or Gray-Scale image that is going to be displayed on the far plane of the frustum. + :param scale: Scale of the frustum and image. + :param color: :ocv:class:`Color` of the frustum. + + Creates viewing frustum of the camera based on its intrinsic matrix K, and displays image on the far end plane. + + .. ocv:function:: WCameraPosition(const Vec2d &fov, InputArray image, double scale = 1.0, const Color &color = Color::white()) + + :param fov: Field of view of the camera (horizontal, vertical). + :param img: BGR or Gray-Scale image that is going to be displayed on the far plane of the frustum. + :param scale: Scale of the frustum and image. + :param color: :ocv:class:`Color` of the frustum. + + Creates viewing frustum of the camera based on its intrinsic matrix K, and displays image on the far end plane. + + .. image:: images/cpw3.png + :alt: Camera viewing frustum with image + :align: center + +viz::WTrajectory +---------------- +.. ocv:class:: WTrajectory + +This 3D Widget represents a trajectory. :: + + class CV_EXPORTS WTrajectory : public Widget3D + { + public: + enum {FRAMES = 1, PATH = 2, BOTH = FRAMES + PATH}; + + //! Displays trajectory of the given path either by coordinate frames or polyline + WTrajectory(InputArray path, int display_mode = WTrajectory::PATH, double scale = 1.0, const Color &color = Color::white(),; + }; + +viz::WTrajectory::WTrajectory +----------------------------- +Constructs a WTrajectory. + +.. ocv:function:: WTrajectory(InputArray path, int display_mode = WTrajectory::PATH, double scale = 1.0, const Color &color = Color::white()) + + :param path: List of poses on a trajectory. Takes std::vector> with T == [float | double] + :param display_mode: Display mode. This can be PATH, FRAMES, and BOTH. + :param scale: Scale of the frames. Polyline is not affected. + :param color: :ocv:class:`Color` of the polyline that represents path. Frames are not affected. + + Displays trajectory of the given path as follows: + + * PATH : Displays a poly line that represents the path. + * FRAMES : Displays coordinate frames at each pose. + * PATH & FRAMES : Displays both poly line and coordinate frames. + +viz::WTrajectoryFrustums +------------------------ +.. ocv:class:: WTrajectoryFrustums + +This 3D Widget represents a trajectory. :: + + class CV_EXPORTS WTrajectoryFrustums : public Widget3D + { + public: + //! Displays trajectory of the given path by frustums + WTrajectoryFrustums(InputArray path, const Matx33d &K, double scale = 1.0, const Color &color = Color::white()); + //! Displays trajectory of the given path by frustums + WTrajectoryFrustums(InputArray path, const Vec2d &fov, double scale = 1.0, const Color &color = Color::white()); + }; + +viz::WTrajectoryFrustums::WTrajectoryFrustums +--------------------------------------------- +Constructs a WTrajectoryFrustums. + +.. ocv:function:: WTrajectoryFrustums(const std::vector &path, const Matx33d &K, double scale = 1.0, const Color &color = Color::white()) + + :param path: List of poses on a trajectory. Takes std::vector> with T == [float | double] + :param K: Intrinsic matrix of the camera. + :param scale: Scale of the frustums. + :param color: :ocv:class:`Color` of the frustums. + + Displays frustums at each pose of the trajectory. + +.. ocv:function:: WTrajectoryFrustums(const std::vector &path, const Vec2d &fov, double scale = 1.0, const Color &color = Color::white()) + + :param path: List of poses on a trajectory. Takes std::vector> with T == [float | double] + :param fov: Field of view of the camera (horizontal, vertical). + :param scale: Scale of the frustums. + :param color: :ocv:class:`Color` of the frustums. + + Displays frustums at each pose of the trajectory. + +viz::WTrajectorySpheres +----------------------- +.. ocv:class:: WTrajectorySpheres + +This 3D Widget represents a trajectory using spheres and lines, where spheres represent the positions of the camera, and lines +represent the direction from previous position to the current. :: + + class CV_EXPORTS WTrajectorySpheres : public Widget3D + { + public: + WTrajectorySpheres(InputArray path, double line_length = 0.05, double radius = 0.007, + const Color &from = Color::red(), const Color &to = Color::white()); + }; + +viz::WTrajectorySpheres::WTrajectorySpheres +------------------------------------------- +Constructs a WTrajectorySpheres. + +.. ocv:function:: WTrajectorySpheres(InputArray path, double line_length = 0.05, double radius = 0.007, const Color &from = Color::red(), const Color &to = Color::white()) + + :param path: List of poses on a trajectory. Takes std::vector> with T == [float | double] + :param line_length: Max length of the lines which point to previous position + :param sphere_radius: Radius of the spheres. + :param from: :ocv:class:`Color` for first sphere. + :param to: :ocv:class:`Color` for last sphere. Intermediate spheres will have interpolated color. + +viz::WCloud +----------- +.. ocv:class:: WCloud + +This 3D Widget defines a point cloud. :: + + class CV_EXPORTS WCloud : public Widget3D + { + public: + //! Each point in cloud is mapped to a color in colors + WCloud(InputArray cloud, InputArray colors); + //! All points in cloud have the same color + WCloud(InputArray cloud, const Color &color = Color::white()); + }; + +viz::WCloud::WCloud +------------------- +Constructs a WCloud. + +.. ocv:function:: WCloud(InputArray cloud, InputArray colors) + + :param cloud: Set of points which can be of type: ``CV_32FC3``, ``CV_32FC4``, ``CV_64FC3``, ``CV_64FC4``. + :param colors: Set of colors. It has to be of the same size with cloud. + + Points in the cloud belong to mask when they are set to (NaN, NaN, NaN). + +.. ocv:function:: WCloud(InputArray cloud, const Color &color = Color::white()) + + :param cloud: Set of points which can be of type: ``CV_32FC3``, ``CV_32FC4``, ``CV_64FC3``, ``CV_64FC4``. + :param color: A single :ocv:class:`Color` for the whole cloud. + + Points in the cloud belong to mask when they are set to (NaN, NaN, NaN). + +.. note:: In case there are four channels in the cloud, fourth channel is ignored. + +viz::WCloudCollection +--------------------- +.. ocv:class:: WCloudCollection + +This 3D Widget defines a collection of clouds. :: + + class CV_EXPORTS WCloudCollection : public Widget3D + { + public: + WCloudCollection(); + + //! Each point in cloud is mapped to a color in colors + void addCloud(InputArray cloud, InputArray colors, const Affine3d &pose = Affine3d::Identity()); + //! All points in cloud have the same color + void addCloud(InputArray cloud, const Color &color = Color::white(), Affine3d &pose = Affine3d::Identity()); + }; + +viz::WCloudCollection::WCloudCollection +--------------------------------------- +Constructs a WCloudCollection. + +.. ocv:function:: WCloudCollection() + +viz::WCloudCollection::addCloud +------------------------------- +Adds a cloud to the collection. + +.. ocv:function:: void addCloud(InputArray cloud, InputArray colors, const Affine3d &pose = Affine3d::Identity()) + + :param cloud: Point set which can be of type: ``CV_32FC3``, ``CV_32FC4``, ``CV_64FC3``, ``CV_64FC4``. + :param colors: Set of colors. It has to be of the same size with cloud. + :param pose: Pose of the cloud. + + Points in the cloud belong to mask when they are set to (NaN, NaN, NaN). + +.. ocv:function:: void addCloud(InputArray cloud, const Color &color = Color::white(), const Affine3d &pose = Affine3d::Identity()) + + :param cloud: Point set which can be of type: ``CV_32FC3``, ``CV_32FC4``, ``CV_64FC3``, ``CV_64FC4``. + :param colors: A single :ocv:class:`Color` for the whole cloud. + :param pose: Pose of the cloud. + + Points in the cloud belong to mask when they are set to (NaN, NaN, NaN). + +.. note:: In case there are four channels in the cloud, fourth channel is ignored. + +viz::WCloudNormals +------------------ +.. ocv:class:: WCloudNormals + +This 3D Widget represents normals of a point cloud. :: + + class CV_EXPORTS WCloudNormals : public Widget3D + { + public: + WCloudNormals(InputArray cloud, InputArray normals, int level = 100, double scale = 0.02f, const Color &color = Color::white()); + }; + +viz::WCloudNormals::WCloudNormals +--------------------------------- +Constructs a WCloudNormals. + +.. ocv:function:: WCloudNormals(InputArray cloud, InputArray normals, int level = 100, double scale = 0.02f, const Color &color = Color::white()) + + :param cloud: Point set which can be of type: ``CV_32FC3``, ``CV_32FC4``, ``CV_64FC3``, ``CV_64FC4``. + :param normals: A set of normals that has to be of same type with cloud. + :param level: Display only every ``level`` th normal. + :param scale: Scale of the arrows that represent normals. + :param color: :ocv:class:`Color` of the arrows that represent normals. + +.. note:: In case there are four channels in the cloud, fourth channel is ignored. + +viz::WMesh +---------- +.. ocv:class:: WMesh + +This 3D Widget defines a mesh. :: + + class CV_EXPORTS WMesh : public Widget3D + { + public: + WMesh(const Mesh &mesh); + WMesh(InputArray cloud, InputArray polygons, InputArray colors = noArray(), InputArray normals = noArray()); + }; + +viz::WMesh::WMesh +----------------- +Constructs a WMesh. + +.. ocv:function:: WMesh(const Mesh &mesh) + + :param mesh: :ocv:class:`Mesh` object that will be displayed. + +.. ocv:function:: WMesh(InputArray cloud, InputArray polygons, InputArray colors = noArray(), InputArray normals = noArray()) + + :param cloud: Points of the mesh object. + :param polygons: Points of the mesh object. + :param colors: Point colors. + :param normals: Point normals. diff --git a/modules/viz/include/opencv2/viz/types.hpp b/modules/viz/include/opencv2/viz/types.hpp new file mode 100644 index 000000000..acbece2ed --- /dev/null +++ b/modules/viz/include/opencv2/viz/types.hpp @@ -0,0 +1,236 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_TYPES_HPP__ +#define __OPENCV_VIZ_TYPES_HPP__ + +#include +#include +#include + +namespace cv +{ + namespace viz + { + class Color : public Scalar + { + public: + Color(); + Color(double gray); + Color(double blue, double green, double red); + + Color(const Scalar& color); + + static Color black(); + static Color blue(); + static Color green(); + static Color cyan(); + + static Color red(); + static Color magenta(); + static Color yellow(); + static Color white(); + + static Color gray(); + + static Color mlab(); + + static Color navy(); + static Color olive(); + static Color maroon(); + static Color teal(); + static Color rose(); + static Color azure(); + static Color lime(); + static Color gold(); + static Color brown(); + static Color orange(); + static Color chartreuse(); + static Color orange_red(); + static Color purple(); + static Color indigo(); + + static Color pink(); + static Color cherry(); + static Color bluberry(); + static Color raspberry(); + static Color silver(); + static Color violet(); + static Color apricot(); + static Color turquoise(); + static Color celestial_blue(); + static Color amethyst(); + + static Color not_set(); + }; + + class CV_EXPORTS Mesh + { + public: + Mat cloud, colors, normals; + + //! Raw integer list of the form: (n,id1,id2,...,idn, n,id1,id2,...,idn, ...) + //! where n is the number of points in the poligon, and id is a zero-offset index into an associated cloud. + Mat polygons; + + Mat texture, tcoords; + + //! Loads mesh from a given ply file (no texture load support for now) + static Mesh load(const String& file); + }; + + class CV_EXPORTS Camera + { + public: + Camera(double fx, double fy, double cx, double cy, const Size &window_size); + explicit Camera(const Vec2d &fov, const Size &window_size); + explicit Camera(const Matx33d &K, const Size &window_size); + explicit Camera(const Matx44d &proj, const Size &window_size); + + const Vec2d & getClip() const { return clip_; } + void setClip(const Vec2d &clip) { clip_ = clip; } + + const Size & getWindowSize() const { return window_size_; } + void setWindowSize(const Size &window_size); + + const Vec2d& getFov() const { return fov_; } + void setFov(const Vec2d& fov) { fov_ = fov; } + + const Vec2d& getPrincipalPoint() const { return principal_point_; } + const Vec2d& getFocalLength() const { return focal_; } + + void computeProjectionMatrix(Matx44d &proj) const; + + static Camera KinectCamera(const Size &window_size); + + private: + void init(double fx, double fy, double cx, double cy, const Size &window_size); + + Vec2d clip_; + Vec2d fov_; + Size window_size_; + Vec2d principal_point_; + Vec2d focal_; + }; + + class CV_EXPORTS KeyboardEvent + { + public: + enum { NONE = 0, ALT = 1, CTRL = 2, SHIFT = 4 }; + enum Action { KEY_UP = 0, KEY_DOWN = 1 }; + + KeyboardEvent(Action action, const String& symbol, unsigned char code, int modifiers); + + Action action; + String symbol; + unsigned char code; + int modifiers; + }; + + class CV_EXPORTS MouseEvent + { + public: + enum Type { MouseMove = 1, MouseButtonPress, MouseButtonRelease, MouseScrollDown, MouseScrollUp, MouseDblClick } ; + enum MouseButton { NoButton = 0, LeftButton, MiddleButton, RightButton, VScroll } ; + + MouseEvent(const Type& type, const MouseButton& button, const Point& pointer, int modifiers); + + Type type; + MouseButton button; + Point pointer; + int modifiers; + }; + } /* namespace viz */ +} /* namespace cv */ + +////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::viz::Color + +inline cv::viz::Color::Color() : Scalar(0, 0, 0) {} +inline cv::viz::Color::Color(double _gray) : Scalar(_gray, _gray, _gray) {} +inline cv::viz::Color::Color(double _blue, double _green, double _red) : Scalar(_blue, _green, _red) {} +inline cv::viz::Color::Color(const Scalar& color) : Scalar(color) {} + +inline cv::viz::Color cv::viz::Color::black() { return Color( 0, 0, 0); } +inline cv::viz::Color cv::viz::Color::green() { return Color( 0, 255, 0); } +inline cv::viz::Color cv::viz::Color::blue() { return Color(255, 0, 0); } +inline cv::viz::Color cv::viz::Color::cyan() { return Color(255, 255, 0); } +inline cv::viz::Color cv::viz::Color::red() { return Color( 0, 0, 255); } +inline cv::viz::Color cv::viz::Color::yellow() { return Color( 0, 255, 255); } +inline cv::viz::Color cv::viz::Color::magenta() { return Color(255, 0, 255); } +inline cv::viz::Color cv::viz::Color::white() { return Color(255, 255, 255); } +inline cv::viz::Color cv::viz::Color::gray() { return Color(128, 128, 128); } + +inline cv::viz::Color cv::viz::Color::mlab() { return Color(255, 128, 128); } + +inline cv::viz::Color cv::viz::Color::navy() { return Color(0, 0, 128); } +inline cv::viz::Color cv::viz::Color::olive() { return Color(0, 128, 128); } +inline cv::viz::Color cv::viz::Color::maroon() { return Color(0, 0, 128); } +inline cv::viz::Color cv::viz::Color::teal() { return Color(128, 128, 0); } +inline cv::viz::Color cv::viz::Color::rose() { return Color(128, 0, 255); } +inline cv::viz::Color cv::viz::Color::azure() { return Color(255, 128, 0); } +inline cv::viz::Color cv::viz::Color::lime() { return Color(0, 255, 191); } +inline cv::viz::Color cv::viz::Color::gold() { return Color(0, 215, 255); } +inline cv::viz::Color cv::viz::Color::brown() { return Color(0, 75, 150); } +inline cv::viz::Color cv::viz::Color::orange() { return Color(0, 165, 255); } +inline cv::viz::Color cv::viz::Color::chartreuse() { return Color(0, 255, 128); } +inline cv::viz::Color cv::viz::Color::orange_red() { return Color(0, 69, 255); } +inline cv::viz::Color cv::viz::Color::purple() { return Color(128, 0, 128); } +inline cv::viz::Color cv::viz::Color::indigo() { return Color(130, 0, 75); } + +inline cv::viz::Color cv::viz::Color::pink() { return Color(203, 192, 255); } +inline cv::viz::Color cv::viz::Color::cherry() { return Color( 99, 29, 222); } +inline cv::viz::Color cv::viz::Color::bluberry() { return Color(247, 134, 79); } +inline cv::viz::Color cv::viz::Color::raspberry() { return Color( 92, 11, 227); } +inline cv::viz::Color cv::viz::Color::silver() { return Color(192, 192, 192); } +inline cv::viz::Color cv::viz::Color::violet() { return Color(226, 43, 138); } +inline cv::viz::Color cv::viz::Color::apricot() { return Color(177, 206, 251); } +inline cv::viz::Color cv::viz::Color::turquoise() { return Color(208, 224, 64); } +inline cv::viz::Color cv::viz::Color::celestial_blue() { return Color(208, 151, 73); } +inline cv::viz::Color cv::viz::Color::amethyst() { return Color(204, 102, 153); } + +inline cv::viz::Color cv::viz::Color::not_set() { return Color(-1, -1, -1); } + +#endif diff --git a/modules/viz/include/opencv2/viz/viz3d.hpp b/modules/viz/include/opencv2/viz/viz3d.hpp new file mode 100644 index 000000000..1a137bcfb --- /dev/null +++ b/modules/viz/include/opencv2/viz/viz3d.hpp @@ -0,0 +1,131 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_VIZ3D_HPP__ +#define __OPENCV_VIZ_VIZ3D_HPP__ + +#if !defined YES_I_AGREE_THAT_VIZ_API_IS_NOT_STABLE_NOW_AND_BINARY_COMPARTIBILITY_WONT_BE_SUPPORTED && !defined CVAPI_EXPORTS + //#error "Viz is in beta state now. Please define macro above to use it" +#endif + +#include +#include +#include + +namespace cv +{ + namespace viz + { + class CV_EXPORTS Viz3d + { + public: + typedef cv::viz::Color Color; + typedef void (*KeyboardCallback)(const KeyboardEvent&, void*); + typedef void (*MouseCallback)(const MouseEvent&, void*); + + Viz3d(const String& window_name = String()); + Viz3d(const Viz3d&); + Viz3d& operator=(const Viz3d&); + ~Viz3d(); + + void showWidget(const String &id, const Widget &widget, const Affine3d &pose = Affine3d::Identity()); + void removeWidget(const String &id); + Widget getWidget(const String &id) const; + void removeAllWidgets(); + + void showImage(InputArray image, const Size& window_size = Size(-1, -1)); + + void setWidgetPose(const String &id, const Affine3d &pose); + void updateWidgetPose(const String &id, const Affine3d &pose); + Affine3d getWidgetPose(const String &id) const; + + void setCamera(const Camera &camera); + Camera getCamera() const; + Affine3d getViewerPose(); + void setViewerPose(const Affine3d &pose); + + void resetCameraViewpoint(const String &id); + void resetCamera(); + + void convertToWindowCoordinates(const Point3d &pt, Point3d &window_coord); + void converTo3DRay(const Point3d &window_coord, Point3d &origin, Vec3d &direction); + + Size getWindowSize() const; + void setWindowSize(const Size &window_size); + String getWindowName() const; + void saveScreenshot(const String &file); + void setWindowPosition(const Point& window_position); + void setFullScreen(bool mode = true); + void setBackgroundColor(const Color& color = Color::black(), const Color& color2 = Color::not_set()); + void setBackgroundTexture(InputArray image = noArray()); + void setBackgroundMeshLab(); + + void spin(); + void spinOnce(int time = 1, bool force_redraw = false); + bool wasStopped() const; + void close(); + + void registerKeyboardCallback(KeyboardCallback callback, void* cookie = 0); + void registerMouseCallback(MouseCallback callback, void* cookie = 0); + + void setRenderingProperty(const String &id, int property, double value); + double getRenderingProperty(const String &id, int property); + + void setRepresentation(int representation); + private: + + struct VizImpl; + VizImpl* impl_; + + void create(const String &window_name); + void release(); + + friend class VizStorage; + }; + + } /* namespace viz */ +} /* namespace cv */ + +#endif diff --git a/modules/viz/include/opencv2/viz/vizcore.hpp b/modules/viz/include/opencv2/viz/vizcore.hpp new file mode 100644 index 000000000..bf44a2c03 --- /dev/null +++ b/modules/viz/include/opencv2/viz/vizcore.hpp @@ -0,0 +1,127 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_HPP__ +#define __OPENCV_VIZ_HPP__ + +#include +#include +#include + +namespace cv +{ + namespace viz + { + //! takes coordiante frame data and builds transfrom to global coordinate frame + CV_EXPORTS Affine3d makeTransformToGlobal(const Vec3d& axis_x, const Vec3d& axis_y, const Vec3d& axis_z, const Vec3d& origin = Vec3d::all(0)); + + //! constructs camera pose from position, focal_point and up_vector (see gluLookAt() for more infromation) + CV_EXPORTS Affine3d makeCameraPose(const Vec3d& position, const Vec3d& focal_point, const Vec3d& y_dir); + + //! retrieves a window by its name. If no window with such name, then it creates new. + CV_EXPORTS Viz3d getWindowByName(const String &window_name); + + //! Unregisters all Viz windows from internal database. After it 'getWindowByName()' will create new windows instead getting existing from the database. + CV_EXPORTS void unregisterAllWindows(); + + //! Displays image in specified window + CV_EXPORTS Viz3d imshow(const String& window_name, InputArray image, const Size& window_size = Size(-1, -1)); + + //! checks float value for Nan + inline bool isNan(float x) + { + unsigned int *u = reinterpret_cast(&x); + return ((u[0] & 0x7f800000) == 0x7f800000) && (u[0] & 0x007fffff); + } + + //! checks double value for Nan + inline bool isNan(double x) + { + unsigned int *u = reinterpret_cast(&x); + return (u[1] & 0x7ff00000) == 0x7ff00000 && (u[0] != 0 || (u[1] & 0x000fffff) != 0); + } + + //! checks vectors for Nans + template inline bool isNan(const Vec<_Tp, cn>& v) + { return isNan(v.val[0]) || isNan(v.val[1]) || isNan(v.val[2]); } + + //! checks point for Nans + template inline bool isNan(const Point3_<_Tp>& p) + { return isNan(p.x) || isNan(p.y) || isNan(p.z); } + + + /////////////////////////////////////////////////////////////////////////////////////////////// + /// Read/write clouds. Supported formats: ply, xyz, obj and stl (readonly) + + CV_EXPORTS void writeCloud(const String& file, InputArray cloud, InputArray colors = noArray(), InputArray normals = noArray(), bool binary = false); + CV_EXPORTS Mat readCloud (const String& file, OutputArray colors = noArray(), OutputArray normals = noArray()); + + /////////////////////////////////////////////////////////////////////////////////////////////// + /// Reads mesh. Only ply format is supported now and no texture load support + + CV_EXPORTS Mesh readMesh(const String& file); + + /////////////////////////////////////////////////////////////////////////////////////////////// + /// Read/write poses and trajectories + + CV_EXPORTS bool readPose(const String& file, Affine3d& pose, const String& tag = "pose"); + CV_EXPORTS void writePose(const String& file, const Affine3d& pose, const String& tag = "pose"); + + //! takes vector> with T = float/dobule and writes to a sequence of files with given filename format + CV_EXPORTS void writeTrajectory(InputArray traj, const String& files_format = "pose%05d.xml", int start = 0, const String& tag = "pose"); + + //! takes vector> with T = float/dobule and loads poses from sequence of files + CV_EXPORTS void readTrajectory(OutputArray traj, const String& files_format = "pose%05d.xml", int start = 0, int end = INT_MAX, const String& tag = "pose"); + + + /////////////////////////////////////////////////////////////////////////////////////////////// + /// Computing normals for mesh + + CV_EXPORTS void computeNormals(const Mesh& mesh, OutputArray normals); + + } /* namespace viz */ +} /* namespace cv */ + +#endif /* __OPENCV_VIZ_HPP__ */ diff --git a/modules/viz/include/opencv2/viz/widget_accessor.hpp b/modules/viz/include/opencv2/viz/widget_accessor.hpp new file mode 100644 index 000000000..734f6ce55 --- /dev/null +++ b/modules/viz/include/opencv2/viz/widget_accessor.hpp @@ -0,0 +1,69 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_WIDGET_ACCESSOR_HPP__ +#define __OPENCV_VIZ_WIDGET_ACCESSOR_HPP__ + +#include +#include +#include + +namespace cv +{ + namespace viz + { + class Widget; + + //The class is only that depends on VTK in its interface. + //It is indended for those users who want to develop own widgets system using VTK library API. + struct CV_EXPORTS WidgetAccessor + { + static vtkSmartPointer getProp(const Widget &widget); + static void setProp(Widget &widget, vtkSmartPointer prop); + }; + } +} + +#endif diff --git a/modules/viz/include/opencv2/viz/widgets.hpp b/modules/viz/include/opencv2/viz/widgets.hpp new file mode 100644 index 000000000..2c49b9d0e --- /dev/null +++ b/modules/viz/include/opencv2/viz/widgets.hpp @@ -0,0 +1,396 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_WIDGETS_HPP__ +#define __OPENCV_VIZ_WIDGETS_HPP__ + +#include + +namespace cv +{ + namespace viz + { + ///////////////////////////////////////////////////////////////////////////// + /// Widget rendering properties + enum RenderingProperties + { + POINT_SIZE, + OPACITY, + LINE_WIDTH, + FONT_SIZE, + REPRESENTATION, + IMMEDIATE_RENDERING, + SHADING + }; + + enum RepresentationValues + { + REPRESENTATION_POINTS, + REPRESENTATION_WIREFRAME, + REPRESENTATION_SURFACE + }; + + enum ShadingValues + { + SHADING_FLAT, + SHADING_GOURAUD, + SHADING_PHONG + }; + + ///////////////////////////////////////////////////////////////////////////// + /// The base class for all widgets + class CV_EXPORTS Widget + { + public: + Widget(); + Widget(const Widget& other); + Widget& operator=(const Widget& other); + ~Widget(); + + //! Create a widget directly from ply file + static Widget fromPlyFile(const String &file_name); + + //! Rendering properties of this particular widget + void setRenderingProperty(int property, double value); + double getRenderingProperty(int property) const; + + //! Casting between widgets + template _W cast(); + private: + class Impl; + Impl *impl_; + friend struct WidgetAccessor; + }; + + ///////////////////////////////////////////////////////////////////////////// + /// The base class for all 3D widgets + class CV_EXPORTS Widget3D : public Widget + { + public: + Widget3D() {} + + //! widget position manipulation, i.e. place where it is rendered + void setPose(const Affine3d &pose); + void updatePose(const Affine3d &pose); + Affine3d getPose() const; + + //! update internal widget data, i.e. points, normals, etc. + void applyTransform(const Affine3d &transform); + + void setColor(const Color &color); + + }; + + ///////////////////////////////////////////////////////////////////////////// + /// The base class for all 2D widgets + class CV_EXPORTS Widget2D : public Widget + { + public: + Widget2D() {} + + void setColor(const Color &color); + }; + + ///////////////////////////////////////////////////////////////////////////// + /// Simple widgets + + class CV_EXPORTS WLine : public Widget3D + { + public: + WLine(const Point3d &pt1, const Point3d &pt2, const Color &color = Color::white()); + }; + + class CV_EXPORTS WPlane : public Widget3D + { + public: + //! created default plane with center point at origin and normal oriented along z-axis + WPlane(const Size2d& size = Size2d(1.0, 1.0), const Color &color = Color::white()); + + //! repositioned plane + WPlane(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis, + const Size2d& size = Size2d(1.0, 1.0), const Color &color = Color::white()); + }; + + class CV_EXPORTS WSphere : public Widget3D + { + public: + WSphere(const cv::Point3d ¢er, double radius, int sphere_resolution = 10, const Color &color = Color::white()); + }; + + class CV_EXPORTS WArrow : public Widget3D + { + public: + WArrow(const Point3d& pt1, const Point3d& pt2, double thickness = 0.03, const Color &color = Color::white()); + }; + + class CV_EXPORTS WCircle : public Widget3D + { + public: + //! creates default planar circle centred at origin with plane normal along z-axis + WCircle(double radius, double thickness = 0.01, const Color &color = Color::white()); + + //! creates repositioned circle + WCircle(double radius, const Point3d& center, const Vec3d& normal, double thickness = 0.01, const Color &color = Color::white()); + }; + + class CV_EXPORTS WCone : public Widget3D + { + public: + //! create default cone, oriented along x-axis with center of its base located at origin + WCone(double length, double radius, int resolution = 6.0, const Color &color = Color::white()); + + //! creates repositioned cone + WCone(double radius, const Point3d& center, const Point3d& tip, int resolution = 6.0, const Color &color = Color::white()); + }; + + class CV_EXPORTS WCylinder : public Widget3D + { + public: + WCylinder(const Point3d& axis_point1, const Point3d& axis_point2, double radius, int numsides = 30, const Color &color = Color::white()); + }; + + class CV_EXPORTS WCube : public Widget3D + { + public: + WCube(const Point3d& min_point = Vec3d::all(-0.5), const Point3d& max_point = Vec3d::all(0.5), + bool wire_frame = true, const Color &color = Color::white()); + }; + + class CV_EXPORTS WPolyLine : public Widget3D + { + public: + WPolyLine(InputArray points, const Color &color = Color::white()); + }; + + ///////////////////////////////////////////////////////////////////////////// + /// Text and image widgets + + class CV_EXPORTS WText : public Widget2D + { + public: + WText(const String &text, const Point &pos, int font_size = 20, const Color &color = Color::white()); + + void setText(const String &text); + String getText() const; + }; + + class CV_EXPORTS WText3D : public Widget3D + { + public: + //! creates text label in 3D. If face_camera = false, text plane normal is oriented along z-axis. Use widget pose to orient it properly + WText3D(const String &text, const Point3d &position, double text_scale = 1., bool face_camera = true, const Color &color = Color::white()); + + void setText(const String &text); + String getText() const; + }; + + class CV_EXPORTS WImageOverlay : public Widget2D + { + public: + WImageOverlay(InputArray image, const Rect &rect); + void setImage(InputArray image); + }; + + class CV_EXPORTS WImage3D : public Widget3D + { + public: + //! Creates 3D image in a plane centered at the origin with normal orientaion along z-axis, + //! image x- and y-axes are oriented along x- and y-axes of 3d world + WImage3D(InputArray image, const Size2d &size); + + //! Creates 3D image at a given position, pointing in the direction of the normal, and having the up_vector orientation + WImage3D(InputArray image, const Size2d &size, const Vec3d ¢er, const Vec3d &normal, const Vec3d &up_vector); + + void setImage(InputArray image); + }; + + ///////////////////////////////////////////////////////////////////////////// + /// Compond widgets + + class CV_EXPORTS WCoordinateSystem : public Widget3D + { + public: + WCoordinateSystem(double scale = 1.0); + }; + + class CV_EXPORTS WGrid : public Widget3D + { + public: + //! Creates grid at the origin and normal oriented along z-axis + WGrid(const Vec2i &cells = Vec2i::all(10), const Vec2d &cells_spacing = Vec2d::all(1.0), const Color &color = Color::white()); + + //! Creates repositioned grid + WGrid(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis, + const Vec2i &cells = Vec2i::all(10), const Vec2d &cells_spacing = Vec2d::all(1.0), const Color &color = Color::white()); + }; + + class CV_EXPORTS WCameraPosition : public Widget3D + { + public: + //! Creates camera coordinate frame (axes) at the origin + WCameraPosition(double scale = 1.0); + //! Creates frustum based on the intrinsic marix K at the origin + WCameraPosition(const Matx33d &K, double scale = 1.0, const Color &color = Color::white()); + //! Creates frustum based on the field of view at the origin + WCameraPosition(const Vec2d &fov, double scale = 1.0, const Color &color = Color::white()); + //! Creates frustum and display given image at the far plane + WCameraPosition(const Matx33d &K, InputArray image, double scale = 1.0, const Color &color = Color::white()); + //! Creates frustum and display given image at the far plane + WCameraPosition(const Vec2d &fov, InputArray image, double scale = 1.0, const Color &color = Color::white()); + }; + + ///////////////////////////////////////////////////////////////////////////// + /// Trajectories + + class CV_EXPORTS WTrajectory : public Widget3D + { + public: + enum {FRAMES = 1, PATH = 2, BOTH = FRAMES + PATH }; + + //! Takes vector> and displays trajectory of the given path either by coordinate frames or polyline + WTrajectory(InputArray path, int display_mode = WTrajectory::PATH, double scale = 1.0, const Color &color = Color::white()); + }; + + class CV_EXPORTS WTrajectoryFrustums : public Widget3D + { + public: + //! Takes vector> and displays trajectory of the given path by frustums + WTrajectoryFrustums(InputArray path, const Matx33d &K, double scale = 1., const Color &color = Color::white()); + + //! Takes vector> and displays trajectory of the given path by frustums + WTrajectoryFrustums(InputArray path, const Vec2d &fov, double scale = 1., const Color &color = Color::white()); + }; + + class CV_EXPORTS WTrajectorySpheres: public Widget3D + { + public: + //! Takes vector> and displays trajectory of the given path + WTrajectorySpheres(InputArray path, double line_length = 0.05, double radius = 0.007, + const Color &from = Color::red(), const Color &to = Color::white()); + }; + + ///////////////////////////////////////////////////////////////////////////// + /// Clouds + + class CV_EXPORTS WCloud: public Widget3D + { + public: + //! Each point in cloud is mapped to a color in colors + WCloud(InputArray cloud, InputArray colors); + //! All points in cloud have the same color + WCloud(InputArray cloud, const Color &color = Color::white()); + }; + + class CV_EXPORTS WPaintedCloud: public Widget3D + { + public: + //! Paint cloud with default gradient between cloud bounds points + WPaintedCloud(InputArray cloud); + + //! Paint cloud with default gradient between given points + WPaintedCloud(InputArray cloud, const Point3d& p1, const Point3d& p2); + + //! Paint cloud with gradient specified by given colors between given points + WPaintedCloud(InputArray cloud, const Point3d& p1, const Point3d& p2, const Color& c1, const Color c2); + }; + + class CV_EXPORTS WCloudCollection : public Widget3D + { + public: + WCloudCollection(); + + //! Each point in cloud is mapped to a color in colors + void addCloud(InputArray cloud, InputArray colors, const Affine3d &pose = Affine3d::Identity()); + //! All points in cloud have the same color + void addCloud(InputArray cloud, const Color &color = Color::white(), const Affine3d &pose = Affine3d::Identity()); + }; + + class CV_EXPORTS WCloudNormals : public Widget3D + { + public: + WCloudNormals(InputArray cloud, InputArray normals, int level = 64, double scale = 0.1, const Color &color = Color::white()); + }; + + class CV_EXPORTS WMesh : public Widget3D + { + public: + WMesh(const Mesh &mesh); + WMesh(InputArray cloud, InputArray polygons, InputArray colors = noArray(), InputArray normals = noArray()); + }; + + ///////////////////////////////////////////////////////////////////////////// + /// Utility exports + + template<> CV_EXPORTS Widget2D Widget::cast(); + template<> CV_EXPORTS Widget3D Widget::cast(); + template<> CV_EXPORTS WLine Widget::cast(); + template<> CV_EXPORTS WPlane Widget::cast(); + template<> CV_EXPORTS WSphere Widget::cast(); + template<> CV_EXPORTS WCylinder Widget::cast(); + template<> CV_EXPORTS WArrow Widget::cast(); + template<> CV_EXPORTS WCircle Widget::cast(); + template<> CV_EXPORTS WCone Widget::cast(); + template<> CV_EXPORTS WCube Widget::cast(); + template<> CV_EXPORTS WCoordinateSystem Widget::cast(); + template<> CV_EXPORTS WPolyLine Widget::cast(); + template<> CV_EXPORTS WGrid Widget::cast(); + template<> CV_EXPORTS WText3D Widget::cast(); + template<> CV_EXPORTS WText Widget::cast(); + template<> CV_EXPORTS WImageOverlay Widget::cast(); + template<> CV_EXPORTS WImage3D Widget::cast(); + template<> CV_EXPORTS WCameraPosition Widget::cast(); + template<> CV_EXPORTS WTrajectory Widget::cast(); + template<> CV_EXPORTS WTrajectoryFrustums Widget::cast(); + template<> CV_EXPORTS WTrajectorySpheres Widget::cast(); + template<> CV_EXPORTS WCloud Widget::cast(); + template<> CV_EXPORTS WPaintedCloud Widget::cast(); + template<> CV_EXPORTS WCloudCollection Widget::cast(); + template<> CV_EXPORTS WCloudNormals Widget::cast(); + template<> CV_EXPORTS WMesh Widget::cast(); + + } /* namespace viz */ +} /* namespace cv */ + +#endif diff --git a/modules/viz/src/clouds.cpp b/modules/viz/src/clouds.cpp new file mode 100644 index 000000000..4b84e8e9e --- /dev/null +++ b/modules/viz/src/clouds.cpp @@ -0,0 +1,441 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Point Cloud Widget implementation + +cv::viz::WCloud::WCloud(InputArray cloud, InputArray colors) +{ + CV_Assert(!cloud.empty() && !colors.empty()); + + vtkSmartPointer cloud_source = vtkSmartPointer::New(); + cloud_source->SetColorCloud(cloud, colors); + cloud_source->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, cloud_source->GetOutput()); + mapper->SetScalarModeToUsePointData(); + mapper->ImmediateModeRenderingOff(); + mapper->SetScalarRange(0, 255); + mapper->ScalarVisibilityOn(); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->BackfaceCullingOn(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WCloud::WCloud(InputArray cloud, const Color &color) +{ + WCloud cloud_widget(cloud, Mat(cloud.size(), CV_8UC3, color)); + *this = cloud_widget; +} + + +template<> cv::viz::WCloud cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Painted Cloud Widget implementation + +cv::viz::WPaintedCloud::WPaintedCloud(InputArray cloud) +{ + vtkSmartPointer cloud_source = vtkSmartPointer::New(); + cloud_source->SetCloud(cloud); + cloud_source->Update(); + + Vec6d bounds(cloud_source->GetOutput()->GetPoints()->GetBounds()); + + vtkSmartPointer elevation = vtkSmartPointer::New(); + elevation->SetInputConnection(cloud_source->GetOutputPort()); + elevation->SetLowPoint(bounds[0], bounds[2], bounds[4]); + elevation->SetHighPoint(bounds[1], bounds[3], bounds[5]); + elevation->SetScalarRange(0.0, 1.0); + elevation->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, vtkPolyData::SafeDownCast(elevation->GetOutput())); + mapper->ImmediateModeRenderingOff(); + mapper->ScalarVisibilityOn(); + mapper->SetColorModeToMapScalars(); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->BackfaceCullingOn(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WPaintedCloud::WPaintedCloud(InputArray cloud, const Point3d& p1, const Point3d& p2) +{ + vtkSmartPointer cloud_source = vtkSmartPointer::New(); + cloud_source->SetCloud(cloud); + + vtkSmartPointer elevation = vtkSmartPointer::New(); + elevation->SetInputConnection(cloud_source->GetOutputPort()); + elevation->SetLowPoint(p1.x, p1.y, p1.z); + elevation->SetHighPoint(p2.x, p2.y, p2.z); + elevation->SetScalarRange(0.0, 1.0); + elevation->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, vtkPolyData::SafeDownCast(elevation->GetOutput())); + mapper->ImmediateModeRenderingOff(); + mapper->ScalarVisibilityOn(); + mapper->SetColorModeToMapScalars(); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->BackfaceCullingOn(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WPaintedCloud::WPaintedCloud(InputArray cloud, const Point3d& p1, const Point3d& p2, const Color& c1, const Color c2) +{ + vtkSmartPointer cloud_source = vtkSmartPointer::New(); + cloud_source->SetCloud(cloud); + + vtkSmartPointer elevation = vtkSmartPointer::New(); + elevation->SetInputConnection(cloud_source->GetOutputPort()); + elevation->SetLowPoint(p1.x, p1.y, p1.z); + elevation->SetHighPoint(p2.x, p2.y, p2.z); + elevation->SetScalarRange(0.0, 1.0); + elevation->Update(); + + Color vc1 = vtkcolor(c1), vc2 = vtkcolor(c2); + vtkSmartPointer color_transfer = vtkSmartPointer::New(); + color_transfer->SetColorSpaceToRGB(); + color_transfer->AddRGBPoint(0.0, vc1[0], vc1[1], vc1[2]); + color_transfer->AddRGBPoint(1.0, vc2[0], vc2[1], vc2[2]); + color_transfer->SetScaleToLinear(); + color_transfer->Build(); + + //if in future some need to replace color table with real scalars, then this can be done usine next calls: + //vtkDataArray *float_scalars = vtkPolyData::SafeDownCast(elevation->GetOutput())->GetPointData()->GetArray("Elevation"); + //vtkSmartPointer polydata = cloud_source->GetOutput(); + //polydata->GetPointData()->SetScalars(color_transfer->MapScalars(float_scalars, VTK_COLOR_MODE_DEFAULT, 0)); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, vtkPolyData::SafeDownCast(elevation->GetOutput())); + mapper->ImmediateModeRenderingOff(); + mapper->ScalarVisibilityOn(); + mapper->SetColorModeToMapScalars(); + mapper->SetLookupTable(color_transfer); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->BackfaceCullingOn(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WPaintedCloud cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Cloud Collection Widget implementation + +cv::viz::WCloudCollection::WCloudCollection() +{ + // Just create the actor + vtkSmartPointer actor = vtkSmartPointer::New(); + WidgetAccessor::setProp(*this, actor); +} + +void cv::viz::WCloudCollection::addCloud(InputArray cloud, InputArray colors, const Affine3d &pose) +{ + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetColorCloud(cloud, colors); + + vtkSmartPointer polydata = VtkUtils::TransformPolydata(source->GetOutputPort(), pose); + + vtkSmartPointer actor = vtkLODActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Incompatible widget type." && actor); + + vtkSmartPointer mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + if (!mapper) + { + // This is the first cloud + mapper = vtkSmartPointer::New(); + mapper->SetScalarRange(0, 255); + mapper->SetScalarModeToUsePointData(); + mapper->ScalarVisibilityOn(); + mapper->ImmediateModeRenderingOff(); + VtkUtils::SetInputData(mapper, polydata); + + actor->SetNumberOfCloudPoints(std::max(1, polydata->GetNumberOfPoints()/10)); + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->BackfaceCullingOn(); + actor->SetMapper(mapper); + return; + } + + vtkPolyData *currdata = vtkPolyData::SafeDownCast(mapper->GetInput()); + CV_Assert("Cloud Widget without data" && currdata); + + vtkSmartPointer append_filter = vtkSmartPointer::New(); + VtkUtils::AddInputData(append_filter, currdata); + VtkUtils::AddInputData(append_filter, polydata); + append_filter->Update(); + + VtkUtils::SetInputData(mapper, append_filter->GetOutput()); + + actor->SetNumberOfCloudPoints(std::max(1, actor->GetNumberOfCloudPoints() + polydata->GetNumberOfPoints()/10)); +} + +void cv::viz::WCloudCollection::addCloud(InputArray cloud, const Color &color, const Affine3d &pose) +{ + addCloud(cloud, Mat(cloud.size(), CV_8UC3, color), pose); +} + +template<> cv::viz::WCloudCollection cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Cloud Normals Widget implementation + +cv::viz::WCloudNormals::WCloudNormals(InputArray _cloud, InputArray _normals, int level, double scale, const Color &color) +{ + Mat cloud = _cloud.getMat(); + Mat normals = _normals.getMat(); + + CV_Assert(cloud.type() == CV_32FC3 || cloud.type() == CV_64FC3 || cloud.type() == CV_32FC4 || cloud.type() == CV_64FC4); + CV_Assert(cloud.size() == normals.size() && cloud.type() == normals.type()); + + int sqlevel = (int)std::sqrt((double)level); + int ystep = (cloud.cols > 1 && cloud.rows > 1) ? sqlevel : 1; + int xstep = (cloud.cols > 1 && cloud.rows > 1) ? sqlevel : level; + + vtkSmartPointer points = vtkSmartPointer::New(); + points->SetDataType(cloud.depth() == CV_32F ? VTK_FLOAT : VTK_DOUBLE); + + vtkSmartPointer lines = vtkSmartPointer::New(); + + int s_chs = cloud.channels(); + int n_chs = normals.channels(); + int total = 0; + + for(int y = 0; y < cloud.rows; y += ystep) + { + if (cloud.depth() == CV_32F) + { + const float *srow = cloud.ptr(y); + const float *send = srow + cloud.cols * s_chs; + const float *nrow = normals.ptr(y); + + for (; srow < send; srow += xstep * s_chs, nrow += xstep * n_chs) + if (!isNan(srow) && !isNan(nrow)) + { + Vec3f endp = Vec3f(srow) + Vec3f(nrow) * (float)scale; + + points->InsertNextPoint(srow); + points->InsertNextPoint(endp.val); + + lines->InsertNextCell(2); + lines->InsertCellPoint(total++); + lines->InsertCellPoint(total++); + } + } + else + { + const double *srow = cloud.ptr(y); + const double *send = srow + cloud.cols * s_chs; + const double *nrow = normals.ptr(y); + + for (; srow < send; srow += xstep * s_chs, nrow += xstep * n_chs) + if (!isNan(srow) && !isNan(nrow)) + { + Vec3d endp = Vec3d(srow) + Vec3d(nrow) * (double)scale; + + points->InsertNextPoint(srow); + points->InsertNextPoint(endp.val); + + lines->InsertNextCell(2); + lines->InsertCellPoint(total++); + lines->InsertCellPoint(total++); + } + } + } + + vtkSmartPointer polyData = vtkSmartPointer::New(); + polyData->SetPoints(points); + polyData->SetLines(lines); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetColorModeToMapScalars(); + mapper->SetScalarModeToUsePointData(); + VtkUtils::SetInputData(mapper, polyData); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WCloudNormals cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Mesh Widget implementation + +cv::viz::WMesh::WMesh(const Mesh &mesh) +{ + CV_Assert(mesh.cloud.rows == 1 && mesh.polygons.type() == CV_32SC1); + + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetColorCloudNormalsTCoords(mesh.cloud, mesh.colors, mesh.normals, mesh.tcoords); + source->Update(); + + Mat lookup_buffer(1, mesh.cloud.total(), CV_32SC1); + int *lookup = lookup_buffer.ptr(); + for(int y = 0, index = 0; y < mesh.cloud.rows; ++y) + { + int s_chs = mesh.cloud.channels(); + + if (mesh.cloud.depth() == CV_32F) + { + const float* srow = mesh.cloud.ptr(y); + const float* send = srow + mesh.cloud.cols * s_chs; + + for (; srow != send; srow += s_chs, ++lookup) + if (!isNan(srow[0]) && !isNan(srow[1]) && !isNan(srow[2])) + *lookup = index++; + } + + if (mesh.cloud.depth() == CV_64F) + { + const double* srow = mesh.cloud.ptr(y); + const double* send = srow + mesh.cloud.cols * s_chs; + + for (; srow != send; srow += s_chs, ++lookup) + if (!isNan(srow[0]) && !isNan(srow[1]) && !isNan(srow[2])) + *lookup = index++; + } + } + lookup = lookup_buffer.ptr(); + + vtkSmartPointer polydata = source->GetOutput(); + polydata->SetVerts(0); + + const int * polygons = mesh.polygons.ptr(); + vtkSmartPointer cell_array = vtkSmartPointer::New(); + + int idx = 0; + size_t polygons_size = mesh.polygons.total(); + for (size_t i = 0; i < polygons_size; ++idx) + { + int n_points = polygons[i++]; + + cell_array->InsertNextCell(n_points); + for (int j = 0; j < n_points; ++j, ++idx) + cell_array->InsertCellPoint(lookup[polygons[i++]]); + } + cell_array->GetData()->SetNumberOfValues(idx); + cell_array->Squeeze(); + polydata->SetStrips(cell_array); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetScalarModeToUsePointData(); + mapper->ImmediateModeRenderingOff(); + VtkUtils::SetInputData(mapper, polydata); + + vtkSmartPointer actor = vtkSmartPointer::New(); + //actor->SetNumberOfCloudPoints(std::max(1, polydata->GetNumberOfPoints() / 10)); + actor->GetProperty()->SetRepresentationToSurface(); + actor->GetProperty()->BackfaceCullingOff(); // Backface culling is off for higher efficiency + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->EdgeVisibilityOff(); + actor->GetProperty()->ShadingOff(); + actor->SetMapper(mapper); + + if (!mesh.texture.empty()) + { + vtkSmartPointer image_source = vtkSmartPointer::New(); + image_source->SetImage(mesh.texture); + + vtkSmartPointer texture = vtkSmartPointer::New(); + texture->SetInputConnection(image_source->GetOutputPort()); + actor->SetTexture(texture); + } + + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WMesh::WMesh(InputArray cloud, InputArray polygons, InputArray colors, InputArray normals) +{ + Mesh mesh; + mesh.cloud = cloud.getMat(); + mesh.colors = colors.getMat(); + mesh.normals = normals.getMat(); + mesh.polygons = polygons.getMat(); + *this = WMesh(mesh); +} + +template<> CV_EXPORTS cv::viz::WMesh cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} diff --git a/modules/viz/src/interactor_style.cpp b/modules/viz/src/interactor_style.cpp new file mode 100644 index 000000000..75003a2b6 --- /dev/null +++ b/modules/viz/src/interactor_style.cpp @@ -0,0 +1,639 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +// OpenCV Viz module is complete rewrite of +// PCL visualization module (www.pointclouds.org) +// +//M*/ + +#include "precomp.hpp" + + +namespace cv { namespace viz +{ + vtkStandardNewMacro(InteractorStyle) +}} + + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::Initialize() +{ + // Set windows size (width, height) to unknown (-1) + win_size_ = Vec2i(-1, -1); + win_pos_ = Vec2i(0, 0); + max_win_size_ = Vec2i(-1, -1); + + init_ = true; + stereo_anaglyph_mask_default_ = true; + + // Initialize the keyboard event callback as none + keyboardCallback_ = 0; + keyboard_callback_cookie_ = 0; + + // Initialize the mouse event callback as none + mouseCallback_ = 0; + mouse_callback_cookie_ = 0; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::saveScreenshot(const String &file) +{ + FindPokedRenderer(Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1]); + + vtkSmartPointer wif = vtkSmartPointer::New(); + wif->SetInput(Interactor->GetRenderWindow()); + + vtkSmartPointer snapshot_writer = vtkSmartPointer::New(); + snapshot_writer->SetInputConnection(wif->GetOutputPort()); + snapshot_writer->SetFileName(file.c_str()); + snapshot_writer->Write(); + + cout << "Screenshot successfully captured (" << file.c_str() << ")" << endl; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::exportScene(const String &file) +{ + vtkSmartPointer exporter; + if (file.size() > 5 && file.substr(file.size() - 5) == ".vrml") + { + exporter = vtkSmartPointer::New(); + vtkVRMLExporter::SafeDownCast(exporter)->SetFileName(file.c_str()); + } + else + { + exporter = vtkSmartPointer::New(); + vtkOBJExporter::SafeDownCast(exporter)->SetFilePrefix(file.c_str()); + } + + exporter->SetInput(Interactor->GetRenderWindow()); + exporter->Write(); + + cout << "Scene successfully exported (" << file.c_str() << ")" << endl; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::zoomIn() +{ + FindPokedRenderer(Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1]); + // Zoom in + StartDolly(); + double factor = 10.0 * 0.2 * .5; + Dolly(std::pow(1.1, factor)); + EndDolly(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::zoomOut() +{ + FindPokedRenderer(Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1]); + // Zoom out + StartDolly(); + double factor = 10.0 * -0.2 * .5; + Dolly(std::pow(1.1, factor)); + EndDolly(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnChar() +{ + // Make sure we ignore the same events we handle in OnKeyDown to avoid calling things twice + FindPokedRenderer(Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1]); + if (Interactor->GetKeyCode() >= '0' && Interactor->GetKeyCode() <= '9') + return; + + String key(Interactor->GetKeySym()); + if (key.find("XF86ZoomIn") != String::npos) + zoomIn(); + else if (key.find("XF86ZoomOut") != String::npos) + zoomOut(); + + int keymod = Interactor->GetAltKey(); + + switch (Interactor->GetKeyCode()) + { + // All of the options below simply exit + case 'h': case 'H': + case 'l': case 'L': + case 'p': case 'P': + case 'j': case 'J': + case 'c': case 'C': + case 43: // KEY_PLUS + case 45: // KEY_MINUS + case 'f': case 'F': + case 'g': case 'G': + case 'o': case 'O': + case 'u': case 'U': + case 'q': case 'Q': + { + break; + } + // S and R have a special !ALT case + case 'r': case 'R': + case 's': case 'S': + { + if (!keymod) + Superclass::OnChar(); + break; + } + default: + { + Superclass::OnChar(); + break; + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::registerMouseCallback(void (*callback)(const MouseEvent&, void*), void* cookie) +{ + // Register the callback function and store the user data + mouseCallback_ = callback; + mouse_callback_cookie_ = cookie; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::registerKeyboardCallback(void (*callback)(const KeyboardEvent&, void*), void *cookie) +{ + // Register the callback function and store the user data + keyboardCallback_ = callback; + keyboard_callback_cookie_ = cookie; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +int cv::viz::InteractorStyle::getModifiers() +{ + int modifiers = KeyboardEvent::NONE; + + if (Interactor->GetAltKey()) + modifiers |= KeyboardEvent::ALT; + + if (Interactor->GetControlKey()) + modifiers |= KeyboardEvent::CTRL; + + if (Interactor->GetShiftKey()) + modifiers |= KeyboardEvent::SHIFT; + return modifiers; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnKeyDown() +{ + CV_Assert("Interactor style not initialized. Please call Initialize() before continuing" && init_); + FindPokedRenderer(Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1]); + + // Save the initial windows width/height + if (win_size_[0] == -1 || win_size_[1] == -1) + win_size_ = Vec2i(Interactor->GetRenderWindow()->GetSize()); + + bool alt = Interactor->GetAltKey() != 0; + + std::string key(Interactor->GetKeySym()); + if (key.find("XF86ZoomIn") != std::string::npos) + zoomIn(); + else if (key.find("XF86ZoomOut") != std::string::npos) + zoomOut(); + + switch (Interactor->GetKeyCode()) + { + case 'h': case 'H': + { + std::cout << "| Help:\n" + "-------\n" + " p, P : switch to a point-based representation\n" + " w, W : switch to a wireframe-based representation (where available)\n" + " s, S : switch to a surface-based representation (where available)\n" + "\n" + " j, J : take a .PNG snapshot of the current window view\n" + " k, K : export scene to Wavefront .obj format\n" + " ALT + k, K : export scene to VRML format\n" + " c, C : display current camera/window parameters\n" + " f, F : fly to point mode, hold the key and move mouse where to fly\n" + "\n" + " e, E : exit the interactor\n" + " q, Q : stop and call VTK's TerminateApp\n" + "\n" + " +/- : increment/decrement overall point size\n" + " +/- [+ ALT] : zoom in/out \n" + "\n" + " r, R [+ ALT] : reset camera [to viewpoint = {0, 0, 0} -> center_{x, y, z}]\n" + "\n" + " ALT + s, S : turn stereo mode on/off\n" + " ALT + f, F : switch between maximized window mode and original size\n" + "\n" + << std::endl; + break; + } + + // Switch representation to points + case 'p': case 'P': + { + vtkSmartPointer ac = CurrentRenderer->GetActors(); + vtkCollectionSimpleIterator ait; + for (ac->InitTraversal(ait); vtkActor* actor = ac->GetNextActor(ait); ) + for (actor->InitPathTraversal(); vtkAssemblyPath* path = actor->GetNextPath(); ) + { + vtkActor* apart = vtkActor::SafeDownCast(path->GetLastNode()->GetViewProp()); + apart->GetProperty()->SetRepresentationToPoints(); + } + break; + } + + // Save a PNG snapshot + case 'j': case 'J': + saveScreenshot(cv::format("screenshot-%d.png", (unsigned int)time(0))); break; + + // Export scene as in obj or vrml format + case 'k': case 'K': + { + String format = alt ? "scene-%d.vrml" : "scene-%d"; + exportScene(cv::format(format.c_str(), (unsigned int)time(0))); + break; + } + + // display current camera settings/parameters + case 'c': case 'C': + { + vtkSmartPointer cam = Interactor->GetRenderWindow()->GetRenderers()->GetFirstRenderer()->GetActiveCamera(); + + Vec2d clip(cam->GetClippingRange()); + Vec3d focal(cam->GetFocalPoint()), pos(cam->GetPosition()), view(cam->GetViewUp()); + Vec2i win_pos(Interactor->GetRenderWindow()->GetPosition()); + Vec2i win_size(Interactor->GetRenderWindow()->GetSize()); + double angle = cam->GetViewAngle () / 180.0 * CV_PI; + + String data = cv::format("clip(%f,%f) focal(%f,%f,%f) pos(%f,%f,%f) view(%f,%f,%f) angle(%f) winsz(%d,%d) winpos(%d,%d)", + clip[0], clip[1], focal[0], focal[1], focal[2], pos[0], pos[1], pos[2], view[0], view[1], view[2], + angle, win_size[0], win_size[1], win_pos[0], win_pos[1]); + + std::cout << data.c_str() << std::endl; + + break; + } + case '=': + { + zoomIn(); + break; + } + case 43: // KEY_PLUS + { + if (alt) + zoomIn(); + else + { + vtkSmartPointer ac = CurrentRenderer->GetActors(); + vtkCollectionSimpleIterator ait; + for (ac->InitTraversal(ait); vtkActor* actor = ac->GetNextActor(ait); ) + for (actor->InitPathTraversal(); vtkAssemblyPath* path = actor->GetNextPath(); ) + { + vtkActor* apart = vtkActor::SafeDownCast(path->GetLastNode()->GetViewProp()); + float psize = apart->GetProperty()->GetPointSize(); + if (psize < 63.0f) + apart->GetProperty()->SetPointSize(psize + 1.0f); + } + } + break; + } + case 45: // KEY_MINUS + { + if (alt) + zoomOut(); + else + { + vtkSmartPointer ac = CurrentRenderer->GetActors(); + vtkCollectionSimpleIterator ait; + for (ac->InitTraversal(ait); vtkActor* actor = ac->GetNextActor(ait); ) + for (actor->InitPathTraversal(); vtkAssemblyPath* path = actor->GetNextPath(); ) + { + vtkActor* apart = vtkActor::SafeDownCast(path->GetLastNode()->GetViewProp()); + float psize = apart->GetProperty()->GetPointSize(); + if (psize > 1.0f) + apart->GetProperty()->SetPointSize(psize - 1.0f); + } + } + break; + } + // Switch between maximize and original window size + case 'f': case 'F': + { + if (alt) + { + Vec2i screen_size(Interactor->GetRenderWindow()->GetScreenSize()); + Vec2i win_size(Interactor->GetRenderWindow()->GetSize()); + + // Is window size = max? + if (win_size == max_win_size_) + { + Interactor->GetRenderWindow()->SetSize(win_size_.val); + Interactor->GetRenderWindow()->SetPosition(win_pos_.val); + Interactor->GetRenderWindow()->Render(); + Interactor->Render(); + } + // Set to max + else + { + win_pos_ = Vec2i(Interactor->GetRenderWindow()->GetPosition()); + win_size_ = win_size; + + Interactor->GetRenderWindow()->SetSize(screen_size.val); + Interactor->GetRenderWindow()->Render(); + Interactor->Render(); + max_win_size_ = Vec2i(Interactor->GetRenderWindow()->GetSize()); + } + } + else + { + AnimState = VTKIS_ANIM_ON; + Interactor->GetPicker()->Pick(Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1], 0.0, CurrentRenderer); + vtkSmartPointer picker = vtkAbstractPropPicker::SafeDownCast(Interactor->GetPicker()); + if (picker) + if (picker->GetPath()) + Interactor->FlyTo(CurrentRenderer, picker->GetPickPosition()); + AnimState = VTKIS_ANIM_OFF; + } + break; + } + // 's'/'S' w/out ALT + case 's': case 'S': + { + if (alt) + { + vtkSmartPointer window = Interactor->GetRenderWindow(); + if (!window->GetStereoRender()) + { + static Vec2i red_blue(4, 3), magenta_green(2, 5); + window->SetAnaglyphColorMask (stereo_anaglyph_mask_default_ ? red_blue.val : magenta_green.val); + stereo_anaglyph_mask_default_ = !stereo_anaglyph_mask_default_; + } + window->SetStereoRender(!window->GetStereoRender()); + Interactor->Render(); + } + else + Superclass::OnKeyDown(); + break; + } + + case 'o': case 'O': + { + vtkSmartPointer cam = CurrentRenderer->GetActiveCamera(); + cam->SetParallelProjection(!cam->GetParallelProjection()); + CurrentRenderer->Render(); + break; + } + + // Overwrite the camera reset + case 'r': case 'R': + { + if (!alt) + { + Superclass::OnKeyDown(); + break; + } + + WidgetActorMap::iterator it = widget_actor_map_->begin(); + // it might be that some actors don't have a valid transformation set -> we skip them to avoid a seg fault. + for (; it != widget_actor_map_->end(); ++it) + { + vtkProp3D * actor = vtkProp3D::SafeDownCast(it->second); + if (actor && actor->GetUserMatrix()) + break; + } + + vtkSmartPointer cam = CurrentRenderer->GetActiveCamera(); + + // if a valid transformation was found, use it otherwise fall back to default view point. + if (it != widget_actor_map_->end()) + { + vtkMatrix4x4* m = vtkProp3D::SafeDownCast(it->second)->GetUserMatrix(); + + cam->SetFocalPoint(m->GetElement(0, 3) - m->GetElement(0, 2), + m->GetElement(1, 3) - m->GetElement(1, 2), + m->GetElement(2, 3) - m->GetElement(2, 2)); + + cam->SetViewUp (m->GetElement(0, 1), m->GetElement(1, 1), m->GetElement(2, 1)); + cam->SetPosition(m->GetElement(0, 3), m->GetElement(1, 3), m->GetElement(2, 3)); + } + else + { + cam->SetPosition(0, 0, 0); + cam->SetFocalPoint(0, 0, 1); + cam->SetViewUp(0, -1, 0); + } + + // go to the next actor for the next key-press event. + if (it != widget_actor_map_->end()) + ++it; + else + it = widget_actor_map_->begin(); + + CurrentRenderer->SetActiveCamera(cam); + CurrentRenderer->ResetCameraClippingRange(); + CurrentRenderer->Render(); + break; + } + + case 'q': case 'Q': + { + Interactor->ExitCallback(); + return; + } + default: + { + Superclass::OnKeyDown(); + break; + } + } + + KeyboardEvent event(KeyboardEvent::KEY_DOWN, Interactor->GetKeySym(), Interactor->GetKeyCode(), getModifiers()); + if (keyboardCallback_) + keyboardCallback_(event, keyboard_callback_cookie_); + Interactor->Render(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnKeyUp() +{ + KeyboardEvent event(KeyboardEvent::KEY_UP, Interactor->GetKeySym(), Interactor->GetKeyCode(), getModifiers()); + if (keyboardCallback_) + keyboardCallback_(event, keyboard_callback_cookie_); + Superclass::OnKeyUp(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnMouseMove() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent event(MouseEvent::MouseMove, MouseEvent::NoButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnMouseMove(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnLeftButtonDown() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent::Type type = (Interactor->GetRepeatCount() == 0) ? MouseEvent::MouseButtonPress : MouseEvent::MouseDblClick; + MouseEvent event(type, MouseEvent::LeftButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnLeftButtonDown(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnLeftButtonUp() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent event(MouseEvent::MouseButtonRelease, MouseEvent::LeftButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnLeftButtonUp(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnMiddleButtonDown() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent::Type type = (Interactor->GetRepeatCount() == 0) ? MouseEvent::MouseButtonPress : MouseEvent::MouseDblClick; + MouseEvent event(type, MouseEvent::MiddleButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnMiddleButtonDown(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnMiddleButtonUp() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent event(MouseEvent::MouseButtonRelease, MouseEvent::MiddleButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnMiddleButtonUp(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnRightButtonDown() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent::Type type = (Interactor->GetRepeatCount() == 0) ? MouseEvent::MouseButtonPress : MouseEvent::MouseDblClick; + MouseEvent event(type, MouseEvent::RightButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnRightButtonDown(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnRightButtonUp() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent event(MouseEvent::MouseButtonRelease, MouseEvent::RightButton, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + Superclass::OnRightButtonUp(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnMouseWheelForward() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent event(MouseEvent::MouseScrollUp, MouseEvent::VScroll, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + if (Interactor->GetRepeatCount() && mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + + if (Interactor->GetAltKey()) + { + // zoom + vtkSmartPointer cam = CurrentRenderer->GetActiveCamera(); + double opening_angle = cam->GetViewAngle(); + if (opening_angle > 15.0) + opening_angle -= 1.0; + + cam->SetViewAngle(opening_angle); + cam->Modified(); + CurrentRenderer->ResetCameraClippingRange(); + CurrentRenderer->Modified(); + CurrentRenderer->Render(); + Interactor->Render(); + } + else + Superclass::OnMouseWheelForward(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnMouseWheelBackward() +{ + Vec2i p(Interactor->GetEventPosition()); + MouseEvent event(MouseEvent::MouseScrollDown, MouseEvent::VScroll, p, getModifiers()); + if (mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + + if (Interactor->GetRepeatCount() && mouseCallback_) + mouseCallback_(event, mouse_callback_cookie_); + + if (Interactor->GetAltKey()) + { + // zoom + vtkSmartPointer cam = CurrentRenderer->GetActiveCamera(); + double opening_angle = cam->GetViewAngle(); + if (opening_angle < 170.0) + opening_angle += 1.0; + + cam->SetViewAngle(opening_angle); + cam->Modified(); + CurrentRenderer->ResetCameraClippingRange(); + CurrentRenderer->Modified(); + CurrentRenderer->Render(); + Interactor->Render(); + } + else + Superclass::OnMouseWheelBackward(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::InteractorStyle::OnTimer() +{ + CV_Assert("Interactor style not initialized." && init_); + Interactor->Render(); +} diff --git a/modules/viz/src/interactor_style.hpp b/modules/viz/src/interactor_style.hpp new file mode 100644 index 000000000..8d01697a8 --- /dev/null +++ b/modules/viz/src/interactor_style.hpp @@ -0,0 +1,119 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_INTERACTOR_STYLE_H__ +#define __OPENCV_VIZ_INTERACTOR_STYLE_H__ + +namespace cv +{ + namespace viz + { + class InteractorStyle : public vtkInteractorStyleTrackballCamera + { + public: + static InteractorStyle *New(); + virtual ~InteractorStyle() {} + + // this macro defines Superclass, the isA functionality and the safe downcast method + vtkTypeMacro(InteractorStyle, vtkInteractorStyleTrackballCamera) + + /** \brief Initialization routine. Must be called before anything else. */ + virtual void Initialize(); + + void setWidgetActorMap(const Ptr& actors) { widget_actor_map_ = actors; } + void registerMouseCallback(void (*callback)(const MouseEvent&, void*), void* cookie = 0); + void registerKeyboardCallback(void (*callback)(const KeyboardEvent&, void*), void * cookie = 0); + void saveScreenshot(const String &file); + void exportScene(const String &file); + + private: + /** \brief Set to true after initialization is complete. */ + bool init_; + + Ptr widget_actor_map_; + + Vec2i win_size_; + Vec2i win_pos_; + Vec2i max_win_size_; + + /** \brief Interactor style internal method. Gets called whenever a key is pressed. */ + virtual void OnChar(); + + // Keyboard events + virtual void OnKeyDown(); + virtual void OnKeyUp(); + + // mouse button events + virtual void OnMouseMove(); + virtual void OnLeftButtonDown(); + virtual void OnLeftButtonUp(); + virtual void OnMiddleButtonDown(); + virtual void OnMiddleButtonUp(); + virtual void OnRightButtonDown(); + virtual void OnRightButtonUp(); + virtual void OnMouseWheelForward(); + virtual void OnMouseWheelBackward(); + + /** \brief Interactor style internal method. Gets called periodically if a timer is set. */ + virtual void OnTimer(); + + void zoomIn(); + void zoomOut(); + + /** \brief True if we're using red-blue colors for anaglyphic stereo, false if magenta-green. */ + bool stereo_anaglyph_mask_default_; + + void (*keyboardCallback_)(const KeyboardEvent&, void*); + void *keyboard_callback_cookie_; + + void (*mouseCallback_)(const MouseEvent&, void*); + void *mouse_callback_cookie_; + + int getModifiers(); + }; + } +} + +#endif diff --git a/modules/viz/src/precomp.hpp b/modules/viz/src/precomp.hpp new file mode 100644 index 000000000..d9681ce83 --- /dev/null +++ b/modules/viz/src/precomp.hpp @@ -0,0 +1,324 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_PRECOMP_HPP__ +#define __OPENCV_VIZ_PRECOMP_HPP__ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) || defined(__CYGWIN__) +# include /* unlink */ +#else +# include /* unlink */ +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace cv +{ + namespace viz + { + typedef std::map > WidgetActorMap; + typedef std::map VizMap; + + class VizStorage + { + public: + static void unregisterAll(); + + //! window names automatically have Viz - prefix even though not provided by the users + static String generateWindowName(const String &window_name); + + private: + VizStorage(); // Static + ~VizStorage(); + + static void add(const Viz3d& window); + static Viz3d& get(const String &window_name); + static void remove(const String &window_name); + static bool windowExists(const String &window_name); + static void removeUnreferenced(); + + static VizMap storage; + friend class Viz3d; + }; + + template inline _Tp normalized(const _Tp& v) { return v * 1/norm(v); } + + template inline bool isNan(const _Tp* data) + { + return isNan(data[0]) || isNan(data[1]) || isNan(data[2]); + } + + inline vtkSmartPointer getActor(const Widget3D& widget) + { + return vtkActor::SafeDownCast(WidgetAccessor::getProp(widget)); + } + + inline vtkSmartPointer getPolyData(const Widget3D& widget) + { + vtkSmartPointer mapper = getActor(widget)->GetMapper(); + return vtkPolyData::SafeDownCast(mapper->GetInput()); + } + + inline vtkSmartPointer vtkmatrix(const cv::Matx44d &matrix) + { + vtkSmartPointer vtk_matrix = vtkSmartPointer::New(); + vtk_matrix->DeepCopy(matrix.val); + return vtk_matrix; + } + + inline Color vtkcolor(const Color& color) + { + Color scaled_color = color * (1.0/255.0); + std::swap(scaled_color[0], scaled_color[2]); + return scaled_color; + } + + inline Vec3d get_random_vec(double from = -10.0, double to = 10.0) + { + RNG& rng = theRNG(); + return Vec3d(rng.uniform(from, to), rng.uniform(from, to), rng.uniform(from, to)); + } + + struct VtkUtils + { + template + static void SetInputData(vtkSmartPointer filter, vtkPolyData* polydata) + { + #if VTK_MAJOR_VERSION <= 5 + filter->SetInput(polydata); + #else + filter->SetInputData(polydata); + #endif + } + template + static void SetSourceData(vtkSmartPointer filter, vtkPolyData* polydata) + { + #if VTK_MAJOR_VERSION <= 5 + filter->SetSource(polydata); + #else + filter->SetSourceData(polydata); + #endif + } + + template + static void SetInputData(vtkSmartPointer filter, vtkImageData* polydata) + { + #if VTK_MAJOR_VERSION <= 5 + filter->SetInput(polydata); + #else + filter->SetInputData(polydata); + #endif + } + + template + static void AddInputData(vtkSmartPointer filter, vtkPolyData *polydata) + { + #if VTK_MAJOR_VERSION <= 5 + filter->AddInput(polydata); + #else + filter->AddInputData(polydata); + #endif + } + + static vtkSmartPointer FillScalars(size_t size, const Color& color) + { + Vec3b rgb = Vec3d(color[2], color[1], color[0]); + Vec3b* color_data = new Vec3b[size]; + std::fill(color_data, color_data + size, rgb); + + vtkSmartPointer scalars = vtkSmartPointer::New(); + scalars->SetName("Colors"); + scalars->SetNumberOfComponents(3); + scalars->SetNumberOfTuples(size); + scalars->SetArray(color_data->val, size * 3, 0); + return scalars; + } + + static vtkSmartPointer ComputeNormals(vtkSmartPointer polydata) + { + vtkSmartPointer normals_generator = vtkSmartPointer::New(); + normals_generator->ComputePointNormalsOn(); + normals_generator->ComputeCellNormalsOff(); + normals_generator->SetFeatureAngle(0.1); + normals_generator->SetSplitting(0); + normals_generator->SetConsistency(1); + normals_generator->SetAutoOrientNormals(0); + normals_generator->SetFlipNormals(0); + normals_generator->SetNonManifoldTraversal(1); + VtkUtils::SetInputData(normals_generator, polydata); + normals_generator->Update(); + return normals_generator->GetOutput(); + } + + static vtkSmartPointer TransformPolydata(vtkSmartPointer algorithm_output_port, const Affine3d& pose) + { + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->SetMatrix(vtkmatrix(pose.matrix)); + + vtkSmartPointer transform_filter = vtkSmartPointer::New(); + transform_filter->SetTransform(transform); + transform_filter->SetInputConnection(algorithm_output_port); + transform_filter->Update(); + return transform_filter->GetOutput(); + } + + static vtkSmartPointer TransformPolydata(vtkSmartPointer polydata, const Affine3d& pose) + { + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->SetMatrix(vtkmatrix(pose.matrix)); + + vtkSmartPointer transform_filter = vtkSmartPointer::New(); + VtkUtils::SetInputData(transform_filter, polydata); + transform_filter->SetTransform(transform); + transform_filter->Update(); + return transform_filter->GetOutput(); + } + }; + } +} + +#include "interactor_style.hpp" +#include "vizimpl.hpp" + + +#endif diff --git a/modules/viz/src/shapes.cpp b/modules/viz/src/shapes.cpp new file mode 100644 index 000000000..f3d24f757 --- /dev/null +++ b/modules/viz/src/shapes.cpp @@ -0,0 +1,1088 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// line widget implementation +cv::viz::WLine::WLine(const Point3d &pt1, const Point3d &pt2, const Color &color) +{ + vtkSmartPointer line = vtkSmartPointer::New(); + line->SetPoint1(pt1.x, pt1.y, pt1.z); + line->SetPoint2(pt2.x, pt2.y, pt2.z); + line->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, line->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WLine cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// sphere widget implementation + +cv::viz::WSphere::WSphere(const Point3d ¢er, double radius, int sphere_resolution, const Color &color) +{ + vtkSmartPointer sphere = vtkSmartPointer::New(); + sphere->SetRadius(radius); + sphere->SetCenter(center.x, center.y, center.z); + sphere->SetPhiResolution(sphere_resolution); + sphere->SetThetaResolution(sphere_resolution); + sphere->LatLongTessellationOff(); + sphere->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, sphere->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WSphere cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// plane widget implementation + +cv::viz::WPlane::WPlane(const Size2d& size, const Color &color) +{ + vtkSmartPointer plane = vtkSmartPointer::New(); + plane->SetOrigin(-0.5 * size.width, -0.5 * size.height, 0.0); + plane->SetPoint1( 0.5 * size.width, -0.5 * size.height, 0.0); + plane->SetPoint2(-0.5 * size.width, 0.5 * size.height, 0.0); + plane->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, plane->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->GetProperty()->LightingOff(); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +cv::viz::WPlane::WPlane(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis, const Size2d& size, const Color &color) +{ + Vec3d zvec = normalize(normal); + Vec3d xvec = normalize(new_yaxis.cross(zvec)); + Vec3d yvec = zvec.cross(xvec); + + WPlane plane(size, color); + plane.applyTransform(makeTransformToGlobal(xvec, yvec, zvec, center)); + *this = plane; +} + +template<> cv::viz::WPlane cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// arrow widget implementation + +cv::viz::WArrow::WArrow(const Point3d& pt1, const Point3d& pt2, double thickness, const Color &color) +{ + vtkSmartPointer arrow_source = vtkSmartPointer::New(); + arrow_source->SetShaftRadius(thickness); + arrow_source->SetTipRadius(thickness * 3.0); + arrow_source->SetTipLength(thickness * 10.0); + + Vec3d arbitrary = get_random_vec(); + Vec3d start_point(pt1.x, pt1.y, pt1.z), end_point(pt2.x, pt2.y, pt2.z); + + double length = norm(end_point - start_point); + + Vec3d xvec = normalized(end_point - start_point); + Vec3d zvec = normalized(xvec.cross(arbitrary)); + Vec3d yvec = zvec.cross(xvec); + + Matx33d R = makeTransformToGlobal(xvec, yvec, zvec).rotation(); + Affine3d transform_with_scale(R * length, start_point); + + vtkSmartPointer polydata = VtkUtils::TransformPolydata(arrow_source->GetOutputPort(), transform_with_scale); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, polydata); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WArrow cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// circle widget implementation + +cv::viz::WCircle::WCircle(double radius, double thickness, const Color &color) +{ + vtkSmartPointer disk = vtkSmartPointer::New(); + disk->SetCircumferentialResolution(30); + disk->SetInnerRadius(radius - thickness); + disk->SetOuterRadius(radius + thickness); + disk->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, disk->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->GetProperty()->LightingOff(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); + +} + +cv::viz::WCircle::WCircle(double radius, const Point3d& center, const Vec3d& normal, double thickness, const Color &color) +{ + Vec3d arbitrary = get_random_vec(); + Vec3d zvec = normalized(normal); + Vec3d xvec = normalized(zvec.cross(arbitrary)); + Vec3d yvec = zvec.cross(xvec); + + WCircle circle(radius, thickness, color); + circle.applyTransform(makeTransformToGlobal(xvec, yvec, zvec, center)); + *this = circle; +} + +template<> cv::viz::WCircle cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// WCone widget implementation + +cv::viz::WCone::WCone(double length, double radius, int resolution, const Color &color) +{ + vtkSmartPointer cone_source = vtkSmartPointer::New(); + cone_source->SetCenter(length*0.5, 0.0, 0.0); + cone_source->SetHeight(length); + cone_source->SetRadius(radius); + cone_source->SetResolution(resolution); + cone_source->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, cone_source->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +cv::viz::WCone::WCone(double radius, const Point3d& center, const Point3d& tip, int resolution, const Color &color) +{ + Vec3d arbitrary = get_random_vec(); + Vec3d xvec = normalized(Vec3d(tip - center)); + Vec3d zvec = normalized(xvec.cross(arbitrary)); + Vec3d yvec = zvec.cross(xvec); + + WCone circle(norm(tip - center), radius, resolution, color); + circle.applyTransform(makeTransformToGlobal(xvec, yvec, zvec, center)); + *this = circle; +} + +template<> cv::viz::WCone cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// cylinder widget implementation + +cv::viz::WCylinder::WCylinder(const Point3d& axis_point1, const Point3d& axis_point2, double radius, int numsides, const Color &color) +{ + vtkSmartPointer line = vtkSmartPointer::New(); + line->SetPoint1(axis_point1.x, axis_point1.y, axis_point1.z); + line->SetPoint2(axis_point2.x, axis_point2.y, axis_point2.z); + + vtkSmartPointer tuber = vtkSmartPointer::New(); + tuber->SetInputConnection(line->GetOutputPort()); + tuber->SetNumberOfSides(numsides); + tuber->SetRadius(radius); + tuber->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, tuber->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WCylinder cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// cylinder widget implementation + +cv::viz::WCube::WCube(const Point3d& min_point, const Point3d& max_point, bool wire_frame, const Color &color) +{ + double bounds[6]; + bounds[0] = std::min(min_point.x, max_point.x); + bounds[1] = std::max(min_point.x, max_point.x); + bounds[2] = std::min(min_point.y, max_point.y); + bounds[3] = std::max(min_point.y, max_point.y); + bounds[4] = std::min(min_point.z, max_point.z); + bounds[5] = std::max(min_point.z, max_point.z); + + vtkSmartPointer cube; + if (wire_frame) + { + cube = vtkSmartPointer::New(); + vtkOutlineSource::SafeDownCast(cube)->SetBounds(bounds); + } + else + { + cube = vtkSmartPointer::New(); + vtkCubeSource::SafeDownCast(cube)->SetBounds(bounds); + } + cube->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, cube->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WCube cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// coordinate system widget implementation + +cv::viz::WCoordinateSystem::WCoordinateSystem(double scale) +{ + vtkSmartPointer axes = vtkSmartPointer::New(); + axes->SetOrigin(0, 0, 0); + axes->SetScaleFactor(scale); + axes->Update(); + + vtkSmartPointer colors = vtkSmartPointer::New(); + colors->SetNumberOfComponents(3); + colors->InsertNextTuple3(255, 0, 0); + colors->InsertNextTuple3(255, 0, 0); + colors->InsertNextTuple3(0, 255, 0); + colors->InsertNextTuple3(0, 255, 0); + colors->InsertNextTuple3(0, 0, 255); + colors->InsertNextTuple3(0, 0, 255); + + vtkSmartPointer polydata = axes->GetOutput(); + polydata->GetPointData()->SetScalars(colors); + + vtkSmartPointer tube_filter = vtkSmartPointer::New(); + VtkUtils::SetInputData(tube_filter, polydata); + tube_filter->SetRadius(axes->GetScaleFactor() / 50.0); + tube_filter->SetNumberOfSides(6); + tube_filter->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetScalarModeToUsePointData(); + VtkUtils::SetInputData(mapper, tube_filter->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WCoordinateSystem cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// polyline widget implementation + +cv::viz::WPolyLine::WPolyLine(InputArray _points, const Color &color) +{ + CV_Assert(_points.type() == CV_32FC3 || _points.type() == CV_32FC4 || _points.type() == CV_64FC3 || _points.type() == CV_64FC4); + + const float *fpoints = _points.getMat().ptr(); + const double *dpoints = _points.getMat().ptr(); + size_t total = _points.total(); + int s_chs = _points.channels(); + + vtkSmartPointer points = vtkSmartPointer::New(); + points->SetDataType(_points.depth() == CV_32F ? VTK_FLOAT : VTK_DOUBLE); + points->SetNumberOfPoints(total); + + if (_points.depth() == CV_32F) + for(size_t i = 0; i < total; ++i, fpoints += s_chs) + points->SetPoint(i, fpoints); + + if (_points.depth() == CV_64F) + for(size_t i = 0; i < total; ++i, dpoints += s_chs) + points->SetPoint(i, dpoints); + + vtkSmartPointer cell_array = vtkSmartPointer::New(); + cell_array->Allocate(cell_array->EstimateSize(1, total)); + cell_array->InsertNextCell(total); + for(size_t i = 0; i < total; ++i) + cell_array->InsertCellPoint(i); + + vtkSmartPointer scalars = VtkUtils::FillScalars(total, color); + + vtkSmartPointer polydata = vtkSmartPointer::New(); + polydata->SetPoints(points); + polydata->SetLines(cell_array); + polydata->GetPointData()->SetScalars(scalars); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, polydata); + mapper->SetScalarRange(0, 255); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WPolyLine cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// grid widget implementation + + +cv::viz::WGrid::WGrid(const Vec2i &cells, const Vec2d &cells_spacing, const Color &color) +{ + vtkSmartPointer grid_data = vtkSmartPointer::New(); + + // Add 1 to dimensions because in ImageData dimensions is the number of lines + // - however here it means number of cells + grid_data->SetDimensions(cells[0]+1, cells[1]+1, 1); + grid_data->SetSpacing(cells_spacing[0], cells_spacing[1], 0.); + + // Set origin of the grid to be the middle of the grid + grid_data->SetOrigin(cells[0] * cells_spacing[0] * (-0.5), cells[1] * cells_spacing[1] * (-0.5), 0); + + // Extract the edges so we have the grid + vtkSmartPointer extract_edges = vtkSmartPointer::New(); + VtkUtils::SetInputData(extract_edges, grid_data); + extract_edges->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, extract_edges->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +cv::viz::WGrid::WGrid(const Point3d& center, const Vec3d& normal, const Vec3d& new_yaxis, const Vec2i &cells, const Vec2d &cells_spacing, const Color &color) +{ + Vec3d zvec = normalize(normal); + Vec3d xvec = normalize(new_yaxis.cross(zvec)); + Vec3d yvec = zvec.cross(xvec); + + WGrid grid(cells, cells_spacing, color); + grid.applyTransform(makeTransformToGlobal(xvec, yvec, zvec, center)); + *this = grid; +} + +template<> cv::viz::WGrid cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// text3D widget implementation + +cv::viz::WText3D::WText3D(const String &text, const Point3d &position, double text_scale, bool face_camera, const Color &color) +{ + vtkSmartPointer textSource = vtkSmartPointer::New(); + textSource->SetText(text.c_str()); + textSource->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetInputConnection(textSource->GetOutputPort()); + + if (face_camera) + { + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->SetPosition(position.x, position.y, position.z); + actor->SetScale(text_scale); + WidgetAccessor::setProp(*this, actor); + } + else + { + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->SetPosition(position.x, position.y, position.z); + actor->SetScale(text_scale); + actor->GetProperty()->LightingOff(); + WidgetAccessor::setProp(*this, actor); + } + + setColor(color); +} + +void cv::viz::WText3D::setText(const String &text) +{ + vtkActor *actor = vtkActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("This widget does not support text." && actor); + + // Update text source + vtkPolyDataMapper *mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + vtkVectorText * textSource = vtkVectorText::SafeDownCast(mapper->GetInputConnection(0,0)->GetProducer()); + CV_Assert("This widget does not support text." && textSource); + + textSource->SetText(text.c_str()); + textSource->Modified(); + textSource->Update(); +} + +cv::String cv::viz::WText3D::getText() const +{ + vtkFollower *actor = vtkFollower::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("This widget does not support text." && actor); + + vtkPolyDataMapper *mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + vtkVectorText * textSource = vtkVectorText::SafeDownCast(mapper->GetInputConnection(0,0)->GetProducer()); + CV_Assert("This widget does not support text." && textSource); + + return textSource->GetText(); +} + +template<> cv::viz::WText3D cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// text widget implementation + +cv::viz::WText::WText(const String &text, const Point &pos, int font_size, const Color &color) +{ + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetDisplayPosition(pos.x, pos.y); + actor->SetInput(text.c_str()); + + actor->GetProperty()->SetDisplayLocationToForeground(); + + vtkSmartPointer tprop = actor->GetTextProperty(); + tprop->SetFontSize(font_size); + tprop->SetFontFamilyToCourier(); + tprop->SetJustificationToLeft(); + tprop->BoldOn(); + + Color c = vtkcolor(color); + tprop->SetColor(c.val); + + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WText cv::viz::Widget::cast() +{ + Widget2D widget = this->cast(); + return static_cast(widget); +} + +void cv::viz::WText::setText(const String &text) +{ + vtkTextActor *actor = vtkTextActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("This widget does not support text." && actor); + actor->SetInput(text.c_str()); +} + +cv::String cv::viz::WText::getText() const +{ + vtkTextActor *actor = vtkTextActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("This widget does not support text." && actor); + return actor->GetInput(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// image overlay widget implementation + +cv::viz::WImageOverlay::WImageOverlay(InputArray image, const Rect &rect) +{ + CV_Assert(!image.empty() && image.depth() == CV_8U); + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetImage(image); + Size sz = image.size(); + + // Scale the image based on the Rect, and flip to match y-ais orientation + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->Scale(sz.width/(double)rect.width, sz.height/(double)rect.height, 1.0); + transform->RotateX(180); + + vtkSmartPointer image_reslice = vtkSmartPointer::New(); + image_reslice->SetResliceTransform(transform); + image_reslice->SetInputConnection(source->GetOutputPort()); + image_reslice->SetOutputDimensionality(2); + image_reslice->InterpolateOn(); + image_reslice->AutoCropOutputOn(); + image_reslice->Update(); + + vtkSmartPointer image_mapper = vtkSmartPointer::New(); + image_mapper->SetInputConnection(image_reslice->GetOutputPort()); + image_mapper->SetColorWindow(255); // OpenCV color + image_mapper->SetColorLevel(127.5); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(image_mapper); + actor->SetPosition(rect.x, rect.y); + actor->GetProperty()->SetDisplayLocationToForeground(); + + WidgetAccessor::setProp(*this, actor); +} + +void cv::viz::WImageOverlay::setImage(InputArray image) +{ + CV_Assert(!image.empty() && image.depth() == CV_8U); + + vtkActor2D *actor = vtkActor2D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("This widget does not support overlay image." && actor); + + vtkImageMapper *mapper = vtkImageMapper::SafeDownCast(actor->GetMapper()); + CV_Assert("This widget does not support overlay image." && mapper); + \ + Vec6i extent; + mapper->GetInput()->GetExtent(extent.val); + Size size(extent[1], extent[3]); + + // Create the vtk image and set its parameters based on input image + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetImage(image); + Size sz = image.size(); + + // Scale the image based on the Rect, and flip to match y-ais orientation + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->Scale(sz.width/(double)size.width, sz.height/(double)size.height, 1.0); + transform->RotateX(180); + + vtkSmartPointer image_reslice = vtkSmartPointer::New(); + image_reslice->SetResliceTransform(transform); + image_reslice->SetInputConnection(source->GetOutputPort()); + image_reslice->SetOutputDimensionality(2); + image_reslice->InterpolateOn(); + image_reslice->AutoCropOutputOn(); + image_reslice->Update(); + + mapper->SetInputConnection(image_reslice->GetOutputPort()); +} + +template<> cv::viz::WImageOverlay cv::viz::Widget::cast() +{ + Widget2D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// image 3D widget implementation + +cv::viz::WImage3D::WImage3D(InputArray image, const Size2d &size) +{ + CV_Assert(!image.empty() && image.depth() == CV_8U); + + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetImage(image); + + vtkSmartPointer texture = vtkSmartPointer::New(); + texture->SetInputConnection(source->GetOutputPort()); + + vtkSmartPointer plane = vtkSmartPointer::New(); + plane->SetOrigin(-0.5 * size.width, -0.5 * size.height, 0.0); + plane->SetPoint1( 0.5 * size.width, -0.5 * size.height, 0.0); + plane->SetPoint2(-0.5 * size.width, 0.5 * size.height, 0.0); + + vtkSmartPointer textured_plane = vtkSmartPointer::New(); + textured_plane->SetInputConnection(plane->GetOutputPort()); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetInputConnection(textured_plane->GetOutputPort()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->SetTexture(texture); + actor->GetProperty()->ShadingOff(); + actor->GetProperty()->LightingOff(); + + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WImage3D::WImage3D(InputArray image, const Size2d &size, const Vec3d ¢er, const Vec3d &normal, const Vec3d &up_vector) +{ + CV_Assert(!image.empty() && image.depth() == CV_8U); + + // Compute the transformation matrix for drawing the camera frame in a scene + Vec3d n = normalize(normal); + Vec3d u = normalize(up_vector.cross(n)); + Vec3d v = n.cross(u); + Affine3d pose = makeTransformToGlobal(u, v, n, center); + + WImage3D image3d(image, size); + image3d.applyTransform(pose); + *this = image3d; +} + +void cv::viz::WImage3D::setImage(InputArray image) +{ + CV_Assert(!image.empty() && image.depth() == CV_8U); + + vtkActor *actor = vtkActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("This widget does not support 3D image." && actor); + + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetImage(image); + + vtkSmartPointer texture = vtkSmartPointer::New(); + texture->SetInputConnection(source->GetOutputPort()); + + actor->SetTexture(texture); +} + +template<> cv::viz::WImage3D cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// camera position widget implementation + +namespace cv { namespace viz { namespace +{ + struct CameraPositionUtils + { + static vtkSmartPointer createFrustum(double aspect_ratio, double fovy, double scale) + { + vtkSmartPointer camera = vtkSmartPointer::New(); + camera->SetViewAngle(fovy); + camera->SetPosition(0.0, 0.0, 0.0); + camera->SetViewUp(0.0, 1.0, 0.0); + camera->SetFocalPoint(0.0, 0.0, 1.0); + camera->SetClippingRange(1e-9, scale); + + double planes_array[24]; + camera->GetFrustumPlanes(aspect_ratio, planes_array); + + vtkSmartPointer planes = vtkSmartPointer::New(); + planes->SetFrustumPlanes(planes_array); + + vtkSmartPointer frustumSource = vtkSmartPointer::New(); + frustumSource->SetPlanes(planes); + + vtkSmartPointer extract_edges = vtkSmartPointer::New(); + extract_edges->SetInputConnection(frustumSource->GetOutputPort()); + extract_edges->Update(); + + return extract_edges->GetOutput(); + } + + static Mat ensureColorImage(InputArray image) + { + Mat color(image.size(), CV_8UC3); + if (image.channels() == 1) + { + Vec3b *drow = color.ptr(); + for(int y = 0; y < color.rows; ++y) + { + const unsigned char *srow = image.getMat().ptr(y); + const unsigned char *send = srow + color.cols; + for(;srow < send;) + *drow++ = Vec3b::all(*srow++); + } + } + else + image.getMat().copyTo(color); + return color; + } + }; +}}} + +cv::viz::WCameraPosition::WCameraPosition(double scale) +{ + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, getPolyData(WCoordinateSystem(scale))); + mapper->SetScalarModeToUsePointData(); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WCameraPosition::WCameraPosition(const Matx33d &K, double scale, const Color &color) +{ + double f_x = K(0,0), f_y = K(1,1), c_y = K(1,2); + + // Assuming that this is an ideal camera (c_y and c_x are at the center of the image) + double fovy = 2.0 * atan2(c_y, f_y) * 180 / CV_PI; + double aspect_ratio = f_y / f_x; + + vtkSmartPointer polydata = CameraPositionUtils::createFrustum(aspect_ratio, fovy, scale); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, polydata); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +cv::viz::WCameraPosition::WCameraPosition(const Vec2d &fov, double scale, const Color &color) +{ + double aspect_ratio = tan(fov[0] * 0.5) / tan(fov[1] * 0.5); + double fovy = fov[1] * 180 / CV_PI; + + vtkSmartPointer polydata = CameraPositionUtils::createFrustum(aspect_ratio, fovy, scale); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, polydata); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +cv::viz::WCameraPosition::WCameraPosition(const Matx33d &K, InputArray _image, double scale, const Color &color) +{ + CV_Assert(!_image.empty() && _image.depth() == CV_8U); + Mat image = CameraPositionUtils::ensureColorImage(_image); + image.at(0, 0) = Vec3d(color.val); //workaround of VTK limitation + + double f_y = K(1,1), c_y = K(1,2); + // Assuming that this is an ideal camera (c_y and c_x are at the center of the image) + double fovy = 2.0 * atan2(c_y, f_y) * 180.0 / CV_PI; + double far_end_height = 2.00 * c_y * scale / f_y; + double aspect_ratio = image.cols/(double)image.rows; + double image_scale = far_end_height/image.rows; + + WImage3D image_widget(image, Size2d(image.cols, image.rows) * image_scale); + image_widget.applyTransform(Affine3d().translate(Vec3d(0, 0, scale))); + vtkSmartPointer plane = getPolyData(image_widget); + + vtkSmartPointer frustum = CameraPositionUtils::createFrustum(aspect_ratio, fovy, scale); + + // Frustum needs to be textured or else it can't be combined with image + vtkSmartPointer frustum_texture = vtkSmartPointer::New(); + VtkUtils::SetInputData(frustum_texture, frustum); + frustum_texture->SetSRange(0.0, 0.0); // Texture mapping with only one pixel + frustum_texture->SetTRange(0.0, 0.0); // from the image to have constant color + + vtkSmartPointer append_filter = vtkSmartPointer::New(); + append_filter->AddInputConnection(frustum_texture->GetOutputPort()); + VtkUtils::AddInputData(append_filter, plane); + + vtkSmartPointer actor = getActor(image_widget); + actor->GetMapper()->SetInputConnection(append_filter->GetOutputPort()); + WidgetAccessor::setProp(*this, actor); +} + +cv::viz::WCameraPosition::WCameraPosition(const Vec2d &fov, InputArray _image, double scale, const Color &color) +{ + CV_Assert(!_image.empty() && _image.depth() == CV_8U); + Mat image = CameraPositionUtils::ensureColorImage(_image); + image.at(0, 0) = Vec3d(color.val); //workaround of VTK limitation + + double fovy = fov[1] * 180.0 / CV_PI; + double far_end_height = 2.0 * scale * tan(fov[1] * 0.5); + double aspect_ratio = image.cols/(double)image.rows; + double image_scale = far_end_height/image.rows; + + WImage3D image_widget(image, Size2d(image.cols, image.rows) * image_scale); + image_widget.applyTransform(Affine3d().translate(Vec3d(0, 0, scale))); + vtkSmartPointer plane = getPolyData(image_widget); + + vtkSmartPointer frustum = CameraPositionUtils::createFrustum(aspect_ratio, fovy, scale); + + // Frustum needs to be textured or else it can't be combined with image + vtkSmartPointer frustum_texture = vtkSmartPointer::New(); + VtkUtils::SetInputData(frustum_texture, frustum); + frustum_texture->SetSRange(0.0, 0.0); // Texture mapping with only one pixel + frustum_texture->SetTRange(0.0, 0.0); // from the image to have constant color + + vtkSmartPointer append_filter = vtkSmartPointer::New(); + append_filter->AddInputConnection(frustum_texture->GetOutputPort()); + VtkUtils::AddInputData(append_filter, plane); + + vtkSmartPointer actor = getActor(image_widget); + actor->GetMapper()->SetInputConnection(append_filter->GetOutputPort()); + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WCameraPosition cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// trajectory widget implementation + +cv::viz::WTrajectory::WTrajectory(InputArray _path, int display_mode, double scale, const Color &color) +{ + vtkSmartPointer append_filter = vtkSmartPointer::New(); + + // Bitwise and with 3 in order to limit the domain to 2 bits + if (display_mode & WTrajectory::PATH) + { + Mat points = vtkTrajectorySource::ExtractPoints(_path); + vtkSmartPointer polydata = getPolyData(WPolyLine(points, color)); + VtkUtils::AddInputData(append_filter, polydata); + } + + if (display_mode & WTrajectory::FRAMES) + { + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetTrajectory(_path); + + vtkSmartPointer glyph = getPolyData(WCoordinateSystem(scale)); + + vtkSmartPointer tensor_glyph = vtkSmartPointer::New(); + tensor_glyph->SetInputConnection(source->GetOutputPort()); + VtkUtils::SetSourceData(tensor_glyph, glyph); + tensor_glyph->ExtractEigenvaluesOff(); // Treat as a rotation matrix, not as something with eigenvalues + tensor_glyph->ThreeGlyphsOff(); + tensor_glyph->SymmetricOff(); + tensor_glyph->ColorGlyphsOff(); + + append_filter->AddInputConnection(tensor_glyph->GetOutputPort()); + } + append_filter->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, append_filter->GetOutput()); + mapper->SetScalarModeToUsePointData(); + mapper->SetScalarRange(0, 255); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WTrajectory cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// WTrajectoryFrustums widget implementation + +cv::viz::WTrajectoryFrustums::WTrajectoryFrustums(InputArray _path, const Matx33d &K, double scale, const Color &color) +{ + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetTrajectory(_path); + + vtkSmartPointer glyph = getPolyData(WCameraPosition(K, scale)); + + vtkSmartPointer tensor_glyph = vtkSmartPointer::New(); + tensor_glyph->SetInputConnection(source->GetOutputPort()); + VtkUtils::SetSourceData(tensor_glyph, glyph); + tensor_glyph->ExtractEigenvaluesOff(); // Treat as a rotation matrix, not as something with eigenvalues + tensor_glyph->ThreeGlyphsOff(); + tensor_glyph->SymmetricOff(); + tensor_glyph->ColorGlyphsOff(); + tensor_glyph->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, tensor_glyph->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +cv::viz::WTrajectoryFrustums::WTrajectoryFrustums(InputArray _path, const Vec2d &fov, double scale, const Color &color) +{ + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetTrajectory(_path); + + vtkSmartPointer glyph = getPolyData(WCameraPosition(fov, scale)); + + vtkSmartPointer tensor_glyph = vtkSmartPointer::New(); + tensor_glyph->SetInputConnection(source->GetOutputPort()); + VtkUtils::SetSourceData(tensor_glyph, glyph); + tensor_glyph->ExtractEigenvaluesOff(); // Treat as a rotation matrix, not as something with eigenvalues + tensor_glyph->ThreeGlyphsOff(); + tensor_glyph->SymmetricOff(); + tensor_glyph->ColorGlyphsOff(); + tensor_glyph->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + VtkUtils::SetInputData(mapper, tensor_glyph->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); + setColor(color); +} + +template<> cv::viz::WTrajectoryFrustums cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// WTrajectorySpheres widget implementation + +cv::viz::WTrajectorySpheres::WTrajectorySpheres(InputArray _path, double line_length, double radius, const Color &from, const Color &to) +{ + CV_Assert(_path.kind() == _InputArray::STD_VECTOR || _path.kind() == _InputArray::MAT); + CV_Assert(_path.type() == CV_32FC(16) || _path.type() == CV_64FC(16)); + + Mat path64; + _path.getMat().convertTo(path64, CV_64F); + Affine3d *traj = path64.ptr(); + size_t total = path64.total(); + + vtkSmartPointer append_filter = vtkSmartPointer::New(); + + for(size_t i = 0; i < total; ++i) + { + Vec3d curr = traj[i].translation(); + + vtkSmartPointer sphere_source = vtkSmartPointer::New(); + sphere_source->SetCenter(curr.val); + sphere_source->SetRadius( (i == 0) ? 2 * radius : radius ); + sphere_source->Update(); + + double alpha = static_cast(i)/total; + Color c = from * (1 - alpha) + to * alpha; + + vtkSmartPointer polydata = sphere_source->GetOutput(); + polydata->GetCellData()->SetScalars(VtkUtils::FillScalars(polydata->GetNumberOfCells(), c)); + VtkUtils::AddInputData(append_filter, polydata); + + if (i > 0) + { + Vec3d prev = traj[i-1].translation(); + Vec3d lvec = prev - curr; + + if(norm(lvec) > line_length) + lvec = normalize(lvec) * line_length; + + Vec3d lend = curr + lvec; + + vtkSmartPointer line_source = vtkSmartPointer::New(); + line_source->SetPoint1(curr.val); + line_source->SetPoint2(lend.val); + line_source->Update(); + vtkSmartPointer polydata_ = line_source->GetOutput(); + polydata_->GetCellData()->SetScalars(VtkUtils::FillScalars(polydata_->GetNumberOfCells(), c)); + VtkUtils::AddInputData(append_filter, polydata_); + } + } + append_filter->Update(); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetScalarModeToUseCellData(); + VtkUtils::SetInputData(mapper, append_filter->GetOutput()); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + WidgetAccessor::setProp(*this, actor); +} + +template<> cv::viz::WTrajectorySpheres cv::viz::Widget::cast() +{ + Widget3D widget = this->cast(); + return static_cast(widget); +} diff --git a/modules/viz/src/types.cpp b/modules/viz/src/types.cpp new file mode 100644 index 000000000..2e32a6327 --- /dev/null +++ b/modules/viz/src/types.cpp @@ -0,0 +1,206 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +//////////////////////////////////////////////////////////////////// +/// Events + +cv::viz::KeyboardEvent::KeyboardEvent(Action _action, const String& _symbol, unsigned char _code, int _modifiers) + : action(_action), symbol(_symbol), code(_code), modifiers(_modifiers) {} + +cv::viz::MouseEvent::MouseEvent(const Type& _type, const MouseButton& _button, const Point& _pointer, int _modifiers) + : type(_type), button(_button), pointer(_pointer), modifiers(_modifiers) {} + +//////////////////////////////////////////////////////////////////// +/// cv::viz::Mesh3d + +cv::viz::Mesh cv::viz::Mesh::load(const String& file) +{ + vtkSmartPointer reader = vtkSmartPointer::New(); + reader->SetFileName(file.c_str()); + reader->Update(); + + vtkSmartPointer polydata = reader->GetOutput(); + CV_Assert("File does not exist or file format is not supported." && polydata); + + Mesh mesh; + vtkSmartPointer sink = vtkSmartPointer::New(); + sink->SetOutput(mesh.cloud, mesh.colors, mesh.normals, mesh.tcoords); + sink->SetInputConnection(reader->GetOutputPort()); + sink->Write(); + + // Now handle the polygons + vtkSmartPointer polygons = polydata->GetPolys(); + mesh.polygons.create(1, polygons->GetSize(), CV_32SC1); + int* poly_ptr = mesh.polygons.ptr(); + + polygons->InitTraversal(); + vtkIdType nr_cell_points, *cell_points; + while (polygons->GetNextCell(nr_cell_points, cell_points)) + { + *poly_ptr++ = nr_cell_points; + for (vtkIdType i = 0; i < nr_cell_points; ++i) + *poly_ptr++ = (int)cell_points[i]; + } + + return mesh; +} + +//////////////////////////////////////////////////////////////////// +/// Camera implementation + +cv::viz::Camera::Camera(double fx, double fy, double cx, double cy, const Size &window_size) +{ + init(fx, fy, cx, cy, window_size); +} + +cv::viz::Camera::Camera(const Vec2d &fov, const Size &window_size) +{ + CV_Assert(window_size.width > 0 && window_size.height > 0); + setClip(Vec2d(0.01, 1000.01)); // Default clipping + setFov(fov); + window_size_ = window_size; + // Principal point at the center + principal_point_ = Vec2f(static_cast(window_size.width)*0.5f, static_cast(window_size.height)*0.5f); + focal_ = Vec2f(principal_point_[0] / tan(fov_[0]*0.5f), principal_point_[1] / tan(fov_[1]*0.5f)); +} + +cv::viz::Camera::Camera(const cv::Matx33d & K, const Size &window_size) +{ + double f_x = K(0,0); + double f_y = K(1,1); + double c_x = K(0,2); + double c_y = K(1,2); + init(f_x, f_y, c_x, c_y, window_size); +} + +cv::viz::Camera::Camera(const Matx44d &proj, const Size &window_size) +{ + CV_Assert(window_size.width > 0 && window_size.height > 0); + + double near = proj(2,3) / (proj(2,2) - 1.0); + double far = near * (proj(2,2) - 1.0) / (proj(2,2) + 1.0); + double left = near * (proj(0,2)-1) / proj(0,0); + double right = 2.0 * near / proj(0,0) + left; + double bottom = near * (proj(1,2)-1) / proj(1,1); + double top = 2.0 * near / proj(1,1) + bottom; + + double epsilon = 2.2204460492503131e-16; + + principal_point_[0] = fabs(left-right) < epsilon ? window_size.width * 0.5 : (left * window_size.width) / (left - right); + principal_point_[1] = fabs(top-bottom) < epsilon ? window_size.height * 0.5 : (top * window_size.height) / (top - bottom); + + focal_[0] = -near * principal_point_[0] / left; + focal_[1] = near * principal_point_[1] / top; + + setClip(Vec2d(near, far)); + fov_[0] = atan2(principal_point_[0], focal_[0]) + atan2(window_size.width-principal_point_[0], focal_[0]); + fov_[1] = atan2(principal_point_[1], focal_[1]) + atan2(window_size.height-principal_point_[1], focal_[1]); + + window_size_ = window_size; +} + +void cv::viz::Camera::init(double fx, double fy, double cx, double cy, const Size &window_size) +{ + CV_Assert(window_size.width > 0 && window_size.height > 0); + setClip(Vec2d(0.01, 1000.01));// Default clipping + + fov_[0] = atan2(cx, fx) + atan2(window_size.width - cx, fx); + fov_[1] = atan2(cy, fy) + atan2(window_size.height - cy, fy); + + principal_point_[0] = cx; + principal_point_[1] = cy; + + focal_[0] = fx; + focal_[1] = fy; + + window_size_ = window_size; +} + +void cv::viz::Camera::setWindowSize(const Size &window_size) +{ + CV_Assert(window_size.width > 0 && window_size.height > 0); + + // Get the scale factor and update the principal points + float scalex = static_cast(window_size.width) / static_cast(window_size_.width); + float scaley = static_cast(window_size.height) / static_cast(window_size_.height); + + principal_point_[0] *= scalex; + principal_point_[1] *= scaley; + focal_ *= scaley; + // Vertical field of view is fixed! Update horizontal field of view + fov_[0] = (atan2(principal_point_[0],focal_[0]) + atan2(window_size.width-principal_point_[0],focal_[0])); + + window_size_ = window_size; +} + +void cv::viz::Camera::computeProjectionMatrix(Matx44d &proj) const +{ + double top = clip_[0] * principal_point_[1] / focal_[1]; + double left = -clip_[0] * principal_point_[0] / focal_[0]; + double right = clip_[0] * (window_size_.width - principal_point_[0]) / focal_[0]; + double bottom = -clip_[0] * (window_size_.height - principal_point_[1]) / focal_[1]; + + double temp1 = 2.0 * clip_[0]; + double temp2 = 1.0 / (right - left); + double temp3 = 1.0 / (top - bottom); + double temp4 = 1.0 / (clip_[0] - clip_[1]); + + proj = Matx44d::zeros(); + proj(0,0) = temp1 * temp2; + proj(1,1) = temp1 * temp3; + proj(0,2) = (right + left) * temp2; + proj(1,2) = (top + bottom) * temp3; + proj(2,2) = (clip_[1]+clip_[0]) * temp4; + proj(3,2) = -1.0; + proj(2,3) = (temp1 * clip_[1]) * temp4; +} + +cv::viz::Camera cv::viz::Camera::KinectCamera(const Size &window_size) +{ + Matx33d K(525.0, 0.0, 320.0, 0.0, 525.0, 240.0, 0.0, 0.0, 1.0); + return Camera(K, window_size); +} diff --git a/modules/viz/src/viz3d.cpp b/modules/viz/src/viz3d.cpp new file mode 100644 index 000000000..56f978c0e --- /dev/null +++ b/modules/viz/src/viz3d.cpp @@ -0,0 +1,148 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +cv::viz::Viz3d::Viz3d(const String& window_name) : impl_(0) { create(window_name); } + +cv::viz::Viz3d::Viz3d(const Viz3d& other) : impl_(other.impl_) +{ + if (impl_) + CV_XADD(&impl_->ref_counter, 1); +} + +cv::viz::Viz3d& cv::viz::Viz3d::operator=(const Viz3d& other) +{ + if (this != &other) + { + release(); + impl_ = other.impl_; + if (impl_) + CV_XADD(&impl_->ref_counter, 1); + } + return *this; +} + +cv::viz::Viz3d::~Viz3d() { release(); } + +void cv::viz::Viz3d::create(const String &window_name) +{ + if (impl_) + release(); + + if (VizStorage::windowExists(window_name)) + *this = VizStorage::get(window_name); + else + { + impl_ = new VizImpl(window_name); + impl_->ref_counter = 1; + + // Register the window + VizStorage::add(*this); + } +} + +void cv::viz::Viz3d::release() +{ + if (impl_ && CV_XADD(&impl_->ref_counter, -1) == 1) + { + delete impl_; + impl_ = 0; + } + + if (impl_ && impl_->ref_counter == 1) + VizStorage::removeUnreferenced(); + + impl_ = 0; +} + +void cv::viz::Viz3d::spin() { impl_->spin(); } +void cv::viz::Viz3d::spinOnce(int time, bool force_redraw) { impl_->spinOnce(time, force_redraw); } +bool cv::viz::Viz3d::wasStopped() const { return impl_->wasStopped(); } +void cv::viz::Viz3d::close() { impl_->close(); } + +void cv::viz::Viz3d::registerKeyboardCallback(KeyboardCallback callback, void* cookie) +{ impl_->registerKeyboardCallback(callback, cookie); } + +void cv::viz::Viz3d::registerMouseCallback(MouseCallback callback, void* cookie) +{ impl_->registerMouseCallback(callback, cookie); } + +void cv::viz::Viz3d::showWidget(const String &id, const Widget &widget, const Affine3d &pose) { impl_->showWidget(id, widget, pose); } +void cv::viz::Viz3d::removeWidget(const String &id) { impl_->removeWidget(id); } +cv::viz::Widget cv::viz::Viz3d::getWidget(const String &id) const { return impl_->getWidget(id); } +void cv::viz::Viz3d::removeAllWidgets() { impl_->removeAllWidgets(); } + +void cv::viz::Viz3d::showImage(InputArray image, const Size& window_size) { impl_->showImage(image, window_size); } + +void cv::viz::Viz3d::setWidgetPose(const String &id, const Affine3d &pose) { impl_->setWidgetPose(id, pose); } +void cv::viz::Viz3d::updateWidgetPose(const String &id, const Affine3d &pose) { impl_->updateWidgetPose(id, pose); } +cv::Affine3d cv::viz::Viz3d::getWidgetPose(const String &id) const { return impl_->getWidgetPose(id); } + +void cv::viz::Viz3d::setCamera(const Camera &camera) { impl_->setCamera(camera); } +cv::viz::Camera cv::viz::Viz3d::getCamera() const { return impl_->getCamera(); } +void cv::viz::Viz3d::setViewerPose(const Affine3d &pose) { impl_->setViewerPose(pose); } +cv::Affine3d cv::viz::Viz3d::getViewerPose() { return impl_->getViewerPose(); } + +void cv::viz::Viz3d::resetCameraViewpoint(const String &id) { impl_->resetCameraViewpoint(id); } +void cv::viz::Viz3d::resetCamera() { impl_->resetCamera(); } + +void cv::viz::Viz3d::convertToWindowCoordinates(const Point3d &pt, Point3d &window_coord) { impl_->convertToWindowCoordinates(pt, window_coord); } +void cv::viz::Viz3d::converTo3DRay(const Point3d &window_coord, Point3d &origin, Vec3d &direction) { impl_->converTo3DRay(window_coord, origin, direction); } + +cv::Size cv::viz::Viz3d::getWindowSize() const { return impl_->getWindowSize(); } +void cv::viz::Viz3d::setWindowSize(const Size &window_size) { impl_->setWindowSize(window_size); } +cv::String cv::viz::Viz3d::getWindowName() const { return impl_->getWindowName(); } +void cv::viz::Viz3d::saveScreenshot(const String &file) { impl_->saveScreenshot(file); } +void cv::viz::Viz3d::setWindowPosition(const Point& window_position) { impl_->setWindowPosition(window_position); } +void cv::viz::Viz3d::setFullScreen(bool mode) { impl_->setFullScreen(mode); } +void cv::viz::Viz3d::setBackgroundColor(const Color& color, const Color& color2) { impl_->setBackgroundColor(color, color2); } + +void cv::viz::Viz3d::setBackgroundTexture(InputArray image) { impl_->setBackgroundTexture(image); } +void cv::viz::Viz3d::setBackgroundMeshLab() {impl_->setBackgroundMeshLab(); } + +void cv::viz::Viz3d::setRenderingProperty(const String &id, int property, double value) { getWidget(id).setRenderingProperty(property, value); } +double cv::viz::Viz3d::getRenderingProperty(const String &id, int property) { return getWidget(id).getRenderingProperty(property); } + +void cv::viz::Viz3d::setRepresentation(int representation) { impl_->setRepresentation(representation); } diff --git a/modules/viz/src/vizcore.cpp b/modules/viz/src/vizcore.cpp new file mode 100644 index 000000000..b4ec83bd4 --- /dev/null +++ b/modules/viz/src/vizcore.cpp @@ -0,0 +1,312 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +cv::Affine3d cv::viz::makeTransformToGlobal(const Vec3d& axis_x, const Vec3d& axis_y, const Vec3d& axis_z, const Vec3d& origin) +{ + Affine3d::Mat3 R(axis_x[0], axis_y[0], axis_z[0], + axis_x[1], axis_y[1], axis_z[1], + axis_x[2], axis_y[2], axis_z[2]); + + return Affine3d(R, origin); +} + +cv::Affine3d cv::viz::makeCameraPose(const Vec3d& position, const Vec3d& focal_point, const Vec3d& y_dir) +{ + // Compute the transformation matrix for drawing the camera frame in a scene + Vec3d n = normalize(focal_point - position); + Vec3d u = normalize(y_dir.cross(n)); + Vec3d v = n.cross(u); + + return makeTransformToGlobal(u, v, n, position); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// VizStorage implementation + +cv::viz::VizMap cv::viz::VizStorage::storage; +void cv::viz::VizStorage::unregisterAll() { storage.clear(); } + +cv::viz::Viz3d& cv::viz::VizStorage::get(const String &window_name) +{ + String name = generateWindowName(window_name); + VizMap::iterator vm_itr = storage.find(name); + CV_Assert(vm_itr != storage.end()); + return vm_itr->second; +} + +void cv::viz::VizStorage::add(const Viz3d& window) +{ + String window_name = window.getWindowName(); + VizMap::iterator vm_itr = storage.find(window_name); + CV_Assert(vm_itr == storage.end()); + storage.insert(std::make_pair(window_name, window)); +} + +bool cv::viz::VizStorage::windowExists(const String &window_name) +{ + String name = generateWindowName(window_name); + return storage.find(name) != storage.end(); +} + +void cv::viz::VizStorage::removeUnreferenced() +{ + for(VizMap::iterator pos = storage.begin(); pos != storage.end();) + if(pos->second.impl_->ref_counter == 1) + storage.erase(pos++); + else + ++pos; +} + +cv::String cv::viz::VizStorage::generateWindowName(const String &window_name) +{ + String output = "Viz"; + // Already is Viz + if (window_name == output) + return output; + + String prefixed = output + " - "; + if (window_name.substr(0, prefixed.length()) == prefixed) + output = window_name; // Already has "Viz - " + else if (window_name.substr(0, output.length()) == output) + output = prefixed + window_name; // Doesn't have prefix + else + output = (window_name == "" ? output : prefixed + window_name); + + return output; +} + +cv::viz::Viz3d cv::viz::getWindowByName(const String &window_name) { return Viz3d (window_name); } +void cv::viz::unregisterAllWindows() { VizStorage::unregisterAll(); } + +cv::viz::Viz3d cv::viz::imshow(const String& window_name, InputArray image, const Size& window_size) +{ + Viz3d viz = getWindowByName(window_name); + viz.showImage(image, window_size); + return viz; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Read/write clouds. Supported formats: ply, stl, xyz, obj + +void cv::viz::writeCloud(const String& file, InputArray cloud, InputArray colors, InputArray normals, bool binary) +{ + CV_Assert(file.size() > 4 && "Extention is required"); + String extention = file.substr(file.size()-4); + + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetColorCloudNormals(cloud, colors, normals); + + vtkSmartPointer writer; + if (extention == ".xyz") + { + writer = vtkSmartPointer::New(); + vtkXYZWriter::SafeDownCast(writer)->SetFileName(file.c_str()); + } + else if (extention == ".ply") + { + writer = vtkSmartPointer::New(); + vtkPLYWriter::SafeDownCast(writer)->SetFileName(file.c_str()); + vtkPLYWriter::SafeDownCast(writer)->SetFileType(binary ? VTK_BINARY : VTK_ASCII); + vtkPLYWriter::SafeDownCast(writer)->SetArrayName("Colors"); + } + else if (extention == ".obj") + { + writer = vtkSmartPointer::New(); + vtkOBJWriter::SafeDownCast(writer)->SetFileName(file.c_str()); + } + else + CV_Assert(!"Unsupported format"); + + writer->SetInputConnection(source->GetOutputPort()); + writer->Write(); +} + +cv::Mat cv::viz::readCloud(const String& file, OutputArray colors, OutputArray normals) +{ + CV_Assert(file.size() > 4 && "Extention is required"); + String extention = file.substr(file.size()-4); + + vtkSmartPointer reader; + if (extention == ".xyz") + { + reader = vtkSmartPointer::New(); + vtkSimplePointsReader::SafeDownCast(reader)->SetFileName(file.c_str()); + } + else if (extention == ".ply") + { + reader = vtkSmartPointer::New(); + CV_Assert(vtkPLYReader::CanReadFile(file.c_str())); + vtkPLYReader::SafeDownCast(reader)->SetFileName(file.c_str()); + } + else if (extention == ".obj") + { + reader = vtkSmartPointer::New(); + vtkOBJReader::SafeDownCast(reader)->SetFileName(file.c_str()); + } + else if (extention == ".stl") + { + reader = vtkSmartPointer::New(); + vtkSTLReader::SafeDownCast(reader)->SetFileName(file.c_str()); + } + else + CV_Assert(!"Unsupported format"); + + cv::Mat cloud; + + vtkSmartPointer sink = vtkSmartPointer::New(); + sink->SetInputConnection(reader->GetOutputPort()); + sink->SetOutput(cloud, colors, normals); + sink->Write(); + + return cloud; +} + +cv::viz::Mesh cv::viz::readMesh(const String& file) { return Mesh::load(file); } + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Read/write poses and trajectories + +bool cv::viz::readPose(const String& file, Affine3d& pose, const String& tag) +{ + FileStorage fs(file, FileStorage::READ); + if (!fs.isOpened()) + return false; + + Mat hdr(pose.matrix, false); + fs[tag] >> hdr; + if (hdr.empty() || hdr.cols != pose.matrix.cols || hdr.rows != pose.matrix.rows) + return false; + + hdr.convertTo(pose.matrix, CV_64F); + return true; +} + +void cv::viz::writePose(const String& file, const Affine3d& pose, const String& tag) +{ + FileStorage fs(file, FileStorage::WRITE); + fs << tag << Mat(pose.matrix, false); +} + +void cv::viz::readTrajectory(OutputArray _traj, const String& files_format, int start, int end, const String& tag) +{ + CV_Assert(_traj.kind() == _InputArray::STD_VECTOR || _traj.kind() == _InputArray::MAT); + + start = max(0, std::min(start, end)); + end = std::max(start, end); + + std::vector traj; + + for(int i = start; i < end; ++i) + { + Affine3d affine; + bool ok = readPose(cv::format(files_format.c_str(), i), affine, tag); + if (!ok) + break; + + traj.push_back(affine); + } + + Mat(traj).convertTo(_traj, _traj.depth()); +} + +void cv::viz::writeTrajectory(InputArray _traj, const String& files_format, int start, const String& tag) +{ + if (_traj.kind() == _InputArray::STD_VECTOR_MAT) + { + std::vector& v = *(std::vector*)_traj.obj; + + for(size_t i = 0, index = max(0, start); i < v.size(); ++i, ++index) + { + Affine3d affine; + Mat pose = v[i]; + CV_Assert(pose.type() == CV_32FC(16) || pose.type() == CV_64FC(16)); + pose.copyTo(affine.matrix); + writePose(cv::format(files_format.c_str(), index), affine, tag); + } + return; + } + + if (_traj.kind() == _InputArray::STD_VECTOR || _traj.kind() == _InputArray::MAT) + { + CV_Assert(_traj.type() == CV_32FC(16) || _traj.type() == CV_64FC(16)); + + Mat traj = _traj.getMat(); + + if (traj.depth() == CV_32F) + for(size_t i = 0, index = max(0, start); i < traj.total(); ++i, ++index) + writePose(cv::format(files_format.c_str(), index), traj.at(i), tag); + + if (traj.depth() == CV_64F) + for(size_t i = 0, index = max(0, start); i < traj.total(); ++i, ++index) + writePose(cv::format(files_format.c_str(), index), traj.at(i), tag); + } + + CV_Assert(!"Unsupported array kind"); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// Computing normals for mesh + +void cv::viz::computeNormals(const Mesh& mesh, OutputArray _normals) +{ + vtkSmartPointer polydata = getPolyData(WMesh(mesh)); + vtkSmartPointer with_normals = VtkUtils::ComputeNormals(polydata); + + vtkSmartPointer generic_normals = with_normals->GetPointData()->GetNormals(); + if(generic_normals) + { + Mat normals(1, generic_normals->GetNumberOfTuples(), CV_64FC3); + Vec3d *optr = normals.ptr(); + + for(int i = 0; i < generic_normals->GetNumberOfTuples(); ++i, ++optr) + generic_normals->GetTuple(i, optr->val); + + normals.convertTo(_normals, mesh.cloud.type()); + } + else + _normals.release(); +} diff --git a/modules/viz/src/vizimpl.cpp b/modules/viz/src/vizimpl.cpp new file mode 100644 index 000000000..5fa49e2f9 --- /dev/null +++ b/modules/viz/src/vizimpl.cpp @@ -0,0 +1,542 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + + +///////////////////////////////////////////////////////////////////////////////////////////// +cv::viz::Viz3d::VizImpl::VizImpl(const String &name) : spin_once_state_(false), + window_position_(Vec2i(std::numeric_limits::min())), widget_actor_map_(new WidgetActorMap) +{ + renderer_ = vtkSmartPointer::New(); + window_name_ = VizStorage::generateWindowName(name); + + // Create render window + window_ = vtkSmartPointer::New(); + cv::Vec2i window_size = cv::Vec2i(window_->GetScreenSize()) / 2; + window_->SetSize(window_size.val); + window_->AddRenderer(renderer_); + + // Create the interactor style + style_ = vtkSmartPointer::New(); + style_->setWidgetActorMap(widget_actor_map_); + style_->UseTimersOn(); + style_->Initialize(); + + timer_callback_ = vtkSmartPointer::New(); + exit_callback_ = vtkSmartPointer::New(); + exit_callback_->viz = this; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::TimerCallback::Execute(vtkObject* caller, unsigned long event_id, void* cookie) +{ + if (event_id == vtkCommand::TimerEvent && timer_id == *reinterpret_cast(cookie)) + { + vtkSmartPointer interactor = vtkRenderWindowInteractor::SafeDownCast(caller); + interactor->TerminateApp(); + } +} + +void cv::viz::Viz3d::VizImpl::ExitCallback::Execute(vtkObject*, unsigned long event_id, void*) +{ + if (event_id == vtkCommand::ExitEvent) + { + viz->interactor_->TerminateApp(); + viz->interactor_ = 0; + } +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +bool cv::viz::Viz3d::VizImpl::wasStopped() const +{ + bool stopped = spin_once_state_ ? interactor_ == 0 : false; + spin_once_state_ &= !stopped; + return stopped; +} + +void cv::viz::Viz3d::VizImpl::close() +{ + if (!interactor_) + return; + interactor_->GetRenderWindow()->Finalize(); + interactor_->TerminateApp(); // This tends to close the window... + interactor_ = 0; +} + +void cv::viz::Viz3d::VizImpl::recreateRenderWindow() +{ +#if !defined _MSC_VER + //recreating is workaround for Ubuntu -- a crash in x-server + Vec2i window_size(window_->GetSize()); + int fullscreen = window_->GetFullScreen(); + + window_ = vtkSmartPointer::New(); + if (window_position_[0] != std::numeric_limits::min()) //also workaround + window_->SetPosition(window_position_.val); + + window_->SetSize(window_size.val); + window_->SetFullScreen(fullscreen); + window_->AddRenderer(renderer_); +#endif +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::spin() +{ + recreateRenderWindow(); + interactor_ = vtkSmartPointer::New(); + interactor_->SetRenderWindow(window_); + interactor_->SetInteractorStyle(style_); + window_->AlphaBitPlanesOff(); + window_->PointSmoothingOff(); + window_->LineSmoothingOff(); + window_->PolygonSmoothingOff(); + window_->SwapBuffersOn(); + window_->SetStereoTypeToAnaglyph(); + window_->Render(); + window_->SetWindowName(window_name_.c_str()); + interactor_->Start(); + interactor_ = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::spinOnce(int time, bool force_redraw) +{ + if (interactor_ == 0) + { + spin_once_state_ = true; + recreateRenderWindow(); + interactor_ = vtkSmartPointer::New(); + interactor_->SetRenderWindow(window_); + interactor_->SetInteractorStyle(style_); + interactor_->AddObserver(vtkCommand::TimerEvent, timer_callback_); + interactor_->AddObserver(vtkCommand::ExitEvent, exit_callback_); + window_->AlphaBitPlanesOff(); + window_->PointSmoothingOff(); + window_->LineSmoothingOff(); + window_->PolygonSmoothingOff(); + window_->SwapBuffersOn(); + window_->SetStereoTypeToAnaglyph(); + window_->Render(); + window_->SetWindowName(window_name_.c_str()); + } + + vtkSmartPointer local = interactor_; + + if (force_redraw) + local->Render(); + + timer_callback_->timer_id = local->CreateRepeatingTimer(std::max(1, time)); + local->Start(); + local->DestroyTimer(timer_callback_->timer_id); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::showWidget(const String &id, const Widget &widget, const Affine3d &pose) +{ + WidgetActorMap::iterator wam_itr = widget_actor_map_->find(id); + bool exists = wam_itr != widget_actor_map_->end(); + if (exists) + { + // Remove it if it exists and add it again + removeActorFromRenderer(wam_itr->second); + } + // Get the actor and set the user matrix + vtkProp3D *actor = vtkProp3D::SafeDownCast(WidgetAccessor::getProp(widget)); + if (actor) + { + // If the actor is 3D, apply pose + vtkSmartPointer matrix = vtkmatrix(pose.matrix); + actor->SetUserMatrix(matrix); + actor->Modified(); + } + // If the actor is a vtkFollower, then it should always face the camera + vtkFollower *follower = vtkFollower::SafeDownCast(actor); + if (follower) + { + follower->SetCamera(renderer_->GetActiveCamera()); + } + + renderer_->AddActor(WidgetAccessor::getProp(widget)); + (*widget_actor_map_)[id] = WidgetAccessor::getProp(widget); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::removeWidget(const String &id) +{ + WidgetActorMap::iterator wam_itr = widget_actor_map_->find(id); + bool exists = wam_itr != widget_actor_map_->end(); + CV_Assert("Widget does not exist." && exists); + CV_Assert("Widget could not be removed." && removeActorFromRenderer(wam_itr->second)); + widget_actor_map_->erase(wam_itr); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +cv::viz::Widget cv::viz::Viz3d::VizImpl::getWidget(const String &id) const +{ + WidgetActorMap::const_iterator wam_itr = widget_actor_map_->find(id); + bool exists = wam_itr != widget_actor_map_->end(); + CV_Assert("Widget does not exist." && exists); + + Widget widget; + WidgetAccessor::setProp(widget, wam_itr->second); + return widget; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::setWidgetPose(const String &id, const Affine3d &pose) +{ + WidgetActorMap::iterator wam_itr = widget_actor_map_->find(id); + bool exists = wam_itr != widget_actor_map_->end(); + CV_Assert("Widget does not exist." && exists); + + vtkProp3D *actor = vtkProp3D::SafeDownCast(wam_itr->second); + CV_Assert("Widget is not 3D." && actor); + + vtkSmartPointer matrix = vtkmatrix(pose.matrix); + actor->SetUserMatrix(matrix); + actor->Modified(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::updateWidgetPose(const String &id, const Affine3d &pose) +{ + WidgetActorMap::iterator wam_itr = widget_actor_map_->find(id); + bool exists = wam_itr != widget_actor_map_->end(); + CV_Assert("Widget does not exist." && exists); + + vtkProp3D *actor = vtkProp3D::SafeDownCast(wam_itr->second); + CV_Assert("Widget is not 3D." && actor); + + vtkSmartPointer matrix = actor->GetUserMatrix(); + if (!matrix) + { + setWidgetPose(id, pose); + return ; + } + Affine3d updated_pose = pose * Affine3d(*matrix->Element); + matrix = vtkmatrix(updated_pose.matrix); + + actor->SetUserMatrix(matrix); + actor->Modified(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +cv::Affine3d cv::viz::Viz3d::VizImpl::getWidgetPose(const String &id) const +{ + WidgetActorMap::const_iterator wam_itr = widget_actor_map_->find(id); + bool exists = wam_itr != widget_actor_map_->end(); + CV_Assert("Widget does not exist." && exists); + + vtkProp3D *actor = vtkProp3D::SafeDownCast(wam_itr->second); + CV_Assert("Widget is not 3D." && actor); + + return Affine3d(*actor->GetUserMatrix()->Element); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::saveScreenshot(const String &file) { style_->saveScreenshot(file.c_str()); } + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::registerMouseCallback(MouseCallback callback, void* cookie) +{ style_->registerMouseCallback(callback, cookie); } + +void cv::viz::Viz3d::VizImpl::registerKeyboardCallback(KeyboardCallback callback, void* cookie) +{ style_->registerKeyboardCallback(callback, cookie); } + + +////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::removeAllWidgets() +{ + widget_actor_map_->clear(); + renderer_->RemoveAllViewProps(); +} +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::showImage(InputArray image, const Size& window_size) +{ + removeAllWidgets(); + if (window_size.width > 0 && window_size.height > 0) + setWindowSize(window_size); + + showWidget("showImage", WImageOverlay(image, Rect(Point(0,0), getWindowSize()))); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +bool cv::viz::Viz3d::VizImpl::removeActorFromRenderer(vtkSmartPointer actor) +{ + vtkPropCollection* actors = renderer_->GetViewProps(); + actors->InitTraversal(); + vtkProp* current_actor = NULL; + while ((current_actor = actors->GetNextProp()) != NULL) + if (current_actor == actor) + { + renderer_->RemoveActor(actor); + return true; + } + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::setBackgroundColor(const Color& color, const Color& color2) +{ + Color c = vtkcolor(color), c2 = vtkcolor(color2); + bool gradient = color2[0] >= 0 && color2[1] >= 0 && color2[2] >= 0; + + if (gradient) + { + renderer_->SetBackground(c2.val); + renderer_->SetBackground2(c.val); + renderer_->GradientBackgroundOn(); + } + else + { + renderer_->SetBackground(c.val); + renderer_->GradientBackgroundOff(); + } +} + +void cv::viz::Viz3d::VizImpl::setBackgroundMeshLab() +{ setBackgroundColor(Color(2, 1, 1), Color(240, 120, 120)); } + +////////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::setBackgroundTexture(InputArray image) +{ + if (image.empty()) + { + renderer_->SetBackgroundTexture(0); + renderer_->TexturedBackgroundOff(); + return; + } + + vtkSmartPointer source = vtkSmartPointer::New(); + source->SetImage(image); + + vtkSmartPointer image_flip = vtkSmartPointer::New(); + image_flip->SetFilteredAxis(1); // Vertical flip + image_flip->SetInputConnection(source->GetOutputPort()); + + vtkSmartPointer texture = vtkSmartPointer::New(); + texture->SetInputConnection(image_flip->GetOutputPort()); + //texture->Update(); + + renderer_->SetBackgroundTexture(texture); + renderer_->TexturedBackgroundOn(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::setCamera(const Camera &camera) +{ + vtkSmartPointer active_camera = renderer_->GetActiveCamera(); + + // Set the intrinsic parameters of the camera + window_->SetSize(camera.getWindowSize().width, camera.getWindowSize().height); + double aspect_ratio = static_cast(camera.getWindowSize().width)/static_cast(camera.getWindowSize().height); + + Matx44d proj_mat; + camera.computeProjectionMatrix(proj_mat); + + // Use the intrinsic parameters of the camera to simulate more realistically + vtkSmartPointer vtk_matrix = active_camera->GetProjectionTransformMatrix(aspect_ratio, -1.0, 1.0); + Matx44d old_proj_mat(*vtk_matrix->Element); + + // This is a hack around not being able to set Projection Matrix + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->SetMatrix(vtkmatrix(proj_mat * old_proj_mat.inv())); + active_camera->SetUserTransform(transform); + + renderer_->ResetCameraClippingRange(); + renderer_->Render(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +cv::viz::Camera cv::viz::Viz3d::VizImpl::getCamera() const +{ + vtkSmartPointer active_camera = renderer_->GetActiveCamera(); + + Size window_size(renderer_->GetRenderWindow()->GetSize()[0], + renderer_->GetRenderWindow()->GetSize()[1]); + double aspect_ratio = window_size.width / (double)window_size.height; + + vtkSmartPointer proj_matrix = active_camera->GetProjectionTransformMatrix(aspect_ratio, -1.0f, 1.0f); + return Camera(Matx44d(*proj_matrix->Element), window_size); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::setViewerPose(const Affine3d &pose) +{ + vtkCamera& camera = *renderer_->GetActiveCamera(); + + // Position = extrinsic translation + cv::Vec3d pos_vec = pose.translation(); + + // Rotate the view vector + cv::Matx33d rotation = pose.rotation(); + cv::Vec3d y_axis(0.0, 1.0, 0.0); + cv::Vec3d up_vec(rotation * y_axis); + + // Compute the new focal point + cv::Vec3d z_axis(0.0, 0.0, 1.0); + cv::Vec3d focal_vec = pos_vec + rotation * z_axis; + + camera.SetPosition(pos_vec.val); + camera.SetFocalPoint(focal_vec.val); + camera.SetViewUp(up_vec.val); + + renderer_->ResetCameraClippingRange(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +cv::Affine3d cv::viz::Viz3d::VizImpl::getViewerPose() +{ + vtkCamera& camera = *renderer_->GetActiveCamera(); + + Vec3d pos(camera.GetPosition()); + Vec3d view_up(camera.GetViewUp()); + Vec3d focal(camera.GetFocalPoint()); + + Vec3d y_axis = normalized(view_up); + Vec3d z_axis = normalized(focal - pos); + Vec3d x_axis = normalized(y_axis.cross(z_axis)); + + return makeTransformToGlobal(x_axis, y_axis, z_axis, pos); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::convertToWindowCoordinates(const Point3d &pt, Point3d &window_coord) +{ + Vec3d window_pt; + vtkInteractorObserver::ComputeWorldToDisplay(renderer_, pt.x, pt.y, pt.z, window_pt.val); + window_coord = window_pt; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::converTo3DRay(const Point3d &window_coord, Point3d &origin, Vec3d &direction) +{ + Vec4d world_pt; + vtkInteractorObserver::ComputeDisplayToWorld(renderer_, window_coord.x, window_coord.y, window_coord.z, world_pt.val); + Vec3d cam_pos(renderer_->GetActiveCamera()->GetPosition()); + origin = cam_pos; + direction = normalize(Vec3d(world_pt.val) - cam_pos); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::resetCameraViewpoint(const String &id) +{ + vtkSmartPointer camera_pose; + static WidgetActorMap::iterator it = widget_actor_map_->find(id); + if (it != widget_actor_map_->end()) + { + vtkProp3D *actor = vtkProp3D::SafeDownCast(it->second); + CV_Assert("Widget is not 3D." && actor); + camera_pose = actor->GetUserMatrix(); + } + else + return; + + // Prevent a segfault + if (!camera_pose) return; + + vtkSmartPointer cam = renderer_->GetActiveCamera(); + cam->SetPosition(camera_pose->GetElement(0, 3), + camera_pose->GetElement(1, 3), + camera_pose->GetElement(2, 3)); + + cam->SetFocalPoint(camera_pose->GetElement(0, 3) - camera_pose->GetElement(0, 2), + camera_pose->GetElement(1, 3) - camera_pose->GetElement(1, 2), + camera_pose->GetElement(2, 3) - camera_pose->GetElement(2, 2)); + + cam->SetViewUp(camera_pose->GetElement(0, 1), + camera_pose->GetElement(1, 1), + camera_pose->GetElement(2, 1)); + + renderer_->SetActiveCamera(cam); + renderer_->ResetCameraClippingRange(); + renderer_->Render(); +} + +/////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::resetCamera() +{ + renderer_->ResetCamera(); +} + +/////////////////////////////////////////////////////////////////////////////////// +void cv::viz::Viz3d::VizImpl::setRepresentation(int representation) +{ + vtkActorCollection * actors = renderer_->GetActors(); + actors->InitTraversal(); + vtkActor * actor; + switch (representation) + { + case REPRESENTATION_POINTS: + { + while ((actor = actors->GetNextActor()) != NULL) + actor->GetProperty()->SetRepresentationToPoints(); + break; + } + case REPRESENTATION_SURFACE: + { + while ((actor = actors->GetNextActor()) != NULL) + actor->GetProperty()->SetRepresentationToSurface(); + break; + } + case REPRESENTATION_WIREFRAME: + { + while ((actor = actors->GetNextActor()) != NULL) + actor->GetProperty()->SetRepresentationToWireframe(); + break; + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////// +cv::String cv::viz::Viz3d::VizImpl::getWindowName() const { return window_name_; } +void cv::viz::Viz3d::VizImpl::setFullScreen(bool mode) { window_->SetFullScreen(mode); } +void cv::viz::Viz3d::VizImpl::setWindowPosition(const Point& position) { window_position_ = position; window_->SetPosition(position.x, position.y); } +void cv::viz::Viz3d::VizImpl::setWindowSize(const Size& window_size) { window_->SetSize(window_size.width, window_size.height); } +cv::Size cv::viz::Viz3d::VizImpl::getWindowSize() const { return Size(Point(Vec2i(window_->GetSize()))); } diff --git a/modules/viz/src/vizimpl.hpp b/modules/viz/src/vizimpl.hpp new file mode 100644 index 000000000..9eb918af6 --- /dev/null +++ b/modules/viz/src/vizimpl.hpp @@ -0,0 +1,138 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __OPENCV_VIZ_VIZ3D_IMPL_HPP__ +#define __OPENCV_VIZ_VIZ3D_IMPL_HPP__ + +struct cv::viz::Viz3d::VizImpl +{ +public: + typedef Viz3d::KeyboardCallback KeyboardCallback; + typedef Viz3d::MouseCallback MouseCallback; + + int ref_counter; + + VizImpl(const String &name); + virtual ~VizImpl() {}; + + bool wasStopped() const; + void close(); + + void spin(); + void spinOnce(int time = 1, bool force_redraw = false); + + void showWidget(const String &id, const Widget &widget, const Affine3d &pose = Affine3d::Identity()); + void removeWidget(const String &id); + Widget getWidget(const String &id) const; + void removeAllWidgets(); + + void showImage(InputArray image, const Size& window_size); + + void setWidgetPose(const String &id, const Affine3d &pose); + void updateWidgetPose(const String &id, const Affine3d &pose); + Affine3d getWidgetPose(const String &id) const; + + void setRepresentation(int representation); + + void setCamera(const Camera &camera); + Camera getCamera() const; + + /** \brief Reset the camera to a given widget */ + void resetCameraViewpoint(const String& id); + void resetCamera(); + + void setViewerPose(const Affine3d &pose); + Affine3d getViewerPose(); + + void convertToWindowCoordinates(const Point3d &pt, Point3d &window_coord); + void converTo3DRay(const Point3d &window_coord, Point3d &origin, Vec3d &direction); + + void saveScreenshot(const String &file); + void setWindowPosition(const Point& position); + Size getWindowSize() const; + void setWindowSize(const Size& window_size); + void setFullScreen(bool mode); + String getWindowName() const; + void setBackgroundColor(const Color& color, const Color& color2); + void setBackgroundTexture(InputArray image); + void setBackgroundMeshLab(); + + void registerKeyboardCallback(KeyboardCallback callback, void* cookie = 0); + void registerMouseCallback(MouseCallback callback, void* cookie = 0); + +private: + struct TimerCallback : public vtkCommand + { + static TimerCallback* New() { return new TimerCallback; } + virtual void Execute(vtkObject* caller, unsigned long event_id, void* cookie); + int timer_id; + }; + + struct ExitCallback : public vtkCommand + { + static ExitCallback* New() { return new ExitCallback; } + virtual void Execute(vtkObject*, unsigned long event_id, void*); + VizImpl* viz; + }; + + mutable bool spin_once_state_; + vtkSmartPointer interactor_; + + vtkSmartPointer window_; + String window_name_; + Vec2i window_position_; + + vtkSmartPointer timer_callback_; + vtkSmartPointer exit_callback_; + + vtkSmartPointer renderer_; + vtkSmartPointer style_; + Ptr widget_actor_map_; + + bool removeActorFromRenderer(vtkSmartPointer actor); + void recreateRenderWindow(); +}; + +#endif diff --git a/modules/viz/src/vtk/vtkCloudMatSink.cpp b/modules/viz/src/vtk/vtkCloudMatSink.cpp new file mode 100644 index 000000000..09ef0cca9 --- /dev/null +++ b/modules/viz/src/vtk/vtkCloudMatSink.cpp @@ -0,0 +1,158 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +namespace cv { namespace viz +{ + vtkStandardNewMacro(vtkCloudMatSink); +}} + +cv::viz::vtkCloudMatSink::vtkCloudMatSink() {} +cv::viz::vtkCloudMatSink::~vtkCloudMatSink() {} + +void cv::viz::vtkCloudMatSink::SetOutput(OutputArray _cloud, OutputArray _colors, OutputArray _normals, OutputArray _tcoords) +{ + cloud = _cloud; + colors = _colors; + normals = _normals; + tcoords = _tcoords; +} + +void cv::viz::vtkCloudMatSink::WriteData() +{ + vtkPolyData *input = this->GetInput(); + if (!input) + return; + + vtkSmartPointer points_Data = input->GetPoints(); + + if (cloud.needed() && points_Data) + { + int vtktype = points_Data->GetDataType(); + CV_Assert(vtktype == VTK_FLOAT || vtktype == VTK_DOUBLE); + + cloud.create(1, points_Data->GetNumberOfPoints(), vtktype == VTK_FLOAT ? CV_32FC3 : CV_64FC3); + Vec3d *ddata = cloud.getMat().ptr(); + Vec3f *fdata = cloud.getMat().ptr(); + + if (cloud.depth() == CV_32F) + for(size_t i = 0; i < cloud.total(); ++i) + *fdata++ = Vec3d(points_Data->GetPoint(i)); + + if (cloud.depth() == CV_64F) + for(size_t i = 0; i < cloud.total(); ++i) + *ddata++ = Vec3d(points_Data->GetPoint(i)); + } + else + cloud.release(); + + vtkSmartPointer scalars_data = input->GetPointData() ? input->GetPointData()->GetScalars() : 0; + + if (colors.needed() && scalars_data) + { + int channels = scalars_data->GetNumberOfComponents(); + int vtktype = scalars_data->GetDataType(); + + CV_Assert((channels == 3 || channels == 4) && "Only 3- or 4-channel color data support is implemented"); + CV_Assert(cloud.total() == (size_t)scalars_data->GetNumberOfTuples()); + + Mat buffer(cloud.size(), CV_64FC(channels)); + Vec3d *cptr = buffer.ptr(); + for(size_t i = 0; i < buffer.total(); ++i) + *cptr++ = Vec3d(scalars_data->GetTuple(i)); + + buffer.convertTo(colors, CV_8U, vtktype == VTK_FLOAT || VTK_FLOAT == VTK_DOUBLE ? 255.0 : 1.0); + } + else + colors.release(); + + vtkSmartPointer normals_data = input->GetPointData() ? input->GetPointData()->GetNormals() : 0; + + if (normals.needed() && normals_data) + { + int channels = normals_data->GetNumberOfComponents(); + int vtktype = normals_data->GetDataType(); + + CV_Assert((vtktype == VTK_FLOAT || VTK_FLOAT == VTK_DOUBLE) && (channels == 3 || channels == 4)); + CV_Assert(cloud.total() == (size_t)normals_data->GetNumberOfTuples()); + + Mat buffer(cloud.size(), CV_64FC(channels)); + Vec3d *cptr = buffer.ptr(); + for(size_t i = 0; i < buffer.total(); ++i) + *cptr++ = Vec3d(normals_data->GetTuple(i)); + + buffer.convertTo(normals, vtktype == VTK_FLOAT ? CV_32F : CV_64F); + } + else + normals.release(); + + vtkSmartPointer coords_data = input->GetPointData() ? input->GetPointData()->GetTCoords() : 0; + + if (tcoords.needed() && coords_data) + { + int vtktype = coords_data->GetDataType(); + + CV_Assert(vtktype == VTK_FLOAT || VTK_FLOAT == VTK_DOUBLE); + CV_Assert(cloud.total() == (size_t)coords_data->GetNumberOfTuples()); + + Mat buffer(cloud.size(), CV_64FC2); + Vec2d *cptr = buffer.ptr(); + for(size_t i = 0; i < buffer.total(); ++i) + *cptr++ = Vec2d(coords_data->GetTuple(i)); + + buffer.convertTo(tcoords, vtktype == VTK_FLOAT ? CV_32F : CV_64F); + + } + else + tcoords.release(); +} + +void cv::viz::vtkCloudMatSink::PrintSelf(ostream& os, vtkIndent indent) +{ + Superclass::PrintSelf(os, indent); + os << indent << "Cloud: " << cloud.needed() << "\n"; + os << indent << "Colors: " << colors.needed() << "\n"; + os << indent << "Normals: " << normals.needed() << "\n"; +} diff --git a/modules/viz/src/vtk/vtkCloudMatSink.h b/modules/viz/src/vtk/vtkCloudMatSink.h new file mode 100644 index 000000000..44d7e52a5 --- /dev/null +++ b/modules/viz/src/vtk/vtkCloudMatSink.h @@ -0,0 +1,79 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __vtkCloudMatSink_h +#define __vtkCloudMatSink_h + +#include +#include + +namespace cv +{ + namespace viz + { + class vtkCloudMatSink : public vtkPolyDataWriter + { + public: + static vtkCloudMatSink *New(); + vtkTypeMacro(vtkCloudMatSink,vtkPolyDataWriter) + void PrintSelf(ostream& os, vtkIndent indent); + + void SetOutput(OutputArray cloud, OutputArray colors = noArray(), OutputArray normals = noArray(), OutputArray tcoords = noArray()); + + protected: + vtkCloudMatSink(); + ~vtkCloudMatSink(); + + void WriteData(); + + _OutputArray cloud, colors, normals, tcoords; + + private: + vtkCloudMatSink(const vtkCloudMatSink&); // Not implemented. + void operator=(const vtkCloudMatSink&); // Not implemented. + }; + } +} + +#endif diff --git a/modules/viz/src/vtk/vtkCloudMatSource.cpp b/modules/viz/src/vtk/vtkCloudMatSource.cpp new file mode 100644 index 000000000..74d01bbd0 --- /dev/null +++ b/modules/viz/src/vtk/vtkCloudMatSource.cpp @@ -0,0 +1,286 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +namespace cv { namespace viz +{ + vtkStandardNewMacro(vtkCloudMatSource); + + template struct VtkDepthTraits; + + template<> struct VtkDepthTraits + { + const static int data_type = VTK_FLOAT; + typedef vtkFloatArray array_type; + }; + + template<> struct VtkDepthTraits + { + const static int data_type = VTK_DOUBLE; + typedef vtkDoubleArray array_type; + }; +}} + +cv::viz::vtkCloudMatSource::vtkCloudMatSource() { SetNumberOfInputPorts(0); } +cv::viz::vtkCloudMatSource::~vtkCloudMatSource() {} + +int cv::viz::vtkCloudMatSource::SetCloud(InputArray _cloud) +{ + CV_Assert(_cloud.depth() == CV_32F || _cloud.depth() == CV_64F); + CV_Assert(_cloud.channels() == 3 || _cloud.channels() == 4); + + Mat cloud = _cloud.getMat(); + + int total = _cloud.depth() == CV_32F ? filterNanCopy(cloud) : filterNanCopy(cloud); + + vertices = vtkSmartPointer::New(); + vertices->Allocate(vertices->EstimateSize(1, total)); + vertices->InsertNextCell(total); + for(int i = 0; i < total; ++i) + vertices->InsertCellPoint(i); + + return total; +} + +int cv::viz::vtkCloudMatSource::SetColorCloud(InputArray _cloud, InputArray _colors) +{ + int total = SetCloud(_cloud); + + if (_colors.empty()) + return total; + + CV_Assert(_colors.depth() == CV_8U && _colors.channels() <= 4 && _colors.channels() != 2); + CV_Assert(_colors.size() == _cloud.size()); + + Mat cloud = _cloud.getMat(); + Mat colors = _colors.getMat(); + + if (cloud.depth() == CV_32F) + filterNanColorsCopy(colors, cloud, total); + else if (cloud.depth() == CV_64F) + filterNanColorsCopy(colors, cloud, total); + + return total; +} + +int cv::viz::vtkCloudMatSource::SetColorCloudNormals(InputArray _cloud, InputArray _colors, InputArray _normals) +{ + int total = SetColorCloud(_cloud, _colors); + + if (_normals.empty()) + return total; + + CV_Assert(_normals.depth() == CV_32F || _normals.depth() == CV_64F); + CV_Assert(_normals.channels() == 3 || _normals.channels() == 4); + CV_Assert(_normals.size() == _cloud.size()); + + Mat c = _cloud.getMat(); + Mat n = _normals.getMat(); + + if (n.depth() == CV_32F && c.depth() == CV_32F) + filterNanNormalsCopy(n, c, total); + else if (n.depth() == CV_32F && c.depth() == CV_64F) + filterNanNormalsCopy(n, c, total); + else if (n.depth() == CV_64F && c.depth() == CV_32F) + filterNanNormalsCopy(n, c, total); + else if (n.depth() == CV_64F && c.depth() == CV_64F) + filterNanNormalsCopy(n, c, total); + else + CV_Assert(!"Unsupported normals/cloud type"); + + return total; +} + +int cv::viz::vtkCloudMatSource::SetColorCloudNormalsTCoords(InputArray _cloud, InputArray _colors, InputArray _normals, InputArray _tcoords) +{ + int total = SetColorCloudNormals(_cloud, _colors, _normals); + + if (_tcoords.empty()) + return total; + + CV_Assert(_tcoords.depth() == CV_32F || _tcoords.depth() == CV_64F); + CV_Assert(_tcoords.channels() == 2 && _tcoords.size() == _cloud.size()); + + Mat cl = _cloud.getMat(); + Mat tc = _tcoords.getMat(); + + if (tc.depth() == CV_32F && cl.depth() == CV_32F) + filterNanTCoordsCopy(tc, cl, total); + else if (tc.depth() == CV_32F && cl.depth() == CV_64F) + filterNanTCoordsCopy(tc, cl, total); + else if (tc.depth() == CV_64F && cl.depth() == CV_32F) + filterNanTCoordsCopy(tc, cl, total); + else if (tc.depth() == CV_64F && cl.depth() == CV_64F) + filterNanTCoordsCopy(tc, cl, total); + else + CV_Assert(!"Unsupported tcoords/cloud type"); + + return total; +} + +int cv::viz::vtkCloudMatSource::RequestData(vtkInformation *vtkNotUsed(request), vtkInformationVector **vtkNotUsed(inputVector), vtkInformationVector *outputVector) +{ + vtkInformation *outInfo = outputVector->GetInformationObject(0); + vtkPolyData *output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + output->SetPoints(points); + output->SetVerts(vertices); + if (scalars) + output->GetPointData()->SetScalars(scalars); + + if (normals) + output->GetPointData()->SetNormals(normals); + + if (tcoords) + output->GetPointData()->SetTCoords(tcoords); + + return 1; +} + +template +int cv::viz::vtkCloudMatSource::filterNanCopy(const Mat& cloud) +{ + CV_DbgAssert(DataType<_Tp>::depth == cloud.depth()); + points = vtkSmartPointer::New(); + points->SetDataType(VtkDepthTraits<_Tp>::data_type); + points->Allocate(cloud.total()); + points->SetNumberOfPoints(cloud.total()); + + int s_chs = cloud.channels(); + int total = 0; + for (int y = 0; y < cloud.rows; ++y) + { + const _Tp* srow = cloud.ptr<_Tp>(y); + const _Tp* send = srow + cloud.cols * s_chs; + + for (; srow != send; srow += s_chs) + if (!isNan(srow)) + points->SetPoint(total++, srow); + } + points->SetNumberOfPoints(total); + points->Squeeze(); + return total; +} + +template +void cv::viz::vtkCloudMatSource::filterNanColorsCopy(const Mat& cloud_colors, const Mat& mask, int total) +{ + Vec3b* array = new Vec3b[total]; + Vec3b* pos = array; + + int s_chs = cloud_colors.channels(); + int m_chs = mask.channels(); + for (int y = 0; y < cloud_colors.rows; ++y) + { + const unsigned char* srow = cloud_colors.ptr(y); + const unsigned char* send = srow + cloud_colors.cols * s_chs; + const _Msk* mrow = mask.ptr<_Msk>(y); + + if (cloud_colors.channels() == 1) + { + for (; srow != send; srow += s_chs, mrow += m_chs) + if (!isNan(mrow)) + *pos++ = Vec3b(srow[0], srow[0], srow[0]); + } + else + for (; srow != send; srow += s_chs, mrow += m_chs) + if (!isNan(mrow)) + *pos++ = Vec3b(srow[2], srow[1], srow[0]); + + } + + scalars = vtkSmartPointer::New(); + scalars->SetName("Colors"); + scalars->SetNumberOfComponents(3); + scalars->SetNumberOfTuples(total); + scalars->SetArray(array->val, total * 3, 0); +} + +template +void cv::viz::vtkCloudMatSource::filterNanNormalsCopy(const Mat& cloud_normals, const Mat& mask, int total) +{ + normals = vtkSmartPointer< typename VtkDepthTraits<_Tn>::array_type >::New(); + normals->SetName("Normals"); + normals->SetNumberOfComponents(3); + normals->SetNumberOfTuples(total); + + int s_chs = cloud_normals.channels(); + int m_chs = mask.channels(); + + int pos = 0; + for (int y = 0; y < cloud_normals.rows; ++y) + { + const _Tn* srow = cloud_normals.ptr<_Tn>(y); + const _Tn* send = srow + cloud_normals.cols * s_chs; + + const _Msk* mrow = mask.ptr<_Msk>(y); + + for (; srow != send; srow += s_chs, mrow += m_chs) + if (!isNan(mrow)) + normals->SetTuple(pos++, srow); + } +} + +template +void cv::viz::vtkCloudMatSource::filterNanTCoordsCopy(const Mat& _tcoords, const Mat& mask, int total) +{ + typedef Vec<_Tn, 2> Vec2; + tcoords = vtkSmartPointer< typename VtkDepthTraits<_Tn>::array_type >::New(); + tcoords->SetName("TextureCoordinates"); + tcoords->SetNumberOfComponents(2); + tcoords->SetNumberOfTuples(total); + + int pos = 0; + for (int y = 0; y < mask.rows; ++y) + { + const Vec2* srow = _tcoords.ptr(y); + const Vec2* send = srow + _tcoords.cols; + const _Msk* mrow = mask.ptr<_Msk>(y); + + for (; srow != send; ++srow, mrow += mask.channels()) + if (!isNan(mrow)) + tcoords->SetTuple(pos++, srow->val); + } +} diff --git a/modules/viz/src/vtk/vtkCloudMatSource.h b/modules/viz/src/vtk/vtkCloudMatSource.h new file mode 100644 index 000000000..4097f9cc8 --- /dev/null +++ b/modules/viz/src/vtk/vtkCloudMatSource.h @@ -0,0 +1,96 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __vtkCloudMatSource_h +#define __vtkCloudMatSource_h + +#include +#include +#include +#include +#include + +namespace cv +{ + namespace viz + { + class vtkCloudMatSource : public vtkPolyDataAlgorithm + { + public: + static vtkCloudMatSource *New(); + vtkTypeMacro(vtkCloudMatSource,vtkPolyDataAlgorithm) + + virtual int SetCloud(InputArray cloud); + virtual int SetColorCloud(InputArray cloud, InputArray colors); + virtual int SetColorCloudNormals(InputArray cloud, InputArray colors, InputArray normals); + virtual int SetColorCloudNormalsTCoords(InputArray cloud, InputArray colors, InputArray normals, InputArray tcoords); + + protected: + vtkCloudMatSource(); + ~vtkCloudMatSource(); + + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *); + + vtkSmartPointer points; + vtkSmartPointer vertices; + vtkSmartPointer scalars; + vtkSmartPointer normals; + vtkSmartPointer tcoords; + private: + vtkCloudMatSource(const vtkCloudMatSource&); // Not implemented. + void operator=(const vtkCloudMatSource&); // Not implemented. + + template int filterNanCopy(const Mat& cloud); + template void filterNanColorsCopy(const Mat& cloud_colors, const Mat& mask, int total); + + template + void filterNanNormalsCopy(const Mat& cloud_normals, const Mat& mask, int total); + + template + void filterNanTCoordsCopy(const Mat& tcoords, const Mat& mask, int total); + }; + } +} + +#endif diff --git a/modules/viz/src/vtk/vtkImageMatSource.cpp b/modules/viz/src/vtk/vtkImageMatSource.cpp new file mode 100644 index 000000000..58a5642d4 --- /dev/null +++ b/modules/viz/src/vtk/vtkImageMatSource.cpp @@ -0,0 +1,143 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +namespace cv { namespace viz +{ + vtkStandardNewMacro(vtkImageMatSource); +}} + +cv::viz::vtkImageMatSource::vtkImageMatSource() +{ + this->SetNumberOfInputPorts(0); + this->ImageData = vtkImageData::New(); +} + +int cv::viz::vtkImageMatSource::RequestInformation(vtkInformation *, vtkInformationVector**, vtkInformationVector *outputVector) +{ + vtkInformation* outInfo = outputVector->GetInformationObject(0); + + outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), this->ImageData->GetExtent(), 6); + outInfo->Set(vtkDataObject::SPACING(), 1.0, 1.0, 1.0); + outInfo->Set(vtkDataObject::ORIGIN(), 0.0, 0.0, 0.0); + + vtkDataObject::SetPointDataActiveScalarInfo(outInfo, this->ImageData->GetScalarType(), this->ImageData->GetNumberOfScalarComponents()); + return 1; +} + +int cv::viz::vtkImageMatSource::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector *outputVector) +{ + vtkInformation *outInfo = outputVector->GetInformationObject(0); + + vtkImageData *output = vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()) ); + output->ShallowCopy(this->ImageData); + return 1; +} + +void cv::viz::vtkImageMatSource::SetImage(InputArray _image) +{ + CV_Assert(_image.depth() == CV_8U && (_image.channels() == 1 || _image.channels() == 3 || _image.channels() == 4)); + + Mat image = _image.getMat(); + + this->ImageData->SetDimensions(image.cols, image.rows, 1); +#if VTK_MAJOR_VERSION <= 5 + this->ImageData->SetNumberOfScalarComponents(image.channels()); + this->ImageData->SetScalarTypeToUnsignedChar(); + this->ImageData->AllocateScalars(); +#else + this->ImageData->AllocateScalars(VTK_UNSIGNED_CHAR, image.channels()); +#endif + + switch(image.channels()) + { + case 1: copyGrayImage(image, this->ImageData); break; + case 3: copyRGBImage (image, this->ImageData); break; + case 4: copyRGBAImage(image, this->ImageData); break; + } + this->ImageData->Modified(); +} + +void cv::viz::vtkImageMatSource::copyGrayImage(const Mat &source, vtkSmartPointer output) +{ + unsigned char* dptr = reinterpret_cast(output->GetScalarPointer()); + size_t elem_step = output->GetIncrements()[1]/sizeof(unsigned char); + + for (int y = 0; y < source.rows; ++y) + { + unsigned char* drow = dptr + elem_step * y; + const unsigned char *srow = source.ptr(y); + for (int x = 0; x < source.cols; ++x) + drow[x] = *srow++; + } +} + +void cv::viz::vtkImageMatSource::copyRGBImage(const Mat &source, vtkSmartPointer output) +{ + Vec3b* dptr = reinterpret_cast(output->GetScalarPointer()); + size_t elem_step = output->GetIncrements()[1]/sizeof(Vec3b); + + for (int y = 0; y < source.rows; ++y) + { + Vec3b* drow = dptr + elem_step * y; + const unsigned char *srow = source.ptr(y); + for (int x = 0; x < source.cols; ++x, srow += source.channels()) + drow[x] = Vec3b(srow[2], srow[1], srow[0]); + } +} + +void cv::viz::vtkImageMatSource::copyRGBAImage(const Mat &source, vtkSmartPointer output) +{ + Vec4b* dptr = reinterpret_cast(output->GetScalarPointer()); + size_t elem_step = output->GetIncrements()[1]/sizeof(Vec4b); + + for (int y = 0; y < source.rows; ++y) + { + Vec4b* drow = dptr + elem_step * y; + const unsigned char *srow = source.ptr(y); + for (int x = 0; x < source.cols; ++x, srow += source.channels()) + drow[x] = Vec4b(srow[2], srow[1], srow[0], srow[3]); + } +} diff --git a/modules/viz/src/vtk/vtkImageMatSource.h b/modules/viz/src/vtk/vtkImageMatSource.h new file mode 100644 index 000000000..db0c093ed --- /dev/null +++ b/modules/viz/src/vtk/vtkImageMatSource.h @@ -0,0 +1,82 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +#ifndef __vtkImageMatSource_h +#define __vtkImageMatSource_h + +namespace cv +{ + namespace viz + { + class vtkImageMatSource : public vtkImageAlgorithm + { + public: + static vtkImageMatSource *New(); + vtkTypeMacro(vtkImageMatSource,vtkImageAlgorithm); + + void SetImage(InputArray image); + + protected: + vtkImageMatSource(); + ~vtkImageMatSource() {} + + vtkSmartPointer ImageData; + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*); + int RequestData (vtkInformation*, vtkInformationVector**, vtkInformationVector*); + private: + vtkImageMatSource(const vtkImageMatSource&); // Not implemented. + void operator=(const vtkImageMatSource&); // Not implemented. + + static void copyGrayImage(const Mat &source, vtkSmartPointer output); + static void copyRGBImage (const Mat &source, vtkSmartPointer output); + static void copyRGBAImage(const Mat &source, vtkSmartPointer output); + }; + } +} + + +#endif diff --git a/modules/viz/src/vtk/vtkOBJWriter.cpp b/modules/viz/src/vtk/vtkOBJWriter.cpp new file mode 100644 index 000000000..452ad19a7 --- /dev/null +++ b/modules/viz/src/vtk/vtkOBJWriter.cpp @@ -0,0 +1,241 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +namespace cv { namespace viz +{ + vtkStandardNewMacro(vtkOBJWriter); +}} + +cv::viz::vtkOBJWriter::vtkOBJWriter() +{ + std::ofstream fout; // only used to extract the default precision + this->DecimalPrecision = fout.precision(); + this->FileName = NULL; + this->FileType = VTK_ASCII; +} + +cv::viz::vtkOBJWriter::~vtkOBJWriter(){} + +void cv::viz::vtkOBJWriter::WriteData() +{ + vtkPolyData *input = this->GetInput(); + if (!input) + return; + + std::ostream *outfilep = this->OpenVTKFile(); + if (!outfilep) + return; + + std::ostream& outfile = *outfilep; + + //write header + outfile << "# wavefront obj file written by the visualization toolkit" << std::endl << std::endl; + outfile << "mtllib NONE" << std::endl << std::endl; + + // write out the points + for (int i = 0; i < input->GetNumberOfPoints(); i++) + { + Vec3d p; + input->GetPoint(i, p.val); + outfile << std::setprecision(this->DecimalPrecision) << "v " << p[0] << " " << p[1] << " " << p[2] << std::endl; + } + + const int idStart = 1; + + // write out the point data + vtkSmartPointer normals = input->GetPointData()->GetNormals(); + if(normals) + { + for (int i = 0; i < normals->GetNumberOfTuples(); i++) + { + Vec3d p; + normals->GetTuple(i, p.val); + outfile << std::setprecision(this->DecimalPrecision) << "vn " << p[0] << " " << p[1] << " " << p[2] << std::endl; + } + } + + vtkSmartPointer tcoords = input->GetPointData()->GetTCoords(); + if (tcoords) + { + for (int i = 0; i < tcoords->GetNumberOfTuples(); i++) + { + Vec2d p; + tcoords->GetTuple(i, p.val); + outfile << std::setprecision(this->DecimalPrecision) << "vt " << p[0] << " " << p[1] << std::endl; + } + } + + // write out a group name and material + outfile << std::endl << "g grp" << idStart << std::endl; + outfile << "usemtl mtlNONE" << std::endl; + + // write out verts if any + if (input->GetNumberOfVerts() > 0) + { + vtkIdType npts = 0, *index = 0; + vtkCellArray *cells = input->GetVerts(); + for (cells->InitTraversal(); cells->GetNextCell(npts, index); ) + { + outfile << "p "; + for (int i = 0; i < npts; i++) + outfile << index[i] + idStart << " "; + outfile << std::endl; + } + } + + // write out lines if any + if (input->GetNumberOfLines() > 0) + { + vtkIdType npts = 0, *index = 0; + vtkCellArray *cells = input->GetLines(); + for (cells->InitTraversal(); cells->GetNextCell(npts, index); ) + { + outfile << "l "; + if (tcoords) + { + for (int i = 0; i < npts; i++) + outfile << index[i] + idStart << "/" << index[i] + idStart << " "; + } + else + for (int i = 0; i < npts; i++) + outfile << index[i] + idStart << " "; + + outfile << std::endl; + } + } + + // write out polys if any + if (input->GetNumberOfPolys() > 0) + { + vtkIdType npts = 0, *index = 0; + vtkCellArray *cells = input->GetPolys(); + for (cells->InitTraversal(); cells->GetNextCell(npts, index); ) + { + outfile << "f "; + for (int i = 0; i < npts; i++) + { + if (normals) + { + if (tcoords) + outfile << index[i] + idStart << "/" << index[i] + idStart << "/" << index[i] + idStart << " "; + else + outfile << index[i] + idStart << "//" << index[i] + idStart << " "; + } + else + { + if (tcoords) + outfile << index[i] + idStart << " " << index[i] + idStart << " "; + else + outfile << index[i] + idStart << " "; + } + } + outfile << std::endl; + } + } + + // write out tstrips if any + if (input->GetNumberOfStrips() > 0) + { + vtkIdType npts = 0, *index = 0; + vtkCellArray *cells = input->GetStrips(); + for (cells->InitTraversal(); cells->GetNextCell(npts, index); ) + { + for (int i = 2, i1, i2; i < npts; ++i) + { + if (i % 2) + { + i1 = i - 1; + i2 = i - 2; + } + else + { + i1 = i - 1; + i2 = i - 2; + } + + if(normals) + { + if (tcoords) + { + outfile << "f " << index[i1] + idStart << "/" << index[i1] + idStart << "/" << index[i1] + idStart << " " + << index[i2]+ idStart << "/" << index[i2] + idStart << "/" << index[i2] + idStart << " " + << index[i] + idStart << "/" << index[i] + idStart << "/" << index[i] + idStart << std::endl; + } + else + { + outfile << "f " << index[i1] + idStart << "//" << index[i1] + idStart << " " << index[i2] + idStart + << "//" << index[i2] + idStart << " " << index[i] + idStart << "//" << index[i] + idStart << std::endl; + } + } + else + { + if (tcoords) + { + outfile << "f " << index[i1] + idStart << "/" << index[i1] + idStart << " " << index[i2] + idStart + << "/" << index[i2] + idStart << " " << index[i] + idStart << "/" << index[i] + idStart << std::endl; + } + else + outfile << "f " << index[i1] + idStart << " " << index[i2] + idStart << " " << index[i] + idStart << std::endl; + } + } /* for (int i = 2; i < npts; ++i) */ + } + } /* if (input->GetNumberOfStrips() > 0) */ + + this->CloseVTKFile(outfilep); + + // Delete the file if an error occurred + if (this->ErrorCode == vtkErrorCode::OutOfDiskSpaceError) + { + vtkErrorMacro("Ran out of disk space; deleting file: " << this->FileName); + unlink(this->FileName); + } +} + +void cv::viz::vtkOBJWriter::PrintSelf(ostream& os, vtkIndent indent) +{ + Superclass::PrintSelf(os, indent); + os << indent << "DecimalPrecision: " << DecimalPrecision << "\n"; +} diff --git a/modules/viz/src/vtk/vtkOBJWriter.h b/modules/viz/src/vtk/vtkOBJWriter.h new file mode 100644 index 000000000..f8889884d --- /dev/null +++ b/modules/viz/src/vtk/vtkOBJWriter.h @@ -0,0 +1,79 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __vtkOBJWriter_h +#define __vtkOBJWriter_h + +#include + +namespace cv +{ + namespace viz + { + class vtkOBJWriter : public vtkPolyDataWriter + { + public: + static vtkOBJWriter *New(); + vtkTypeMacro(vtkOBJWriter,vtkPolyDataWriter) + void PrintSelf(ostream& os, vtkIndent indent); + + vtkGetMacro(DecimalPrecision, int); + vtkSetMacro(DecimalPrecision, int); + + protected: + vtkOBJWriter(); + ~vtkOBJWriter(); + + void WriteData(); + + int DecimalPrecision; + + private: + vtkOBJWriter(const vtkOBJWriter&); // Not implemented. + void operator=(const vtkOBJWriter&); // Not implemented. + }; + } +} + +#endif diff --git a/modules/viz/src/vtk/vtkTrajectorySource.cpp b/modules/viz/src/vtk/vtkTrajectorySource.cpp new file mode 100644 index 000000000..e098a1d55 --- /dev/null +++ b/modules/viz/src/vtk/vtkTrajectorySource.cpp @@ -0,0 +1,110 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +namespace cv { namespace viz +{ + vtkStandardNewMacro(vtkTrajectorySource); +}} + +cv::viz::vtkTrajectorySource::vtkTrajectorySource() { SetNumberOfInputPorts(0); } +cv::viz::vtkTrajectorySource::~vtkTrajectorySource() {} + +void cv::viz::vtkTrajectorySource::SetTrajectory(InputArray _traj) +{ + CV_Assert(_traj.kind() == _InputArray::STD_VECTOR || _traj.kind() == _InputArray::MAT); + CV_Assert(_traj.type() == CV_32FC(16) || _traj.type() == CV_64FC(16)); + + Mat traj; + _traj.getMat().convertTo(traj, CV_64F); + const Affine3d* dpath = traj.ptr(); + size_t total = traj.total(); + + points = vtkSmartPointer::New(); + points->SetDataType(VTK_DOUBLE); + points->SetNumberOfPoints(total); + + tensors = vtkSmartPointer::New(); + tensors->SetNumberOfComponents(9); + tensors->SetNumberOfTuples(total); + + for(size_t i = 0; i < total; ++i, ++dpath) + { + Matx33d R = dpath->rotation().t(); // transposed because of + tensors->SetTuple(i, R.val); // column major order + + Vec3d p = dpath->translation(); + points->SetPoint(i, p.val); + } +} + +cv::Mat cv::viz::vtkTrajectorySource::ExtractPoints(InputArray _traj) +{ + CV_Assert(_traj.kind() == _InputArray::STD_VECTOR || _traj.kind() == _InputArray::MAT); + CV_Assert(_traj.type() == CV_32FC(16) || _traj.type() == CV_64FC(16)); + + Mat points(1, _traj.total(), CV_MAKETYPE(_traj.depth(), 3)); + const Affine3d* dpath = _traj.getMat().ptr(); + const Affine3f* fpath = _traj.getMat().ptr(); + + if (_traj.depth() == CV_32F) + for(int i = 0; i < points.cols; ++i) + points.at(i) = fpath[i].translation(); + + if (_traj.depth() == CV_64F) + for(int i = 0; i < points.cols; ++i) + points.at(i) = dpath[i].translation(); + + return points; +} + +int cv::viz::vtkTrajectorySource::RequestData(vtkInformation *vtkNotUsed(request), vtkInformationVector **vtkNotUsed(inputVector), vtkInformationVector *outputVector) +{ + vtkInformation *outInfo = outputVector->GetInformationObject(0); + vtkPolyData *output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + output->SetPoints(points); + output->GetPointData()->SetTensors(tensors); + return 1; +} diff --git a/modules/viz/src/vtk/vtkTrajectorySource.h b/modules/viz/src/vtk/vtkTrajectorySource.h new file mode 100644 index 000000000..f6c9c77b9 --- /dev/null +++ b/modules/viz/src/vtk/vtkTrajectorySource.h @@ -0,0 +1,84 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __vtkTrajectorySource_h +#define __vtkTrajectorySource_h + +#include +#include +#include +#include +#include + +namespace cv +{ + namespace viz + { + class vtkTrajectorySource : public vtkPolyDataAlgorithm + { + public: + static vtkTrajectorySource *New(); + vtkTypeMacro(vtkTrajectorySource,vtkPolyDataAlgorithm) + + virtual void SetTrajectory(InputArray trajectory); + + static Mat ExtractPoints(InputArray trajectory); + + protected: + vtkTrajectorySource(); + ~vtkTrajectorySource(); + + vtkSmartPointer points; + vtkSmartPointer tensors; + + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *); + private: + vtkTrajectorySource(const vtkTrajectorySource&); // Not implemented. + void operator=(const vtkTrajectorySource&); // Not implemented. + + }; + } +} + +#endif diff --git a/modules/viz/src/vtk/vtkXYZWriter.cpp b/modules/viz/src/vtk/vtkXYZWriter.cpp new file mode 100644 index 000000000..4518a0103 --- /dev/null +++ b/modules/viz/src/vtk/vtkXYZWriter.cpp @@ -0,0 +1,93 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +namespace cv { namespace viz +{ + vtkStandardNewMacro(vtkXYZWriter); +}} + +cv::viz::vtkXYZWriter::vtkXYZWriter() +{ + std::ofstream fout; // only used to extract the default precision + this->DecimalPrecision = fout.precision(); +} + +void cv::viz::vtkXYZWriter::WriteData() +{ + vtkPolyData *input = this->GetInput(); + if (!input) + return; + + // OpenVTKFile() will report any errors that happen + ostream *outfilep = this->OpenVTKFile(); + if (!outfilep) + return; + + ostream &outfile = *outfilep; + + for(vtkIdType i = 0; i < input->GetNumberOfPoints(); ++i) + { + Vec3d p; + input->GetPoint(i, p.val); + outfile << std::setprecision(this->DecimalPrecision) << p[0] << " " << p[1] << " " << p[2] << std::endl; + } + + // Close the file + this->CloseVTKFile(outfilep); + + // Delete the file if an error occurred + if (this->ErrorCode == vtkErrorCode::OutOfDiskSpaceError) + { + vtkErrorMacro("Ran out of disk space; deleting file: " << this->FileName); + unlink(this->FileName); + } +} + +void cv::viz::vtkXYZWriter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os,indent); + os << indent << "DecimalPrecision: " << this->DecimalPrecision << "\n"; +} diff --git a/modules/viz/src/vtk/vtkXYZWriter.h b/modules/viz/src/vtk/vtkXYZWriter.h new file mode 100644 index 000000000..3db18b793 --- /dev/null +++ b/modules/viz/src/vtk/vtkXYZWriter.h @@ -0,0 +1,78 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifndef __vtkXYZWriter_h +#define __vtkXYZWriter_h + +#include "vtkPolyDataWriter.h" + +namespace cv +{ + namespace viz + { + class vtkXYZWriter : public vtkPolyDataWriter + { + public: + static vtkXYZWriter *New(); + vtkTypeMacro(vtkXYZWriter,vtkPolyDataWriter) + void PrintSelf(ostream& os, vtkIndent indent); + + vtkGetMacro(DecimalPrecision, int) + vtkSetMacro(DecimalPrecision, int) + + protected: + vtkXYZWriter(); + ~vtkXYZWriter(){} + + void WriteData(); + + int DecimalPrecision; + + private: + vtkXYZWriter(const vtkXYZWriter&); // Not implemented. + void operator=(const vtkXYZWriter&); // Not implemented. + }; + } +} +#endif diff --git a/modules/viz/src/widget.cpp b/modules/viz/src/widget.cpp new file mode 100644 index 000000000..33b467ebc --- /dev/null +++ b/modules/viz/src/widget.cpp @@ -0,0 +1,327 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#include "precomp.hpp" + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// widget implementation + +class cv::viz::Widget::Impl +{ +public: + vtkSmartPointer prop; + Impl() : prop(0) {} +}; + +cv::viz::Widget::Widget() : impl_( new Impl() ) { } + +cv::viz::Widget::Widget(const Widget& other) : impl_( new Impl() ) +{ + if (other.impl_ && other.impl_->prop) + impl_->prop = other.impl_->prop; +} + +cv::viz::Widget& cv::viz::Widget::operator=(const Widget& other) +{ + if (!impl_) + impl_ = new Impl(); + + if (other.impl_) + impl_->prop = other.impl_->prop; + return *this; +} + +cv::viz::Widget::~Widget() +{ + if (impl_) + { + delete impl_; + impl_ = 0; + } +} + +cv::viz::Widget cv::viz::Widget::fromPlyFile(const String &file_name) +{ + CV_Assert(vtkPLYReader::CanReadFile(file_name.c_str())); + + vtkSmartPointer reader = vtkSmartPointer::New(); + reader->SetFileName(file_name.c_str()); + + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetInputConnection( reader->GetOutputPort() ); + mapper->ImmediateModeRenderingOff(); + + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->GetProperty()->SetInterpolationToFlat(); + actor->GetProperty()->BackfaceCullingOn(); + actor->SetMapper(mapper); + + Widget widget; + WidgetAccessor::setProp(widget, actor); + return widget; +} + +void cv::viz::Widget::setRenderingProperty(int property, double value) +{ + vtkActor *actor = vtkActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget type is not supported." && actor); + + switch (property) + { + case POINT_SIZE: actor->GetProperty()->SetPointSize(float(value)); break; + case OPACITY: actor->GetProperty()->SetOpacity(value); break; + case LINE_WIDTH: actor->GetProperty()->SetLineWidth(float(value)); break; + case IMMEDIATE_RENDERING: actor->GetMapper()->SetImmediateModeRendering(int(value)); break; + case FONT_SIZE: + { + vtkTextActor* text_actor = vtkTextActor::SafeDownCast(actor); + CV_Assert("Widget does not have text content." && text_actor); + text_actor->GetTextProperty()->SetFontSize(int(value)); + break; + } + case REPRESENTATION: + { + switch (int(value)) + { + case REPRESENTATION_POINTS: actor->GetProperty()->SetRepresentationToPoints(); break; + case REPRESENTATION_WIREFRAME: actor->GetProperty()->SetRepresentationToWireframe(); break; + case REPRESENTATION_SURFACE: actor->GetProperty()->SetRepresentationToSurface(); break; + } + break; + } + case SHADING: + { + switch (int(value)) + { + case SHADING_FLAT: actor->GetProperty()->SetInterpolationToFlat(); break; + case SHADING_GOURAUD: + { + if (!actor->GetMapper()->GetInput()->GetPointData()->GetNormals()) + { + vtkSmartPointer mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + CV_Assert("Can't set shading property for such type of widget" && mapper); + + vtkSmartPointer with_normals = VtkUtils::ComputeNormals(mapper->GetInput()); + VtkUtils::SetInputData(mapper, with_normals); + } + actor->GetProperty()->SetInterpolationToGouraud(); + break; + } + case SHADING_PHONG: + { + if (!actor->GetMapper()->GetInput()->GetPointData()->GetNormals()) + { + vtkSmartPointer mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + CV_Assert("Can't set shading property for such type of widget" && mapper); + + vtkSmartPointer with_normals = VtkUtils::ComputeNormals(mapper->GetInput()); + VtkUtils::SetInputData(mapper, with_normals); + } + actor->GetProperty()->SetInterpolationToPhong(); + break; + } + } + break; + } + default: + CV_Assert("setPointCloudRenderingProperties: Unknown property"); + } + actor->Modified(); +} + +double cv::viz::Widget::getRenderingProperty(int property) const +{ + vtkActor *actor = vtkActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget type is not supported." && actor); + + double value = 0.0; + switch (property) + { + case POINT_SIZE: value = actor->GetProperty()->GetPointSize(); break; + case OPACITY: value = actor->GetProperty()->GetOpacity(); break; + case LINE_WIDTH: value = actor->GetProperty()->GetLineWidth(); break; + case IMMEDIATE_RENDERING: value = actor->GetMapper()->GetImmediateModeRendering(); break; + + case FONT_SIZE: + { + vtkTextActor* text_actor = vtkTextActor::SafeDownCast(actor); + CV_Assert("Widget does not have text content." && text_actor); + value = text_actor->GetTextProperty()->GetFontSize();; + break; + } + case REPRESENTATION: + { + switch (actor->GetProperty()->GetRepresentation()) + { + case VTK_POINTS: value = REPRESENTATION_POINTS; break; + case VTK_WIREFRAME: value = REPRESENTATION_WIREFRAME; break; + case VTK_SURFACE: value = REPRESENTATION_SURFACE; break; + } + break; + } + case SHADING: + { + switch (actor->GetProperty()->GetInterpolation()) + { + case VTK_FLAT: value = SHADING_FLAT; break; + case VTK_GOURAUD: value = SHADING_GOURAUD; break; + case VTK_PHONG: value = SHADING_PHONG; break; + } + break; + } + default: + CV_Assert("getPointCloudRenderingProperties: Unknown property"); + } + return value; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// widget accessor implementaion + +vtkSmartPointer cv::viz::WidgetAccessor::getProp(const Widget& widget) +{ + return widget.impl_->prop; +} + +void cv::viz::WidgetAccessor::setProp(Widget& widget, vtkSmartPointer prop) +{ + widget.impl_->prop = prop; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// widget3D implementation + +void cv::viz::Widget3D::setPose(const Affine3d &pose) +{ + vtkProp3D *actor = vtkProp3D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget is not 3D." && actor); + + vtkSmartPointer matrix = vtkmatrix(pose.matrix); + actor->SetUserMatrix(matrix); + actor->Modified(); +} + +void cv::viz::Widget3D::updatePose(const Affine3d &pose) +{ + vtkProp3D *actor = vtkProp3D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget is not 3D." && actor); + + vtkSmartPointer matrix = actor->GetUserMatrix(); + if (!matrix) + { + setPose(pose); + return; + } + + Affine3d updated_pose = pose * Affine3d(*matrix->Element); + matrix = vtkmatrix(updated_pose.matrix); + + actor->SetUserMatrix(matrix); + actor->Modified(); +} + +cv::Affine3d cv::viz::Widget3D::getPose() const +{ + vtkProp3D *actor = vtkProp3D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget is not 3D." && actor); + return Affine3d(*actor->GetUserMatrix()->Element); +} + +void cv::viz::Widget3D::applyTransform(const Affine3d &transform) +{ + vtkActor *actor = vtkActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget is not 3D actor." && actor); + + vtkSmartPointer mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + CV_Assert("Widget doesn't have a polydata mapper" && mapper); + mapper->Update(); + + VtkUtils::SetInputData(mapper, VtkUtils::TransformPolydata(mapper->GetInput(), transform)); +} + +void cv::viz::Widget3D::setColor(const Color &color) +{ + // Cast to actor instead of prop3d since prop3d doesn't provide getproperty + vtkActor *actor = vtkActor::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget type is not supported." && actor); + + Color c = vtkcolor(color); + actor->GetMapper()->ScalarVisibilityOff(); + actor->GetProperty()->SetColor(c.val); + actor->GetProperty()->SetEdgeColor(c.val); + actor->Modified(); +} + +template<> cv::viz::Widget3D cv::viz::Widget::cast() +{ + vtkProp3D *actor = vtkProp3D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget cannot be cast." && actor); + + Widget3D widget; + WidgetAccessor::setProp(widget, actor); + return widget; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/// widget2D implementation + +void cv::viz::Widget2D::setColor(const Color &color) +{ + vtkActor2D *actor = vtkActor2D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget type is not supported." && actor); + Color c = vtkcolor(color); + actor->GetProperty()->SetColor(c.val); + actor->Modified(); +} + +template<> cv::viz::Widget2D cv::viz::Widget::cast() +{ + vtkActor2D *actor = vtkActor2D::SafeDownCast(WidgetAccessor::getProp(*this)); + CV_Assert("Widget cannot be cast." && actor); + + Widget2D widget; + WidgetAccessor::setProp(widget, actor); + return widget; +} diff --git a/modules/viz/test/test_main.cpp b/modules/viz/test/test_main.cpp new file mode 100644 index 000000000..e737d2db3 --- /dev/null +++ b/modules/viz/test/test_main.cpp @@ -0,0 +1,3 @@ +#include "test_precomp.hpp" + +CV_TEST_MAIN("viz") diff --git a/modules/viz/test/test_precomp.cpp b/modules/viz/test/test_precomp.cpp new file mode 100644 index 000000000..c2673fee6 --- /dev/null +++ b/modules/viz/test/test_precomp.cpp @@ -0,0 +1,24 @@ +#include "test_precomp.hpp" + +cv::String cv::Path::combine(const String& item1, const String& item2) +{ + if (item1.empty()) + return item2; + + if (item2.empty()) + return item1; + + char last = item1[item1.size()-1]; + + bool need_append = last != '/' && last != '\\'; + return item1 + (need_append ? "/" : "") + item2; +} + +cv::String cv::Path::combine(const String& item1, const String& item2, const String& item3) +{ return combine(combine(item1, item2), item3); } + +cv::String cv::Path::change_extension(const String& file, const String& ext) +{ + String::size_type pos = file.find_last_of('.'); + return pos == String::npos ? file : file.substr(0, pos+1) + ext; +} diff --git a/modules/viz/test/test_precomp.hpp b/modules/viz/test/test_precomp.hpp new file mode 100644 index 000000000..cd00b6e73 --- /dev/null +++ b/modules/viz/test/test_precomp.hpp @@ -0,0 +1,104 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// Authors: +// * Ozan Tonkal, ozantonkal@gmail.com +// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com +// +//M*/ + +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wmissing-declarations" +# if defined __clang__ || defined __APPLE__ +# pragma GCC diagnostic ignored "-Wmissing-prototypes" +# pragma GCC diagnostic ignored "-Wextra" +# endif +#endif + +#ifndef __OPENCV_TEST_PRECOMP_HPP__ +#define __OPENCV_TEST_PRECOMP_HPP__ + +#include "opencv2/ts/ts.hpp" +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace cv +{ + struct Path + { + static String combine(const String& item1, const String& item2); + static String combine(const String& item1, const String& item2, const String& item3); + static String change_extension(const String& file, const String& ext); + }; + + inline cv::String get_dragon_ply_file_path() + { + return Path::combine(cvtest::TS::ptr()->get_data_path(), "dragon.ply"); + } + + template + inline std::vector< Affine3<_Tp> > generate_test_trajectory() + { + std::vector< Affine3<_Tp> > result; + + for (int i = 0, j = 0; i <= 270; i += 3, j += 10) + { + double x = 2 * cos(i * 3 * CV_PI/180.0) * (1.0 + 0.5 * cos(1.2 + i * 1.2 * CV_PI/180.0)); + double y = 0.25 + i/270.0 + sin(j * CV_PI/180.0) * 0.2 * sin(0.6 + j * 1.5 * CV_PI/180.0); + double z = 2 * sin(i * 3 * CV_PI/180.0) * (1.0 + 0.5 * cos(1.2 + i * CV_PI/180.0)); + result.push_back(viz::makeCameraPose(Vec3d(x, y, z), Vec3d::all(0.0), Vec3d(0.0, 1.0, 0.0))); + } + return result; + } + + inline Mat make_gray(const Mat& image) + { + Mat chs[3]; split(image, chs); + return 0.114 * chs[0] + 0.58 * chs[1] + 0.3 * chs[2]; + } +} + +#endif diff --git a/modules/viz/test/test_tutorial2.cpp b/modules/viz/test/test_tutorial2.cpp new file mode 100644 index 000000000..a901adc2c --- /dev/null +++ b/modules/viz/test/test_tutorial2.cpp @@ -0,0 +1,54 @@ +#include "test_precomp.hpp" + +using namespace cv; +using namespace std; + +void tutorial2() +{ + /// Create a window + viz::Viz3d myWindow("Coordinate Frame"); + + /// Add coordinate axes + myWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem()); + + /// Add line to represent (1,1,1) axis + viz::WLine axis(Point3f(-1.0, -1.0, -1.0), Point3d(1.0, 1.0, 1.0)); + axis.setRenderingProperty(viz::LINE_WIDTH, 4.0); + myWindow.showWidget("Line Widget", axis); + + /// Construct a cube widget + viz::WCube cube_widget(Point3d(0.5, 0.5, 0.0), Point3d(0.0, 0.0, -0.5), true, viz::Color::blue()); + cube_widget.setRenderingProperty(viz::LINE_WIDTH, 4.0); + + /// Display widget (update if already displayed) + myWindow.showWidget("Cube Widget", cube_widget); + + /// Rodrigues vector + Vec3d rot_vec = Vec3d::all(0); + double translation_phase = 0.0, translation = 0.0; + while(!myWindow.wasStopped()) + { + /* Rotation using rodrigues */ + /// Rotate around (1,1,1) + rot_vec[0] += CV_PI * 0.01; + rot_vec[1] += CV_PI * 0.01; + rot_vec[2] += CV_PI * 0.01; + + /// Shift on (1,1,1) + translation_phase += CV_PI * 0.01; + translation = sin(translation_phase); + + /// Construct pose + Affine3d pose(rot_vec, Vec3d(translation, translation, translation)); + + myWindow.setWidgetPose("Cube Widget", pose); + + myWindow.spinOnce(1, true); + } +} + + +TEST(Viz, DISABLED_tutorial2_pose_of_widget) +{ + tutorial2(); +} diff --git a/modules/viz/test/test_tutorial3.cpp b/modules/viz/test/test_tutorial3.cpp new file mode 100644 index 000000000..590e29ebf --- /dev/null +++ b/modules/viz/test/test_tutorial3.cpp @@ -0,0 +1,64 @@ +#include "test_precomp.hpp" + +using namespace cv; +using namespace std; + +/** + * @function main + */ +void tutorial3(bool camera_pov) +{ + /// Create a window + viz::Viz3d myWindow("Coordinate Frame"); + + /// Add coordinate axes + myWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem()); + + /// Let's assume camera has the following properties + Point3d cam_pos(3.0, 3.0, 3.0), cam_focal_point(3.0, 3.0, 2.0), cam_y_dir(-1.0, 0.0, 0.0); + + /// We can get the pose of the cam using makeCameraPose + Affine3d cam_pose = viz::makeCameraPose(cam_pos, cam_focal_point, cam_y_dir); + + /// We can get the transformation matrix from camera coordinate system to global using + /// - makeTransformToGlobal. We need the axes of the camera + Affine3d transform = viz::makeTransformToGlobal(Vec3d(0.0, -1.0, 0.0), Vec3d(-1.0, 0.0, 0.0), Vec3d(0.0, 0.0, -1.0), cam_pos); + + /// Create a cloud widget. + Mat dragon_cloud = viz::readCloud(get_dragon_ply_file_path()); + viz::WCloud cloud_widget(dragon_cloud, viz::Color::green()); + + /// Pose of the widget in camera frame + Affine3d cloud_pose = Affine3d().translate(Vec3d(0.0, 0.0, 3.0)); + /// Pose of the widget in global frame + Affine3d cloud_pose_global = transform * cloud_pose; + + /// Visualize camera frame + if (!camera_pov) + { + viz::WCameraPosition cpw(0.5); // Coordinate axes + viz::WCameraPosition cpw_frustum(Vec2f(0.889484f, 0.523599f)); // Camera frustum + myWindow.showWidget("CPW", cpw, cam_pose); + myWindow.showWidget("CPW_FRUSTUM", cpw_frustum, cam_pose); + } + + /// Visualize widget + myWindow.showWidget("bunny", cloud_widget, cloud_pose_global); + + /// Set the viewer pose to that of camera + if (camera_pov) + myWindow.setViewerPose(cam_pose); + + /// Start event loop. + myWindow.spin(); +} + +TEST(Viz, DISABLED_tutorial3_global_view) +{ + tutorial3(false); +} + +TEST(Viz, DISABLED_tutorial3_camera_view) +{ + tutorial3(true); +} diff --git a/modules/viz/test/test_viz3d.cpp b/modules/viz/test/test_viz3d.cpp new file mode 100644 index 000000000..45d3cdc3c --- /dev/null +++ b/modules/viz/test/test_viz3d.cpp @@ -0,0 +1,64 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// + // + // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. + // + // By downloading, copying, installing or using the software you agree to this license. + // If you do not agree to this license, do not download, install, + // copy or use the software. + // + // + // License Agreement + // For Open Source Computer Vision Library + // + // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. + // Copyright (C) 2008-2013, Willow Garage Inc., all rights reserved. + // Third party copyrights are property of their respective owners. + // + // Redistribution and use in source and binary forms, with or without modification, + // are permitted provided that the following conditions are met: + // + // * Redistribution's of source code must retain the above copyright notice, + // this list of conditions and the following disclaimer. + // + // * Redistribution's in binary form must reproduce the above copyright notice, + // this list of conditions and the following disclaimer in the documentation + // and / or other materials provided with the distribution. + // + // * The name of the copyright holders may not be used to endorse or promote products + // derived from this software without specific prior written permission. + // + // This software is provided by the copyright holders and contributors "as is" and + // any express or implied warranties, including, but not limited to, the implied + // warranties of merchantability and fitness for a particular purpose are disclaimed. + // In no event shall the Intel Corporation or contributors be liable for any direct, + // indirect, incidental, special, exemplary, or consequential damages + // (including, but not limited to, procurement of substitute goods or services; + // loss of use, data, or profits; or business interruption) however caused + // and on any theory of liability, whether in contract, strict liability, + // or tort (including negligence or otherwise) arising in any way out of + // the use of this software, even if advised of the possibility of such damage. + // + //M*/ +#include "test_precomp.hpp" + +using namespace cv; + +TEST(Viz_viz3d, DISABLED_develop) +{ + cv::Mat cloud = cv::viz::readCloud(get_dragon_ply_file_path()); + + cv::viz::Viz3d viz("abc"); + viz.setBackgroundMeshLab(); + viz.showWidget("coo", cv::viz::WCoordinateSystem(1)); + viz.showWidget("cloud", cv::viz::WPaintedCloud(cloud)); + + //---->>>>> + //std::vector gt, es; + //cv::viz::readTrajectory(gt, "d:/Datasets/trajs/gt%05d.xml"); + //cv::viz::readTrajectory(es, "d:/Datasets/trajs/es%05d.xml"); + //cv::Mat cloud = cv::viz::readCloud(get_dragon_ply_file_path()); + //---->>>>> + + + viz.spin(); +} diff --git a/modules/viz/test/tests_simple.cpp b/modules/viz/test/tests_simple.cpp new file mode 100644 index 000000000..f84b60a47 --- /dev/null +++ b/modules/viz/test/tests_simple.cpp @@ -0,0 +1,407 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// + // + // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. + // + // By downloading, copying, installing or using the software you agree to this license. + // If you do not agree to this license, do not download, install, + // copy or use the software. + // + // + // License Agreement + // For Open Source Computer Vision Library + // + // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. + // Copyright (C) 2008-2013, Willow Garage Inc., all rights reserved. + // Third party copyrights are property of their respective owners. + // + // Redistribution and use in source and binary forms, with or without modification, + // are permitted provided that the following conditions are met: + // + // * Redistribution's of source code must retain the above copyright notice, + // this list of conditions and the following disclaimer. + // + // * Redistribution's in binary form must reproduce the above copyright notice, + // this list of conditions and the following disclaimer in the documentation + // and / or other materials provided with the distribution. + // + // * The name of the copyright holders may not be used to endorse or promote products + // derived from this software without specific prior written permission. + // + // This software is provided by the copyright holders and contributors "as is" and + // any express or implied warranties, including, but not limited to, the implied + // warranties of merchantability and fitness for a particular purpose are disclaimed. + // In no event shall the Intel Corporation or contributors be liable for any direct, + // indirect, incidental, special, exemplary, or consequential damages + // (including, but not limited to, procurement of substitute goods or services; + // loss of use, data, or profits; or business interruption) however caused + // and on any theory of liability, whether in contract, strict liability, + // or tort (including negligence or otherwise) arising in any way out of + // the use of this software, even if advised of the possibility of such damage. + // + //M*/ + +#include "test_precomp.hpp" + +using namespace cv; +using namespace cv::viz; + +TEST(Viz, show_cloud_bluberry) +{ + Mat dragon_cloud = readCloud(get_dragon_ply_file_path()); + + Affine3d pose = Affine3d().rotate(Vec3d(0, 0.8, 0)); + + Viz3d viz("show_cloud_bluberry"); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("dragon", WCloud(dragon_cloud, Color::bluberry()), pose); + + viz.showWidget("text2d", WText("Bluberry cloud", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_cloud_random_color) +{ + Mat dragon_cloud = readCloud(get_dragon_ply_file_path()); + + Mat colors(dragon_cloud.size(), CV_8UC3); + theRNG().fill(colors, RNG::UNIFORM, 0, 255); + + Affine3d pose = Affine3d().rotate(Vec3d(0, 0.8, 0)); + + Viz3d viz("show_cloud_random_color"); + viz.setBackgroundMeshLab(); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("dragon", WCloud(dragon_cloud, colors), pose); + viz.showWidget("text2d", WText("Random color cloud", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_cloud_masked) +{ + Mat dragon_cloud = readCloud(get_dragon_ply_file_path()); + + Vec3f qnan = Vec3f::all(std::numeric_limits::quiet_NaN()); + for(size_t i = 0; i < dragon_cloud.total(); ++i) + if (i % 15 != 0) + dragon_cloud.at(i) = qnan; + + Affine3d pose = Affine3d().rotate(Vec3d(0, 0.8, 0)); + + Viz3d viz("show_cloud_masked"); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("dragon", WCloud(dragon_cloud), pose); + viz.showWidget("text2d", WText("Nan masked cloud", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_cloud_collection) +{ + Mat cloud = readCloud(get_dragon_ply_file_path()); + + WCloudCollection ccol; + ccol.addCloud(cloud, Color::white(), Affine3d().translate(Vec3d(0, 0, 0)).rotate(Vec3d(CV_PI/2, 0, 0))); + ccol.addCloud(cloud, Color::blue(), Affine3d().translate(Vec3d(1, 0, 0))); + ccol.addCloud(cloud, Color::red(), Affine3d().translate(Vec3d(2, 0, 0))); + + Viz3d viz("show_cloud_collection"); + viz.setBackgroundColor(Color::mlab()); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("ccol", ccol); + viz.showWidget("text2d", WText("Cloud collection", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_painted_clouds) +{ + Mat cloud = readCloud(get_dragon_ply_file_path()); + + Viz3d viz("show_painted_clouds"); + viz.setBackgroundMeshLab(); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("cloud1", WPaintedCloud(cloud), Affine3d(Vec3d(0.0, -CV_PI/2, 0.0), Vec3d(-1.5, 0.0, 0.0))); + viz.showWidget("cloud2", WPaintedCloud(cloud, Vec3d(0.0, -0.75, -1.0), Vec3d(0.0, 0.75, 0.0)), Affine3d(Vec3d(0.0, CV_PI/2, 0.0), Vec3d(1.5, 0.0, 0.0))); + viz.showWidget("cloud3", WPaintedCloud(cloud, Vec3d(0.0, 0.0, -1.0), Vec3d(0.0, 0.0, 1.0), Color::blue(), Color::red())); + viz.showWidget("arrow", WArrow(Vec3d(0.0, 1.0, -1.0), Vec3d(0.0, 1.0, 1.0), 0.009, Color::raspberry())); + viz.showWidget("text2d", WText("Painted clouds", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_mesh) +{ + Mesh mesh = Mesh::load(get_dragon_ply_file_path()); + + Affine3d pose = Affine3d().rotate(Vec3d(0, 0.8, 0)); + + Viz3d viz("show_mesh"); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("mesh", WMesh(mesh), pose); + viz.showWidget("text2d", WText("Just mesh", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_mesh_random_colors) +{ + Mesh mesh = Mesh::load(get_dragon_ply_file_path()); + theRNG().fill(mesh.colors, RNG::UNIFORM, 0, 255); + + Affine3d pose = Affine3d().rotate(Vec3d(0, 0.8, 0)); + + Viz3d viz("show_mesh_random_color"); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("mesh", WMesh(mesh), pose); + viz.setRenderingProperty("mesh", SHADING, SHADING_PHONG); + viz.showWidget("text2d", WText("Random color mesh", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_textured_mesh) +{ + Mat lena = imread(Path::combine(cvtest::TS::ptr()->get_data_path(), "lena.png")); + + std::vector points; + std::vector tcoords; + std::vector polygons; + for(size_t i = 0; i < 64; ++i) + { + double angle = CV_PI/2 * i/64.0; + points.push_back(Vec3d(0.00, cos(angle), sin(angle))*0.75); + points.push_back(Vec3d(1.57, cos(angle), sin(angle))*0.75); + tcoords.push_back(Vec2d(0.0, i/64.0)); + tcoords.push_back(Vec2d(1.0, i/64.0)); + } + + for(size_t i = 0; i < points.size()/2-1; ++i) + { + int polys[] = {3, 2*i, 2*i+1, 2*i+2, 3, 2*i+1, 2*i+2, 2*i+3}; + polygons.insert(polygons.end(), polys, polys + sizeof(polys)/sizeof(polys[0])); + } + + cv::viz::Mesh mesh; + mesh.cloud = Mat(points, true).reshape(3, 1); + mesh.tcoords = Mat(tcoords, true).reshape(2, 1); + mesh.polygons = Mat(polygons, true).reshape(1, 1); + mesh.texture = lena; + + Viz3d viz("show_textured_mesh"); + viz.setBackgroundMeshLab(); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("mesh", WMesh(mesh)); + viz.setRenderingProperty("mesh", SHADING, SHADING_PHONG); + viz.showWidget("text2d", WText("Textured mesh", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_polyline) +{ + Mat polyline(1, 32, CV_64FC3); + for(size_t i = 0; i < polyline.total(); ++i) + polyline.at(i) = Vec3d(i/16.0, cos(i * CV_PI/6), sin(i * CV_PI/6)); + + Viz3d viz("show_polyline"); + viz.showWidget("polyline", WPolyLine(Mat(polyline), Color::apricot())); + viz.showWidget("coosys", WCoordinateSystem()); + viz.showWidget("text2d", WText("Polyline", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_sampled_normals) +{ + Mesh mesh = Mesh::load(get_dragon_ply_file_path()); + computeNormals(mesh, mesh.normals); + + Affine3d pose = Affine3d().rotate(Vec3d(0, 0.8, 0)); + + Viz3d viz("show_sampled_normals"); + viz.showWidget("mesh", WMesh(mesh), pose); + viz.showWidget("normals", WCloudNormals(mesh.cloud, mesh.normals, 30, 0.1f, Color::green()), pose); + viz.setRenderingProperty("normals", LINE_WIDTH, 2.0); + viz.showWidget("text2d", WText("Cloud or mesh normals", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_trajectories) +{ + std::vector path = generate_test_trajectory(), sub0, sub1, sub2, sub3, sub4, sub5; + + Mat(path).rowRange(0, path.size()/10+1).copyTo(sub0); + Mat(path).rowRange(path.size()/10, path.size()/5+1).copyTo(sub1); + Mat(path).rowRange(path.size()/5, 11*path.size()/12).copyTo(sub2); + Mat(path).rowRange(11*path.size()/12, path.size()).copyTo(sub3); + Mat(path).rowRange(3*path.size()/4, 33*path.size()/40).copyTo(sub4); + Mat(path).rowRange(33*path.size()/40, 9*path.size()/10).copyTo(sub5); + Matx33d K(1024.0, 0.0, 320.0, 0.0, 1024.0, 240.0, 0.0, 0.0, 1.0); + + Viz3d viz("show_trajectories"); + viz.showWidget("coos", WCoordinateSystem()); + viz.showWidget("sub0", WTrajectorySpheres(sub0, 0.25, 0.07)); + viz.showWidget("sub1", WTrajectory(sub1, WTrajectory::PATH, 0.2, Color::brown())); + viz.showWidget("sub2", WTrajectory(sub2, WTrajectory::FRAMES, 0.2)); + viz.showWidget("sub3", WTrajectory(sub3, WTrajectory::BOTH, 0.2, Color::green())); + viz.showWidget("sub4", WTrajectoryFrustums(sub4, K, 0.3, Color::yellow())); + viz.showWidget("sub5", WTrajectoryFrustums(sub5, Vec2d(0.78, 0.78), 0.15)); + viz.showWidget("text2d", WText("Different kinds of supported trajectories", Point(20, 20), 20, Color::green())); + + int i = 0; + while(!viz.wasStopped()) + { + double a = --i % 360; + Vec3d pose(sin(a * CV_PI/180), 0.7, cos(a * CV_PI/180)); + viz.setViewerPose(makeCameraPose(pose * 7.5, Vec3d(0.0, 0.5, 0.0), Vec3d(0.0, 0.1, 0.0))); + viz.spinOnce(20, true); + } + viz.resetCamera(); + viz.spin(); +} + +TEST(Viz, show_trajectory_reposition) +{ + std::vector path = generate_test_trajectory(); + + Viz3d viz("show_trajectory_reposition_to_origin"); + viz.showWidget("coos", WCoordinateSystem()); + viz.showWidget("sub3", WTrajectory(Mat(path).rowRange(0, path.size()/3), WTrajectory::BOTH, 0.2, Color::brown()), path.front().inv()); + viz.showWidget("text2d", WText("Trajectory resposition to origin", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_camera_positions) +{ + Matx33d K(1024.0, 0.0, 320.0, 0.0, 1024.0, 240.0, 0.0, 0.0, 1.0); + Mat lena = imread(Path::combine(cvtest::TS::ptr()->get_data_path(), "lena.png")); + Mat gray = make_gray(lena); + + Affine3d poses[2]; + for(int i = 0; i < 2; ++i) + { + Vec3d pose = 5 * Vec3d(sin(3.14 + 2.7 + i*60 * CV_PI/180), 0.4 - i*0.3, cos(3.14 + 2.7 + i*60 * CV_PI/180)); + poses[i] = makeCameraPose(pose, Vec3d(0.0, 0.0, 0.0), Vec3d(0.0, -0.1, 0.0)); + } + + Viz3d viz("show_camera_positions"); + viz.showWidget("sphe", WSphere(Point3d(0,0,0), 1.0, 10, Color::orange_red())); + viz.showWidget("coos", WCoordinateSystem(1.5)); + viz.showWidget("pos1", WCameraPosition(0.75), poses[0]); + viz.showWidget("pos2", WCameraPosition(Vec2d(0.78, 0.78), lena, 2.2, Color::green()), poses[0]); + viz.showWidget("pos3", WCameraPosition(0.75), poses[1]); + viz.showWidget("pos4", WCameraPosition(K, gray, 3, Color::indigo()), poses[1]); + viz.showWidget("text2d", WText("Camera positions with images", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_overlay_image) +{ + Mat lena = imread(Path::combine(cvtest::TS::ptr()->get_data_path(), "lena.png")); + Mat gray = make_gray(lena); + + Size2d half_lsize = Size2d(lena.cols, lena.rows) * 0.5; + + Viz3d viz("show_overlay_image"); + viz.setBackgroundMeshLab(); + Size vsz = viz.getWindowSize(); + + viz.showWidget("coos", WCoordinateSystem()); + viz.showWidget("cube", WCube()); + viz.showWidget("img1", WImageOverlay(lena, Rect(Point(10, 10), half_lsize))); + viz.showWidget("img2", WImageOverlay(gray, Rect(Point(vsz.width-10-lena.cols/2, 10), half_lsize))); + viz.showWidget("img3", WImageOverlay(gray, Rect(Point(10, vsz.height-10-lena.rows/2), half_lsize))); + viz.showWidget("img5", WImageOverlay(lena, Rect(Point(vsz.width-10-lena.cols/2, vsz.height-10-lena.rows/2), half_lsize))); + viz.showWidget("text2d", WText("Overlay images", Point(20, 20), 20, Color::green())); + + int i = 0; + while(!viz.wasStopped()) + { + double a = ++i % 360; + Vec3d pose(sin(a * CV_PI/180), 0.7, cos(a * CV_PI/180)); + viz.setViewerPose(makeCameraPose(pose * 3, Vec3d(0.0, 0.5, 0.0), Vec3d(0.0, 0.1, 0.0))); + viz.getWidget("img1").cast().setImage(lena * pow(sin(i*10*CV_PI/180) * 0.5 + 0.5, 1.0)); + viz.spinOnce(1, true); + } + viz.showWidget("text2d", WText("Overlay images (stopped)", Point(20, 20), 20, Color::green())); + viz.spin(); +} + + +TEST(Viz, show_image_method) +{ + Mat lena = imread(Path::combine(cvtest::TS::ptr()->get_data_path(), "lena.png")); + + Viz3d viz("show_image_method"); + viz.showImage(lena); + viz.spinOnce(1500, true); + viz.showImage(lena, lena.size()); + viz.spinOnce(1500, true); + + cv::viz::imshow("show_image_method", make_gray(lena)).spin(); +} + +TEST(Viz, show_image_3d) +{ + Mat lena = imread(Path::combine(cvtest::TS::ptr()->get_data_path(), "lena.png")); + Mat gray = make_gray(lena); + + Viz3d viz("show_image_3d"); + viz.setBackgroundMeshLab(); + viz.showWidget("coos", WCoordinateSystem()); + viz.showWidget("cube", WCube()); + viz.showWidget("arr0", WArrow(Vec3d(0.5, 0.0, 0.0), Vec3d(1.5, 0.0, 0.0), 0.009, Color::raspberry())); + viz.showWidget("img0", WImage3D(lena, Size2d(1.0, 1.0)), Affine3d(Vec3d(0.0, CV_PI/2, 0.0), Vec3d(.5, 0.0, 0.0))); + viz.showWidget("arr1", WArrow(Vec3d(-0.5, -0.5, 0.0), Vec3d(0.2, 0.2, 0.0), 0.009, Color::raspberry())); + viz.showWidget("img1", WImage3D(gray, Size2d(1.0, 1.0), Vec3d(-0.5, -0.5, 0.0), Vec3d(1.0, 1.0, 0.0), Vec3d(0.0, 1.0, 0.0))); + + viz.showWidget("arr3", WArrow(Vec3d::all(-0.5), Vec3d::all(0.5), 0.009, Color::raspberry())); + + viz.showWidget("text2d", WText("Images in 3D", Point(20, 20), 20, Color::green())); + + int i = 0; + while(!viz.wasStopped()) + { + viz.getWidget("img0").cast().setImage(lena * pow(sin(i++*7.5*CV_PI/180) * 0.5 + 0.5, 1.0)); + viz.spinOnce(1, true); + } + viz.showWidget("text2d", WText("Images in 3D (stopped)", Point(20, 20), 20, Color::green())); + viz.spin(); +} + +TEST(Viz, show_simple_widgets) +{ + Viz3d viz("show_simple_widgets"); + viz.setBackgroundMeshLab(); + + viz.showWidget("coos", WCoordinateSystem()); + viz.showWidget("cube", WCube()); + viz.showWidget("cub0", WCube(Vec3d::all(-1.0), Vec3d::all(-0.5), false, Color::indigo())); + viz.showWidget("arro", WArrow(Vec3d::all(-0.5), Vec3d::all(0.5), 0.009, Color::raspberry())); + viz.showWidget("cir1", WCircle(0.5, 0.01, Color::bluberry())); + viz.showWidget("cir2", WCircle(0.5, Point3d(0.5, 0.0, 0.0), Vec3d(1.0, 0.0, 0.0), 0.01, Color::apricot())); + + viz.showWidget("cyl0", WCylinder(Vec3d(-0.5, 0.5, -0.5), Vec3d(0.5, 0.5, -0.5), 0.125, 30, Color::brown())); + viz.showWidget("con0", WCone(0.25, 0.125, 6, Color::azure())); + viz.showWidget("con1", WCone(0.125, Point3d(0.5, -0.5, 0.5), Point3d(0.5, -1.0, 0.5), 6, Color::turquoise())); + + viz.showWidget("text2d", WText("Different simple widgets", Point(20, 20), 20, Color::green())); + viz.showWidget("text3d", WText3D("Simple 3D text", Point3d( 0.5, 0.5, 0.5), 0.125, false, Color::green())); + + viz.showWidget("plane1", WPlane(Size2d(0.25, 0.75))); + viz.showWidget("plane2", WPlane(Vec3d(0.5, -0.5, -0.5), Vec3d(0.0, 1.0, 1.0), Vec3d(1.0, 1.0, 0.0), Size2d(1.0, 0.5), Color::gold())); + + viz.showWidget("grid1", WGrid(Vec2i(7,7), Vec2d::all(0.75), Color::gray()), Affine3d().translate(Vec3d(0.0, 0.0, -1.0))); + + viz.spin(); + viz.getWidget("text2d").cast().setText("Different simple widgets (updated)"); + viz.getWidget("text3d").cast().setText("Updated text 3D"); + viz.spin(); +} + +TEST(Viz, show_follower) +{ + Viz3d viz("show_follower"); + + viz.showWidget("coos", WCoordinateSystem()); + viz.showWidget("cube", WCube()); + viz.showWidget("t3d_2", WText3D("Simple 3D follower", Point3d(-0.5, -0.5, 0.5), 0.125, true, Color::green())); + viz.showWidget("text2d", WText("Follower: text always facing camera", Point(20, 20), 20, Color::green())); + viz.setBackgroundMeshLab(); + viz.spin(); + viz.getWidget("t3d_2").cast().setText("Updated follower 3D"); + viz.spin(); +}