nvImageCodec with cv-cuda (Linux only)

[1]:
import os
from matplotlib import pyplot as plt
import cupy as cp

import cvcuda

Setting resource folder

[2]:
resources_dir = os.getenv("PYNVIMGCODEC_EXAMPLES_RESOURCES_DIR", "../assets/images/")

Import nvImageCodec module and create Decoder

[3]:
from nvidia import nvimgcodec
decoder = nvimgcodec.Decoder()

Read image with nvImageCodec

[4]:
inputImage = decoder.read(resources_dir + "tabby_tiger_cat.jpg")
print("size:{}x{}".format(inputImage.width, inputImage.height))
size:720x720

Pass it to cvcuda using as_tensor

[5]:
nvcvInputTensor = cvcuda.as_tensor(inputImage, "HWC")

Resize with cvcuda

[6]:
cvcuda_stream = cvcuda.Stream()
with cvcuda_stream:
    nvcvResizeTensor = cvcuda.resize(nvcvInputTensor, (320, 320, 3), cvcuda.Interp.CUBIC)
    nvcvResizeTensor.cuda().__cuda_array_interface__

Write with nvImageCodec

[7]:
encoder = nvimgcodec.Encoder()
encoder.write("tabby_tiger_cat_320x320.jpg", nvimgcodec.as_image(nvcvResizeTensor.cuda(), cuda_stream = cvcuda_stream.handle))

Verify with OpenCV

[8]:
import cv2
image = cv2.imread("tabby_tiger_cat_320x320.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)

[8]:
<matplotlib.image.AxesImage at 0x7f5322009460>
../_images/samples_cvcuda_sample_15_1.png

Resnet50 classification example from cv-cuda

[9]:
import torch
from torchvision.io.image import read_file, decode_jpeg
from torchvision import models
import numpy as np
file_name = resources_dir + "tabby_tiger_cat.jpg"
labelsfile = resources_dir + "../imagenet-classes.txt"

Orignal example code uses torchvision to load image (under the hood it uses nvJpeg)

[10]:
data = read_file(file_name)
inputImageTmp = decode_jpeg(data, device="cuda")

Now we can change this code to use nvImageCodec and use all formats available with plugins. Please uncomment lines with other images to test it

[11]:
#file_name = resources_dir + "cat-1046544_640.jp2"
#file_name = resources_dir + "Weimaraner.jpg"

# nvImageCodec has fallback for cpu decoder (only when necessary plugins are installed)
# for codec do not supported yet on GPU so we can read e.g. png
#file_name = resources_dir + "cat-1245673_640.png"

inputImage = decoder.read(file_name)
[12]:
# A torch tensor/ or nvImageCodec Image can be wrapped into a CVCUDA Object using the "as_tensor"
# function in the specified layout. The datatype and dimensions are derived
# directly from the torch tensor.
nvcvInputTensor = cvcuda.as_tensor(inputImage, "HWC")
image = cp.asnumpy(nvcvInputTensor.cuda())
plt.imshow(image)

#Need 4 dimensions when first is batch size
image_tensors = torch.stack((torch.as_tensor(nvcvInputTensor.cuda()),))
nvcvInputTensor = cvcuda.as_tensor(image_tensors.cuda(), "NHWC")

../_images/samples_cvcuda_sample_22_0.png
[13]:
"""
Preprocessing includes the following sequence of operations.
Resize -> DataType Convert(U8->F32) -> Normalize(Apply mean and std deviation)
-> Interleaved to Planar
"""

# Model settings
layerHeight = 224
layerWidth = 224
batchSize = 1

# Resize
# Resize to the input network dimensions
nvcvResizeTensor = cvcuda.resize(nvcvInputTensor, (1, layerHeight, layerWidth, 3), cvcuda.Interp.CUBIC)

# Convert to the data type and range of values needed by the input layer
# i.e uint8->float. A Scale is applied to normalize the values in the range 0-1
nvcvConvertTensor = cvcuda.convertto(nvcvResizeTensor, np.float32, scale=1 / 255)

"""
The input to the network needs to be normalized based on the mean and
std deviation value to standardize the input data.
"""

# Create a torch tensor to store the mean and standard deviation values for R,G,B
scale = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
scaleTensor = torch.Tensor(scale)
stdTensor = torch.Tensor(std)

# Reshape the the number of channels. The R,G,B values scale and offset will be
# applied to every color plane respectively across the batch
scaleTensor = torch.reshape(scaleTensor, (1, 1, 1, 3)).cuda()
stdTensor = torch.reshape(stdTensor, (1, 1, 1, 3)).cuda()

# Wrap the torch tensor in a CVCUDA Tensor
nvcvScaleTensor = cvcuda.as_tensor(scaleTensor, "NHWC")
nvcvBaseTensor = cvcuda.as_tensor(stdTensor, "NHWC")

# Apply the normalize operator and indicate the scale values are std deviation
# i.e scale = 1/stddev
nvcvNormTensor = cvcuda.normalize(nvcvConvertTensor,
    nvcvBaseTensor, nvcvScaleTensor, cvcuda.NormalizeFlags.SCALE_IS_STDDEV
)

# The final stage in the preprocess pipeline includes converting the RGB buffer
# into a planar buffer
nvcvPreprocessedTensor = cvcuda.reformat(nvcvNormTensor, "NCHW")

# Inference uses pytorch to run a resnet50 model on the preprocessed input and outputs
# the classification scores for 1000 classes
# Load Resnet model pretrained on Imagenet
resnet50 = models.resnet50(pretrained=True)
resnet50.to("cuda")
resnet50.eval()

# Run inference on the preprocessed input
torchPreprocessedTensor = torch.as_tensor(nvcvPreprocessedTensor.cuda(), device="cuda")
inferOutput = resnet50(torchPreprocessedTensor)

"""
Postprocessing function normalizes the classification score from the network and sorts
the scores to get the TopN classification scores.
"""
# top results to print out
topN = 5

# Read and parse the classes
with open(labelsfile, "r") as f:
    classes = [line.strip() for line in f.readlines()]

# Apply softmax to Normalize scores between 0-1
scores = torch.nn.functional.softmax(inferOutput, dim=1)[0]

# Sort output scores in descending order
_, indices = torch.sort(inferOutput, descending=True)

# Display Top N Results
for idx in indices[0][:topN]:
    print("Class : ", classes[idx], " Score : ", scores[idx].item())

/usr/local/lib/python3.8/dist-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.
  warnings.warn(
/usr/local/lib/python3.8/dist-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ResNet50_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet50_Weights.DEFAULT` to get the most up-to-date weights.
  warnings.warn(msg)
Downloading: "https://download.pytorch.org/models/resnet50-0676ba61.pth" to /root/.cache/torch/hub/checkpoints/resnet50-0676ba61.pth
100.0%
Class :  tiger cat  Score :  0.7251120209693909
Class :  tabby, tabby cat  Score :  0.1548738181591034
Class :  Egyptian cat  Score :  0.08538039773702621
Class :  lynx, catamount  Score :  0.02093645930290222
Class :  leopard, Panthera pardus  Score :  0.002835477003827691