NVIDIA / VideoProcessingFramework

Set of Python bindings to C++ libraries which provides full HW acceleration for video decoding, encoding and GPU-accelerated color space and pixel format conversions
Apache License 2.0
1.32k stars 233 forks source link

OpenCV image to encode? #557

Closed rizwanishaq closed 11 months ago

rizwanishaq commented 11 months ago

Is there any example where we can convert OpenCV image np.array() to h264 encoding?

niujiabenbeng commented 11 months ago

Hi @rizwanishaq

Here is a simple demo. I will be glad if this script could provide some help.

#! /usr/bin/env python
# coding: utf-8

import cv2
import numpy as np
import PyNvCodec as nvc

frame_width  = 640
frame_height = 480
frame_fps    = 30
frame_count  = 300

def cvtColor_BGR2YUV_NV12(image):
    height, width = image.shape[:2]
    yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420)
    uuvv = yuv[height:].reshape(2, -1)
    uvuv = np.transpose(uuvv, axes=(1, 0))
    yuv[height:] = uvuv.reshape(-1, width)
    return yuv

# create an encoder
encoder = nvc.PyNvEncoder({
    "preset": "P5",
    "tuning_info": "high_quality",
    "profile": "high",
    "codec": "h264",
    "s": f"{frame_width}x{frame_height}",
    "fps": str(frame_fps),
    "gop": str(frame_fps),
}, gpu_id=0)

with open("output.h264", "wb") as dstfile:
    for i in range(frame_count):
        # generate bgr frame
        bgr = np.full((frame_height, frame_width, 3), i % 256, dtype=np.uint8)
        # convert bgr to nv12
        nv12 = cvtColor_BGR2YUV_NV12(bgr)
        # encode frame
        packet = np.ndarray(shape=(0), dtype=np.uint8)
        if encoder.EncodeSingleFrame(nv12, packet):
            dstfile.write(bytearray(packet))
    # encoder is asynchronous, so we need to flush it
    packet = np.ndarray(shape=(0), dtype=np.uint8)
    while encoder.FlushSinglePacket(packet):
        dstfile.write(bytearray(packet))
rizwanishaq commented 11 months ago

Thanks, working.

rizwanishaq commented 11 months ago

Is there any way that we make this part

def cvtColor_BGR2YUV_NV12(image):
    height, width = image.shape[:2]
    yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420)
    uuvv = yuv[height:].reshape(2, -1)
    uvuv = np.transpose(uuvv, axes=(1, 0))
    yuv[height:] = uvuv.reshape(-1, width)
    return yuv

on GPU, as this is using all the CPU.

niujiabenbeng commented 11 months ago

maybe this could help.

#! /usr/bin/env python
# coding: utf-8

import cv2
import numpy as np
import PyNvCodec as nvc

frame_width  = 640
frame_height = 480
frame_fps    = 30
frame_count  = 300

class ColorConverter:

    def __init__(self, width, height, gpuid):
        # vpf does not support rgb_planar -> nv12, so we need the following chain:
        #     rgb_planar -> rgb -> yuv420 -> nv12
        self.to_rgb = nvc.PySurfaceConverter(width, height, nvc.PixelFormat.RGB_PLANAR, nvc.PixelFormat.RGB, gpuid)
        self.to_yuv420 = nvc.PySurfaceConverter(width, height, nvc.PixelFormat.RGB, nvc.PixelFormat.YUV420, gpuid)
        self.to_nv12 = nvc.PySurfaceConverter(width, height, nvc.PixelFormat.YUV420, nvc.PixelFormat.NV12, gpuid)
        self.context = nvc.ColorspaceConversionContext(nvc.ColorSpace.BT_601, nvc.ColorRange.MPEG)
        self.uploader = nvc.PyFrameUploader(width, height, nvc.PixelFormat.RGB_PLANAR, gpuid)

    def convert(self, image):
        # convert opencv-style image to rgb_planar
        image = image[:, :, ::-1].transpose((2, 0, 1))
        image = np.ascontiguousarray(image)
        # copy data: cpu -> gpu
        surface = self.uploader.UploadSingleFrame(image)
        if surface.Empty(): return None
        # do actual convertions
        surface = self.to_rgb.Execute(surface, self.context)
        if surface.Empty(): return None
        surface = self.to_yuv420.Execute(surface, self.context)
        if surface.Empty(): return None
        surface = self.to_nv12.Execute(surface, self.context)
        if surface.Empty(): return None
        return surface

encoder = nvc.PyNvEncoder({
    "preset": "P5",
    "tuning_info": "high_quality",
    "profile": "high",
    "codec": "h264",
    "s": f"{frame_width}x{frame_height}",
    "fps": str(frame_fps),
    "gop": str(frame_fps),
}, gpu_id=0)

converter = ColorConverter(frame_width, frame_height, 0)
with open("output.h264", "wb") as dstfile:
    for i in range(frame_count):
        # generate bgr frame
        bgr = np.full((frame_height, frame_width, 3), i % 256, dtype=np.uint8)
        # convert bgr(cpu) to nv12(gpu)
        nv12 = converter.convert(bgr)
        # encode frame, using: EncodeSingleSurface
        packet = np.ndarray(shape=(0), dtype=np.uint8)
        if encoder.EncodeSingleSurface(nv12, packet):
            dstfile.write(packet.data)
    # encoder is asynchronous, so we need to flush it
    packet = np.ndarray(shape=(0), dtype=np.uint8)
    while encoder.FlushSinglePacket(packet):
        dstfile.write(packet.data)
rizwanishaq commented 11 months ago

yes, it is now working on GPU, thanks.

rizwanishaq commented 8 months ago

"Is it possible to generate H.264 encoded frames, feed them back into a decoder, and then convert them back to cv2 images for testing purposes?"

RomanArzumanyan commented 8 months ago

Hi @rizwanishaq

Please check out https://github.com/RomanArzumanyan/VALI. It’s a VPF successor which is actively developed and maintained. It has compatible API and module naming.

WRT you question - yes, it’s possible. Please check out SampleMeasureVideoQuality in VPF repository or VALI tests.