14.6.2018
V minulém příspěvku jsem ukázal jak obrázek načtený pomocí knihovny OpenCV zobrazit ve vlastním okně aplikace pro Windows. V tomto příspěvku si ukážeme jak obdobným způsobem zobrazit (živé) video z kamery počítače (integrované nebo připojené přes USB port).
Základní kostra aplikace bude stejná jako v předchozím příkladě zobrazování obrázku. Navíc budeme muset načítat jednotlivé obrázky (frame) z kamery a ve vhodném intervalu aktualizovat okno, tedy překreslit aktuálním obrázkem z kamery.
Ke grabování videa z kamery použijeme třídu knihovny OpenCV cv::VideoCapture. Tato třída umožňuje mimo jiné načítat jednotlivé obrázky do minule zmíněného a použitého objektu cv::Mat, což využijeme pro vykreslování. Dále lze pomočí této třídy zjistit a popřípadě nastavit některé parametry videostreamu, jak si hned ukážeme.
Pro naše potřeby si opět vytvoříme několik globálních proměnných:
HWND _hwnd = NULL; cv::Mat _cv_mat; unsigned int _bi_pocet_bitu = 0; cv::VideoCapture _kamera; unsigned int _sirka_videa = 0; unsigned int _vyska_videa = 0; unsigned int _obr_za_sekundu = 0;
Na začátku programu se pokusíme otevřít výchozí kameru (to určuje ta 0 v parametru metody open, zatímco offset cv::CAP_DSHOW určuje že chceme jako „low-level“ použít rozhraní DirectShow). Dále se pokusíme nastavit snímkovou frekvenci na 25 snímku za sekundu, pokud se to nepodaří zkusíme ještě nižší hodmotu - 15 fps a pokud se ani toto nezdaří, budeme považovat kameru za nepoužitelnou… Pokud se vše podaří, načteme si jeden snímek do objektu cv::Mat, abychom zjistili bitovou hloubku a dále si načteme do globálních proměnných šířku a výšku videa a obnovovací frekvenci. Vše zmíněné je v následujícím fragmentu kódu ze začátku funkce WinMain:
if (!_kamera.open(0 + cv::CAP_DSHOW)) { MessageBox(NULL, L"Nepodařilo se spustit žádnou kameru", L"Sorry jako...", MB_ICONERROR); return 0; } if (!_kamera.read(_cv_mat)) return 0; if (!_kamera.set(CAP_PROP_FPS, 25.0)) if (!_kamera.set(CAP_PROP_FPS, 15.0)) { MessageBox(NULL, L"Tahle kamera je na 2 věci...", L"Sorry jako...", MB_ICONERROR); return 0; } _bi_pocet_bitu = (int)((_cv_mat.dataend - _cv_mat.datastart) / (_cv_mat.cols * _cv_mat.rows) * 8); _sirka_videa = (unsigned int)_kamera.get(CAP_PROP_FRAME_WIDTH); _vyska_videa = (unsigned int)_kamera.get(CAP_PROP_FRAME_HEIGHT); _obr_za_sekundu = (unsigned int)_kamera.get(CAP_PROP_FPS);
Zobrazování „živého“ videa dosáhneme tak že spustíme timer jehož interval nastavíme na prodlevu mezi jednotlivými obrázky, vypočítanou z již zjištěné snímkové frekvence. Na tento timer, tedy při přijetí zprávy WM_TIMER v proceduře okna si načteme aktuální obrázek do objektu cv::Mat a vyvoláme okamžité překreslení okna, a v obsluze WM_PAINT pak zobrazíme aktuálně načtený snímek stejně jako v minulém příkladu zobrazení obrázku ze souboru. Navíc ještě jako ukázku možností zápisu do zobrazeného videa zapíšeme do obrázku aktuální čas.
// obsluha zprávy WM_TIMER void wm_timer() noexcept { if (!_kamera.read(_cv_mat)) { PostMessage(_hwnd, WM_CLOSE, 0, 0); return; } SYSTEMTIME st; GetLocalTime(&st); char sz_cas[50]; sprintf_s(sz_cas, sizeof(sz_cas), "%d:%.2d:%.2d", st.wHour, st.wMinute, st.wSecond); cv::putText(_cv_mat, sz_cas, cv::Point(20, _vyska_videa - 40), CV_FONT_HERSHEY_PLAIN, 2, CV_RGB(255, 255,0), 2); RedrawWindow(_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); }
Výsledkem bude „živé“ video v našem okně ve kterém bude dole „běžet“ aktuální čas jako hodiny:minuty:sekundy.
Celý ukázkový projekt (ve Visual Studiu 2017 Community) si můžete stáhnout zde.
Na závěr opět kompletní výpis kódu programu:
// // hlavička 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='*'\"") // // zdojový kód opencv-kamera-winapi.cpp // #include "pch.h" #include "resource.h" HWND _hwnd = NULL; cv::Mat _cv_mat; unsigned int _bi_pocet_bitu = 0; cv::VideoCapture _kamera; unsigned int _sirka_videa = 0; unsigned int _vyska_videa = 0; unsigned int _obr_za_sekundu = 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 wm_paint() 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, _cv_mat.cols, _cv_mat.rows, 0, 0, _cv_mat.cols, _cv_mat.rows, (RGBTRIPLE*)_cv_mat.data, &bmi, DIB_RGB_COLORS, SRCCOPY); EndPaint(_hwnd, &ps); } void wm_create(HWND hwnd) noexcept { SetTimer(hwnd, 1, 1000 / _obr_za_sekundu, nullptr); } // obsluha zprávy WM_TIMER void wm_timer() noexcept { if (!_kamera.read(_cv_mat)) { PostMessage(_hwnd, WM_CLOSE, 0, 0); return; } SYSTEMTIME st; GetLocalTime(&st); char sz_cas[50]; sprintf_s(sz_cas, sizeof(sz_cas), "%d:%.2d:%.2d", st.wHour, st.wMinute, st.wSecond); cv::putText(_cv_mat, sz_cas, cv::Point(20, _vyska_videa - 40), CV_FONT_HERSHEY_PLAIN, 2, CV_RGB(255, 255,0), 2); RedrawWindow(_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); } LRESULT CALLBACK window_proc(HWND hwnd, UINT zprava, WPARAM wparam, LPARAM lparam) noexcept { switch (zprava) { case WM_COMMAND: switch (LOWORD(wparam)) { case ID_APLIKACE_KONEC: PostMessage(hwnd, WM_CLOSE, 0, 0); break; } break; case WM_CREATE: wm_create(hwnd); break; case WM_CLOSE: KillTimer(hwnd, 1); return DefWindowProc(hwnd, zprava, wparam, lparam); case WM_TIMER: wm_timer(); break; case WM_PAINT: if (!_cv_mat.empty()) wm_paint(); else return DefWindowProc(hwnd, zprava, wparam, lparam); break; case WM_ERASEBKGND: if (!_cv_mat.empty()) return 0; 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) { if (!_kamera.open(0 + cv::CAP_DSHOW)) { MessageBox(NULL, L"Nepodařilo se spustit žádnou kameru", L"Sorry jako...", MB_ICONERROR); return 0; } if (!_kamera.read(_cv_mat)) return 0; if (!_kamera.set(CAP_PROP_FPS, 25.0)) if (!_kamera.set(CAP_PROP_FPS, 15.0)) { MessageBox(NULL, L"Tahle kamera je na 2 věci...", L"Sorry jako...", MB_ICONERROR); return 0; } _bi_pocet_bitu = (int)((_cv_mat.dataend - _cv_mat.datastart) / (_cv_mat.cols * _cv_mat.rows) * 8); _sirka_videa = (unsigned int)_kamera.get(CAP_PROP_FRAME_WIDTH); _vyska_videa = (unsigned int)_kamera.get(CAP_PROP_FRAME_HEIGHT); _obr_za_sekundu = (unsigned int)_kamera.get(CAP_PROP_FPS); 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(); RECT rect = { 0, 0, (LONG)_sirka_videa, (LONG)_vyska_videa }; AdjustWindowRect(&rect, WS_SYSMENU | WS_SIZEBOX, TRUE); SetWindowPos(_hwnd, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER); ShowWindow(_hwnd, SW_SHOW); UpdateWindow(_hwnd); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
Příloha: opencv-kamera-winapi.zip.
Školení
Kontakt
739 219 991
live:radekchalupa_1
Nové články