Hi,
I work on my Jetson with VPI version 2.2.4. I try to CROP ROI from an image that exists at the device (CUDA backend) and create a new image that is a sub-image of the original one.
Here is my method:
/**
* @brief Copy a device image ROI into a device-owned cache (optionally format-convert).
*
* @param src Device CUDA backend VPIImage (input)
* @param roi ROI in pixels; clamped to src bounds (local copy, caller’s ROI not mutated).
* @param sync If true, waits and destroys the temporary view before return.
* @return VPIImage handle to class-owned roiCache_ with the ROI contents.
*/
VPIImage VPI_Preprocess::roiFromVPI(VPIImage src,
cv::Rect& roi,
bool sync)
{
if (!src) throw std::runtime_error("roiFromVPI: src is null");
int W=0, H=0;
VPI_THROW_IF(vpiImageGetSize(src, &W, &H));
// Ensure ROI in valid Range
roi.x = roi.x > 0 ? roi.x : 0;
roi.y = roi.y > 0 ? roi.y : 0;
roi.width = std::min(roi.width, W - roi.x);
roi.height = std::min(roi.height , H - roi.y);
// Determine formats
VPIImageFormat srcFmt = VPI_IMAGE_FORMAT_INVALID;
VPI_THROW_IF(vpiImageGetFormat(src, &srcFmt));
// Create a zero-copy view over ROI
VPIRectangleI r{roi.x, roi.y, roi.width, roi.height};
VPIImage view = nullptr;
VPI_THROW_IF(vpiImageCreateView(src, &r, 0, &view));
// Ensure destination device buffer
ensureImage(roi.width, roi.height, srcFmt, roiCache_);
// Device-side blit/convert view → roiCache_
VPI_THROW_IF(vpiSubmitConvertImageFormat(stream_, backendConvert_, view, roiCache_, nullptr));
if (sync) {
VPI_THROW_IF(vpiStreamSync(stream_));
vpiImageDestroy(view); // destroy temporary header
} else {
// If async, you must keep 'view' alive until you later sync; otherwise it will dangle.
// Either store it and provide a flush() that syncs and destroys, or just use sync=true.
// pendingViews_.push_back(view); // if you add bookkeeping
// TODO: keep 'view' alive until a later sync, then destroy it.
pendingWraps_.push_back(view);
}
return roiCache_; // class-owned, reused on next call
}
The method crash on the line:
VPI_THROW_IF(vpiImageCreateView(srcWrap, &r, 0, &srcView));
And the error that I get is:
VPI error VPI_ERROR_INVALID_OPERATION @/home/uvison/VPI_examples/VPI_Preprocess.cpp:159
When I run the same concept where the matrix came from host it’s work fine for example:
/**
* @brief Crop a region from a host cv::Mat and materialize it as a device-owned VPIImage.
*
* This method wraps the input OpenCV image in a VPI header (zero-copy), creates a VPI
* view on the requested ROI, and then enqueues a device-side copy/convert of that ROI
* into an internal cached device image (roiCache_).
* @param bgr Input frame in host memory (OpenCV). Expected 8-bit depth
* (e.g., CV_8UC1/3/4). The input data is never modified.
* @param roi Region of interest in pixel coordinates. The rectangle is clamped
* to the input image bounds before processing.
* @param sync When true, waits for the GPU work to complete and destroys the
* temporary wrappers before returning. When false, the copy/convert
* is enqueued on @c stream_; the caller must synchronize later and
* ensure the wrapped input (and its VPI view) remain alive until done.
*
* @return VPIImage handle to the class-owned cached device image containing the ROI.
* Ownership remains with VPI_Preprocess; subsequent calls may reuse/overwrite
* the same buffer, and it will be reallocated if size/format changes.
*
*/
VPIImage VPI_Preprocess::roiFromMat(const cv::Mat& bgr,
cv::Rect& roi,
bool sync)
{
if (bgr.empty())
throw std::runtime_error("roiFromMat: src is empty");
// Ensure ROI in valid Range
roi.x = roi.x > 0 ? roi.x : 0;
roi.y = roi.y > 0 ? roi.y : 0;
roi.width = std::min(roi.width, bgr.cols - roi.x);
roi.height = std::min(roi.height , bgr.rows - roi.y);
// Wrap the OpenCV Mat (no copy)
VPIImage srcWrap = nullptr;
VPI_THROW_IF(vpiImageCreateWrapperOpenCVMat(bgr, 0, &srcWrap));
// Discover the VPI format of the wrapped image
VPIImageFormat srcFmt = VPI_IMAGE_FORMAT_INVALID;
VPI_THROW_IF(vpiImageGetFormat(srcWrap, &srcFmt));
// ROI view crop ROI get pointer view
VPIImage srcView = nullptr;
VPIRectangleI r{roi.x, roi.y, roi.width, roi.height};
VPI_THROW_IF(vpiImageCreateView(srcWrap, &r, 0, &srcView));
// Ensure destination (same format as source → device "clone" of ROI)
ensureImage(roi.width, roi.height, srcFmt, roiCache_);
// Device-side copy (formats equal) or convert (if VPI decides to normalize layout)
VPI_THROW_IF(vpiSubmitConvertImageFormat(stream_, backendConvert_, srcView, roiCache_, nullptr));
if (sync) {
VPI_THROW_IF(vpiStreamSync(stream_));
vpiImageDestroy(srcView);
vpiImageDestroy(srcWrap);
} else {
// If async, keep 'img', srcWrap and srcView alive until work finishes.
// (You can add an event system to defer destruction.)
pendingWraps_.push_back(srcView);
pendingWraps_.push_back(srcWrap);
}
return roiCache_;
}
I don’t succeed to understand what I am doing wrong. I will be glad to receive any help.
