VPI - 视觉编程接口

3.2 版本

ORB 特征检测器

概述

Oriented FAST and rBRIEF (ORB) 是一种特征检测和描述算法。它在输入图像金字塔中检测特征,并为每个特征生成描述符,返回每个特征的坐标及其相关的位串描述符。此示例应用程序执行以下操作:(1)读取输入图像;(2)从图像创建高斯金字塔;(3)在金字塔上运行 ORB;(4)将 ORB 特征绘制为关键点,并用颜色表示 ORB 描述符;(5)写入带有彩色关键点的输出图像。每个特征都作为圆形绘制在输入图像之上,圆形的颜色根据与特征关联的描述符从蓝色到红色映射。该映射使用每个描述符到第一个描述符的汉明距离,因此蓝色关键点是第一个描述符,而黄色到红色的阴影则表示逐渐增加的距离。

说明

命令行参数为

<backend> <input image>

其中

  • backend:cpucuda;它定义了将执行处理的后端。
  • input image:要用作源图像的输入图像文件名,它接受 png、jpeg 和其他格式。

这是一个示例

  • C++
    ./vpi_sample_18_orb_feature_detector cuda ../assets/kodim08.png
  • Python
    python3 main.py cuda ../assets/kodim08.png

这是使用 CUDA 后端和提供的示例图像之一。您可以尝试其他图像,但需遵守算法施加的约束。

结果

输入图像输入图像和翻转图像之间匹配的特征

源代码

为方便起见,以下代码也安装在 samples 目录中。

语言
27 import sys
28 import vpi
29 import numpy as np
30 from PIL import Image, ImageOps
31 from argparse import ArgumentParser
32 import cv2
33 
34 
35 # Parse command line arguments
36 parser = ArgumentParser()
37 parser.add_argument('backend', choices=['cpu','cuda'],
38  help='Backend to be used for processing')
39 
40 parser.add_argument('s', metavar='filename',
41  help='Image to be used as source')
42 
43 args = parser.parse_args()
44 
45 if args.backend == 'cpu'
46  backend = vpi.Backend.CPU
47 elif args.backend == 'cuda'
48  backend = vpi.Backend.CUDA
49 else
50  sys.exit("Un-supported backend")
51 
52 # Load input image into a vpi.Image
53 try
54  srcData = np.asarray(ImageOps.grayscale(Image.open(args.s)))
55 except IOError
56  sys.exit("Source file not found")
57 except
58  sys.exit("Error with source file")
59 
60 src = vpi.asimage(srcData)
61 
62 # Using the chosen backend to build the input pyramid and run ORB
63 with backend
64  pyr = src.gaussian_pyramid(3)
65  corners, descriptors = pyr.orb(intensity_threshold=142, max_features_per_level=88, max_pyr_levels=3)
66 
67 # Draw the keypoints in the output image
68 
69 out = src.convert(vpi.Format.BGR8, backend=vpi.Backend.CUDA)
70 
71 if corners.size > 0
72  distances = []
73  with descriptors.rlock_cpu() as descriptors_data
74  first_desc = descriptors_data[0][0]
75  for i in range(descriptors.size)
76  curr_desc = descriptors_data[i][0]
77  hamm_dist = sum([bin(c ^ f).count('1') for c, f in zip(curr_desc, first_desc)])
78  distances.append(hamm_dist)
79 
80  max_dist = max(distances)
81 
82  cmap = cv2.applyColorMap(np.arange(0, 256, dtype=np.uint8), cv2.COLORMAP_JET)
83  cmap_idx = lambda i: int(round((distances[i] / max_dist) * 255))
84 
85  with out.lock_cpu() as out_data, corners.rlock_cpu() as corners_data
86  for i in range(corners.size)
87  color = tuple([int(x) for x in cmap[cmap_idx(i), 0]])
88  kpt = tuple(corners_data[i].astype(np.int16))
89  x = kpt[0] * (2 ** kpt[2])
90  y = kpt[1] * (2 ** kpt[2])
91  cv2.circle(out_data, (x, y), 3, color, -1)
92 
93 # Save the output image to disk
94 cv2.imwrite('orb_feature_python'+str(sys.version_info[0])+'_'+args.backend+'.png', out.cpu())
29 #include <opencv2/core.hpp>
30 #include <opencv2/features2d.hpp>
31 #include <opencv2/imgcodecs.hpp>
32 #include <opencv2/imgproc.hpp>
33 #include <vpi/OpenCVInterop.hpp>
34 
35 #include <vpi/Array.h>
36 #include <vpi/Image.h>
37 #include <vpi/Pyramid.h>
38 #include <vpi/Status.h>
39 #include <vpi/Stream.h>
42 #include <vpi/algo/ImageFlip.h>
43 #include <vpi/algo/ORB.h>
44 
45 #include <bitset>
46 #include <cmath>
47 #include <cstdio>
48 #include <cstring>
49 #include <iostream>
50 #include <numeric>
51 #include <sstream>
52 #include <vector>
53 
54 #define CHECK_STATUS(STMT) \
55  do \
56  { \
57  VPIStatus status = (STMT); \
58  if (status != VPI_SUCCESS) \
59  { \
60  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
61  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
62  std::ostringstream ss; \
63  ss << vpiStatusGetName(status) << ": " << buffer; \
64  throw std::runtime_error(ss.str()); \
65  } \
66  } while (0);
67 
68 static cv::Mat DrawKeypoints(cv::Mat img, VPIPyramidalKeypointF32 *kpts, VPIBriefDescriptor *descs, int numKeypoints)
69 {
70  cv::Mat out;
71  img.convertTo(out, CV_8UC1);
72  cvtColor(out, out, cv::COLOR_GRAY2BGR);
73 
74  if (numKeypoints == 0)
75  {
76  return out;
77  }
78 
79  std::vector<int> distances(numKeypoints, 0);
80  float maxDist = 0.f;
81 
82  for (int i = 0; i < numKeypoints; i++)
83  {
84  for (int j = 0; j < VPI_BRIEF_DESCRIPTOR_ARRAY_LENGTH; j++)
85  {
86  distances[i] += std::bitset<8 * sizeof(uint8_t)>(descs[i].data[j] ^ descs[0].data[j]).count();
87  }
88  if (distances[i] > maxDist)
89  {
90  maxDist = distances[i];
91  }
92  }
93 
94  uint8_t ids[256];
95  std::iota(&ids[0], &ids[0] + 256, 0);
96  cv::Mat idsMat(256, 1, CV_8UC1, ids);
97 
98  cv::Mat cmap;
99  applyColorMap(idsMat, cmap, cv::COLORMAP_JET);
100 
101  for (int i = 0; i < numKeypoints; i++)
102  {
103  int cmapIdx = static_cast<int>(std::round((distances[i] / maxDist) * 255));
104 
105  float rescale = std::pow(2, kpts[i].octave);
106  float x = kpts[i].x * rescale;
107  float y = kpts[i].y * rescale;
108 
109  circle(out, cv::Point(x, y), 3, cmap.at<cv::Vec3b>(cmapIdx, 0), -1);
110  }
111 
112  return out;
113 }
114 
115 int main(int argc, char *argv[])
116 {
117  // OpenCV image that will be wrapped by a VPIImage.
118  // Define it here so that it's destroyed *after* wrapper is destroyed
119  cv::Mat cvImage;
120 
121  // VPI objects that will be used
122  VPIImage imgInput = NULL;
123  VPIImage imgGrayScale = NULL;
124 
125  VPIPyramid pyrInput = NULL;
126  VPIArray keypoints = NULL;
127  VPIArray descriptors = NULL;
128  VPIPayload orbPayload = NULL;
129  VPIStream stream = NULL;
130 
131  int retval = 0;
132 
133  try
134  {
135  // =============================
136  // Parse command line parameters
137 
138  if (argc != 3)
139  {
140  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <cpu|cuda> <input image>");
141  }
142 
143  std::string strBackend = argv[1];
144  std::string strInputFileName = argv[2];
145 
146  // Now parse the backend
147  VPIBackend backend;
148 
149  if (strBackend == "cpu")
150  {
151  backend = VPI_BACKEND_CPU;
152  }
153  else if (strBackend == "cuda")
154  {
155  backend = VPI_BACKEND_CUDA;
156  }
157  else
158  {
159  throw std::runtime_error("Backend '" + strBackend + "' not recognized, it must be either cpu or cuda.");
160  }
161 
162  // Use the selected backend with CPU to be able to read data back from CUDA to CPU for example.
163  const VPIBackend backendWithCPU = static_cast<VPIBackend>(backend | VPI_BACKEND_CPU);
164 
165  // =====================
166  // Load the input image
167 
168  cvImage = cv::imread(strInputFileName);
169  if (cvImage.empty())
170  {
171  throw std::runtime_error("Can't open first image: '" + strInputFileName + "'");
172  }
173 
174  // =================================
175  // Allocate all VPI resources needed
176 
177  // Create the stream where processing will happen
178  CHECK_STATUS(vpiStreamCreate(0, &stream));
179 
180  // Define the algorithm parameters.
181  VPIORBParams orbParams;
182  CHECK_STATUS(vpiInitORBParams(&orbParams));
183 
184  orbParams.fastParams.intensityThreshold = 142;
185  orbParams.maxFeaturesPerLevel = 88;
186  orbParams.maxPyramidLevels = 3;
187 
188  // We now wrap the loaded image into a VPIImage object to be used by VPI.
189  // VPI won't make a copy of it, so the original image must be in scope at all times.
190  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImage, 0, &imgInput));
191  CHECK_STATUS(vpiImageCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_U8, 0, &imgGrayScale));
192 
193  // For the output arrays capacity we can use the maximum number of features per level multiplied by the
194  // maximum number of pyramid levels, this will be the de factor maximum for all levels of the input.
195  int outCapacity = orbParams.maxFeaturesPerLevel * orbParams.maxPyramidLevels;
196 
197  // Create the output keypoint array.
198  CHECK_STATUS(vpiArrayCreate(outCapacity, VPI_ARRAY_TYPE_PYRAMIDAL_KEYPOINT_F32, backendWithCPU, &keypoints));
199 
200  // Create the output descriptors array. To output corners only use NULL instead.
201  CHECK_STATUS(vpiArrayCreate(outCapacity, VPI_ARRAY_TYPE_BRIEF_DESCRIPTOR, backendWithCPU, &descriptors));
202 
203  // For the internal buffers capacity we can use the maximum number of features per level multiplied by 20.
204  // This will make FAST find a large number of corners so then ORB can select the top N corners in
205  // accordance to Harris score of each corner, where N = maximum number of features per level.
206  int bufCapacity = orbParams.maxFeaturesPerLevel * 20;
207 
208  // Create the payload for ORB Feature Detector algorithm
209  CHECK_STATUS(vpiCreateORBFeatureDetector(backend, bufCapacity, &orbPayload));
210 
211  // ================
212  // Processing stage
213 
214  // First convert input to grayscale
215  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, backend, imgInput, imgGrayScale, NULL));
216 
217  // Then, create the Gaussian Pyramid for the image and wait for the execution to finish
218  CHECK_STATUS(vpiPyramidCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_U8, orbParams.maxPyramidLevels, 0.5,
219  backend, &pyrInput));
220  CHECK_STATUS(vpiSubmitGaussianPyramidGenerator(stream, backend, imgGrayScale, pyrInput, VPI_BORDER_CLAMP));
221 
222  // Then get ORB features and wait for the execution to finish
223  CHECK_STATUS(vpiSubmitORBFeatureDetector(stream, backend, orbPayload, pyrInput, keypoints, descriptors,
224  &orbParams, VPI_BORDER_LIMITED));
225 
226  CHECK_STATUS(vpiStreamSync(stream));
227 
228  // =======================================
229  // 输出处理并保存到磁盘
230 
231  // 锁定输出关键点和分数,以在 CPU 内存上检索其数据
232  VPIArrayData outKeypointsData;
233  VPIArrayData outDescriptorsData;
234  VPIImageData imgData;
235  CHECK_STATUS(vpiArrayLockData(keypoints, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &outKeypointsData));
236  CHECK_STATUS(vpiArrayLockData(descriptors, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &outDescriptorsData));
237  CHECK_STATUS(vpiImageLockData(imgGrayScale, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgData));
238 
239  VPIPyramidalKeypointF32 *outKeypoints = (VPIPyramidalKeypointF32 *)outKeypointsData.buffer.aos.data;
240  VPIBriefDescriptor *outDescriptors = (VPIBriefDescriptor *)outDescriptorsData.buffer.aos.data;
241 
242  cv::Mat img;
243  CHECK_STATUS(vpiImageDataExportOpenCVMat(imgData, &img));
244 
245  // 在输出图像中绘制关键点
246  cv::Mat outImage = DrawKeypoints(img, outKeypoints, outDescriptors, *outKeypointsData.buffer.aos.sizePointer);
247 
248  // 将输出图像保存到磁盘
249  imwrite("orb_feature_detector_" + strBackend + ".png", outImage);
250 
251  // 完成输出处理,不要忘记解锁它们。
252  CHECK_STATUS(vpiImageUnlock(imgGrayScale));
253  CHECK_STATUS(vpiArrayUnlock(keypoints));
254  CHECK_STATUS(vpiArrayUnlock(descriptors));
255  }
256  catch (std::exception &e)
257  {
258  std::cerr << e.what() << std::endl;
259  retval = 1;
260  }
261 
262  // ========
263  // 清理
264 
265  // 确保在销毁可能仍在使用的对象之前同步流。
266  // 确保在销毁可能仍在使用的对象之前同步流。
267  vpiStreamSync(stream);
268 
269  vpiImageDestroy(imgInput);
270  vpiImageDestroy(imgGrayScale);
271  vpiArrayDestroy(keypoints);
272  vpiArrayDestroy(descriptors);
273  vpiPayloadDestroy(orbPayload);
274  vpiStreamDestroy(stream);
275 
276  return retval;
277 }
用于处理 VPI 数组的函数和结构。
声明处理图像格式转换的函数。
声明处理高斯金字塔的函数。
声明实现图像翻转算法的函数。
#define VPI_IMAGE_FORMAT_U8
具有一个 8 位无符号整数通道的单平面。
Definition: ImageFormat.h:100
用于处理 VPI 图像的函数和结构。
声明实现 ORB 支持的函数。
用于处理 VPI 的 OpenCV 互操作性的函数。
用于处理 VPI 金字塔的函数和结构。
VPI 状态代码处理函数的声明。
声明处理 VPI 流的函数。
#define VPI_BRIEF_DESCRIPTOR_ARRAY_LENGTH
Brief 描述符数组的长度。
Definition: Types.h:363
存储 BRIEF 描述符。
Definition: Types.h:374
void * data
指向数组的第一个元素。
Definition: Array.h:135
VPIArrayBuffer buffer
存储数组内容。
Definition: Array.h:175
int32_t * sizePointer
指向数组中元素的数量。
Definition: Array.h:122
VPIArrayBufferAOS aos
以结构体数组布局存储的数组。
Definition: Array.h:162
VPIStatus vpiArrayUnlock(VPIArray array)
释放数组对象的锁定。
VPIStatus vpiArrayLockData(VPIArray array, VPILockMode mode, VPIArrayBufferType bufType, VPIArrayData *data)
获取数组对象的锁定并返回数组内容。
void vpiArrayDestroy(VPIArray array)
销毁数组实例。
VPIStatus vpiArrayCreate(int32_t capacity, VPIArrayType type, uint64_t flags, VPIArray *array)
创建空数组实例。
struct VPIArrayImpl * VPIArray
数组的句柄。
Definition: Types.h:232
@ VPI_ARRAY_TYPE_BRIEF_DESCRIPTOR
VPIBriefDescriptor 元素。
Definition: ArrayType.h:84
@ VPI_ARRAY_TYPE_PYRAMIDAL_KEYPOINT_F32
VPIPyramidalKeypointF32 元素。
Definition: ArrayType.h:87
@ VPI_ARRAY_BUFFER_HOST_AOS
主机可访问的结构体数组。
Definition: Array.h:146
存储有关数组特性和内容的信息。
Definition: Array.h:168
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
将图像内容转换为所需的格式,具有可选的缩放和偏移。
float intensityThreshold
选择像素作为关键点候选点周围圆弧一部分的阈值。
Definition: FASTCorners.h:112
VPIStatus vpiSubmitGaussianPyramidGenerator(VPIStream stream, uint64_t backend, VPIImage input, VPIPyramid output, VPIBorderExtension border)
从输入图像计算高斯金字塔。
void vpiImageDestroy(VPIImage img)
销毁图像实例。
struct VPIImageImpl * VPIImage
图像的句柄。
Definition: Types.h:256
VPIStatus vpiImageLockData(VPIImage img, VPILockMode mode, VPIImageBufferType bufType, VPIImageData *data)
获取图像对象的锁定并返回图像内容。
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用指定的标志创建空图像实例。
VPIStatus vpiImageUnlock(VPIImage img)
释放图像对象的锁定。
@ VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR
主机可访问,平面采用 pitch-linear 内存布局。
Definition: Image.h:172
存储有关图像特性和内容的信息。
Definition: Image.h:234
VPIStatus vpiImageCreateWrapperOpenCVMat(const cv::Mat &mat, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用给定的图像格式将 cv::Mat 封装到 VPIImage 中。
VPIStatus vpiImageDataExportOpenCVMat(const VPIImageData &imgData, cv::Mat *mat)
使用来自锁定 VPIImage 的 VPIImageData 数据填充现有的 cv::Mat。
int32_t maxFeaturesPerLevel
ORB 在输入金字塔的每个级别使用的最大特征数 N。
Definition: ORB.h:106
VPIFASTCornerDetectorParams fastParams
FAST 角点检测器的参数,有关更多详细信息,请参见 FAST 角点检测器。
Definition: ORB.h:94
int32_t maxPyramidLevels
要利用的输入金字塔中的最大级别数。
Definition: ORB.h:111
VPIStatus vpiInitORBParams(VPIORBParams *params)
使用默认值初始化 VPIORBParams。
VPIStatus vpiSubmitORBFeatureDetector(VPIStream stream, uint64_t backend, VPIPayload payload, VPIPyramid input, VPIArray outCorners, VPIArray outDescriptors, const VPIORBParams *params, VPIBorderExtension border)
向流提交 ORB 特征检测器操作。
VPIStatus vpiCreateORBFeatureDetector(uint64_t backends, int32_t capacity, VPIPayload *payload)
创建 ORB 特征检测器负载。
定义 vpiSubmitORBFeatureDetector 参数的结构。
Definition: ORB.h:89
struct VPIPayloadImpl * VPIPayload
算法负载的句柄。
Definition: Types.h:268
void vpiPayloadDestroy(VPIPayload payload)
释放负载对象和所有关联的资源。
VPIStatus vpiPyramidCreate(int32_t width, int32_t height, VPIImageFormat fmt, int32_t numLevels, float scale, uint64_t flags, VPIPyramid *pyr)
使用指定的标志创建空图像金字塔实例。
struct VPIPyramidImpl * VPIPyramid
图像金字塔的句柄。
Definition: Types.h:262
struct VPIStreamImpl * VPIStream
流的句柄。
Definition: Types.h:250
VPIStatus vpiStreamSync(VPIStream stream)
阻止调用线程,直到此流队列中的所有已提交命令完成(队列为空)...
VPIBackend
VPI 后端类型。
Definition: Types.h:91
void vpiStreamDestroy(VPIStream stream)
销毁流实例并释放所有硬件资源。
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
创建流实例。
@ VPI_BACKEND_CUDA
CUDA 后端。
Definition: Types.h:93
@ VPI_BACKEND_CPU
CPU 后端。
Definition: Types.h:92
float y
关键点的 y 坐标。
Definition: Types.h:322
float x
关键点的 x 坐标。
Definition: Types.h:321
@ VPI_BORDER_LIMITED
将图像视为受限,不访问外部像素。
Definition: Types.h:282
@ VPI_BORDER_CLAMP
无限重复边界像素。
Definition: Types.h:279
@ VPI_LOCK_READ
仅锁定内存以进行读取。
Definition: Types.h:617
存储 float32 基于金字塔的关键点坐标。坐标包括 (x,...
Definition: Types.h:320