Как получить массив пикселей из TBitmap? - PullRequest
1 голос
/ 16 января 2020

В приложении камеры растровые массивы пикселей извлекаются из потоковой камеры. Пиксельные массивы захватываются путем записи их в именованный канал, где на другом конце канала ffmpeg извлекает их и создает файл AVI.

Мне нужно будет создать один пользовательский кадр (с включенным пользовательским текстом) и передать пиксели в качестве первого кадра в результирующем mov ie.

. Вопрос в том, как я могу использовать TBitmap (для удобства) для

  1. Создание растрового изображения X по Y mono chrome (8 бит) с нуля, с включенным пользовательским текстом. Я хочу, чтобы фон был белым, а текст - черным. (В основном это был шаг, см. Ниже.)

  2. Получить массив пикселей, который я могу отправить / записать в канал

Шаг 1 : Следующий код создает TBitmap и записывает на него текст:

int w = 658;
int h = 492;
TBitmap* bm = new TBitmap();
bm->Width = w;
bm->Height = h;
bm->HandleType = bmDIB;
bm->PixelFormat = pf8bit;

bm->Canvas->Font->Name = "Tahoma";
bm->Canvas->Font->Size = 8;

int textY = 10;
string info("some Text");

bm->Canvas->TextOut(10, textY, info.c_str());

Вышеприведенный код в основном завершает шаг 1.

Код записи / передачи ожидает байтовый массив с пикселями растровых изображений; например,

unsigned long numWritten;
WriteFile(mPipeHandle, pImage, size, &numWritten, NULL);

, где pImage - указатель на неподписанный буфер символов (пиксели растровых изображений), а размер - длина этого буфера.

Обновление: использование созданного TBitmap и TMemoryStream для передачи данных в конвейер ffmpeg не генерирует правильный результат. Я получаю искаженное изображение с 3 диагональными линиями.

Размер буфера для кадровых буферов камеры, которые я получаю, в точности равен 323736 , что равно числу пикселей в изображении, т.е. 658x492. ПРИМЕЧАНИЕ Я пришел к выводу, что это «растровое изображение» не дополняется. 658 не делится на четыре.

Размер буфера, который я получаю после выгрузки моего сгенерированного растрового изображения в поток памяти, однако, имеет размер 325798 , что на 2062 байт больше, чем должно быть , Как @Spektre указал ниже, это расхождение может быть заполнением?

Использование следующего кода для получения массива пикселей:

ByteBuffer CustomBitmap::getPixArray()
{
    // --- Local variables --- //
    unsigned int iInfoHeaderSize=0;
    unsigned int iImageSize=0;
    BITMAPINFO *pBitmapInfoHeader;

    unsigned char *pBitmapImageBits;

    // First we call GetDIBSizes() to determine the amount of
    // memory that must be allocated before calling GetDIB()
    // NB: GetDIBSizes() is a part of the VCL.
    GetDIBSizes(mTheBitmap->Handle,
                iInfoHeaderSize,
                iImageSize);

    // Next we allocate memory according to the information
    // returned by GetDIBSizes()
    pBitmapInfoHeader = new BITMAPINFO[iInfoHeaderSize];
    pBitmapImageBits = new unsigned char[iImageSize];

    // Call GetDIB() to convert a device dependent bitmap into a
    // Device Independent Bitmap (a DIB).
    // NB: GetDIB() is a part of the VCL.
    GetDIB(mTheBitmap->Handle,
            mTheBitmap->Palette,
            pBitmapInfoHeader,
            pBitmapImageBits);

    delete []pBitmapInfoHeader;

    ByteBuffer buf;
    buf.buffer = pBitmapImageBits;
    buf.size = iImageSize;
    return buf;
}

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

Ответы [ 2 ]

3 голосов
/ 16 января 2020

TBitmap имеет свойство PixelFormat для установки глубины в битах.

TBitmap имеет свойство HandleType для управления наличием DDB или DIB создан. DIB используется по умолчанию.

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

Кроме того, эта строка кода:

Image1->Picture->Bitmap->Handle = bm->Handle;

Вместо этого следует изменить это:

Image1->Picture->Bitmap->Assign(bm);
// or:
// Image1->Picture->Bitmap = bm;

Или это:

Image1->Picture->Assign(bm);

В любом случае, не забудьте delete bm; впоследствии, так как TPicture делает копию ввода TBitmap, он не становится владельцем.

Чтобы получить данные BMP как буфер байтов, вы можно использовать метод TBitmap::SaveToStream(), сохраняя в TMemoryStream. Или, если вам нужны только данные пикселей, а не полные данные BMP (ie, без заголовков BMP - см. Память растровых изображений ), вы можете использовать функцию Win32 GetDiBits() , который выводит пиксели в формате DIB. Вы не можете получить байтовый буфер пикселей для DDB, так как они зависят от устройства, к которому они относятся. DDB можно использовать только в памяти вместе с HDC s, их нельзя передавать. Но вы можете преобразовать DIB в DDB, как только у вас будет готовое устройство для его рендеринга.

Другими словами, получите пиксели с камеры, сохраните их в DIB, передайте их по мере необходимости (ie, через канал), а затем выполните с ним все, что вам нужно - сохраните в файл, преобразуйте в DDB для рендеринга на экране и т. Д. c.

1 голос
/ 17 января 2020

Это просто дополнение к существующему ответу (с дополнительной информацией после редактирования OP).

Формат растрового файла имеет байты выравнивания в каждой строке (поэтому обычно в конце каждой строки есть несколько байтов, которые не пиксели) до некоторого ByteLength (присутствует в заголовке bmp). Они создают косые и диагональные линии. В вашем случае несоответствие размера составляет 4 байта на строку:

(xs + align)*ys  + header = size
(658+     4)*492 + 94     = 325798

, но будьте осторожны, размер выравнивания зависит от ширины изображения и заголовка bmp ...

Попробуйте вместо этого:

    // create bmp
    Graphics::TBitmap *bmp=new Graphics::TBitmap;
//  bmp->Assign(???);       // a) copy image from ???
    bmp->SetSize(658,492);  // b) in case you use Assign do not change resolution
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf8bit;
//  bmp->Canvas->Draw(0,0,???); // b) copy image from ???
    // here render your text using
    bmp->Canvas->Brush->Style=bsSolid;
    bmp->Canvas->Brush->Color=clWhite;
    bmp->Canvas->Font->Color=clBlack;
    bmp->Canvas->Font->Name = "Tahoma";
    bmp->Canvas->Font->Size = 8;
    bmp->Canvas->TextOutA(5,5,"Text");
    // Byte data
    for (int y=0;y<bmp->Height;y++)
     {
     BYTE *p=(BYTE*)bmp->ScanLine[y]; // pf8bit -> BYTE*
     // here send/write/store ... bmp->Width bytes from p[]
     }
//  Canvas->Draw(0,0,bmp);  // just renfder it on Form
    delete bmp; bmp=NULL;

смешивание вызовов WinDi GDI для доступа к массиву пикселей (bitblt et c ...) с битовой картой VCL bmDIB может вызвать проблемы и утечки ресурсов (отсюда и ошибку при выходе), а также медленнее, чем использование ScanLine[] (если кодирован правильно), поэтому я настоятельно рекомендую использовать собственные функции VCL (как я это делал в приведенном выше примере) вместо вызовов GDI / winapi, где это возможно.

для получения дополнительной информации см .:

Также вы упоминаете, что ваш источник изображения - камера. Если вы используете pf8bit, это означает его индексированный цвет палитры, который является относительно медленным и некрасивым, если используется родной GDI al go (для преобразования из истинного / высокого качества изображения с цветной камеры) для лучшего преобразования, см .:

...