一、問題提出
ViZ對於顯示3維的效果圖來說,非常有幫助;我在使用OpenCV進行雙目測距的過程中,有一些參數希望能夠通過可視化的方法顯示出來,所以參考了這方面相關的資料、做了一些實驗,這里整理如下。這篇文章主要講的是環境架設,並且假設閱讀者已經有成功編譯OpenCV的經驗。出於系統穩定的考慮,我沒有選擇最新版本,而是使用了OpenCV3.2+VIZ6.3.0,編譯環境為vs2012。
二、具體步驟
1、下載安裝cmake,
下載鏈接:
https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-win64-x64.msi
建議這步這樣選擇:

2、下載VTK,
下載鏈接:
https://vtk.org/download/#latest
注意,對於VTK來說,6.3的話vs2012就可以,最新的8.2需要vs2015。由於現有工具鏈的原因,我優先選擇了6.3

3、 編譯VTK,
打開cmake-gui進行配置
選擇vtk源碼的路徑,新建一個文件夾build存放編譯后的文件

只需要勾選這5項就可以。兩次configure,一次generate,然后打開vs生成解決方案。

相對來說,VTK的編譯時比較簡單的。
4、重新編譯OpenCV,特別需要注意configure的操作。

默認情況下,WITH_VTK是選中的,VTK_DIR沒有配置,而BUILD_opencv_viz你是着不到的,就像下圖這個樣子。

這里的VTK庫的路徑,其實這里是要找VTKConfig.cmake文件存在的路徑,比如前面我們編譯過VTK,就有這樣的地址

正確填入地址,就會出現Build_opecv_viz這個選項,這個也是默認選中的。

其他一些注意,不是必須的:

將world選上,這樣生成的結果只有一個文件。

這里的build_tests可以去掉,為了使提高編譯效率。

然后
兩次generate,一次confige,這個不變的永恆。

使用批生成最為便捷,這里展示的只是debug版本。

VTK自己帶的許多.dll,需要放到PATH能夠找到的地方,比如我比較喜歡放windows/system
三、測試例子
這里就是生成一個可以運行的例子,僅僅是測試viz編譯成功
#
include
"stdafx.h"
#
include
<opencv2
/viz
/vizcore.hpp
>
#
include
<opencv2
/calib3d
/calib3d.hpp
>
#
include
<iostream
>
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
<
float
>(
0,
0)
+= CV_PI
*
0.
01f;
rot_vec.at
<
float
>(
0,
1)
+= CV_PI
*
0.
01f;
rot_vec.at
<
float
>(
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;
}

附:參考文章
1、《OpenCV 中viz模塊的編譯和使用(VS2015)》
https://blog.csdn.net/A_L_A_N/article/details/81571165;
2、《OpenCV 3D顯示Viz模塊》
https://blog.csdn.net/Asimov_Liu/article/details/83539345
3、《【opencv】viz 3D虛擬空間模塊編譯及使用》
https://blog.csdn.net/qq_15947787/article/details/79225845
p.s.1 添加Contribute編譯
由於深度相關的操作,很多時候都需要使用特征點,所以聯編contribute是需要的,這里將主要步驟截圖說明,首先是保證前面的操作都不變,可以直接在前面的基礎上進行操作

所說的不同,也主要是這兩個地方


然后后面的操作也是一樣的。

當然它自己也會下一堆的東西。


在vs2012下編譯DNN會報這樣的錯誤,由於我們這里不使用DNN,所以直接去掉。
在項目配置的時候,使用兩個world
p.s.2 OpenCV自己帶的幾個例子運行

1、Pose of a widget
這個就是上面的例子。具體解釋:
Explanation
Here is the general structure of the program:
- Create a visualization window.
viz : :Viz3d myWindow( "Coordinate Frame");
- Show coordinate axes in the window using CoordinateSystemWidget.
myWindow.showWidget( "Coordinate Widget", viz : :WCoordinateSystem());
- Display a line representing the axis (1,1,1).
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.
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
rot_vec.at < float >( 0, 0) += CV_PI * 0. 01f;
rot_vec.at < float >( 0, 1) += CV_PI * 0. 01f;
rot_vec.at < float >( 0, 2) += CV_PI * 0. 01f;
...
Mat rot_mat;
Rodrigues(rot_vec, rot_mat); - Use Affine3f to set pose of the cube.
- Animate the rotation using wasStopped and spinOnce
while( !myWindow.wasStopped())
{
...
myWindow.spinOnce( 1, true);
}
2、Transformations
這
#include "stdafx.h"
#include <opencv2/viz.hpp>
#include <iostream>
#include <fstream>
using namespace cv;
using namespace std;
static void help()
{
cout
<< "--------------------------------------------------------------------------" << endl
<< "This program shows how to use makeTransformToGlobal() to compute required pose,"
<< "how to use makeCameraPose and Viz3d::setViewerPose. You can observe the scene "
<< "from camera point of view (C) or global point of view (G)" << endl
<< "Usage:" << endl
<< "./transformations [ G | C ]" << endl
<< endl;
}
static 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<cv::Point3f>();
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;
}
int main(int argn, char **argv)
{
help();
if (argn < 2)
{
cout << "Missing arguments." << endl;
return 1;
}
bool camera_pov = (argv[1][0] == 'C');
viz::Viz3d myWindow("Coordinate Frame");
myWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem());
Vec3f 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);
Affine3f cam_pose = viz::makeCameraPose(cam_pos, cam_focal_point, cam_y_dir);
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);
Mat bunny_cloud = cvcloud_load();
viz::WCloud cloud_widget(bunny_cloud, viz::Color::green());
Affine3f cloud_pose = Affine3f().translate(Vec3f(0.0f,0.0f,3.0f));
Affine3f cloud_pose_global = transform * cloud_pose;
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);
}
myWindow.showWidget("bunny", cloud_widget, cloud_pose_global);
if (camera_pov)
myWindow.setViewerPose(cam_pose);
myWindow.spin();
return 0;
}
Explanation
Here is the general structure of the program:
- Create a visualization window.
viz : :Viz3d myWindow( "Transformations");
- Get camera pose from camera position, camera focal point and y direction.
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);
Affine3f cam_pose = viz : :makeCameraPose(cam_pos, cam_focal_point, cam_y_dir); - Obtain transform matrix knowing the axes of camera coordinate system.
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
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.
Affine3f cloud_pose = Affine3f().translate(Vec3f( 0.0f, 0.0f, 3.0f));
Affine3f cloud_pose_global = transform * cloud_pose; - If the view point is set to be global, visualize camera coordinate frame and viewing frustum.
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
myWindow.showWidget( "bunny", cloud_widget, cloud_pose_global);
- If the view point is set to be camera's, set viewer pose to cam_pose.
if (camera_pov)
myWindow.setViewerPose(cam_pose);
#include "stdafx.h"#include <opencv2/core.hpp>#include <opencv2/imgproc.hpp>#include <opencv2/highgui.hpp>#include <iostream>using namespace std;using namespace cv;#ifdef HAVE_OPENCV_VIZ#include <opencv2/viz.hpp>const String keys ="{Aide h usage ? help | | print this message }""{@arg1 | | Full path to color imag (3 channels)}";struct Histo3DData {Mat histogram;int seuil;double threshold;Ptr<viz::Viz3d> fen3D;int nbWidget;bool status;double maxH;int code;};void DrawHistogram3D(Histo3DData &);void AddSlidebar(String sliderName, String windowName, int sliderMin, int sliderMax, int valeurDefaut, int *sliderVal, void(*f)(int, void *), void *r);void UpdateThreshold(int , void * r);void KeyboardViz3d(const viz::KeyboardEvent &w, void *t);void DrawHistogram3D(Histo3DData &h){int planSize = (int)h.histogram.step1(0);int cols = (int)h.histogram.step1(1);int rows = (int)planSize / cols;int plans = (int)h.histogram.total() / planSize;h.fen3D->removeAllWidgets();h.nbWidget=0;if (h.nbWidget==0)h.fen3D->showWidget("Axis", viz::WCoordinateSystem(10));for (int k = 0; k < plans; k++){for (int i = 0; i < rows; i++){for (int j = 0; j < cols; j++){double x = h.histogram.at<float>(k, i, j);if (x >= h.threshold){double r=std::max(x/h.maxH,0.1);viz::WCube s(Point3d(k - r / 2, i - r / 2, j - r / 2), Point3d(k + r / 2, i + r / 2, j + r / 2), false, viz::Color(j / double(plans) * 255, i / double(rows) * 255, k / double(cols) * 255));h.fen3D->showWidget(format("I3d%d", h.nbWidget++), s);}}}}h.status = false;}void KeyboardViz3d(const viz::KeyboardEvent &w, void *t){Histo3DData *x=(Histo3DData *)t;if (w.action)cout << "you pressed "<< w.symbol<< " in viz window "<<x->fen3D->getWindowName()<<"\n";x->code= w.code;switch (w.code) {case '/':x->status=true;x->threshold *= 0.9;break;case '*':x->status = true;x->threshold *= 1.1;break;}if (x->status){cout << x->threshold << "\n";DrawHistogram3D(*x);}}void AddSlidebar(String sliderName, String windowName, int sliderMin, int sliderMax, int defaultSlider, int *sliderVal, void(*f)(int, void *), void *r){createTrackbar(sliderName, windowName, sliderVal, 1, f, r);setTrackbarMin(sliderName, windowName, sliderMin);setTrackbarMax(sliderName, windowName, sliderMax);setTrackbarPos(sliderName, windowName, defaultSlider);}void UpdateThreshold(int , void * r){Histo3DData *h = (Histo3DData *)r;h->status=true;h->threshold = h->seuil/1000000.0;cout<<"Widget : "<<h->nbWidget<<","<< h->threshold<<"\n";}int main (int argc,char **argv){CommandLineParser parser(argc, argv, keys);if (parser.has("help")){parser.printMessage();return 0;}String nomFic = parser.get<String>(0);Mat img;if (nomFic.length() != 0){img = imread(nomFic, IMREAD_COLOR);if (img.empty()){cout << "Image does not exist!";return 0;}}else{img = Mat(512,512,CV_8UC3);parser.printMessage();RNG r;r.fill(img(Rect(0, 0, 256, 256)), RNG::NORMAL, Vec3b(60, 40, 50), Vec3b(10, 5, 20));r.fill(img(Rect(256, 0, 256, 256)), RNG::NORMAL, Vec3b(160, 10, 50), Vec3b(20, 5, 10));r.fill(img(Rect(0, 256, 256, 256)), RNG::NORMAL, Vec3b(90, 100, 50), Vec3b(10, 20, 20));r.fill(img(Rect(256, 256, 256, 256)), RNG::NORMAL, Vec3b(100, 10, 150), Vec3b(10, 5, 40));}Histo3DData h;h.status=true;h.seuil=90;h.threshold= h.seuil/1000000.0;float hRange[] = { 0, 256 };const float* etendu[] = { hRange, hRange,hRange };int hBins = 32;int histSize[] = { hBins, hBins , hBins };int channel[] = { 2, 1,0 };calcHist(&img, 1, channel, Mat(), h.histogram, 3, histSize, etendu, true, false);normalize(h.histogram, h.histogram, 100.0/(img.total()), 0, NORM_MINMAX, -1, Mat());minMaxIdx(h.histogram,NULL,&h.maxH,NULL,NULL);namedWindow("Image");imshow("Image",img);AddSlidebar("threshold","Image",0,100,h.seuil,&h.seuil, UpdateThreshold,&h);waitKey(30);h.fen3D = makePtr<viz::Viz3d>("3D Histogram");h.nbWidget=0;h.fen3D->registerKeyboardCallback(KeyboardViz3d,&h);DrawHistogram3D(h);while (h.code!=27){h.fen3D->spinOnce(1);if (h.status)DrawHistogram3D(h);if (h.code!=27)h.code= waitKey(30);}return 0;}#elseint main(int argc, char **argv){cout << " you need VIZ module\n";return 0;}#endif
小結:這里做的,都是VTK的操作,所以想把這塊搞明白,應該去搞VTK.而由於VTK本身自成一套,所以要以VIZ作為一個動機最好。
附件列表