Проблема конвертации бгр в юв420п с куда - PullRequest
0 голосов
/ 27 апреля 2020

Мне нужно конвертировать изображение из bgr в yuv420p , и я сначала использую OpenCV для этого.

Mat img = imread("1.bmp");
Mat yuvImg;
cvtColor(img,yuvImg,COLOR_BGR2YUV_I420);

Результат нормальный. Однако , мое изображение слишком большое и его пиксель почти 6400 * 2000. Я считаю, что слишком много времени преобразования bgr в yuv420p с помощью opencv api cvtcolor.

Затем я решаю преобразовать сам и ускори его с помощью cuda. ​​

Вот код в ЦП :

void bgr_to_yuv420p(unsigned  char* yuv420p, unsigned char* bgr, int width, int height)
{
    if (yuv420p == NULL || bgr== NULL)
        return;
    int frameSize = width*height;
    int chromaSize = frameSize / 4;

    int yIndex = 0;
    int uIndex = frameSize;
    int vIndex = frameSize + chromaSize;

    int R, G, B, Y, U, V;
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            B = bgr[(i * width + j) * 3 + 0];
            G = bgr[(i * width + j) * 3 + 1];
            R = bgr[(i * width + j) * 3 + 2];

            //BGR to YUV
            Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
            U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
            V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

            yuv420p[yIndex++] = (unsigned char)((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
            if (i % 2 == 0 && j % 2 == 0)
            {
                yuv420p[uIndex++] = (unsigned char)((U < 0) ? 0 : ((U > 255) ? 255 : U));
                yuv420p[vIndex++] = (unsigned char)((V < 0) ? 0 : ((V > 255) ? 255 : V));
            }
        }
    }
}

Я проверяю код bgr_to_yuv420p (... ) и результат также нормальный.

Затем я ускоряю его с помощью cuda. ​​

Вот весь мой код, включающий функцию ядра и тестовую функцию.

#include <iostream>
#include <time.h>
#include <vector_types.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include "opencv2/highgui.hpp" 
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;

//kernel function to convert bgr to yuv420p
__global__ void bgr2yuv420p(uchar3 *  d_in, unsigned char * d_out,
                               uint imgheight, uint imgwidth)
{

    int col_num = blockIdx.x*blockDim.x+threadIdx.x;
    int row_num = blockIdx.y*blockDim.y+threadIdx.y;

    if ((row_num < imgheight) && (col_num < imgwidth))
    {
//        uint32_t a = *((uint32_t *)&dinput[global_offset*3]);
        int global_offset = row_num*imgwidth+col_num;

        int r,g,b;
        r = int(d_in[global_offset].z);
        g = int (d_in[global_offset].y);
        b = int (d_in[global_offset].x);


        d_out[row_num * imgwidth + col_num] = ((66*r + 129*g + 25*b) >> 8) + 16;
        if(((threadIdx.x & 1) == 0)  && ((threadIdx.y & 1) == 0)){
            int uv_offset = imgwidth*imgheight+((row_num*imgwidth))+col_num;
            d_out[uv_offset] = ((112*r + -94*g + -18*b) >> 8) + 128;
            d_out[uv_offset+1] = ((-38*r + -74*g + 112*b) >> 8) + 128;

        }

    }
}

int main(void)
{

    Mat srcImage = imread("1.bmp");
    imshow("srcImage", srcImage);
    const uint imgheight = srcImage.rows;
    const uint imgwidth = srcImage.cols;

    Mat nv12Image(imgheight * 3 / 2, imgwidth, CV_8UC1, Scalar(255));

    //input and output 
    uchar3 *d_in;
    unsigned char *d_out;

    // malloc memo in gpu
    cudaMalloc((void**)&d_in, imgheight*imgwidth*sizeof(uchar3));
    cudaMalloc((void**)&d_out, imgheight*imgwidth*sizeof(unsigned char) * 3 / 2);

    //copy image from cpu to gpu
    cudaMemcpy(d_in, srcImage.data, imgheight*imgwidth*sizeof(uchar3), cudaMemcpyHostToDevice);

    dim3 threadsPerBlock(32, 32);
    dim3 blocksPerGrid((imgwidth + threadsPerBlock.x - 1) / threadsPerBlock.x,
                       (imgheight + threadsPerBlock.y - 1) / threadsPerBlock.y);

    //run kernel function
    bgr2yuv420p<<<blocksPerGrid, threadsPerBlock>>>(d_in, d_out, imgheight, imgwidth);

    cudaDeviceSynchronize();

    //copy yuv420p from gpu to cpu
    cudaMemcpy(nv12Image.data, d_out, imgheight*imgwidth*sizeof(unsigned char) * 3 / 2, cudaMemcpyDeviceToHost);

    imshow("nv12",nv12Image);
    imwrite("cuda.bmp",nv12Image);

    cudaFree(d_in);
    cudaFree(d_out);


    return 0;

}

код с Cuda может работать, но результат не является нормальным. Y из YUV420p - это нормально, но что-то не так с U и V. Я думаю, причина здесь в __global__ void bgr2yuv420p(...)

if(((threadIdx.x & 1) == 0)  && ((threadIdx.y & 1) == 0)){
                int uv_offset = imgwidth*imgheight+((row_num*imgwidth))+col_num;
                d_out[uv_offset] = ((112*r + -94*g + -18*b) >> 8) + 128;
                d_out[uv_offset+1] = ((-38*r + -74*g + 112*b) >> 8) + 128;

            }

Я стараюсь много, но все еще не могу решить это. И я нахожу небольшой код о преобразовании RGB в YUV420P, Больше кодов о преобразовании YUV420P в RGB. Итак, я хочу знать, кто-то сталкивается с тем же вопросом или дает мне какой-нибудь совет?

Спасибо, Роберт Кровелла. Вот мой update-1 .

Я следую за Робертом Совет Кровеллы и измените функцию ядра следующим образом:

//kernel function to convert bgr to yuv420p
    __global__ void bgr2yuv420p(uchar3 *  d_in, unsigned char * d_out,
                                   uint imgheight, uint imgwidth)
    {

        int col_num = blockIdx.x*blockDim.x+threadIdx.x;
        int row_num = blockIdx.y*blockDim.y+threadIdx.y;

        if ((row_num < imgheight) && (col_num < imgwidth))
        {
    //        uint32_t a = *((uint32_t *)&dinput[global_offset*3]);
            int global_offset = row_num*imgwidth+col_num;

            int r,g,b;
            r = int(d_in[global_offset].z);
            g = int (d_in[global_offset].y);
            b = int (d_in[global_offset].x);


            d_out[row_num * imgwidth + col_num] = ((66*r + 129*g + 25*b) >> 8) + 16;
            if(((threadIdx.x & 1) == 0)  && ((threadIdx.y & 1) == 0)){
                int uv_offset = imgwidth*imgheight+((row_num>>1)*imgwidth)+col_num;
                d_out[uv_offset] = ((112*r + -94*g + -18*b) >> 8) + 128;
                d_out[uv_offset+1] = ((-38*r + -74*g + 112*b) >> 8) + 128;

            }

        }
    }

Я испытываю новое ядро ​​с волнением, но результат также не является нормальным. Вот мой результат изображения с обновленной функцией ядра. yuv420p изображение, преобразованное мной

Тогда нормальное изображение результата, преобразованное opencv api, здесь. yuv420p изображение, преобразованное с помощью opencv api

Как мы видим, разница между этими двумя изображениями - это U и V. Я уже изменил индекс U и V в функции ядра, т.е. 1047 *

if(((threadIdx.x & 1) == 0)  && ((threadIdx.y & 1) == 0)){
                int uv_offset = imgwidth*imgheight+((row_num >>1)*imgwidth)+col_num;
                d_out[uv_offset] = ((112*r + -94*g + -18*b) >> 8) + 128;
                d_out[uv_offset+1] = ((-38*r + -74*g + 112*b) >> 8) + 128;

            }

Я думаю, что это будет работать, но это не так. Любой другой совет? Роберт Кровелла

1 Ответ

1 голос
/ 29 апреля 2020

Существует множество проблем:

  • расчеты для преобразования R, G, B в Y, U, V между кодами вашего CPU и GPU не идентичны. Да, это важно.
  • Код вашего ЦП имеет планарное хранилище Y, U, V. Это означает, что у Y есть своя плоскость, у U своя собственная плоскость, а у V своя собственная плоскость. Ваши коды GPU являются полуплоскостным (NV12) форматом. Это означает, что Y имеет свою плоскость, а U, V чередуются в одной плоскости: UVUVUVUVUVUV .... Очевидно, что выходные данные этих двух кодов никогда не могут совпадать одинаково.
  • IMO, нет необходимости перетаскивать OpenCV в это.
  • Ваш расчет УФ-смещения в коде ядра (GPU) был нарушен. Смещение imgwidth*imgheight позволяет вам преодолеть область Y (правильно), но с этой точки некорректно использовать row_num*imgwidth для индексации по строке в плоскости UV. У вас не так много строк в плоской области UV, у вас есть только половина числа строк.
  • В вашем ядре GPU у вас был порядок U, V в обратном порядке, вы эффективно выполняли VUVUVUVU ...

Я бы рекомендовал начать с согласования различий в расчетах и ​​порядка / формата хранения. В следующем коде устранены вышеуказанные проблемы, и я получаю результаты сопоставления между кодами процессора и графического процессора:

$ cat t1708.cu
#include <iostream>
#include <time.h>
#include <cstdlib>
using namespace std;
// I have no idea if these are the correct conversion formulas
// I simply lifted what I saw in your host code so that we 
// are using the same conversion calculations in host and device
__host__ __device__ unsigned char bgr2y(int R, int G, int B){
  int Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
  return (unsigned char)((Y<0)? 0 : ((Y > 255) ? 255 : Y));}
__host__ __device__ int bgr2u(int R, int G, int B){
  int U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
  return (unsigned char)((U<0)? 0 : ((U > 255) ? 255 : U));}
__host__ __device__ int bgr2v(int R, int G, int B){
  int V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
  return (unsigned char)((V<0)? 0 : ((V > 255) ? 255 : V));}

void bgr_to_yuv420p(unsigned  char* yuv420p, unsigned char* bgr, int width, int height)
{
    if (yuv420p == NULL || bgr== NULL)
        return;
    int frameSize = width*height;

    int yIndex = 0;
    int uIndex = frameSize;

    int R, G, B;
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            B = bgr[(i * width + j) * 3 + 0];
            G = bgr[(i * width + j) * 3 + 1];
            R = bgr[(i * width + j) * 3 + 2];

            //BGR to YUV
            yuv420p[yIndex++] = bgr2y(R,G,B);
            if (i % 2 == 0 && j % 2 == 0)
            {
                yuv420p[uIndex] = bgr2u(R,G,B);
                yuv420p[uIndex+1] = bgr2v(R,G,B);
                uIndex+=2;
            }
        }
    }
}

//kernel function to convert bgr to yuv420p
__global__ void bgr2yuv420p(uchar3 *  d_in, unsigned char * d_out,
                               uint imgheight, uint imgwidth)
{

    int col_num = blockIdx.x*blockDim.x+threadIdx.x;
    int row_num = blockIdx.y*blockDim.y+threadIdx.y;

    if ((row_num < imgheight) && (col_num < imgwidth))
    {
//        uint32_t a = *((uint32_t *)&dinput[global_offset*3]);
        int global_offset = row_num*imgwidth+col_num;

        int r,g,b;
        r = int(d_in[global_offset].z);
        g = int (d_in[global_offset].y);
        b = int (d_in[global_offset].x);


        d_out[row_num * imgwidth + col_num] = bgr2y(r,g,b);
        if(((threadIdx.x & 1) == 0)  && ((threadIdx.y & 1) == 0)){
            int uv_offset = imgwidth*imgheight+((row_num>>1)*imgwidth)+col_num;
            d_out[uv_offset] = bgr2u(r,g,b);
            d_out[uv_offset+1] = bgr2v(r,g,b);

        }

    }
}

int main(void)
{

    const uint imgheight = 1000;
    const uint imgwidth = 1500;

    //input and output
    uchar3 *d_in;
    unsigned char *d_out;
    uchar3 *idata = new uchar3[imgheight*imgwidth];
    unsigned char *odata = new unsigned char[imgheight*imgwidth*3/2];
    unsigned char *cdata = new unsigned char[imgheight*imgwidth*3/2];
    uchar3 pix;
    for (int i = 0; i < imgheight*imgwidth; i++){
      pix.x = (rand()%30)+40;
      pix.y = (rand()%30)+40;
      pix.z = (rand()%30)+40;
      idata[i] = pix;}
    for (int i = 0; i < imgheight*imgwidth; i++) idata[i] = pix;
    bgr_to_yuv420p(cdata, (unsigned char*) idata, imgwidth, imgheight);
    // malloc memo in gpu
    cudaMalloc((void**)&d_in, imgheight*imgwidth*sizeof(uchar3));
    cudaMalloc((void**)&d_out, imgheight*imgwidth*sizeof(unsigned char) * 3 / 2);

    //copy image from cpu to gpu
    cudaMemcpy(d_in, idata, imgheight*imgwidth*sizeof(uchar3), cudaMemcpyHostToDevice);

    dim3 threadsPerBlock(32, 32);
    dim3 blocksPerGrid((imgwidth + threadsPerBlock.x - 1) / threadsPerBlock.x,
                       (imgheight + threadsPerBlock.y - 1) / threadsPerBlock.y);

    //run kernel function
    bgr2yuv420p<<<blocksPerGrid, threadsPerBlock>>>(d_in, d_out, imgheight, imgwidth);

    cudaDeviceSynchronize();

    //copy yuv420p from gpu to cpu
    cudaMemcpy(odata, d_out, imgheight*imgwidth*sizeof(unsigned char) * 3 / 2, cudaMemcpyDeviceToHost);
    for (int i = 0; i < (imgwidth*imgheight*3/2); i++) if (odata[i] != cdata[i]) {std::cout << "mismatch at: " << i << " was: " << (int)odata[i] << " should be: " << (int)cdata[i] << std::endl; return 0;}
    cudaFree(d_in);
    cudaFree(d_out);


    return 0;

}
$ nvcc -o t1708 t1708.cu
$ cuda-memcheck ./t1708
========= CUDA-MEMCHECK
========= ERROR SUMMARY: 0 errors
$

Каждый раз, когда у вас возникают проблемы с кодом CUDA, я рекомендую

  1. Правильная проверка ошибок CUDA
  2. Запуск вашего кода с cuda-memcheck

Я не претендую на правильность этого кода или любого другого кода, который я публикую. Любой, кто использует любой код, который я публикую, делает это на свой страх и риск. Я просто утверждаю, что попытался устранить недостатки, обнаруженные в первоначальной публикации, и приведу некоторые объяснения этого. Я не утверждаю, что мой код не имеет дефектов или что он подходит для какой-либо конкретной цели. Используйте его (или нет) на свой страх и риск.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...