NVIDIA Jetson Nano視頻解碼需要注意的一個問題

    NVIDIA的邊緣計算的序列板子都配備了視頻編碼器和解碼器,使用解碼器硬件解碼當然比使用OpenCV+ffmpeg之類的軟解碼要快多了。使用Jetson Nano的解碼程序遇到個問題就是Jetson Nano在存放解碼出來的圖像的YUV數據時沒有完全遵循一般的規範來做。

     一般在壓縮視頻(DV設備生成)中YUV420格式使用較多,YUV420準確的說應該叫YCbCr420,YCbCr是YUV(Y的取值範圍爲0~255,U的取值範圍爲-122~122、V的取值範圍爲-157~157)的縮放和偏移版本,Y的取值範圍爲16-255,Cb和Cr的取值範圍爲16-240,YCbCr有4:4:4、4:2:2、4:2:0、4:1:1這幾張採樣格式,YUV420指YCbCr在水平和垂直方向都對Cb和Cr採樣減少爲2:1,Y爲黑白分量,在水平和垂直方向採樣比值都是4,下圖左邊是H261/H263/MPEG1的採樣方式,右圖是H264/MPEG2的採樣方式:

   

 

YUV的數據存放格式分爲planar和packed兩種。planar格式最常用,它是先連續存放所有像素點的Y分量數據,緊接着存放所有像素點的U分量數據,最後存放所有像素點的V分量數據。packed格式則是每個像素點的Y,U,V數據是交錯連續存儲的。YUV420中,一個像素點對應一個Y,一個4X4的小方塊對應一個U和V,對於YUV420p,也就是說planar格式存放的,先存放完Y分量數據再存放U分量數據,再存放V分量數據,以一個分辨率爲8X4的YUV圖像爲例,它們的420p存放格式如下圖:

         

      所以當我們知道一副圖像的height和width後,可以立即算出YUV420p格式數據存放佔用的空間 size=height*width*3/2,也就是說,這個空間的寬度爲width,高度則爲 height*3/2。

      可是在NVIDIA jetson nano的docode代碼裏在存放數據時沒用完全遵循規範,而是在分配空間時寬度給Y分配的2048,給UV都是分配的1024,而不是圖像的實際寬度:

[decode.cpp]

    ret = NvBufferGetParams(temp_dma_fd, &parm);

    ...

    int ysize = parm.pitch[0] * parm.height[0];       #pitch[0]=2k
    int uvsize = parm.pitch[1] * parm.height[1];     #pitch[1]=1k

   pYuvData->pData[0] = new unsigned char[ysize];
   pYuvData->pData[1] = new unsigned char[uvsize];
   pYuvData->pData[2] = new unsigned char[uvsize];

   for (int plane = 0; plane < parm.num_planes; plane++) {
                    pYuvData->nWidth[plane] = parm.width[plane];
                    pYuvData->nHeight[plane] = parm.height[plane];
                    pYuvData->nPitch[plane] = parm.pitch[plane];

圖片是1080x1920的,2048這個寬度倒是夠了,但是留有空洞,不明白爲何要這麼做,猜測可能是爲了迎合jetson nano的解碼器在輸出數據時的什麼特殊要求吧?如果不知道這點,我們寫代碼在接受它解碼輸出的數據時分配的空間按照height*width*3/2大小分配空間後從 pYuvData->pData拷貝數據時仍然以pYuvData->nWidth[plan]作爲數據的寬度來拷貝的話,得到的數據就是錯的,在使用OpenCV或libyuv把YUV數據轉換成BGR數據後看到的圖片就是一遍花的。

要解決這個問題,首先要實現一個去空洞的精準拷貝函數來把YUV420p數據的nano存儲方式改成標準格式:

bool copyYUV(StNanoYuvData *pYuvData, unsigned char* mem) {
    for (int i=0; i<pYuvData->nHeight[0]; ++i)
    {
        memcpy(mem, pYuvData->pData[0]+i*pYuvData->nPitch[0], pYuvData->nWidth[0]);
        mem += pYuvData->nWidth[0];
    }
    for (int i=0; i<pYuvData->nHeight[1]; ++i)
    {
        memcpy(mem, pYuvData->pData[1]+i*pYuvData->nPitch[1], pYuvData->nWidth[1]);
        mem += pYuvData->nWidth[1];
    }
    for (int i=0; i<pYuvData->nHeight[1]; ++i)
    {
        memcpy(mem, pYuvData->pData[2]+i*pYuvData->nPitch[2], pYuvData->nWidth[2]);
        mem += pYuvData->nWidth[2];
    }
    return true;
}   

然後把YUV數據轉換成BGR數據並顯示:

cv::Mat image; 

image.create(pYuvData->nHeight[0], pYuvData->nWidth[0], CV_8UC3);

Mat yuvMat;
yuvMat.create(pYuvData->nHeight[0] * 3 / 2, pYuvData->nWidth[0], CV_8UC1);

copyYUV(pYuvData,yuvMat.data);

//用libyuv轉換:

libyuv::I420ToRGB24(yuvMat.data ,pYuvData->nWidth[0],yuvMat.data+pYuvData->nWidth[0]*pYuvData->nHeight[0],pYuvData->nWidth[1]/2, yuvMat.data+pYuvData->nWidth[0]*pYuvData->nHeight[0]+pYuvData->nWidth[1]*pYuvData->nHeight[1],pYuvData->nWidth[2]/2,image.data, pYuvData->nWidth[0]*3,pYuvData->nWidth[0]*pYuvData->nHeight[0])

//或用OpenCV轉換:

cv::cvtColor(yuvMat, image, CV_YUV2BGR_I420);

//show

cv::imshow("image",image);
cv::waitKey(0)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章