вторник, 28 января 2014 г.

Конвертирование изображений OpenCV в CBitmap (MFC-winapi).

Так и не смог найти ресурс, где бы это обсуждалось более сниппета кода с использованем каких-то не документированных функций winapi и OpenCV. Что странно, тема-то популярная. В основном помогла разобраться википедия . Русская версия достаточно скудна, потому долго не мог понять, как правильно выравнивать буфер данных в памяти.
Имеем: указатель на структуру IplImage из OpenCV и указатель на CBitmap (тонкая обертка над BITMAP в MFC).



В последнем нас интересует исключительно метод CBitmap::CreateBitmap(), который инициализирует класс.

BOOL CreateBitmap( 
int nWidth,
int nHeight,
UINT nPlanes,
UINT nBitcount,
const void* lpBits
);

Как видно из названия аргументов, в функцию необходимо передать размеры будущего битмапа в пикселах, количество каналов цвета, глубину цвета (количество бит, выражающих каждый канал - это 1, 2, 8, 16, 24, 32 бита) и, наконец, указатель на буфер пикселов. В изображении OpenCV у нас три канала - RGB (которые содержатся в порядке B-G-R) в виде переменных с глубиной в 8 бит. Нам подходит конфигурация "3 канала, 8 бит", т.е. пиксел "весит" 24 бита. Однако, из-за *долбанного* выравнивания по 4 байта, необходимо после двух записанных пикселов (в сумме составляющих 3 байта) добавить пустой байт. *Согласитесь, в цикле так обходить пикселы было бы не очень удобно* Как вариант, зачастую, программисты прибегают к использованию этого четвертого байта, как абсолютно непрозрачного альфа-канала. То есть, все-таки удобнее записывать пиксел в 4 байта.

typedef unsigned char uchar;

struct Pixel
{
uchar Blue;
uchar Green;
uchar Red;
uchar Alpha;
};
Как установить OpenCV, как получить изображение из файла, камеры и т.п. подробно описано на официальном сайте проекта. Много полезного, в т.ч. обращение со структурой IplImage, доступ к пикселам описано на русской вики . Однако, все это для C-интерфейса. Пригодится референс к функции cvLoadImage().

Еще раз подчеркну, что обычный RGBA в OpenCV хранится как B-G-R (в альфа-канал библиотека еще не умеет, к сожалению), а CBitmap (BITMAP - нативная виндовая структура) - также, но с альфой в конце - R-G-B-A.

Чудо-код функции:

void ConvertCVImg2Bmp(IplImage* img, CBitmap *bmp)
{
const int CHANNELS_COUNT = 4;
const int BIT_PER_CHANNEL = 8;
const uchar ALPHA_VALUE = 255;

Pixel *data = new Pixel[ img->width * img->height ];

for (int row=0, dataPos=0; row < img->height; row++)
{
uchar *curRow = reinterpret_cast< uchar* >(
img->imageData + row * img->widthStep
);
for (int col=0; col < img->width; col++, dataPos++)
{
data[dataPos].Blue = curRow[ img->CHANNELS_COUNT * col + 0 ];
data[dataPos].Green = curRow[ img->CHANNELS_COUNT * col + 1 ];
data[dataPos].Red = curRow[ img->CHANNELS_COUNT * col + 2 ];
data[dataPos].Alpha = ALPHA_VALUE;
}
}
bmp->CreateBitmap(img->width, img->height, CHANNELS_COUNT, BIT_PER_CHANNEL , data);
}

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

void CProjectNameDlg::OnPaint()
{
// ........

CDC *dc = GetDC();

// Открываем некую картинку
IplImage* img = ::cvLoadImage("C:\\picture.jpg", CV_LOAD_IMAGE_COLOR);
CBitmap bmp;
::ConvertCVImg2Bmp(&img, &bmp);

// Создаем контекст, ставим его битмепом - наш
CDC memDC;
memDC.CreateCompatibleDC(dc);
memDC.SelectObject(&bmp);

// Побитно копируем контекст из памяти в текущий контекст
dc->BitBlt(0, 0, img->width, img->height, &memDC, 0, 0, SRCCOPY);

// Освобождаем память
memDC.DeleteDC();
cvReleaseImage(&img);
}

Если мы собираемся записывать/читать файлы средствами Windows, понадобится также заполнить структуры заголовка битмапа (BITMAPINFO) и заголовка bmp-файла (BITMAPFILEHEADER)

ЗЫ



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

Комментариев нет:

Отправить комментарий