Как распараллелить это для цикла для быстрого преобразования YUV422 в RGB888?

Я использую API v4l2 для захвата изображений с Microsoft Lifecam, а затем переношу эти изображения по TCP на удаленный компьютер. Я также кодирую видеокадры в MPEG2VIDEO, используя ffmpeg API. Эти записанные видео воспроизводятся слишком быстро, что, вероятно, связано с недостаточным количеством кадров и неправильными настройками FPS.

Ниже приведен код, который преобразует источник YUV422 в изображение RGB888. Этот фрагмент кода является узким местом в моем коде, так как для его выполнения требуется около 100-150 мс, что означает, что я не могу регистрировать более 6-10 кадров в секунду при разрешении 1280 x 720. Загрузка процессора также составляет 100%.

for (int line = 0; line < image_height; line++) {
for (int column = 0; column < image_width; column++) {
*dst++ = CLAMP((double)*py + 1.402*((double)*pv - 128.0));                                                  // R - first byte
*dst++ = CLAMP((double)*py - 0.344*((double)*pu - 128.0) - 0.714*((double)*pv - 128.0));    // G - next byte
*dst++ = CLAMP((double)*py + 1.772*((double)*pu - 128.0));                                                            // B - next byte

vid_frame->data[0][line * frame->linesize[0] + column] = *py;

// increment py, pu, pv here

}

Затем dst сжимается как jpeg и отправляется по TCP, а vid_frame сохраняется на диск.

Как я могу сделать этот фрагмент кода быстрее, чтобы получить как минимум 30 FPS с разрешением 1280×720 по сравнению с существующими 5-6 FPS?

Я попытался распараллелить цикл for для трех потоков, используя p_thread, обработав одну треть строк в каждом потоке.

for (int line = 0; line < image_height/3; line++) // thread 1
for (int line = image_height/3; line < 2*image_height/3; line++) // thread 2
for (int line = 2*image_height/3; line < image_height; line++) // thread 3

Это дало мне лишь небольшое улучшение — 20-30 миллисекунд на кадр.
Каков наилучший способ распараллеливания таких циклов? Могу ли я использовать вычисления на GPU или что-то вроде OpenMP? Скажем, потрачено около 100 потоков на вычисления?

Я также заметил более высокую частоту кадров с веб-камерой моего ноутбука по сравнению с Microsoft USB Lifecam.

Вот другие детали:

  • Ubuntu 12.04, ffmpeg 2.6
  • Четырехъядерный процессор AMG-A8 с 6 ГБ оперативной памяти
  • Настройки кодера:
    • кодек: AV_CODEC_ID_MPEG2VIDEO
    • битрейт: 4000000
    • time_base: (AVRational) {1, 20}
    • pix_fmt: AV_PIX_FMT_YUV420P
    • гоп: 10
    • max_b_frames: 1

0

Решение

Если все, что вас волнует, это fps, а не мс на кадр (задержка), другой опцией будет отдельный поток на кадр.

Threading — не единственный вариант для улучшения скорости. Вы также можете выполнять целочисленные операции, а не с плавающей точкой. И SIMD это вариант. Использование существующей библиотеки, такой как sws_scale, вероятно, даст вам лучшую производительность.

Мак уверен, что вы компилируете -O3 (или -Os).

Убедитесь, что символы отладки отключены.

Переместить повторяющиеся операции за пределы цикла, например

// compiler cant optimize this because another thread could change frame->linesize[0]
int row = line * frame->linesize[0];
for (int column = 0; column < image_width; column++) {
...
vid_frame->data[0][row + column] = *py;

Вы можете предварительно вычислять таблицы, поэтому в цикле нет математики:

init() {
for(int py = 0; py <= 255 ; ++py)
for(int pv = 0; pv <= 255 ; ++pv)
ytable[pv][py] =  CLAMP(pv + 1.402*(py - 128.0));
}

for (int column = 0; column < image_width; column++) {
*dst++ = ytable[*pv][*py];

Просто назвать несколько вариантов.

1

Другие решения

Я думаю, что если вы не хотите заново изобретать болезненное колесо, гораздо лучше использовать уже существующие опции (ffmpeg ‘libswscale или ffmpeg, фильтр масштабирования, плагин масштабирования gstreamer и т. Д.).

Но если вы хотите изобрести велосипед по любой причине, покажите код, который вы использовали. Например, запуск потоков стоит дорого, поэтому вы захотите создать потоки перед измерением времени цикла и повторно использовать потоки от кадра к кадру. Еще лучше поточная обработка кадров, но это добавляет задержку. Обычно это нормально, но зависит от вашего варианта использования. Что еще более важно, не пишите код на C, учитесь писать сборку x86 (simd), все ранее упомянутые библиотеки используют simd для таких преобразований, и это даст вам ускорение в 3-4 раза (так как это позволяет вам делать 4-8). пикселей вместо 1 за итерацию).

1

Вы можете построить блоки из х строк и конвертировать каждый блок в отдельный поток

0

  • не смешивайте целочисленную и арифметику с плавающей точкой!

    char x;
    char y=((double)x*1.5); /* ouch casting double<->int is slow! */
    char z=(x*3)>>1;        /* fixed point arithmetic rulez */
    
  • использование SIMD (хотя это было бы проще, если бы входные и выходные данные были правильно выровнены … например, используя RGB8888 как вывод)

  • использование openMP

альтернатива, которая не требует любой кодирование обработки будет состоять в том, чтобы просто выполнять всю вашу обработку с использованием инфраструктуры, которая выполняет правильную метку времени по всему конвейеру (начиная со времени получения изображения) и, как мы надеемся, достаточно оптимизирована для работы с большими данными. например gstreamer

0

Будет ли что-то подобное не работать?

#pragma omp parallel for
for (int line = 0; line < image_height; line++) {
for (int column = 0; column < image_width; column++) {
dst[ ( image_width*line + column )*3    ] = CLAMP((double)*py + 1.402*((double)*pv - 128.0));                                                  // R - first byte
dst[ ( image_width*line + column )*3 + 1] = CLAMP((double)*py - 0.344*((double)*pu - 128.0) - 0.714*((double)*pv - 128.0));    // G - next byte
dst[ ( image_width*line + column )*3 + 2] = CLAMP((double)*py + 1.772*((double)*pu - 128.0));                                                            // B - next byte

vid_frame->data[0][line * frame->linesize[0] + column] = *py;

// increment py, pu, pv here

}

Конечно, вы также должны обрабатывать увеличивающиеся части py, py, pv соответственно.

0

Обычно преобразование формата пикселя выполняется с использованием только целочисленных переменных.
Это позволяет предотвратить преобразование между плавающей точкой и целочисленными переменными.
Также это позволяет более эффективно использовать SIMD-расширения современных процессоров.
Например, это код преобразования YUV в BGR:

const int Y_ADJUST = 16;
const int UV_ADJUST = 128;
const int YUV_TO_BGR_AVERAGING_SHIFT = 13;
const int YUV_TO_BGR_ROUND_TERM = 1 << (YUV_TO_BGR_AVERAGING_SHIFT - 1);
const int Y_TO_RGB_WEIGHT = int(1.164*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int U_TO_BLUE_WEIGHT = int(2.018*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int U_TO_GREEN_WEIGHT = -int(0.391*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int V_TO_GREEN_WEIGHT = -int(0.813*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int V_TO_RED_WEIGHT = int(1.596*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);

inline int RestrictRange(int value, int min = 0, int max = 255)
{
return value < min ? min : (value > max ?  max : value);
}

inline int YuvToBlue(int y, int u)
{
return RestrictRange((Y_TO_RGB_WEIGHT*(y - Y_ADJUST) +
U_TO_BLUE_WEIGHT*(u - UV_ADJUST) +
YUV_TO_BGR_ROUND_TERM) >> YUV_TO_BGR_AVERAGING_SHIFT);
}

inline int YuvToGreen(int y, int u, int v)
{
return RestrictRange((Y_TO_RGB_WEIGHT*(y - Y_ADJUST) +
U_TO_GREEN_WEIGHT*(u - UV_ADJUST) +
V_TO_GREEN_WEIGHT*(v - UV_ADJUST) +
YUV_TO_BGR_ROUND_TERM) >> YUV_TO_BGR_AVERAGING_SHIFT);
}

inline int YuvToRed(int y, int v)
{
return RestrictRange((Y_TO_RGB_WEIGHT*(y - Y_ADJUST) +
V_TO_RED_WEIGHT*(v - UV_ADJUST) +
YUV_TO_BGR_ROUND_TERM) >> YUV_TO_BGR_AVERAGING_SHIFT);
}

Этот код взят здесь (http://simd.sourceforge.net/). Также здесь есть код, оптимизированный для разных SIMD.

0
По вопросам рекламы [email protected]