1.Jetson Orin /Xavier 对于图片的输入以及输出处理有一套特定的API。代码存放在/usr/src/jetson_multimedia_api中。
2.其中最主要的几个头文件:缓存管理申请 nvbufsurface.h 缓存转换 nvbufsurftransform.h 显示 nvosd.h 以及显示模块。
3.YUV转RGB来分析一下各个头文件是的使用。
先展示一下 YUV转换效果图:视频原始文件来源 squirrel-720x576-422P.yuv
从输入开始
YUV片源是422P的格式。这个格式说明YUV三个分量是分开存放。因此解析时候要注意图片的格式。【422相对420格式只有一个plane,420的格式会有三个plane分别存放Y U V 分量】
YUV422P 存放在文件中其实也有两种格式:左图UV 左右结构,右图为上下结构。具体要按照YUV片源读取后判断格式形状。
知道代码格式后就是从YUV源文件读取数据,读取的内容如下:
std::ifstream *stream
stream->read(sufaceList->mappedAddr.addr[plane] + surfaceList->planeParams.pitch[plane],
width*bytesPerPix);
解释一下pitch和width的区别:
首先是按照格式参数 YUV422 YUV420 YUV444等,具体按照图片的长宽来分配具体的缓存大小。对于422而言 pitch = width x 2 + margin。pitch 多少是实际的输入参数宽度对齐的结果。值得注意的是V4L2框架分配的缓存对齐的效果和nvsurface是相同的。这些会在后续文章会详细介绍。
有了图片同样有了输入的缓存大小以及形状,后面就可以处理YUV的数据了。
处理通过CUDA来处理,说明一点 这里的处理后的outbuf 是cudaMallocHost的分配结果,CPU和GPU可以共享,不需要额外的映射 Map的操作。输入的YUV数据缓存是nvsurface分配而来,分配后需要注册到GPU空间,即CUDA可以访问。同时也要通过surface的映射函数映射到CPU空间。应为该区域通过文件操作输入YUV数据。这块内容后续文章详细介绍。
核函数分析:
首先分析了一下 720 x 576 读取后的图片格式为 1440 x 576 如上图所示。特别需要注意的是 U V 分量开始的点 分别为 288 和 432,高 / 2 和 高度 3/4的起点。
大循环 288 次 图片
for(int row = 0;row < height/2; ++row)
{
if(idx > 720)
return;
Y0 = SRC[p*row+idx*2 + 0] ;
Y1 = SRC[p*row+idx*2 + 1] ;
if(row%2 == 0)
{
U = SRC[p*row+ p*height/2 + idx];
V = SRC[p*row+ p*height*3/4 + idx];
} else {
U = SRC[p*row+ p*height/2 + width + idx];
V = SRC[p*row+ p*height*3/4 + width + idx];
UV_ROW++;
}
//偶数行 对应 720个 RGB ,如果当idx > 360 后换行存放在奇数行
dst[2*row*width*3 + idx * 6 + 0] = R1;
dst[2*row*width*3 + idx * 6 + 1] = G1;
dst[2*row*width*3 + idx * 6 + 2] = B1;
dst[2*row*width*3 + idx * 6 + 3] = R2;
dst[2*row*width*3 + idx * 6 + 4] = G2;
dst[2*row*width*3 + idx * 6 + 5] = B2;
if(idx > 360)
{
dst[(2*row+1)*width*3 + (idx-360) * 6 + 0] = R1;
dst[(2*row+1)*width*3 + (idx-360) * 6+ 1] = G1;
dst[(2*row+1)*width*3 + (idx-360) * 6 + 2] = B1;
dst[(2*row+1)*width*3 + (idx-360) * 6 + 3] = R2;
dst[(2*row+1)*width*3 + (idx-360) * 6 + 4] = G2;
dst[(2*row+1)*width*3 + (idx-360) * 6 + 5] = B2;
}
}
转换示意图: