VisionWorks快速入门--Graph Mode
本教程的第一部分(VisionWorks快速启动(立即模式))展示了如何使用优化的VisionWorks立即模式函数替换几个耗时的OpenCV函数,从而提高video stabilization应用程序的性能。visionworks库实现openvx标准,还可以使用图形模式执行来使应用程序更快。
本教程的第二部分与第一部分一样基于“video stabilization”示例。算法和完整的代码包含在第一部分中;您还可以在其中找到visionworks即时模式的完整代码。本教程的第二部分将修改此代码,例如,与数据转换相关的代码行。本教程重点介绍立即模式和图形模式之间的区别。
原语可以通过两种方式执行:
基于graph的执行原语被实例化为graph节点。graph是提前构建、验证和优化的,并且可以在运行时多次执行而无需重新验证。基于graph的执行对于多次执行的视觉管道(例如处理视频流)是首选的,因为它提供了最佳的性能。
立即执行原语通过调用函数(前缀为vxu或nvxu)直接执行,类似于OpenCV或NPP执行模式。当基本设置开销不是一个大问题时,立即执行模型对于一次性处理非常有用。它也可以作为应用程序开发的中间步骤,比如移植使用opencv的应用程序时。
本节讨论这些模式之间的对应关系。每个立即模式功能都有相应的节点,例如:
Immediate mode |
Graph mode |
vxuFastCorners() |
vxFastCornersNode() |
nvxuFindHomography() |
nvxFindHomographyNode() |
每个节点被附加到一个graph中,然后验证graph。每次算法迭代只调用vxProcessGraph()函数,即代码的很大一部分从负责算法执行的函数转换到负责初始化的函数。函数后面都加一个“node”
让我们看看本例子如何操作。
从立即模式过渡到图形模式
在每次迭代中按顺序调用以下函数:
vxuFastCorners();
vxuColorConvert();
vxuGaussianPyramid();
vxuOpticalFlowPyrLK();
nvxuFindHomography();
homography_smoother_->push();
homography_smoother_->getSmoothedHomography();
vxuWarpPerspective();
上面所有VISIONWORKS函数都存在对应的节点,但是两个用户函数在库中没有对应的节点。不可能画出一个有“洞”的图
;它必须包含一个算法的连续部分。解决这个问题有两种方法:
我们选择第二个是因为它保持了设计的纯洁性和代码的可读性。
在转换描述之前,请注意一种VisionWorks数据类型。库中有一种特殊的数据类型,名为vx_delay。vx_delay是一个循环缓冲区。我们使用它来存储smoothing_window_size_ + 1个最近的图像、2 * smoothing_window_size_ + 1个单应矩阵、最新的 2个 grayscale frame和 最新的2个 金字塔。如您所见,使用此数据类型存储有关最后几帧的信息非常方便。
让我们将从立即模式到图形模式的转换划分为以下步骤:
-
创建新节点。
-
向GraphModestabilizer类添加新字段和函数。
-
初始化字段。
-
执行算法的迭代。
-
Release objects。
1. 创建新节点。
我们必须将所有位于nvxuFindHomography()和vxuWarpPerspective()函数之间的代码包装到用户节点我们把它命名为homographySmootherNode。
新节点必须执行以下操作:
由于节点不应该存储其状态,因此它必须采用高斯权重数组(也可以在每次迭代中计算)、单应矩阵缓冲区和新的单应矩阵。转换矩阵作为输出参数传递给节点。
代码在文件homography_smooler_node.hpp和homography_smooler_node.cpp中。
2. 向GraphModestabilizer类添加新字段和函数。
有些字段必须删除,有些要替换,有些要添加。特别是,不使用“宽度”和“高度”字段。它们只是创建图像所必需的,这些图像被添加到帧队列中。现在我们使用vx_delay代替std::queue,并且这种数据类型保存有关帧大小的信息。
替换字段有:
Immediate mode stabilizer |
Graph mode stabilizer |
std::gueue<vx_image> frames |
vx_delay frames_delay_ |
vx_image gray_latest_frame_, vx_image gray_current_frame_ |
vx_delay gray_frames_delay_ |
vx_pyramid latest_pyr_, vx_pyramid current_pyr_ |
vx_delay pyr_delay_ |
仍然需要为HomographySmoother节点添加字段:
vx_array gaussian_weights_;
vx_delay homography_matrices_;
图的字段,即图及其节点:
vx_graph main_graph_;
vx_node fast_corners_node_;
vx_node color_convert_node_;
vx_node gaussian_pyramid_node_;
vx_node opt_flow_node_;
vx_node find_homography_node_;
vx_node homography_smoother_node_;
vx_node warp_perspective_node_;
我们添加了参数初始化和图形创建功能。
void createMainGraph();
vx_status initGaussianWeights();
vx_status initHomographyMatrices();
3. 初始化字段。
与立即模式相比,有哪些初始化要做呢?新字段出现在类中,一些旧字段已更改,因此我们必须初始化它们。类字段之间有几个vx_delay对象。我们必须传递实例(例如,如果是图像的vx_delay,我们必须传递vx_image)和缓冲区大小来初始化vx_delay。vx_delay从delay-s元素读取元数据(图像大小和类型)。例如,frames_delay_ 初始化:
frames_delay_ = vxCreateDelay(context_, (vx_reference)start_frame, params_.smoothing_window_size + 1);
NVXIO_CHECK_REFERENCE(frames_delay_);
NVXIO_SAFE_CALL(nvxuCopyImage(context_, start_frame, (vx_image)vxGetReferenceFromDelay(frames_delay_, 0)));
在initGaussianWeights()函数中初始化高斯权重数组:
vx_status GraphModeStabilizer::initGaussianWeights()
{
vx_status status = VX_SUCCESS;
vx_float32 sigma = (vx_float32)params_.smoothing_window_size * 0.7;
vx_int32 num_items = 2 * (vx_int32)params_.smoothing_window_size + 1;
std::vector<vx_float32> gaussian_weights_data;
gaussian_weights_data.resize(num_items);
vx_float32 sum = 0;
for (vx_int32 i = 0; i < num_items; ++i)
{
gaussian_weights_data[i] = exp(-(i - (vx_float32)params_.smoothing_window_size) * (i - (vx_float32)params_.smoothing_window_size) / (2.f * sigma * sigma));
sum += gaussian_weights_data[i];
}
//normalize weights
assert((sum > 0.00000000001) || (sum < - 0.00000000001));
vx_float32 scaler = 1.f / sum;
for (vx_int32 i = 0; i < num_items; i++)
{
gaussian_weights_data[i] *= scaler;
}
status |= vxAddArrayItems(gaussian_weights_, (vx_size)num_items, (void *)&gaussian_weights_data[0], sizeof(gaussian_weights_data[0]));
return status;
}
在initHomographyMatrices()函数中,用身份矩阵初始化单应矩阵的vx_delay:
vx_status GraphModeStabilizer::initHomographyMatrices()
{
vx_status status = VX_SUCCESS;
vx_float32 homography_data[3][3] = { {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f} };
for (vx_size i = 0; i < 2 * params_.smoothing_window_size; i++)
{
status |= vxCopyMatrix((vx_matrix)vxGetReferenceFromDelay(homography_matrices_, -i), homography_data, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST);
}
return status;
}
graph的初始化:
void GraphModeStabilizer::createMainGraph()
{
main_graph_ = vxCreateGraph(context_);
fast_corners_node_ = vxFastCornersNode(main_graph_,
(vx_image)vxGetReferenceFromDelay(gray_frames_delay_, -1),
params_.s_fast_threshold,
vx_true_e,
points_,
0);
color_convert_node_ = vxColorConvertNode( main_graph_,
(vx_image)vxGetReferenceFromDelay(frames_delay_, 0),
(vx_image)vxGetReferenceFromDelay(gray_frames_delay_, 0));
gaussian_pyramid_node_ = vxGaussianPyramidNode(main_graph_,
(vx_image)vxGetReferenceFromDelay(gray_frames_delay_, 0),
(vx_pyramid)vxGetReferenceFromDelay(pyr_delay_, 0));
opt_flow_node_ = vxOpticalFlowPyrLKNode(main_graph_,
(vx_pyramid)vxGetReferenceFromDelay(pyr_delay_, -1),
(vx_pyramid)vxGetReferenceFromDelay(pyr_delay_, 0),
points_,
points_,
corresponding_points_,
VX_TERM_CRITERIA_BOTH,
params_.s_opt_flow_epsilon,
params_.s_opt_flow_num_iterations,
params_.s_opt_flow_use_initial_estimate,
params_.opt_flow_win_size);
find_homography_node_ = nvxFindHomographyNode(main_graph_,points_,
corresponding_points_,
(vx_matrix)vxGetReferenceFromDelay(homography_matrices_, 0),
params_.homography_method,
params_.homography_ransac_threshold,
params_.homography_max_estimate_iters,
params_.homography_max_refine_iters,
params_.homography_confidence,
params_.homography_outlier_ratio,
NULL);
registerHomographySmootherKernel(context_);
homography_smoother_node_ = homographySmootherNode(main_graph_,
gaussian_weights_,
homography_matrices_,
perspective_matrix_);
warp_perspective_node_ = vxWarpPerspectiveNode(main_graph_,
(vx_image)vxGetReferenceFromDelay(frames_delay_, -params_.smoothing_window_size),
perspective_matrix_,
VX_INTERPOLATION_TYPE_BILINEAR,
stabilized_frame_);
//
// Graph verification
// Note: This verification is mandatory prior to graph execution
//
NVXIO_SAFE_CALL(vxVerifyGraph(main_graph_));
}
4. 执行算法的迭代。
由于大多数算法都转换为图形创建,因此每个步骤只有4个操作:
-
将当前帧添加到帧的循环缓冲区中;
-
执行图形;
-
缓冲区移动;
-
返回稳定帧。
以下代码中显示了此操作序列
NVXIO_SAFE_CALL(vxAgeDelay(homography_matrices_));
NVXIO_SAFE_CALL(vxAgeDelay(pyr_delay_));
NVXIO_SAFE_CALL(vxAgeDelay(gray_frames_delay_));
NVXIO_SAFE_CALL(vxAgeDelay(frames_delay_));
NVXIO_SAFE_CALL(nvxuCopyImage(context_, current_frame, (vx_image)vxGetReferenceFromDelay(frames_delay_, 0)));
NVXIO_SAFE_CALL(vxProcessGraph(main_graph_));
return stabilized_frame_;
5. Release objects。
GraphModeStabilizer::~GraphModeStabilizer()
{
vxReleaseArray(&points_);
vxReleaseArray(&corresponding_points_);
vxReleaseArray(&gaussian_weights_);
vxReleaseDelay(&homography_matrices_);
vxReleaseMatrix(&perspective_matrix_);
vxReleaseImage(&stabilized_frame_);
vxReleaseDelay(&gray_frames_delay_);
vxReleaseDelay(&pyr_delay_);
vxReleaseDelay(&frames_delay_);
vxReleaseGraph(&main_graph_);
};
结果
FastCorners : 0.544 ms
ColorConvert : 0.030 ms
BuildPyramid : 0.090 ms
OpticalFlow : 4.435 ms
FindHomography : 1.295 ms
HomographySmoother : 0.019 ms
WarpPerspective : 0.146 ms
TOTAL : 6.766 ms
这个版本比以前快了5%,立即模式和graph模式的性能优化主要在于,graph模式不需要在执行过程中对内存分配以及内存的复制。
从开发流程上来说最好是先使用opencv或者立即模式开发算法流程,测试处理流程没问题了然后再移植到graph模式下,最大程度的优化性能。这个处理算法不算复杂,所以性能提供有限,对于一些节点可以并行执行的更复杂算法,可以获得更大的性能增益。