2014-12-18 2 views
3

Из SDK я получаю изображения, которые имеют формат BGR в пикселях, то есть BGRBGRBGR. Для другого приложения мне нужно преобразовать этот формат в план RGB RRRGGGBBB. Я не хочу использовать дополнительную библиотеку только для этой задачи, поэтому мне нужно использовать собственный код для преобразования между форматами.Ускорение преобразования формата пикселей - BGR, упакованное в план RGB

Я использую C# .NET 4.5 32bit, а данные в массивах байтов имеют одинаковый размер.

Прямо сейчас я выполняю итерацию через источник массива и присваиваю значения BGR их соответствующим местам в целевом массиве, но это занимает слишком много времени (250 мс для 1,3-мегапиксельного изображения). Процессором, на котором выполняется код, является Intel Atom E680 и имеет доступ к MMX, SSE, SSE2, SSE3, SSSE3.

К сожалению, у меня нет знаний о intrinsics и не удалось преобразовать код для аналогичной проблемы, например, Fast method to copy memory with translation - ARGB to BGR в соответствии с моими потребностями.

Код настоящее время я использую для преобразования между форматами пиксельных является:

// the array with the BGRBGRBGR pixel data 
byte[] source; 
// the array with the RRRGGGBBB pixel data 
byte[] result; 
// the amount of pixels in one channel, width*height 
int imageSize; 

for (int i = 0; i < source.Length; i += 3) 
{ 
    result[i/3] = source[i + 2]; // R 
    result[i/3 + imageSize] = source[i + 1]; // G 
    result[i/3 + imageSize * 2] = source[i]; // B 
} 

Я попытался расколоть доступ к исходного массива на три петли, по одному для каждого канала, но это не помогло , Поэтому я открыт для предложений.

for (int i = 0; i < source.Length; i += 3) 
{ 
    result[i/3] = source[i + 2]; // R 
} 

for (int i = 0; i < source.Length; i += 3) 
{ 
    result[i/3 + imageSize] = source[i + 1]; // G 
} 

for (int i = 0; i < source.Length; i += 3) 
{ 
    result[i/3 + imageSize * 2] = source[i]; // B 
} 

редактировать: Я получил его вниз, удаляя звука 180 мс на деление и умножение, как это, но есть способ, чтобы сделать его еще быстрее? Он все еще очень медленный, и я думаю, это потому, что чтение/запись в память не очень оптимально.

int targetPosition = 0; 
int imageSize2 = imageSize * 2; 
for (int i = 0; i < source.Length; i += 3) 
{ 
    result[targetPosition] = source[i + 2]; // R 
    targetPosition++; 
} 

targetPosition = 0; 

for (int i = 0; i < source.Length; i += 3) 
{ 
    result[targetPosition + imageSize] = source[i + 1]; // G 
    targetPosition++; 
} 

targetPosition = 0; 

for (int i = 0; i < source.Length; i += 3) 
{ 
    result[targetPosition + imageSize2] = source[i]; // B 
    targetPosition++; 
} 

Благодаря ответу Мбо, я смог сократить время от 180 мс до 90 мс! Вот код:

Converter.cpp:

#include "stdafx.h" 

BOOL __stdcall DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) { 
return TRUE; 
} 

const unsigned char Mask[] = { 0, 3, 6, 9, 
          1, 4, 7, 10, 
          2, 5, 8, 11, 
          12, 13, 14, 15}; 

extern "C" __declspec(dllexport) char* __stdcall ConvertPixelFormat(unsigned char* source, unsigned char *target, int imgSize) { 

_asm { 
    //interleave r1g1b1 r2g2b2 r3g3b3 r4b4g4 r5b5g5 r6... to planar 
    //   r1r2r3r4r5..... g1g2g3g4g5... b1b2b3b4b5... 
     push edi 
     push esi 
     mov eax, source  //A address 
     mov edx, target  //B address 
     mov ecx, imgSize 
     movdqu xmm5, Mask //load shuffling mask 
     mov edi, imgSize  //load interleave step 
     mov esi, eax 
     add esi, edi 
     add esi, edi 
     add esi, edi 
     shr ecx, 2   //divide count by 4 
     dec ecx    //exclude last array chunk 
     jle Rest 

    Cycle: 
     movdqu xmm0, [eax]  //load 16 bytes 
     pshufb xmm0, xmm5   //shuffle bytes, we are interested in 12 ones 
     movd [edx], xmm0   //store 4 bytes of R 
     psrldq xmm0, 4   //shift right register, now G is on the end 
     movd [edx + edi], xmm0 //store 4 bytes of G to proper place 
     psrldq xmm0, 4   //do the same for B 
     movd [edx + 2 * edi], xmm0 
     add eax, 12    //shift source index to the next portion 
     add edx, 4    //shift destination index 
     loop Cycle 

    Rest:      //treat the rest of array 
     cmp eax, esi 
     jae Finish 
     mov ecx, [eax] 
     mov [edx], cl   //R 
     mov [edx + edi], ch  //G 
     shr ecx, 16 
     mov [edx + 2 * edi], cl //B 
     add eax, 3 
     add edx, 1 
     jmp Rest 

    Finish: 
     pop esi 
     pop edi 
    } 
} 

C# Файл:

// Code to define the method 
[DllImport("Converter.dll")] 
unsafe static extern void ConvertPixelFormat(byte* source, byte* target, int imgSize); 

// Code to execute the conversion 
unsafe 
{ 
    fixed (byte* sourcePointer = &source[0]) 
    { 
     fixed (byte* resultPointer = &result[0]) 
     { 
      ConvertPixelFormat(sourcePointer, resultPointer, imageSize); 
     } 
    } 
} 
+0

Просто проверьте несколько способов, распечатайте время и выберите самый быстрый. Я предлагаю удалить деления и умножения изнутри цикла. –

+0

Спасибо, я смог сделать это быстрее, но задаюсь вопросом, есть ли еще способ его улучшить. – RBS

+0

Я думаю, что вы достигли предела скорости вашего подхода. Вы можете улучшить его, изменив подход - например, используя несколько процессоров (потоков). –

ответ

1

Я реализовал эту проблему с чередованием в Delphi и рассмотрел встроенный asm. У меня нет встроенных функций, поэтому используется простой ассемблер.
pshufb равен _mm_shuffle_epi8 (SSSE3 intrinsic)

На каждом шаге цикла я загрузить 16 байт (r1g1b1 r2g2b2 r3g3b3 r4b4g4 r5b5g5 r6) до 128-битного XMM регистра, перетасовать их в (r1r2r3r4 g1g2g3g4 b1b2b3b4 xxxx) порядок, и сохранить R, G, B куски в память назначения (без учета последних 4 байта). Следующий шаг нагрузки (r5b5g5 r6g6b6 r7g7b7 ...) и так далее.

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

Первый пример версия выпуск:
imgSize = 32
массив размер = 96 байт
32/4 = 8 циклов
последний цикл начинается с 84-го байта и прочитать 16 байт Шифрование до 99-го байта - таким образом, мы запустить из диапазона массивов!
Я просто добавил здесь байты защиты: GetMem(A, Size * 3 + 15);, но для реальной задачи это может быть неприменимо, поэтому стоит иметь специальную обработку последнего куска массива.

Этот код принимает 967 мс для варианта с паскалем и 140 мс для варианта asm для преобразования двухсот 1,3-мегагерцовых кадров на машине i5-4670 (сам процессор на 6-8 раз быстрее для одного потока, чем Atom 680). Скорость составляет около 0,75 ГБ/с (pas) и 5,4 ГБ/сек (asm)

const 
    Mask: array[0..15] of Byte = (0, 3, 6, 9, 
           1, 4, 7, 10, 
           2, 5, 8, 11, 
           12, 13, 14, 15); 
var 
    A, B: PByteArray; 
    i, N, Size: Integer; 
    t1, t2: DWord; 
begin 
    Size := 1280 * 960 * 200; 
    GetMem(A, Size * 3); 
    GetMem(B, Size * 3); 

    for i := 0 to Size - 1 do begin 
    A[3 * i] := 1; 
    A[3 * i + 1] := 2; 
    A[3 * i + 2] := 3; 
    end; 

    t1 := GetTickCount; 
    for i := 0 to Size - 1 do begin 
    B[i] := A[3 * i]; 
    B[i + Size] := A[3 * i + 1]; 
    B[i + 2 * Size] := A[3 * i + 2]; 
    end; 
    t2:= GetTickCount; 

    //interleave r1g1b1 r2g2b2 r3g3b3 r4b4g4 r5b5g5 r6... to planar 
    //r1r2r3r4r5..... g1g2g3g4g5... b1b2b3b4b5... 
    asm 
    push edi 
    push esi 
    mov eax, A  //A address 
    mov edx, B  //B address 
    mov ecx, Size 
    movdqu xmm5, Mask //load shuffling mask 
    mov edi, Size  //load interleave step 
    mov esi, eax 
    add esi, edi 
    add esi, edi 
    add esi, edi 
    shr ecx, 2  //divide count by 4 
    dec ecx   //exclude last array chunk 
    jle @@Rest 

    @@Cycle: 
    movdqu xmm0, [eax] //load 16 bytes 
    pshufb xmm0, xmm5 //shuffle bytes, we are interested in 12 ones 
    movd [edx], xmm0  //store 4 bytes of R 
    psrldq xmm0, 4  //shift right register, now G is on the end 
    movd [edx + edi], xmm0 //store 4 bytes of G to proper place 
    psrldq xmm0, 4   //do the same for B 
    movd [edx + 2 * edi], xmm0 
    add eax, 12    //shift source index to the next portion 
    add edx, 4    //shift destination index 
    loop @@Cycle 

    @@Rest:  //treat the rest of array 
    cmp eax, esi 
    jae @@Finish 
    mov ecx, [eax] 
    mov [edx], cl //R 
    mov [edx + edi], ch //G 
    shr ecx, 16 
    mov [edx + 2 * edi], cl //B 
    add eax, 3 
    add edx, 1 
    jmp @@Rest 
    @@Finish: 

    pop esi 
    pop edi 
    end; 

    Memo1.Lines.Add(Format('pas %d asm %d', [t2-t1, GetTickCount - t2])); 
    FreeMem(A); 
    FreeMem(B); 
+0

Очень полезно! Это сократило необходимое время в 2 раза. Спасибо. – RBS

+0

Поскольку моя ширина изображения должна быть делимой на 8, мне не нужно беспокоиться о хвосте массива, потому что размер шага 4 означает, что он всегда будет обрабатывать весь массив, не так ли? – RBS

+0

Нет, я добавил объяснения и внес поправки, чтобы избежать потенциальных проблем (нарушение прав доступа). См. Исправленный код (как до, так и после основного цикла) – MBo

0

Вы можете попробовать отсчитывается, т.е. int i = source.Length - 1; i >=0 ; i -= 3, поэтому свойство source.Length считывается только один раз в течение цикла , а не на каждой итерации.

0

Я последовал совету Ивана и придумал это улучшение, которое избавляется от деления (реализованного в C):

int offset = 0; 
    for (int i = 0; i < ARRAYSIZE(source); i += 3) { 
     offset++; 
     result[offset] = source[i + 2]; // R 
     result[offset + imageSize] = source[i + 1]; // G 
     result[offset + imageSize * 2] = source[i]; // B 
    } 

это экономит около 40% времени работы на моей машине.

+1

Спасибо, но я был быстрее, см. Edit :) Это улучшилось совсем немного. – RBS

0

Первый шаг: избегать чтения источника несколько раз (см. Ответ https://stackoverflow.com/a/27542680/949044). Это также хорошо сочетается с кэшем процессора, который в настоящее время недоиспользуется: вы читаете 1 байт из 3, поэтому 2/3ds строки кэша выбрасываются. Так он может идти, как это:

int targetPositionR = 0; 
int targetPositionG = imageSize; 
int targetPositionB = imageSize * 2; 
for (int i = 0; i < source.Length; i += 3) 
{ 
    result[targetPositionB] = source[i]; // B 
    result[targetPositionG] = source[i + 1]; // G 
    result[targetPositionR] = source[i + 2]; // R 
    targetPositionB++; 
    targetPositionG++; 
    targetPositionR++; 
} 

Второй шаг: пишет 4 байта, в то время, а не 1 байт. Однако, это требует дополнительного буфера и копии:

int[] dwPlanar = new int[imageSize*3/4]; 
int targetPositionR = 0; 
int targetPositionG = imageSize/4; 
int targetPositionB = imageSize * 2/4; 
for (int i = 0; i < source.Length; i += 12) 
{ 
    int dwB = (source[i ]) | (source[i+3] << 8) | (source[i+6] << 16) | (source[i+9] << 24); 
    int dwG = (source[i+1]) | (source[i+4] << 8) | (source[i+7] << 16) | (source[i+10] << 24); 
    int dwR = (source[i+2]) | (source[i+5] << 8) | (source[i+8] << 16) | (source[i+11] << 24); 
    dwPlanar[targetPositionB] = dwB; // B 
    dwPlanar[targetPositionG] = dwG; // G 
    dwPlanar[targetPositionR] = dwR; // R 
    targetPositionB++; 
    targetPositionG++; 
    targetPositionR++; 
} 
Buffer.BlockCopy(dwPlanar,0,result,0,imageSize * 3); 

Я полагаю, что это поможет, потому что C# будет делать меньше границ массива проверки, и это в общем, лучше писать в больших кусках, когда это возможно.

(Отказ от ответственности: я не знаком с C#, и я не знаю, будет ли этот код даже компилироваться, это всего лишь алгоритм).

Смежные вопросы