本教程演示如何通过用visionworks函数直接替换opencv函数,轻松地在计算机视觉应用程序中利用visionworks库。 好处是应用程序的性能得到了显著的提高(x2.5),而没有明显的代码复杂性。
需要安装以下软件
CMake2.8.12opencv2.4+VisionWorks 1.4+我们使用视频去抖作为说明。视频去抖输入是一个抖动的视频,输出是一个稳定之后的视频。 视频稳定一个有点经典的算法是用来完成这项任务的。下面是一个简短的描述。 在执行过程中,算法保持了一个包含N个最新帧的队列 Q f Q_f Qf。每两个图像之间的Homography矩阵组成另一个队列Ah,长度为 2 ∗ ( N − 1 ) 2 *(N-1) 2∗(N−1).
下面说明一下步骤:
初始化 Q f Q_f Qf,包含0个frame,Ah中包含单位矩阵,i=0i= i+1,采集一张图片加入到 Q f Q_f Qf中,如果 Q f Q_f Qf中达到N了就删除最先压入的图像。使用cv::Fast()从i-1帧中找特征点使用特征点使用cv::calcOpticalFlowPyrLK()在当前帧上计算光流。两帧图像中特征点计算出一个单应矩阵H(i)Push H(i)到Ah队列中,如果满了删除最早的如果 i<4,这时图像还不够多,重复1,否则,通过多个单应矩相乘转换关系矩阵 T n T_n Tn,计算(i-3)帧和当前帧的变换关系。 T1=H(i−5)⋅H(i−4)⋅H(i−3), T2=H(i−4)⋅H(i−3) , T3=H(i−3), T4=E, T5=H−1(i−2), T6=(H(i−2)⋅H(i−1))−1, T7=(H(i−2)⋅H(i−1)⋅H(i))−1 使用1维高斯过滤矩阵 T n T_n Tn,使用 HomographySmoother::getSmoothedHomography()函数, T = ∑ w n T n T=∑w_nT_n T=∑wnTn (n =1到7) , 其中 w i w_i wi是沿高斯曲线选择的高斯权重,使得帧最接近当前帧并且对结果矩阵的影响最大。基于计算的矩阵T(cv::warpPerspective())将透视变换应用于第(i-3)帧。在屏幕上显示第(i-3)帧并返回步骤1。 这就是整个视频去抖的原理,主要在于特征匹配然后计算位移,再进行高斯滤波。下图展示了代码的流水线操作流程,蓝色的表示数据,黑色的表示执行算法:
为了使从opencv到visionworks的转换更容易,我们在所谓的即时模式中使用这些函数。这意味着每个函数都会立即执行,结果马上就可以得到,类似于普通的opencv函数。另一种选择是所谓的图形模式,将在下一个教程中讨论。 让我们看看opencv程序的性能。
FastCorners : 1.053 ms ColorConvert : 0.198 ms BuildPyramid : 0.770 ms OpticalFlow : 11.354 ms FindHomography : 1.563 ms WarpPerspective : 2.286 ms TOTAL : 17.368 ms这些每帧结果是在ntel i5-6500 CPU和GeForce GTX 650 GPU电脑上获得的。 显然, cv::calcOpticalFlowPyrLK()函数占用了大部分时间,因此它是我们优化的第一个目标,即用VisionWorks替代品替换它。为了简化数据转换,我们还替换了两个相关函数:cv::buildOpticalFlowPyramid() and cv::calcOpticalFlowPyrLK().
包含6步:
包含VisionWorks库添加VisionWorks类数据代码,并初始化它们将函数的输入数据从opencv数据类型转换为visionworks数据类型。使用相应的visionworks函数。将函数输出数据从visionworks数据类型转换为opencv类型。释放申请的objects。以下各节详细描述了这些步骤。
CMakeLists.txt添加添加三行代码
find_package(VisionWorks)include_directories("${VisionWorks_INCLUDE_DIRS}")target_link_libraries(vstab_demo ${VisionWorks_LIBRARIES}) 库文件 头文件,然后添加上链接目标。然后 就可以在程序中调用 VisionWorks函数了。 类似地,我们也需要添加 NVXIO库,这个库包含工具和IO函数。例如, NVXIO_SAFE_CALL用于处理VisionWorks函数返回的错误代码。 CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(vstab_demo) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") find_package(OpenCV REQUIRED core imgproc video highgui calib3d) find_package(VisionWorks REQUIRED) find_package(VisionWorks-NVXIO) include_directories("${OpenCV_INCLUDE_DIRS}") include_directories("${VisionWorks_INCLUDE_DIRS}") include_directories("${VisionWorks-NVXIO_INCLUDE_DIRS}") add_executable( vstab_demo main.cpp homography_smoother.cpp homography_smoother.hpp stabilizer.cpp stabilizer.hpp ) target_link_libraries(vstab_demo ${OpenCV_LIBS} ${VisionWorks_LIBRARIES} ${VisionWorks-NVXIO_LIBRARIES} )必要的函数
vxuGaussianPyramid(vx_context context, vx_image input, vx_pyramid gaussian ) vxuOpticalFlowPyrLK(vx_context context, vx_pyramid old_images, vx_pyramid new_images, vx_array old_points, vx_array new_points_estimates, vx_array new_points, vx_enum termination, vx_scalar epsilon, vx_scalar num_iterations, vx_scalar use_initial_estimate, vx_size window_dimension )OpenCVStabilizer类中创建以下字段:
vx_context context_; vx_image gray_frame_; vx_pyramid latest_pyr_; vx_pyramid current_pyr_; vx_array vx_points_; vx_array vx_corresponding_points_;注意,vx_pyramid latest_pyr_ 和 vx_pyramid current_pyr_用于替换程序opencv版本中的相应字段,即std::vectorcv::Mat latest_pyr_ and std::vectorcv::Mat current_pyr_. 这些字段初始化在OpenCVStabilizer::init()中
context_ = vxCreateContext(); vx_image gray = nvx_cv::createVXImageFromCVMat(context_, gray_latest_frame_); NVXIO_CHECK_REFERENCE(gray); vx_size width = gray_latest_frame_.cols; vx_size height = gray_latest_frame_.rows; latest_pyr_ = vxCreatePyramid(context_, (vx_size)params_.num_pyramid_levels, VX_SCALE_PYRAMID_HALF, width, height, VX_DF_IMAGE_U8); NVXIO_CHECK_REFERENCE(latest_pyr_); NVXIO_SAFE_CALL(vxuGaussianPyramid(context_, gray, latest_pyr_)); current_pyr_ = vxCreatePyramid(context_, (vx_size)params_.num_pyramid_levels, VX_SCALE_PYRAMID_HALF, width, height, VX_DF_IMAGE_U8); vxReleaseImage(&gray); vx_size capacity = 15000; vx_enum item_type = NVX_TYPE_KEYPOINTF; vx_points_ = vxCreateArray(context_, item_type, capacity); NVXIO_CHECK_REFERENCE(vx_points_); vx_corresponding_points_ = vxCreateArray(context_, item_type, capacity); NVXIO_CHECK_REFERENCE(vx_corresponding_points_);数据格式转换已经在《VisionWorks学习之 如何和Opencv交互使用》中有专门的介绍,但在这里,我们将简要介绍一些重要的要点,例如opencv和visionworks数据类型之间的差异、使用visionworks类型的特殊性、用户很容易产生的错误/假设。 众所周知,opencv库中的基本数据类型是cv:Mat。从名称上看,它用于矩阵处理。同一类型用于存储“普通”矩阵和图像(cv::Mat可以有多个通道来表示图像平面)。VisionWorks有单独的数据类型用于这些目的:vx_image和vx_matrix。有关cv::Mat或cv::Gpu::GpuMat与vx_image或vx_matrix之间的转换的信息,请参见:《VisionWorks学习之 如何和Opencv交互使用》 我们的这个例子中相应的输入参数对应如下表
OpenCVVisionWorkscv::Mat gray_current_frame_vx_image inputstd::vector<cv::KeyPoint> key_points_vx_array old_pointsfloat opt_flow_epsilonvx_scalar epsilonint opt_flow_num_iterationsvx_scalar num_iterationsint opt_flow_use_initial_estimatevx_scalar use_initial_estimateint opt_flow_win_sizevx_size window_dimension我们已经提到了如何将cv::Mat转换为vx_image,所以让我们考虑数组的转换。由于cv::KeyPoint和nvx::nvx_keypointf_t不兼容,我们需要在循环中转换元素。注意, cv::calcOpticalFlowPyrLK()使用std::vector<cv::Point2f>, vxuOpticalFlowPyrLK()使用VX_TYPE_KEYPOINT。与opencv函数不同,对vxuOpticalFlowPyrLK()来说,输入数组点的tracking_status非常重要。因为我们有所有有效点,所以将状态设置为1。
int points_count = key_points_.size(); std::vector<nvx_keypointf_t> vx_keypoint; vx_keypoint.resize(points_count); for (int i = 0; i < points_count; i++) { vx_keypoint[i].x = key_points_[i].pt.x; vx_keypoint[i].y = key_points_[i].pt.y; vx_keypoint[i].tracking_status = 1; }std::vector转化为vx_array还是比较方便的,因为他们有相同的数据类型。
vxTruncateArray(vx_points_, 0); NVXIO_SAFE_CALL(vxAddArrayItems(vx_points_, (vx_size)points_count, (void *)&vx_keypoint[0], sizeof(vx_keypoint[0])));其余的就是参数的转换了,float和int 转化为vx_scalar, int 转化为vx_size.
vx_scalar s_opt_flow_epsilon = vxCreateScalar(context_, VX_TYPE_FLOAT32, ¶ms_.opt_flow_epsilon); NVXIO_CHECK_REFERENCE(s_opt_flow_epsilon); vx_scalar s_opt_flow_num_iterations = vxCreateScalar(context_, VX_TYPE_UINT32, ¶ms_.opt_flow_num_iterations); NVXIO_CHECK_REFERENCE(s_opt_flow_num_iterations); assert(params_.opt_flow_use_initial_estimate == 0); vx_bool use_initial_estimate = vx_false_e; vx_scalar s_opt_flow_use_initial_estimate = vxCreateScalar(context_, VX_TYPE_BOOL, &use_initial_estimate); NVXIO_CHECK_REFERENCE(s_opt_flow_use_initial_estimate);请注意,必须将对应于值类型的枚举值传递给vxCreateScalar。在不匹配的情况下,这可能会导致很难找到的错误,例如,当我们需要32位整数,而传递32位浮点类型而不是32位整数。由于vx_size是size_t的包装,我们使用static_cast转换opt_flow_win_size,这个数是浮点而不是整数,是不确定的。
从算法中可以清楚地看出,只需要corresponding_points_ and status_ arrays就可以进行进一步的运算。用vx_corresponding_points_ array填充它们:
status_.clear(); corresponding_points_.clear(); vx_size stride = 0; vx_size num_items = 0; vxQueryArray(vx_corresponding_points_, VX_ARRAY_ATTRIBUTE_NUMITEMS, &num_items, sizeof(num_items)); corresponding_points_.resize(num_items); status_.resize(num_items); void *base; vx_map_id map_id; NVXIO_SAFE_CALL(vxMapArrayRange(vx_corresponding_points_, 0, num_items, &map_id, &stride, (void**)&base, VX_READ_ONLY, VX_MEMORY_TYPE_HOST, 0)); for (vx_size i = 0; i < num_items; i++) { nvx_keypointf_t cur_item = vxArrayItem(nvx_keypointf_t, base, i, stride); corresponding_points_[i].x = cur_item.x; corresponding_points_[i].y = cur_item.y; status_[i] = cur_item.tracking_status; } NVXIO_SAFE_CALL(vxUnmapArrayRange(vx_corresponding_points_, map_id));下面来看一下处理性能 opencv版本:
FastCorners : 1.047 ms ColorConvert : 0.201 ms BuildPyramid : 0.779 ms OpticalFlow : 11.407 ms FindHomography : 1.556 ms WarpPerspective : 2.293 ms TOTAL : 17.397 ms block for change with OpenCV: 12.194 msVisionWorks version results (replaces BuildPyramid and OpticalFlow):
FastCorners : 1.194 ms ColorConvert : 0.215 ms BuildPyramid : 0.307 ms OpticalFlow : 4.098 ms FindHomography : 2.608 ms WarpPerspective : 2.978 ms TOTAL : 11.899 ms changed block with Visionworks : 4.505 msVisionWorks version results (replaces all OpenCV functions):
FastCorners : 0.802 ms ColorConvert : 0.124 ms BuildPyramid : 0.131 ms OpticalFlow : 4.411 ms FindHomography : 1.249 ms WarpPerspective : 0.115 ms TOTAL : 7.170 ms与OpenCV版本相比,这给出了大约2.5X的加速。
未完。。。。。
