У попередній статті ми розробили бібліотеку LiteCam для доступу до камери на Linux. Щоб розширити її функціональність для Windows, ми скористаємося API Media Foundation.
Ця стаття розглядає, як використовувати Media Foundation для доступу до камери на Windows, інтегрувати її з бібліотекою LiteCam і повторно використовувати існуючий код для сканування штрих-кодів для створення сканера штрих-кодів на базі камери для Windows.
Демонстраційне відео з камери для Windows
Реалізація функцій, пов'язаних з камерою, для Windows
Оновлення заголовочного файлу для підтримки як Windows, так і Linux
Щоб підтримати як Windows, так і Linux, заголовочний файл Camera.h
потребує наступних оновлень:
- Додати специфічні для платформи заголовки:
#ifdef _WIN32
#include
#include
#include
#include
#include
#include
#include
#elif __linux__
#include
#include
#include
#include
#include
struct Buffer
{
void *start;
size_t length;
};
#endif
- Визначте макрос `CAMERA_API` для специфічного експортного доступу на різних платформах:
ifdef _WIN32
#ifdef CAMERAEXPORTS
#define CAMERAAPI declspec(dllexport)
#else
#define CAMERAAPI _declspec(dllimport)
#endif
#elif defined(linux) || defined(APPLE)
#define CAMERAAPI _attribute((visibility("default")))
#else
#define CAMERA_API
#endif
```
- Змініть структури
MediaTypeInfo
таCaptureDeviceInfo
для використання відповідних типів рядків:
struct CAMERA_API MediaTypeInfo
{
uint32_t width;
uint32_t height;
#ifdef _WIN32
wchar_t subtypeName[512];
#else
char subtypeName[512];
#endif
};
struct CAMERA_API CaptureDeviceInfo
{
#ifdef _WIN32
wchar_t friendlyName[512];
#else
char friendlyName[512];
#endif
};
- Коригуйте логіку конвертації пікселів у функції
ConvertYUY2ToRGB
:
void ConvertYUY2ToRGB(const unsigned char *yuy2Data, unsigned char *rgbData, int width, int height)
{
int rgbIndex = 0;
for (int i = 0; i < width * height * 2; i += 4)
{
unsigned char y1 = yuy2Data[i];
unsigned char u = yuy2Data[i + 1];
unsigned char y2 = yuy2Data[i + 2];
unsigned char v = yuy2Data[i + 3];
#ifdef _WIN32
rgbData[rgbIndex++] = clamp(y1 + 1.772 * (u - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y1 - 0.344136 * (u - 128) - 0.714136 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y1 + 1.402 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y2 + 1.772 * (u - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y2 - 0.344136 * (u - 128) - 0.714136 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y2 + 1.402 * (v - 128), 0.0, 255.0);
#else
rgbData[rgbIndex++] = clamp(y1 + 1.402 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y1 - 0.344136 * (u - 128) - 0.714136 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y1 + 1.772 * (u - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y2 + 1.402 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y2 - 0.344136 * (u - 128) - 0.714136 * (v - 128), 0.0, 255.0);
rgbData[rgbIndex++] = clamp(y2 + 1.772 * (u - 128), 0.0, 255.0);
#endif
}
}
Порядок пікселів для червоного та синього каналів змінюється між Windows та Linux.
Визначення класу Camera
з платформонезалежними членами та методами
class CAMERA_API Camera
{
public:
#ifdef _WIN32
Camera();
~Camera();
#elif __linux__
Camera() : fd(-1), frameWidth(640), frameHeight(480), buffers(nullptr), bufferCount(0) {}
~Camera() { Release(); }
#endif
private:
#ifdef _WIN32
void *reader;
bool initialized;
void InitializeMediaFoundation();
void ShutdownMediaFoundation();
#endif
#ifdef __linux__
int fd;
Buffer *buffers;
unsigned int bufferCount;
bool InitDevice();
void UninitDevice();
bool StartCapture();
void StopCapture();
#endif
};
## Запит камер
Використовуйте API `Media Foundation` для перерахунку доступних камер:
std::vector ListCaptureDevices()
{
HRESULT hr = S_OK;
ComPtr attributes;
std::vector devicesInfo;
hr = MFCreateAttributes(&attributes, 1);
if (FAILED(hr))
{
std::cerr << "Не вдалося створити атрибути." << std::endl;
return devicesInfo;
}
hr = attributes->SetGUID(MFDEVSOURCEATTRIBUTESOURCETYPE, MFDEVSOURCEATTRIBUTESOURCETYPEVIDCAPGUID);
if (FAILED(hr))
{
std::cerr << "Не вдалося встановити атрибут пристрою відеозахоплення." << std::endl;
return devicesInfo;
}
UINT32 count = 0;
IMFActivate **devices = nullptr;
hr = MFEnumDeviceSources(attributes.Get(), &devices, &count);
if (FAILED(hr) || count == 0)
{
std::cerr << "Не знайдено пристроїв відеозахоплення." << std::endl;
return devicesInfo;
}
for (UINT32 i = 0; i < count; ++i)
{
WCHAR *friendlyName = nullptr;
UINT32 nameLength = 0;
hr = devices[i]->GetAllocatedString(MFDEVSOURCEATTRIBUTEFRIENDLYNAME, &friendlyName, &nameLength);
if (SUCCEEDED(hr))
{
CaptureDeviceInfo info = {};
wcsncpy(info.friendlyName, friendlyName, nameLength);
devicesInfo.push_back(info);
CoTaskMemFree(friendlyName);
}
devices[i]->Release();
}
CoTaskMemFree(devices);
return devicesInfo;
}
```
Пояснення
- Створіть об'єкт
IMFAttributes
, щоб вказати пристрій відеозахоплення. - Перерахуйте пристрої відеозахоплення за допомогою
MFEnumDeviceSources
. - Отримайте зручну назву кожного пристрою за допомогою
GetAllocatedString
.
Відкриття камери
1.
Активуйте вказану камеру за індексом:
ComPtr mediaSource;
hr = devices[cameraIndex]->ActivateObject(IID_PPV_ARGS(&mediaSource));
for (UINT32 i = 0; i < count; i++)
devices[i]->Release();
CoTaskMemFree(devices);
if (FAILED(hr))
return false;
ComPtr mfReader;
hr = MFCreateSourceReaderFromMediaSource(mediaSource.Get(), nullptr, &mfReader);
if (FAILED(hr))
return false;
Об'єкт IMFSourceReader
використовується для зчитування відеоданих з камери.
- Налаштуйте ширину відео, висоту та формат пікселів.
Наприклад, форматYUY2
з розміром кадру640x480
:
ComPtr mediaType;
hr = MFCreateMediaType(&mediaType);
if (FAILED(hr))
return false;
hr = mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
hr = mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2);
hr = MFSetAttributeSize(mediaType.Get(), MF_MT_FRAME_SIZE, frameWidth, frameHeight);
if (SUCCEEDED(hr))
{
hr = mfReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, mediaType.Get());
if (SUCCEEDED(hr))
{
reader = reinterpret_cast(mfReader.Detach());
return true;
}
}
Після налаштування типу медіа, об'єкт IMFSourceReader
зберігається у змінній-члені reader
.
Зйомка кадру
1.
Зчитування зразка з камери:
HRESULT hr;
DWORD streamIndex, flags;
LONGLONG timestamp;
ComPtr sample;
FrameData frame;
frame.width = frameWidth;
frame.height = frameHeight;
frame.rgbData = nullptr;
IMFSourceReader *mfReader = reinterpret_cast(reader);
hr = mfReader->ReadSample(
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
&streamIndex,
&flags,
×tamp,
&sample);
if (FAILED(hr))
{
std::cerr << "Не вдалося зчитати зразок." << std::endl;
return frame;
}
2.
Отримання сирих даних з зразка та їх конвертація в формат RGB888:
if (sample)
{
ComPtr buffer;
hr = sample->ConvertToContiguousBuffer(&buffer);
if (FAILED(hr))
{
std::cerr << "Не вдалося конвертувати зразок в безперервний буфер." << std::endl;
return frame;
}
BYTE *rawData = nullptr;
DWORD maxLength = 0, currentLength = 0;
hr = buffer->Lock(&rawData, &maxLength, ¤tLength);
if (SUCCEEDED(hr))
{
frame.size = frameWidth * frameHeight * 3;
frame.rgbData = new unsigned char[frame.size];
if (!frame.rgbData)
{
std::cerr << "Не вдалося виділити пам'ять для RGB даних." << std::endl;
return frame;
}
ConvertYUY2ToRGB(rawData, frame.rgbData, frameWidth, frameHeight);
buffer->Unlock();
}
}
Закриття камери
Звільнення об'єкта IMFSourceReader
та ресурсів Media Foundation:
if (reader)
{
ComPtr mfReader(static_cast<IMFSourceReader*>(reader));
reader = nullptr;
}
if (initialized)
{
MFShutdown();
initialized = false;
}
Реалізація функцій для відображення в Windows
Оновлення заголовочного файлу для підтримки Windows та Linux
Для підтримки кросплатформної сумісності було оновлено заголовочний файл CameraPreview.h
наступним чином:
- Визначення макроса
CAMERA_API
для Windows та Linux.
#ifdef _WIN32
#include <windows.h>
#elif __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#elif __APPLE__
#include <Cocoa/Cocoa.h>
#endif
#ifdef _WIN32
#ifdef CAMERA_EXPORTS
#define CAMERA_API __declspec(dllexport)
#else
#define CAMERA_API __declspec(dllimport)
#endif
#elif defined(__linux__) || defined(__APPLE__)
#define CAMERA_API __attribute__((visibility("default")))
#else
#define CAMERA_API
#endif
- Додавання платформозалежних компонентів для вікна та рендерингу:
class CAMERA_API CameraWindow
{
private:
#ifdef _WIN32
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
HWND hwnd;
HDC hdc;
WNDCLASS wc;
HINSTANCE hInstance;
#elif __linux__
Display *display;
Window window;
GC gc;
Atom wmDeleteMessage;
#endif
};
Конструктор та Деструктор
Конструктор ініціалізує клас вікна та зворотний виклик подій.
Деструктор очищає ресурси:
CameraWindow::CameraWindow(int w, int h, const std::string &t)
: width(w), height(h), title(t), hwnd(nullptr), hdc(nullptr)
{
hInstance = GetModuleHandle(nullptr);
wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "CameraWindowClass";
}
CameraWindow::~CameraWindow()
{
if (hdc)
{
ReleaseDC(hwnd, hdc);
}
if (hwnd)
{
DestroyWindow(hwnd);
}
UnregisterClass("CameraWindowClass", hInstance);
}
LRESULT CALLBACK CameraWindow::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Створення вікна
Викликаємо CreateWindowEx
, щоб створити вікно, та GetDC
, щоб отримати контекст пристрою:
bool CameraWindow::Create()
{
if (!RegisterClass(&wc))
{
std::cerr << "Не вдалося зареєструвати клас вікна." << std::endl;
return false;
}
hwnd = CreateWindowEx(0,
wc.lpszClassName,
title.c_str(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, width, height,
nullptr, nullptr, hInstance, nullptr);
if (!hwnd)
{
std::cerr << "Не вдалося створити вікно." << std::endl;
return false;
}
hdc = GetDC(hwnd);
if (!hdc)
{
std::cerr << "Не вдалося отримати контекст пристрою." << std::endl;
return false;
}
return true;
}
std::cerr << "Не вдалося зареєструвати клас вікна." << std::endl;
return false;
}
hwnd = CreateWindowEx(
0, "CameraWindowClass", title.c_str(), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, width, height,
nullptr, nullptr, hInstance, nullptr);
if (!hwnd)
{
std::cerr << "Не вдалося створити вікно." << std::endl;
return false;
}
hdc = GetDC(hwnd);
return true;
}
Показ вікна
Викликаємо ShowWindow
, щоб відобразити вікно:
void CameraWindow::Show()
{
ShowWindow(hwnd, SW_SHOW);
}
Обробка події з клавіатури
Перехоплюємо ввід з клавіатури для виходу з програми:
bool CameraWindow::WaitKey(char key)
{
MSG msg = {};
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT)
{
return false;
}
if (msg.message == WM_KEYDOWN)
{
char keyPressed = static_cast<char>(msg.wParam);
if (keyPressed == key)
{
return true;
}
}
}
return false;
}
if (key != '\0' && (keyPressed == key || keyPressed == std::toupper(key)))
{
return false;
}
}
}
return true;
}
Відображення кадру з камери
Використовуємо функцію StretchDIBits
, щоб відобразити кадр з камери.
void CameraWindow::ShowFrame(const unsigned char *rgbData, int frameWidth, int frameHeight)
{
if (!hdc || !rgbData)
return;
BITMAPINFO bmpInfo = {};
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = frameWidth;
bmpInfo.bmiHeader.biHeight = -frameHeight;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;
StretchDIBits(
hdc,
0, 0, frameWidth, frameHeight,
0, 0, frameWidth, frameHeight,
rgbData,
&bmpInfo,
DIB_RGB_COLORS,
SRCCOPY
);
}
Малювання тексту на вікні
Малюємо текст на вікні за допомогою функції TextOut
.
void CameraWindow::DrawText(const std::string &text, int x, int y, int fontSize, const Color &color)
{
if (!hdc)
return;
SetTextColor(hdc, RGB(color.r, color.g, color.b));
SetBkMode(hdc, TRANSPARENT);
HFONT hFont = CreateFont(
fontSize,
0,
0,
0,
FWNORMAL,
FALSE,
FALSE,
FALSE,
DEFAULTCHARSET,
OUTDEFAULTPRECIS,
CLIPDEFAULTPRECIS,
DEFAULTQUALITY,
DEFAULTPITCH | FF_DONTCARE,
"Arial");
if (!hFont)
return;
HGDIOBJ oldFont = SelectObject(hdc, hFont);
TextOut(hdc, x, y, text.cstr(), staticcast(text.length()));
SelectObject(hdc, oldFont);
DeleteObject(hFont);
}
```
Малювання контурів на вікні
Малюємо контури на вікні за допомогою функцій MoveToEx
та LineTo
.
void CameraWindow::DrawContour(const std::vector<Point> &points)
{
if (!hdc || points.size() < 4)
return;
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(0, 255, 0));
HGDIOBJ oldPen = SelectObject(hdc, hPen);
MoveToEx(hdc, points[0].first, points[0].second, nullptr);
for (size_t i = 1; i < points.size(); ++i)
{
LineTo(hdc, points[i].first, points[i].second);
}
LineTo(hdc, points[0].first, points[0].second);
SelectObject(hdc, oldPen);
DeleteObject(hPen);
}
Створення програми для сканера штрих-кодів на Windows
Для створення програми сканера штрих-кодів не потрібно змінювати логіку сканування штрих-кодів.
Дотримуйтесь цих кроків:
- Підготуйте бібліотеку для роботи з камерами та Dynamsoft C++ Barcode SDK для Windows.
2.
Оновіть файл CMakeLists.txt
, щоб включити конфігурацію, специфічну для Windows.
cmake_minimum_required(VERSION 3.10)
project(BarcodeScanner)
if(WIN32)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../dist/lib/windows/release ${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/10.x/sdk/platforms/win/lib)
else()
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../dist/lib/windows/debug ${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/10.x/sdk/platforms/win/lib)
endif()
set(DBR_LIBS "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64")
elseif(UNIX)
SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
SET(CMAKE_INSTALL_RPATH "$ORIGIN")
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../dist/lib/linux ${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/10.x/sdk/platforms/linux)
set(DBR_LIBS "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility" pthread)
endif()
# Створити виконуваний файл
add_executable(BarcodeScanner main.cpp)
target_include_directories(BarcodeScanner PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../dist/include ${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/10.x/sdk/include)
target_link_libraries(BarcodeScanner litecam ${DBR_LIBS})
if(WIN32)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_custom_command(TARGET BarcodeScanner POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/../../dist/lib/windows/release
$)
else()
add_custom_command(TARGET BarcodeScanner POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/../../dist/lib/windows/debug
$)
endif()
add_custom_command(TARGET BarcodeScanner POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/10.x/sdk/platforms/win/bin/
$)
elseif(UNIX)
add_custom_command(TARGET BarcodeScanner POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/../../../examples/10.x/sdk/platforms/linux/
$)
endif()
3.
Збудуйте додаток за допомогою CMake.
mkdir build
cd build
cmake ..
cmake --build .
Джерело коду
https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/litecam
Перекладено з: How to Implement Camera Preview with Windows Media Foundation API in C++