Проблема с записью кадра изображения YUV на C / C ++

Я пытаюсь преобразовать кадр RGB, взятый из OpenGL glReadPixels (), в кадр YUV и записать кадр YUV в файл (.yuv). Позже я хотел бы записать его в named_pipe как вход для FFMPEG, но пока я просто хочу записать его в файл и просмотреть результат изображения с помощью средства просмотра изображений YUV. Так что пока просто не обращайте внимания на «писать в трубу».

После запуска моего кода я обнаружил следующие ошибки:

  1. Количество кадров, отображаемое в программе просмотра изображений YUV Image Viewer, всегда составляет 1/3 от количества кадров, заявленных в моей программе. Когда я объявляю fps равным 10, я мог просматривать только 3 кадра. Когда я объявил 30 кадров в секунду, я мог просматривать только 10 кадров. Однако, когда я просматривал файл в текстовом редакторе, я мог видеть, что в файле напечатано правильное количество слова «FRAME». Это пример вывода, который я получил: http://www.bobdanani.net/image.yuv

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

Я прочитал о формате YUV из http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 и http://www.fourcc.org/fccyvrgb.php#mikes_answer и http://kylecordes.com/2007/pipe-ffmpeg

Вот что я пробовал до сих пор. Я знаю, что такой подход к преобразованию неэффективен, и я могу оптимизировать его позже. Теперь я просто хочу, чтобы этот наивный подход работал и изображение отображалось правильно.

int frameCounter = 1; 
int windowWidth = 0, windowHeight = 0;
unsigned char *yuvBuffer;
unsigned long bufferLength = 0;
unsigned long frameLength = 0;
int fps = 10;

void display(void) {

    /* clear the color buffers */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* DRAW some OPENGL animation, i.e. cube, sphere, etc 
     .......
     .......
    */

    glutSwapBuffers();

    if ((frameCounter % fps) == 1){
        bufferLength = 0;
        windowWidth = glutGet(GLUT_WINDOW_WIDTH);
        windowHeight = glutGet (GLUT_WINDOW_HEIGHT);
        frameLength = (long) (windowWidth * windowHeight * 1.5 * fps) + 100; // YUV 420 length (width*height*1.5) + header length
        yuvBuffer = new unsigned char[frameLength];
        write_yuv_frame_header();
    }

    write_yuv_frame();

    frameCounter = (frameCounter % fps) + 1;

    if ( (frameCounter % fps) == 1){
        snprintf(filename, 100, "out/image-%d.yuv", seq_num);
        ofstream out(filename, ios::out | ios::binary); 
        if(!out) { 
            cout << "Cannot open file.\n"; 
        } 

        out.write (reinterpret_cast<char*> (yuvBuffer), bufferLength);
        out.close();
        bufferLength = 0;
        delete[] yuvBuffer;
    }
}


void write_yuv_frame_header (){
    char *yuvHeader = new char[100];
    sprintf (yuvHeader, "YUV4MPEG2 W%d H%d F%d:1 Ip A0:0 C420mpeg2 XYSCSS=420MPEG2\n", windowWidth, windowHeight, fps);
    memcpy ((char*)yuvBuffer + bufferLength, yuvHeader, strlen(yuvHeader));
    bufferLength += strlen (yuvHeader);
    delete (yuvHeader);
}

void write_yuv_frame() {
    int width = glutGet(GLUT_WINDOW_WIDTH);
    int height = glutGet(GLUT_WINDOW_HEIGHT);
    memcpy ((void*) (yuvBuffer+bufferLength), (void*) "FRAME\n", 6);
    bufferLength +=6;

    long length = windowWidth * windowHeight;
    long yuv420FrameLength = (float)length * 1.5;
    long lengthRGB = length * 3;
    unsigned char *rgb      = (unsigned char *) malloc(lengthRGB * sizeof(unsigned char));
    unsigned char *yuvdest  = (unsigned char *) malloc(yuv420FrameLength * sizeof(unsigned char));
    glReadPixels(0, 0, windowWidth, windowHeight, GL_RGB, GL_UNSIGNED_BYTE, rgb);

    int r, g, b, y, u, v, ypos, upos, vpos;

    for (int j = 0; j <  windowHeight; ++j){
        for (int i = 0; i < windowWidth; ++i){
            r = (int)rgb[(j * windowWidth + i) * 3 + 0];
            g = (int)rgb[(j * windowWidth + i) * 3 + 1];
            b = (int)rgb[(j * windowWidth + i) * 3 + 2];

            y = (int)(r *  0.257 + g *  0.504 + b *  0.098) + 16;
            u = (int)(r *  0.439 + g * -0.368 + b *  -0.071) + 128;
            v = (int)(r *  -0.148 + g * -0.291 + b * 0.439 + 128);

            ypos = j * windowWidth + i;
            upos = (j/2) * (windowWidth/2) + i/2 + length;
            vpos = (j/2) * (windowWidth/2) + i/2 + length + length/4;

            yuvdest[ypos] = y;
            yuvdest[upos] = u;
            yuvdest[vpos] = v;            
        } 
    }

    memcpy ((void*) (yuvBuffer + bufferLength), (void*)yuvdest, yuv420FrameLength);
    bufferLength += yuv420FrameLength;
    free (yuvdest);   
    free (rgb);
}

Это только самый простой подход, и я смогу оптимизировать алгоритм преобразования позже. Может ли кто-нибудь сказать мне, что не так в моем подходе? Я предполагаю, что одна из проблем связана с вызовом outstream.write (), потому что я преобразовал данные unsigned char * в данные char *, что может привести к потере точности данных. Но если я не приведу его к char *, я получу ошибку компиляции. Однако это не объясняет, почему выходные кадры повреждены (составляет только 1/3 от общего числа кадров).


person all_by_grace    schedule 25.03.2012    source источник
comment
Вы решили это?   -  person Michael IV    schedule 12.03.2013


Ответы (2)


Мне кажется, у вас слишком много байтов на кадр для данных 4: 2: 0. В соответствии со спецификацией, с которой вы связались, количество байтов для кадра размером 200x200 пикселей 4: 2: 0 должно быть 200 * 200 * 3/2 = 60000. Но у вас есть ~ 90 000 байт. Глядя на ваш код, я не вижу, где вы конвертируете из 4: 4: 4 в 4: 2: 0. Итак, у вас есть 2 варианта: либо установить заголовок на 4: 4: 4, либо преобразовать данные YCbCr в 4: 2: 0 перед их записью.

person user1118321    schedule 25.03.2012
comment
Я уже установил количество байтов на 200 * 200 * 3/2 (в yuv420FrameLength). Таким образом, размер буфера yuv составляет 60 000 для изображения размером 200 * 200 пикселей. Я также установил длину кадра шириной * высотой * 1,5 * кадра в секунду. Кроме того, следующие коды позиционируют данные в формате 4: 2: 0: ypos = j * windowWidth + i; upos = (j / 2) * (windowWidth / 2) + i / 2 + длина; vpos = (j / 2) * (ширина окна / 2) + i / 2 + длина + длина / 4; yuvdest [ypos] = y; ювдест [упос] = и; yuvdest [vpos] = v; - person all_by_grace; 25.03.2012
comment
Ах, прости. Я загрузил ваш выходной файл, и когда я посмотрел на него в текстовом редакторе, было около 90 000 байтов между первым КАДРОМ и вторым КАДРОМ, но оказалось, что это потому, что он был преобразован в UTF8 текстовым редактором. Глядя на это в шестнадцатеричном редакторе, всего 60 000 байт, что кажется правильным. - person user1118321; 25.03.2012
comment
Можете ли вы опубликовать фотографии до и после? Это может сделать более очевидным, что происходит. Кроме того, что произойдет, если вы напишете данные 4: 4: 4 (с правильным заголовком)? Значит, вы получите все кадры? Еще одна вещь - я заметил, что ссылка на википедию упоминает, что заголовки начинаются с «YUVMPEG4» (с пробелом в конце) и что все поля начинаются с пробела. Вам нужно 2 пробела между маркером YUV и маркером ширины? (Из Википедии мне не ясно, так ли это.) - person user1118321; 25.03.2012
comment
Можете выложить код, куда записываете данные? Кроме того, можете ли вы проверить в отладчике, что при преобразовании u и v из подписанных целых чисел в беззнаковые символы вы получаете правильный битовый шаблон? - person user1118321; 25.03.2012

Я скомпилировал ваш код и наверняка возникла проблема при вычислении значений upos и vpos. Для меня это сработало (от RGB до YUV NV12):

vpos = length + (windowWidth * (j/2)) + (i/2)*2;
upos = vpos + 1;
person Alex Dima    schedule 10.09.2014