VPI - 视觉编程接口

3.2 版本

模板匹配

概述

模板匹配是一种在较大的源图像中搜索和查找模板图像位置的方法。 输出是包含模板图像在每个位置的匹配得分的图像。 输出中的最大位置对应于最佳匹配位置。

说明

命令行参数为

<backend> <输入图像> <边界框>

其中

  • backend:cpucuda; 它定义了将执行处理的后端。
  • input image:要用作源图像的输入图像文件名,它接受 png、jpeg 以及可能的其他格式。
  • bounding box:从输入图像中提取定义的边界框,并将其用作模板图像。

这是一个例子

  • C++
    ./vpi_sample_17_template_matching cuda ../assets/kodim08.png 100,200,100,100
  • Python
    python3 main.py cuda ../assets/kodim08.png 100,200,100,100

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

结果

输入图像转换为 U8 的输出图像

源代码

为了方便起见,这里是也安装在 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 parser = ArgumentParser()
36 parser.add_argument('backend', choices=['cpu','cuda'],
37  help='Backend to be used for processing')
38 
39 parser.add_argument('s', metavar='filename',
40  help='Image to be used as source')
41 
42 parser.add_argument('t', metavar='X,Y,W,H',
43  help='Use specified bounding box of source as template image')
44 
45 args = parser.parse_args()
46 
47 if args.backend == 'cpu'
48  backend = vpi.Backend.CPU
49 elif args.backend == 'cuda'
50  backend = vpi.Backend.CUDA
51 else
52  sys.exit("Un-supported backend")
53 
54 try
55  templBbox = np.array([int(x) for x in args.t.split(',')])
56 except ValueError
57  exit("Error parsing template bounding box")
58 
59 # 将输入加载到 vpi.Image 中
60 try
61  srcData = np.asarray(ImageOps.grayscale(Image.open(args.s)))
62 except IOError
63  sys.exit("Source file not found")
64 except
65  sys.exit("Error with source file")
66 
67 src = vpi.asimage(srcData)
68 templData = srcData[templBbox[1]:templBbox[1]+templBbox[3], templBbox[0]:templBbox[0]+templBbox[2]]
69 templ = vpi.asimage(templData)
70 
71 # Using the chosen backend,
72 with backend
73  output = vpi.templateMatching(src, templ)
74 
75  temp = output.convert(vpi.Format.F32, backend=vpi.Backend.CUDA, scale = 255)
76 
77  min_coords, max_coords = temp.minmaxloc(min_capacity=10000, max_capacity=10000)
78 
79  output = temp.convert(vpi.Format.U8, backend=vpi.Backend.CUDA)
80 
81 # Lock output
82 with max_coords.rlock_cpu() as max_data
83  max_loc = tuple(max_data[0].astype(int)[::-1])
84 
85 # Save result to disk
86 print('Provided coord of bounding box for the template is [{}, {}] with w={} and h={}'.format(templBbox[1], templBbox[0], templBbox[2], templBbox[3]))
87 print('Template matching location coord is [{}, {}]'.format(max_loc[0], max_loc[1]))
88 outputData = output.cpu()
89 cv2.rectangle(outputData, (max_loc[1]-20, max_loc[0]-20), (max_loc[1]+20, max_loc[0]+20), 255, 2)
90 Image.fromarray(outputData).save('template_matching_score_python'+str(sys.version_info[0])+'_'+args.backend+'.png')
29 #include <opencv2/core/version.hpp>
30 #if CV_MAJOR_VERSION >= 3
31 # include <opencv2/imgcodecs.hpp>
32 #else
33 # include <opencv2/highgui/highgui.hpp>
34 #endif
35 #include <opencv2/imgproc/imgproc.hpp>
36 #include <vpi/OpenCVInterop.hpp>
37 
38 #include <vpi/Array.h>
39 #include <vpi/Image.h>
40 #include <vpi/Status.h>
41 #include <vpi/Stream.h>
43 #include <vpi/algo/MinMaxLoc.h>
45 
46 #include <cassert>
47 #include <cstring> // for memset
48 #include <iostream>
49 #include <sstream>
50 
51 #define CHECK_STATUS(STMT) \
52  do \
53  { \
54  VPIStatus status = (STMT); \
55  if (status != VPI_SUCCESS) \
56  { \
57  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
58  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
59  std::ostringstream ss; \
60  ss << vpiStatusGetName(status) << ": " << buffer; \
61  throw std::runtime_error(ss.str()); \
62  } \
63  } while (0);
64 
65 int main(int argc, char *argv[])
66 {
67  // OpenCV image that will be wrapped by a VPIImage.
68  // Define it here so that it's destroyed *after* wrapper is destroyed
69  cv::Mat cvImage;
70  cv::Mat cvImageU8;
71 
72  cv::Mat cvTempl;
73 
74  VPIStream stream = NULL;
75 
76  // VPI objects that will be used
77  VPIImage input = NULL;
78  VPIImage templ = NULL;
79  int originX, originY, templWidth, templHeight;
80 
81  int outWidth, outHeight;
82  VPIImage output = NULL;
83  VPIImage outputScaled = NULL;
84  VPIImage outputU8 = NULL;
85 
86  VPIArray minCoords = NULL;
87  VPIArray maxCoords = NULL;
88 
89  VPIPayload payload = NULL;
90  VPIPayload payloadMinMax = NULL;
91 
92  int retval = 0;
93 
94  try
95  {
96  if (argc != 4)
97  {
98  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <cpu|cuda> <input image> <x,y,w,h> ");
99  }
100 
101  std::string strBackend = argv[1];
102  std::string strInputFileName = argv[2];
103 
104  if (sscanf(argv[3], "%d,%d,%d,%d", &originX, &originY, &templWidth, &templHeight) != 4)
105  {
106  throw std::runtime_error(
107  "Invalid data format. Specify the bounding box of the input image as template image.");
108  }
109 
110  // Load the input image
111  cvImage = cv::imread(strInputFileName);
112  if (cvImage.empty())
113  {
114  throw std::runtime_error("Can't open '" + strInputFileName + "'");
115  }
116 
117  assert(cvImage.type() == CV_8UC3);
118 
119  // convert image to gray scale
120  cvtColor(cvImage, cvImageU8, cv::COLOR_BGR2GRAY);
121 
122  if (originX + templWidth > cvImage.cols || originY + templHeight > cvImage.rows)
123  {
124  throw std::runtime_error("Bounding box is out of range of input image size");
125  }
126 
127  cv::Rect templROI(originX, originY, templWidth, templHeight);
128  cv::Mat croppedRef(cvImageU8, templROI);
129  croppedRef.copyTo(cvTempl);
130 
131  // Now parse the backend
132  VPIBackend backend;
133 
134  if (strBackend == "cpu")
135  {
136  backend = VPI_BACKEND_CPU;
137  }
138  else if (strBackend == "cuda")
139  {
140  backend = VPI_BACKEND_CUDA;
141  }
142  else
143  {
144  throw std::runtime_error("Backend '" + strBackend + "' not recognized, it must be either cpu, cuda");
145  }
146 
147  // 1. Initialization phase ---------------------------------------
148 
149  CHECK_STATUS(vpiStreamCreate(backend, &stream));
150 
151  // We now wrap the loaded image into a VPIImage object to be used by VPI.
152  // VPI won't make a copy of it, so the original
153  // image must be in scope at all times.
154  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImageU8, 0, &input));
155 
156  // Create template iamge
157  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvTempl, 0, &templ));
158 
159  // Now create the output image.
160  outWidth = cvImage.cols - templWidth + 1;
161  outHeight = cvImage.rows - templHeight + 1;
162  CHECK_STATUS(vpiImageCreate(outWidth, outHeight, VPI_IMAGE_FORMAT_F32, 0, &output));
163  CHECK_STATUS(vpiImageCreate(outWidth, outHeight, VPI_IMAGE_FORMAT_F32, 0, &outputScaled));
164  CHECK_STATUS(vpiImageCreate(outWidth, outHeight, VPI_IMAGE_FORMAT_U8, 0, &outputU8));
165 
166  CHECK_STATUS(vpiArrayCreate(10000, VPI_ARRAY_TYPE_KEYPOINT_F32, 0, &minCoords));
167  CHECK_STATUS(vpiArrayCreate(10000, VPI_ARRAY_TYPE_KEYPOINT_F32, 0, &maxCoords));
168 
169  // Create payload
170  CHECK_STATUS(vpiCreateTemplateMatching(backend, cvImage.cols, cvImage.rows, &payload));
171 
172  CHECK_STATUS(vpiCreateMinMaxLoc(backend, outWidth, outHeight, VPI_IMAGE_FORMAT_F32, &payloadMinMax));
173 
174  // 2. Computation phase ---------------------------------------
175 
176  // Set source image
177  CHECK_STATUS(vpiTemplateMatchingSetSourceImage(stream, backend, payload, input));
178 
179  // Set source image
180  CHECK_STATUS(vpiTemplateMatchingSetTemplateImage(stream, backend, payload, templ, NULL));
181 
182  // Submit
183  CHECK_STATUS(vpiSubmitTemplateMatching(stream, backend, payload, output, VPI_TEMPLATE_MATCHING_NCC));
184 
185  CHECK_STATUS(vpiSubmitMinMaxLoc(stream, backend, payloadMinMax, output, minCoords, maxCoords));
186 
187  // Convert output from F32 to U8
189  CHECK_STATUS(vpiInitConvertImageFormatParams(&params));
190  params.scale = 255;
191  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, backend, output, outputScaled, &params));
192 
193  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, backend, outputScaled, outputU8, NULL));
194 
195  // Wait until the algorithm finishes processing
196  CHECK_STATUS(vpiStreamSync(stream));
197 
198  // Now let's retrieve the output image contents and output it to disk
199  {
200  // Lock output image to retrieve its data on cpu memory
201  VPIArrayData maxCoordsData;
202  CHECK_STATUS(vpiArrayLockData(maxCoords, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &maxCoordsData));
203 
204  VPIKeypointF32 *max_coords = (VPIKeypointF32 *)maxCoordsData.buffer.aos.data;
205  int max_i = max_coords[0].y;
206  int max_j = max_coords[0].x;
207 
208  VPIImageData outData;
209  CHECK_STATUS(vpiImageLockData(outputU8, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &outData));
210 
211  // Returned data consists of host-accessible memory buffers in pitch-linear layout.
212  assert(outData.bufferType == VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR);
213 
214  cv::Mat cvOut;
215  CHECK_STATUS(vpiImageDataExportOpenCVMat(outData, &cvOut));
216 
217  cv::Rect rect(max_i - 20, max_j - 20, 40, 40);
218  rectangle(cvOut, rect, 255, 2);
219  imwrite("template_matching_score_" + strBackend + ".png", cvOut);
220 
221  printf("Provided coord of bounding box for the template is [%d, %d] with w=%d and h=%d \n", originY,
222  originX, templWidth, templHeight);
223  printf("Template matching location coord is [%d, %d] \n", max_j, max_i);
224 
225  // Done handling output image, don't forget to unlock it.
226  CHECK_STATUS(vpiArrayUnlock(maxCoords));
227  CHECK_STATUS(vpiImageUnlock(outputU8));
228  }
229  }
230  catch (std::exception &e)
231  {
232  std::cerr << e.what() << std::endl;
233  retval = 1;
234  }
235 
236  # 清理
237 
238  // Make sure stream is synchronized before destroying the objects
239  // that might still be in use.
240  vpiStreamSync(stream);
241 
242  vpiPayloadDestroy(payload);
243  vpiPayloadDestroy(payloadMinMax);
244 
245  vpiImageDestroy(input);
246  vpiImageDestroy(templ);
247  vpiImageDestroy(output);
248  vpiImageDestroy(outputU8);
249  vpiArrayDestroy(minCoords);
250  vpiArrayDestroy(maxCoords);
251  vpiStreamDestroy(stream);
252 
253  return retval;
254 }
用于处理 VPI 数组的函数和结构。
声明处理图像格式转换的函数。
#define VPI_IMAGE_FORMAT_F32
具有一个 32 位浮点通道的单平面。
#define VPI_IMAGE_FORMAT_U8
具有一个 8 位无符号整数通道的单平面。
用于处理 VPI 图像的函数和结构。
声明在图像中执行查找最小和最大位置的函数。
用于处理 OpenCV 与 VPI 的互操作性的函数。
VPI 状态码处理函数的声明。
声明处理 VPI 流的函数。
声明实现模板匹配算法的函数。
void * data
指向数组的第一个元素。
定义: Array.h:135
VPIArrayBuffer buffer
存储数组内容。
定义: Array.h:175
VPIArrayBufferAOS aos
以结构体数组布局存储的数组。
定义: 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
数组的句柄。
定义: Types.h:232
@ VPI_ARRAY_TYPE_KEYPOINT_F32
VPIKeypointF32 元素。
定义: ArrayType.h:77
@ VPI_ARRAY_BUFFER_HOST_AOS
主机可访问的结构体数组。
定义: Array.h:146
存储关于数组特性和内容的信息。
定义: Array.h:168
VPIStatus vpiInitConvertImageFormatParams(VPIConvertImageFormatParams *params)
使用默认值初始化 VPIConvertImageFormatParams。
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
将图像内容转换为所需的格式,可选择缩放和偏移。
用于自定义图像格式转换的参数。
void vpiImageDestroy(VPIImage img)
销毁图像实例。
struct VPIImageImpl * VPIImage
图像的句柄。
定义: 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 内存布局排列。
定义: Image.h:172
存储关于图像特性和内容的信息。
定义: Image.h:234
VPIStatus vpiCreateMinMaxLoc(uint64_t backends, int32_t imageWidth, int32_t imageHeight, VPIImageFormat imageFormat, VPIPayload *payload)
为 vpiSubmitMinMaxLoc 创建 payload。
VPIStatus vpiSubmitMinMaxLoc(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage input, VPIArray minCoords, VPIArray maxCoords)
在图像中查找最小值和最大值的位置。
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。
struct VPIPayloadImpl * VPIPayload
算法 payload 的句柄。
定义: Types.h:268
void vpiPayloadDestroy(VPIPayload payload)
释放 payload 对象和所有相关的资源。
struct VPIStreamImpl * VPIStream
流的句柄。
定义: Types.h:250
VPIStatus vpiStreamSync(VPIStream stream)
阻塞调用线程,直到此流队列中所有提交的命令完成(队列为空)。..
VPIBackend
VPI 后端类型。
定义: Types.h:91
void vpiStreamDestroy(VPIStream stream)
销毁流实例并释放所有硬件资源。
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
创建流实例。
@ VPI_BACKEND_CUDA
CUDA 后端。
定义: Types.h:93
@ VPI_BACKEND_CPU
CPU 后端。
定义: Types.h:92
VPIStatus vpiCreateTemplateMatching(uint64_t backends, int32_t imageWidth, int32_t imageHeight, VPIPayload *payload)
为 vpiSubmitTemplateMatching 创建 payload。
VPIStatus vpiTemplateMatchingSetSourceImage(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage srcImage)
设置源图像。
VPIStatus vpiTemplateMatchingSetTemplateImage(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage templImage, VPIImage mask)
设置模板图像。
VPIStatus vpiSubmitTemplateMatching(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage output, VPITemplateMatchingMethod method)
使用提供的模板运行模板匹配算法。
@ VPI_TEMPLATE_MATCHING_NCC
归一化互相关。
@ VPI_LOCK_READ
仅为读取锁定内存。
定义: Types.h:617
存储 float32 关键点坐标。坐标相对于图像的左上角。
定义: Types.h:334