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函數都存在對應的節點,但是兩個用戶函數在庫中沒有對應的節點。不可能畫出一個有“洞”的圖
;它必須包含一個算法的連續部分。解決這個問題有兩種方法:
-
將算法分成兩部分,由用戶代碼分隔。因此,第一部分包含與前五個函數相對應的節點,而第二個圖僅包含一個節點(或者不創建單節點圖並保留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。
新節點必須執行以下操作:
-
將新的單應矩陣添加到(2 * smoothing_window_size_)矩陣的循環緩衝區中;
-
對單應矩陣應用了更平滑的單應運算穩定映射。
由於節點不應該存儲其狀態,因此它必須採用高斯權重數組(也可以在每次迭代中計算)、單應矩陣緩衝區和新的單應矩陣。轉換矩陣作爲輸出參數傳遞給節點。
代碼在文件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模式下,最大程度的優化性能。這個處理算法不算複雜,所以性能提供有限,對於一些節點可以並行執行的更復雜算法,可以獲得更大的性能增益。