OpenCV ve WinAPI - obrázek ve vlastním okně
10.6.2018
V minulém příspěvku věnovaném přípravě vývojového prostředí a úvodu do OpenCV jsme si ukázali tu nejjednodušší aplikaci která zobrazila obrázek zadaný jako cesta k souboru ve vlastním okně. Šlo o standardní konzolovou aplikaci. K častým požadavkům (a dotazům na internetu) patří otázka jak zobrazovat obrázek (samozřejmě myšleno obrázek načtený do OpenCV třídy Mat) zobrazit ve vlastním okně aplikace s uživatelským rozhraním (GUI). Protože existují různé GUI frameworky, existuje také více různým možností řešení. V tomto článku si ukážeme možnost pro windows aplikaci bez použití nějaké GUI nadstavby, tj. napsanou ve Windows API.
Vstupní funkcí bude tedy WinMain a aplikace bude mít jedno okno, do kterého budeme vykreslovat načtený obrázek v obsluze zprávy WM_PAINT. Pro plné pochopení dále uvedeného kódu je nutné znát alespoň základní principy programování ve Win API.
Nejprve se podíváme na funkci která načte zadaný soubor do objektu cv::Mat.
void otevrit_soubor(const char* sz_cesta) noexcept
{
if (!_cv_mat.empty())
_cv_mat.release();
_cv_mat = cv::imread(sz_cesta, -1);
if (_cv_mat.empty())
return;
_bi_pocet_bitu = (int)((_cv_mat.dataend - _cv_mat.datastart) / (_cv_mat.cols * _cv_mat.rows) * 8);
RedrawWindow(_hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_ERASENOW);
}Při vykreslování budeme potřebovat znát bitovou hloubku obrázku. Abychom ji nemuseli vypočítávat při každém překreslení, uložíme si ji do globální proměnné
unsigned int _bi_pocet_bitu;
Tím se dostáváme k další klíčové funkci, kterou je obsluha zprávy WM_PAINT, tedy překreslení okna vykreslením obrázku do jeho klientské oblasti. V tomto ukázkovém případě budeme obrázek roztahovat do celé klientské oblasti bez ohledu na zachování poměru stran. Vykreslení se zachováním poměru je o několika operacích násobení a dělení a to není tématem tohoto článku. Celá obsluha zprávy WM_PAINT vypadá takto:
void wm_paint(HWND hwnd) noexcept
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect;
GetClientRect(hwnd, &rect);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biWidth = _cv_mat.cols;
bmi.bmiHeader.biHeight = _cv_mat.rows * -1;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = _bi_pocet_bitu;
StretchDIBits(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
0, 0, bmi.bmiHeader.biWidth, abs(bmi.bmiHeader.biHeight),
(RGBTRIPLE*)_cv_mat.data,
&bmi, DIB_RGB_COLORS, SRCCOPY);
EndPaint(hwnd, &ps);
}Jak je vidět z kódu, využijeme členy třídy cv::Mat ve kterých jsou uloženy hodnoty potřebné pro parametry použité funkce StretchDIBits, a předem vypočítaná bitová hloubka, která bude buď 24 (standardní RGB) nebo 32 (to v případě že obrázek obsahuje alfa-kanál).
V ukázkovém projektu je pro ilustraci ukázka použití OpenCV pro aplikaci jednoho z mnoha dostupných filtrů, zde konkrétně rozostření obrázku, tj funkce cv::blur aplikované na objekt cv::Mat, jak vidíte na kódu funkce volané při výběru položky menu.
void obr_blur() noexcept
{
if (_cv_mat.empty())
return;
cv::blur(_cv_mat, _cv_mat, cv::Size(15, 15));
RedrawWindow(_hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_ERASENOW);
}Celý ukázkový projekt (ve Visual Studiu 2017 Community) si můžete stáhnout zde.
Na závěr kompletní výpis zdrojového kódu:
//
// hlavičkový soubor pch.h
//
#pragma once
#include
#include
#include
using namespace cv;
#ifdef _DEBUG
#pragma comment (lib, "opencv_world341d.lib")
#else
#pragma comment (lib, "opencv_world341.lib")
#endif // DEBUG
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
//
// zdrojový kód opencv-winapi.cpp
//
#include "resource.h"
HWND _hwnd = NULL;
cv::Mat _cv_mat;
RGBTRIPLE* _rgb_triple = nullptr;
unsigned int _bi_pocet_bitu = 0;
inline const wchar_t* trida_hlavni() noexcept
{
return L"opencv_winapi";
}
inline void __declspec(noreturn) kriticke_ukonceni() noexcept
{
_CrtDbgBreak();
FatalAppExit(0, L"Došlo k závažné chybě! Aplikace bude ukončena...");
}
void otevrit_soubor(const char* sz_cesta) noexcept
{
if (!_cv_mat.empty())
_cv_mat.release();
_cv_mat = cv::imread(sz_cesta, -1);
if (_cv_mat.empty())
return;
_bi_pocet_bitu = (int)((_cv_mat.dataend - _cv_mat.datastart) / (_cv_mat.cols * _cv_mat.rows) * 8);
RedrawWindow(_hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_ERASENOW);
}
void vybrat_soubor() noexcept
{
char* soubor = (char*)malloc(32768);
soubor[0] = 0;
OPENFILENAMEA ofn;
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = _hwnd;
ofn.lpstrFile = soubor;
ofn.nMaxFile = 32768;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (!GetOpenFileNameA(&ofn))
{
free(soubor);
return;
}
otevrit_soubor(soubor);
free(soubor);
}
void obr_blur() noexcept
{
if (_cv_mat.empty())
return;
cv::blur(_cv_mat, _cv_mat, cv::Size(15, 15));
RedrawWindow(_hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_ERASENOW);
}
void wm_paint(HWND hwnd) noexcept
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect;
GetClientRect(hwnd, &rect);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biWidth = _cv_mat.cols;
bmi.bmiHeader.biHeight = _cv_mat.rows * -1;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = _bi_pocet_bitu;
StretchDIBits(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
0, 0, bmi.bmiHeader.biWidth, abs(bmi.bmiHeader.biHeight),
(RGBTRIPLE*)_cv_mat.data,
&bmi, DIB_RGB_COLORS, SRCCOPY);
EndPaint(hwnd, &ps);
}
LRESULT CALLBACK window_proc(HWND hwnd, UINT zprava, WPARAM wparam, LPARAM lparam) noexcept
{
switch (zprava)
{
case WM_COMMAND:
switch (LOWORD(wparam))
{
case ID_OBR_BLUR:
obr_blur();
break;
case ID_SOUBOR_OTEVRIT:
vybrat_soubor();
break;
case ID_KONEC:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
}
break;
case WM_PAINT:
if (!_cv_mat.empty())
wm_paint(hwnd);
else
return DefWindowProc(hwnd, zprava, wparam, lparam);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, zprava, wparam, lparam);
}
return 0;
}
int APIENTRY wWinMain(_In_ HINSTANCE hinstance, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int)
{
WNDCLASSEXW wcex;
memset(&wcex, 0, sizeof(wcex));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = window_proc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hinstance;
wcex.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(IDI_HLAVNI));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszClassName = trida_hlavni();
wcex.lpszMenuName = MAKEINTRESOURCEW(IDR_HLAVNI);
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_HLAVNI));
if (!RegisterClassExW(&wcex))
kriticke_ukonceni();
_hwnd = CreateWindowEx(0, trida_hlavni(), L"OpenCV ve WinAPI", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hinstance, nullptr);
if (!_hwnd)
kriticke_ukonceni();
ShowWindow(_hwnd, SW_SHOW);
UpdateWindow(_hwnd);
vybrat_soubor();
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}Příloha: opencv-winapi.zip.