И снова Motion Estimation Library (часть вторая, про пять строчек кода)

В предыдущих постах мы попытались объять необъятное: хотя бы в общих чертах представить себе работу алгоритма оценки движения как самой главной составляющей современных видеокодеков. Судя по количеству комментариев, а что еще важнее – вопросов в комментариях – тема оказалась интересной и обширной. Но к хитрым нюансам Motion Estimation мы вернемся позже, а сегодня попробуем разобраться с программной реализацией.


В самом деле, желание с нуля написать что-то, связанное с обработкой видео, сильно уменьшается как только речь заходит о референс-кадрах, суммарной абсолютной разнице, векторах движения и прочих антинаучных вещах. К счастью, существует множество библиотек, позволяющих реализовать все хитрости буквально в пяти строчках кода. Попробуем?


Хм… Давайте для начала повторим, что же мы хотим сделать:




  • Инициализировать объект оценки движения.

  • Выделить память.

  • Положить в память данные.

  • Подготовить структуры для выходных данных.

  • Наконец, запустить алгоритм оценки движения.



Надеюсь, вы не столь наивны, как наивен был я, выбирая эту работу ;). Тех, кто действительно поверил в пять строк кода, немного разочарую. С другой стороны, не все так страшно. Нас не напугать developer guide! Поехали.



Начинаем с инициализации


Представим, что приложение предоставляет нам чистое видео в формате YCbCr, размером width x height.



// the main hardware me structure

hw_me_handle_t hwhandle = new _HW_ME_HANDLE;


// parameters of hardware me

HW_ME_PARAMETERS hwPar;


// auxiliary variables

Ipp32u sts, numMb, imageStep, imageSize;


// initialize hardware me parameters

hwPar.codecStandard = HW_ME_H264;

hwPar.meType = HW_ME_WHOLE_PIXEL;

hwPar.algType = HW_ME_RESIDUAL;

hwPar.strictWithinFrame = ippFalse;

hwPar.minSubblockSize = HW_ME_DIV_16X16;

hwPar.imageSize.width = width;

hwPar.imageSize.height = height;


pMeInfo.pMbInfo = NULL;


// number of reference frames for motion estimation

hwPar.maxNumRef = 2;


// initialize me handle by me parameters 

InitializeHWME(&hwPar, &hwhandle);


Мы инициализировали объект оценки движения, как адаптированный для стандарта H264, в полнопиксельном режиме (без интерполяции), алгоритм полного поиска (full search) c минимальным разбиением 16x16 (то есть разбивать даже и не будет пытаться). Максимальное количество ссылочных кадров равно двум.



Следующий шаг - выделение памяти под исходный и референс кадры



При этом мы объявляем, что структура кадров прогрессивная.



// reference and source images

HW_ME_IMAGE hwRefImage;

HW_ME_IMAGE hwSrcImage;


// set images properties

hwRefImage.imageStructure = HW_ME_FRAME;

imageStep = (Ipp32u) align_value<size_t> (width, ALIGN_VALUE) | ALIGN_VALUE;

imageSize = (Ipp32u) align_value<size_t> ((imageStep * height * 3) / 2);

hwRefImage.imageStep = hwSrcImage.imageStep = imageStep;


// allocate memory

Ipp8u *pAllocatedReferenceMemory = new Ipp8u [imageSize + ALIGN_VALUE];

Ipp8u *pRefPlane = align_pointer<Ipp8u *> (pAllocatedReferenceMemory);


hwRefImage.pPlanes[0] = pRefPlane;

hwRefImage.pPlanes[1] = pRefPlane + imageStep * height;

hwRefImage.pPlanes[2] = pRefPlane + (imageStep * height * 5) / 4;


Ipp8u *pAllocatedSourceMemory = new Ipp8u [imageSize + ALIGN_VALUE];

Ipp8u *pSrcPlane = align_pointer<Ipp8u *> (pAllocatedSourceMemory);


hwSrcImage.pPlanes[0] = pSrcPlane;

hwSrcImage.pPlanes[1] = pSrcPlane + imageStep * height;

hwSrcImage.pPlanes[2] = pSrcPlane + (imageStep * height * 5) / 4;


Положим данные на место


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



// YUV reader

CYUVReader reader;


// initialize YUV reader

sts = reader.Init(filename, width, height);

CHECK_STS(sts);


// read the first frame

sts = reader.ReadYUVData();

CHECK_STS(sts);


// copy data from yuv reader to source image memory

IppiSize iSize;

iSize.height = height;

iSize.width = width;


CopyPlane(reader.m_pYUVData[0], width, hwSrcImage.pPlanes[0], imageStep, iSize);


iSize.width = iSize.width / 2;

iSize.height = iSize.height / 2;


CopyPlane(reader.m_pYUVData[1], width / 2, hwSrcImage.pPlanes[1], imageStep / 2, iSize);

CopyPlane(reader.m_pYUVData[2], width / 2, hwSrcImage.pPlanes[2], imageStep / 2, iSize);


sts = CopyHWMESource(hwhandle, &hwSrcImage);

CHECK_STS(sts);


// start preparing of reference frames

for (Ipp32u i = 0; i < hwPar.maxNumRef; i += 1)

{

    // read the next frame

    reader.ReadYUVData();


    // copy data from YUV reader to reference image memory


    iSize.height = height;

    iSize.width = width;


    CopyPlane(reader.m_pYUVData[0], width, hwRefImage.pPlanes[0], imageStep, iSize);


    iSize.width = iSize.width / 2;

    iSize.height = iSize.height / 2;


    CopyPlane(reader.m_pYUVData[1], width / 2, hwRefImage.pPlanes[1], imageStep / 2, iSize);

    CopyPlane(reader.m_pYUVData[2], width / 2, hwRefImage.pPlanes[2], imageStep / 2, iSize);


    // add reference image to the handle

    sts = CopyHWMEReference(hwhandle, i, &hwRefImage);

    CHECK_STS(sts);

}




Запуск алгоритма


Ну, и наконец, подготовка выходных структур и запуск алгоритма:



// results storage of me

HW_ME_INFO pMeInfo;


pMeInfo.numFrameRefs[1] = hwPar.maxNumRef;


// calculate the number of macro blocks

numMb = (width * height) / (16 * 16);


// allocate mb info 

pMeInfo.pMbInfo = new HW_ME_MB_INFO [numMb];

pMeInfo.numFrameRefs[1] = hwPar.maxNumRef;


sts = DoHWME(hwhandle, &pMeInfo);

CHECK_STS(sts);




Вопрос на засыпку!


Традиционный вопрос. В библиотеке будет использоваться потоковая модель, ранее описанная на ISN и даже запатентованная моим хорошим другом и коллегой. Тот, кто первый правильно укажет на эту запись, тот и победил :)
For more complete information about compiler optimizations, see our Optimization Notice.
Categories: