نحوه پیاده سازی پیش نمایش دوربین با Windows Media Foundation API در C++

در مقاله قبلی، یک را توسعه دادیم کتابخانه LiteCam برای دسترسی به دوربین در لینوکس برای گسترش عملکرد آن به ویندوز، از Media Foundation API استفاده خواهیم کرد.
این مقاله نحوه استفاده از Media Foundation را برای دسترسی به دوربین در ویندوز، ادغام آن با کتابخانه LiteCam و استفاده مجدد از کد نمونه اسکن بارکد موجود برای ساخت یک اسکنر بارکد مبتنی بر دوربین برای ویندوز را بررسی میکند.
ویدئوی نمایشی دوربین ویندوز
پیاده سازی توابع مرتبط با دوربین برای ویندوز
به روز رسانی فایل هدر برای پشتیبانی از ویندوز و لینوکس
برای پشتیبانی از هر دو ویندوز و لینوکس، 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 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
-
را اصلاح کنید
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 } }
ترتیب پیکسل برای کانال های قرمز و آبی بین ویندوز و لینوکس مبادله می شود.
-
را تعریف کنید
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 };
پرس و جو دوربین ها
استفاده کنید Media Foundation
API برای شمارش دوربین های موجود:
std::vector<CaptureDeviceInfo> ListCaptureDevices()
{
HRESULT hr = S_OK;
ComPtr<IMFAttributes> attributes;
std::vector<CaptureDeviceInfo> devicesInfo;
hr = MFCreateAttributes(&attributes, 1);
if (FAILED(hr))
{
std::cerr << "Failed to create attributes." << std::endl;
return devicesInfo;
}
hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr))
{
std::cerr << "Failed to set video capture device attribute." << std::endl;
return devicesInfo;
}
UINT32 count = 0;
IMFActivate **devices = nullptr;
hr = MFEnumDeviceSources(attributes.Get(), &devices, &count);
if (FAILED(hr) || count == 0)
{
std::cerr << "No video capture devices found." << std::endl;
return devicesInfo;
}
for (UINT32 i = 0; i < count; ++i)
{
WCHAR *friendlyName = nullptr;
UINT32 nameLength = 0;
hr = devices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &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
.
باز کردن دوربین
-
یک دوربین مشخص شده را با شاخص فعال کنید:
ComPtr<IMFMediaSource> 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<IMFSourceReader> mfReader; hr = MFCreateSourceReaderFromMediaSource(mediaSource.Get(), nullptr, &mfReader); if (FAILED(hr)) return false;
را
IMFSourceReader
شی برای خواندن داده های ویدئویی از دوربین استفاده می شود. -
عرض، ارتفاع و قالب پیکسلی ویدئو را پیکربندی کنید. به عنوان مثال،
YUY2
فرمت با اندازه قاب640x480
:ComPtr<IMFMediaType> 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<void *>(mfReader.Detach()); return true; } }
پس از تنظیم نوع رسانه،
IMFSourceReader
شی در ذخیره می شودreader
متغیر عضو
گرفتن یک قاب
-
نمونه ای از دوربین را بخوانید:
HRESULT hr; DWORD streamIndex, flags; LONGLONG timestamp; ComPtr<IMFSample> sample; FrameData frame; frame.width = frameWidth; frame.height = frameHeight; frame.rgbData = nullptr; IMFSourceReader *mfReader = reinterpret_cast<IMFSourceReader *>(reader); hr = mfReader->ReadSample( MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &streamIndex, &flags, ×tamp, &sample); if (FAILED(hr)) { std::cerr << "Failed to read sample." << std::endl; return frame; }
-
داده های خام را از نمونه بگیرید و آن را به RGB888 قالب:
if (sample) { ComPtr<IMFMediaBuffer> buffer; hr = sample->ConvertToContiguousBuffer(&buffer); if (FAILED(hr)) { std::cerr << "Failed to convert sample to contiguous buffer." << 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 << "Failed to allocate memory for RGB data." << std::endl; return frame; } ConvertYUY2ToRGB(rawData, frame.rgbData, frameWidth, frameHeight); buffer->Unlock(); } }
بستن یک دوربین
را آزاد کنید IMFSourceReader
منابع شی و بنیاد رسانه:
if (reader)
{
ComPtr<IMFSourceReader> mfReader(static_cast<IMFSourceReader *>(reader));
reader = nullptr;
}
if (initialized)
{
MFShutdown();
initialized = false;
}
پیاده سازی توابع مرتبط با نمایشگر برای ویندوز
به روز رسانی فایل هدر برای پشتیبانی از هر دو ویندوز و لینوکس
برای پشتیبانی از سازگاری بین پلتفرم، CameraPreview.h
فایل هدر به شرح زیر به روز شد:
-
را تعریف کنید
CAMERA_API
ماکرو برای ویندوز و لینوکس.#ifdef _WIN32 #include
#elif __linux__ #include #include #elif __APPLE__ #include #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 << "Failed to register window class." << 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 << "Failed to create window." << 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 (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,
FW_NORMAL,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE,
"Arial");
if (!hFont)
return;
HGDIOBJ oldFont = SelectObject(hdc, hFont);
TextOut(hdc, x, y, text.c_str(), static_cast<int>(text.length()));
SelectObject(hdc, oldFont);
DeleteObject(hFont);
}
ترسیم خطوط روی پنجره
با استفاده از MoveToEx
و LineTo
توابع
void CameraWindow::DrawContour(const std::vector<std::pair<int, int>> &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);
}
ساخت یک برنامه اسکنر بارکد ویندوز
برای ساخت اسکنر بارکد، هیچ تغییری در منطق اسکن بارکد لازم نیست. این مراحل را دنبال کنید:
- کتابخانه دوربین و Dynamsoft C++ Barcode SDK را برای ویندوز آماده کنید.
-
را به روز کنید
CMakeLists.txt
فایلی که شامل پیکربندی خاص ویندوز باشد.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() # Create the executable 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() -
برنامه را با استفاده از CMake بسازید.
mkdir build cd build cmake .. cmake --build .
کد منبع
https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/litecam