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.
Školení
Kontakt
739 219 991
live:radekchalupa_1
Nové články