【nv12 格式转换】不同图像数据格式之间转换代码实操

1 问题先行

  1. nv12是什么格式?和常见的rgb/bgr有什么关系吗?他们之间能互相转换吗?
  2. 如何读取一张图片,然后把图片转换成nv12格式?有代码嘛?
  3. 部署时,为何要用nv12数据格式作为输入?rgb不行吗?

2 nv12介绍

2.1 YUV格式

YUV格式主要用于优化彩色视频信号的传输。
YUV分为三个分量:Y表示明亮度,也就是灰度值;U和V表示色度,用于描述影像色彩及饱和度,指定像素的颜色。

2.2 NV12排布

NV12图像格式属于YUV颜色空间中的YUV420SP格式,每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序。

排列方式如下:
在这里插入图片描述

3 不同数据格式之间转换实操

读取一张图片,从bgr转成nv12,再从nv12转成yuv44,具体内容看看代码和输出即可。

import cv2 
import numpy as np 
from PIL import Image 

# -------------------------------------------------------#
# 注意: image.shape = (h, w, c)
# nv12数据与YUV_I420的uv分量排列方式不同,具体可参考本文第2章节描述
# -------------------------------------------------------#
def bgr2nv12(image): 
    image = image.astype(np.uint8) 
    height, width = image.shape[0], image.shape[1] 
    yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((height * width * 3 // 2, )) 
    y = yuv420p[:height * width] 
    uv_planar = yuv420p[height * width:].reshape((2, height * width // 4)) 
    uv_packed = uv_planar.transpose((1, 0)).reshape((height * width // 2, )) 
    nv12 = np.zeros_like(yuv420p) 
    nv12[:height * width] = y 
    nv12[height * width:] = uv_packed 
    return nv12

# nv12转yuv444
def nv12Toyuv444(nv12, target_size):
    height = target_size[0] 
    width = target_size[1] 
    nv12_data = nv12.flatten() 
    yuv444 = np.empty([height, width, 3], dtype=np.uint8) 
    yuv444[:, :, 0] = nv12_data[:width * height].reshape(height, width) 
    u = nv12_data[width * height::2].reshape(height // 2, width // 2) 
    yuv444[:, :, 1] = Image.fromarray(u).resize((width, height),resample=0) 
    v = nv12_data[width * height + 1::2].reshape(height // 2, width // 2) 
    yuv444[:, :, 2] = Image.fromarray(v).resize((width, height),resample=0) 
    return yuv444


if __name__=='__main__': 
    img = cv2.imread("./zebra_cls.jpg")     # bgr  hwc
    print("cv2读图shape:", img.shape)

    # 直接resize,其它缩放图片方式个性化使用 
    img = cv2.resize(img,(672,672))         # bgr  hwc
    print("resize后的图片shape:", img.shape)

    # 将bgr的数据格式转换成nv12的数据格式
    # 注意上方转换函数写的时候,输入img需要是(h, w, c)的layout排布
    nv12 = bgr2nv12(img)
    # 677376=672x672x3/2,这儿为什么除以2,参考本文第1章理解理解~
    print("nv12的shape:", nv12.shape)
    
    # 将nv12的数据保存下来供给模型,作为真实输入
    nv12.tofile("nv12_input_data.bin")
    
    # nv12转yuv444
    img = nv12Toyuv444(nv12, (672,672))
    print("yuv444的shape:", img.shape)

    # 变换数据排布layout
    img = img.transpose(2,0,1) 
    print("yuv444 transpose之后的shape:", img.shape)

    # 增加一个N维度
    img = img[np.newaxis,:,:,:] 
    print("增加一个N维度的shape:",img.shape)

输出

cv2读图shape: (376, 376, 3)
resize后的图片shape: (672, 672, 3)
nv12的shape: (677376,)# 677376=672x672x3/2,为什么除以2,参考本文第2章理解理解~
yuv444的shape: (672, 672, 3)
yuv444 transpose之后的shape: (3, 672, 672)
增加一个N维度的shape: (1, 3, 672, 672)

:部分代码有参考地平线OE包中的内容。

之所以用nv12作为部署时的数据输入,是因为其数据量是rgb/bgr等格式的一半,可以减少模型load输入数据的时间。

3.1 Image将图像转为nv12

import sys
from PIL import Image

# 读取图片文件
def generate_nv12(input_path, output_path):
    img = Image.open(input_path)
    # 将图片转换为YUV格式
    yuv_img = img.convert('YCbCr')
    y_data, u_data, v_data = yuv_img.split()
    
    # 将Y、U、V通道数据转换为字节流
    y_data_bytes = y_data.tobytes()
    u_data_bytes = u_data.resize((u_data.width // 2, u_data.height // 2)).tobytes()
    v_data_bytes = v_data.resize((v_data.width // 2, v_data.height // 2)).tobytes()

    # 将UV数据按UVUVUV...的形式排列
    uvuvuv_data = bytearray()
    for u_byte, v_byte in zip(u_data_bytes, v_data_bytes):
        uvuvuv_data.extend([u_byte, v_byte])

    # y data
    y_path = output_path + "_y.bin"
    with open(y_path, 'wb') as f:
        f.write(y_data_bytes)

    # uv data
    uv_path = output_path + "_uv.bin"
    with open(uv_path, 'wb') as f:
        f.write(uvuvuv_data)

    nv12_data = y_data_bytes + uvuvuv_data
    # 保存为NV12格式文件
    nv12_path = output_path + "_nv12.bin"
    with open(nv12_path, 'wb') as f:
        f.write(nv12_data)

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python resize_image.py <input_path> <output_path>")
        sys.exit(1)

    input_path = sys.argv[1]
    output_path = sys.argv[2]

    generate_nv12(input_path, output_path)

3.2 cv2将图像转为nv12

import cv2
import numpy as np

def image2nv12(image): 
    image = image.astype(np.uint8) 
    height, width = image.shape[0], image.shape[1] 
    yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((height * width * 3 // 2, )) 
    y = yuv420p[:height * width] 
    uv_planar = yuv420p[height * width:].reshape((2, height * width // 4)) 
    uv_packed = uv_planar.transpose((1, 0)).reshape((height * width // 2, )) 
    nv12 = np.zeros_like(yuv420p) 
    nv12[:height * width] = y         # y分量
    nv12[height * width:] = uv_packed # uv分量,uvuv交替存储
    return nv12 

image = cv2.imread("./image.jpg")
nv12 = image2nv12(image)

如果您觉得这篇文章对您有用,欢迎给点个赞,谢谢~

4 参考链接

1. https://developer.horizon.ai/forumDetail/118364000835765839
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值