VPI - Vision Programming Interface

3.2 Release

Template Matching

Overview

Template Matching is a method for searching and finding the location of a template image in the larger source image. The output is the image holding the matching score of the template image in each location. The maximum location in the output corresponds to the best match location.

Instructions

The command line parameters are:

<backend> <input image> <bounding box>

where

  • backend: either cpu or cuda; it defines the backend that will perform the processing.
  • input image: input image file name to be used as source image, it accepts png, jpeg and possibly others.
  • bounding box: extract the defined bounding box from the input image and use that as template image.

Here's one example:

  • 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

This is using the CUDA backend and one of the provided sample images. You can try with other images, respecting the constraints imposed by the algorithm.

Results

Input imageOutput image converted to U8

Source Code

For convenience, here's the code that is also installed in the samples directory.

Language:
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 # Parse command line arguments
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 # Load input into a 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  // Clean up
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 }
Functions and structures for dealing with VPI arrays.
Declares functions that handle image format conversion.
#define VPI_IMAGE_FORMAT_F32
Single plane with one 32-bit floating point channel.
Definition: ImageFormat.h:136
#define VPI_IMAGE_FORMAT_U8
Single plane with one 8-bit unsigned integer channel.
Definition: ImageFormat.h:100
Functions and structures for dealing with VPI images.
Declares functions to perform minimum and maximum location finding in images.
Functions for handling OpenCV interoperability with VPI.
Declaration of VPI status codes handling functions.
Declares functions dealing with VPI streams.
Declares functions that implement the template matching algorithm.
void * data
Points to the first element of the array.
Definition: Array.h:135
VPIArrayBuffer buffer
Stores the array contents.
Definition: Array.h:175
VPIArrayBufferAOS aos
Array stored in array-of-structures layout.
Definition: Array.h:162
VPIStatus vpiArrayUnlock(VPIArray array)
Releases the lock on array object.
VPIStatus vpiArrayLockData(VPIArray array, VPILockMode mode, VPIArrayBufferType bufType, VPIArrayData *data)
Acquires the lock on an array object and returns the array contents.
void vpiArrayDestroy(VPIArray array)
Destroy an array instance.
VPIStatus vpiArrayCreate(int32_t capacity, VPIArrayType type, uint64_t flags, VPIArray *array)
Create an empty array instance.
struct VPIArrayImpl * VPIArray
A handle to an array.
Definition: Types.h:232
@ VPI_ARRAY_TYPE_KEYPOINT_F32
VPIKeypointF32 element.
Definition: ArrayType.h:77
@ VPI_ARRAY_BUFFER_HOST_AOS
Host-accessible array-of-structures.
Definition: Array.h:146
Stores information about array characteristics and contents.
Definition: Array.h:168
float scale
Scaling factor.
VPIStatus vpiInitConvertImageFormatParams(VPIConvertImageFormatParams *params)
Initialize VPIConvertImageFormatParams with default values.
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
Converts the image contents to the desired format, with optional scaling and offset.
Parameters for customizing image format conversion.
void vpiImageDestroy(VPIImage img)
Destroy an image instance.
struct VPIImageImpl * VPIImage
A handle to an image.
Definition: Types.h:256
VPIStatus vpiImageLockData(VPIImage img, VPILockMode mode, VPIImageBufferType bufType, VPIImageData *data)
Acquires the lock on an image object and returns the image contents.
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
Create an empty image instance with the specified flags.
VPIStatus vpiImageUnlock(VPIImage img)
Releases the lock on an image object.
@ VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR
Host-accessible with planes in pitch-linear memory layout.
Definition: Image.h:172
Stores information about image characteristics and content.
Definition: Image.h:234
VPIStatus vpiCreateMinMaxLoc(uint64_t backends, int32_t imageWidth, int32_t imageHeight, VPIImageFormat imageFormat, VPIPayload *payload)
Creates payload for vpiSubmitMinMaxLoc.
VPIStatus vpiSubmitMinMaxLoc(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage input, VPIArray minCoords, VPIArray maxCoords)
Finds minimum and maximum value locations in an image.
VPIStatus vpiImageCreateWrapperOpenCVMat(const cv::Mat &mat, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
Wraps a cv::Mat in an VPIImage with the given image format.
VPIStatus vpiImageDataExportOpenCVMat(const VPIImageData &imgData, cv::Mat *mat)
Fills an existing cv::Mat with data from VPIImageData coming from a locked VPIImage.
struct VPIPayloadImpl * VPIPayload
A handle to an algorithm payload.
Definition: Types.h:268
void vpiPayloadDestroy(VPIPayload payload)
Deallocates the payload object and all associated resources.
struct VPIStreamImpl * VPIStream
A handle to a stream.
Definition: Types.h:250
VPIStatus vpiStreamSync(VPIStream stream)
Blocks the calling thread until all submitted commands in this stream queue are done (queue is empty)...
VPIBackend
VPI Backend types.
Definition: Types.h:91
void vpiStreamDestroy(VPIStream stream)
Destroy a stream instance and deallocate all HW resources.
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
Create a stream instance.
@ VPI_BACKEND_CUDA
CUDA backend.
Definition: Types.h:93
@ VPI_BACKEND_CPU
CPU backend.
Definition: Types.h:92
VPIStatus vpiCreateTemplateMatching(uint64_t backends, int32_t imageWidth, int32_t imageHeight, VPIPayload *payload)
Creates payload for vpiSubmitTemplateMatching.
VPIStatus vpiTemplateMatchingSetSourceImage(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage srcImage)
Set the source image.
VPIStatus vpiTemplateMatchingSetTemplateImage(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage templImage, VPIImage mask)
Set the template image.
VPIStatus vpiSubmitTemplateMatching(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage output, VPITemplateMatchingMethod method)
Runs the template matching algorithm with provided template.
@ VPI_TEMPLATE_MATCHING_NCC
Normalized cross correlation.
@ VPI_LOCK_READ
Lock memory only for reading.
Definition: Types.h:617
Stores a float32 keypoint coordinate The coordinate is relative to the top-left corner of an image.
Definition: Types.h:334