В предыдущих постах мы попытались объять необъятное: хотя бы в общих чертах представить себе работу алгоритма оценки движения как самой главной составляющей современных видеокодеков. Судя по количеству комментариев, а что еще важнее – вопросов в комментариях – тема оказалась интересной и обширной. Но к хитрым нюансам 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 и даже запатентованная моим хорошим другом и коллегой. Тот, кто первый правильно укажет на эту запись, тот и победил :)
