برنامه نویسی

نحوه پیاده سازی پیش نمایش دوربین با 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.

باز کردن دوربین

  1. یک دوربین مشخص شده را با شاخص فعال کنید:

    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 شی برای خواندن داده های ویدئویی از دوربین استفاده می شود.

  2. عرض، ارتفاع و قالب پیکسلی ویدئو را پیکربندی کنید. به عنوان مثال، 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 متغیر عضو

گرفتن یک قاب

  1. نمونه ای از دوربین را بخوانید:

    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,
        &timestamp,
        &sample);
    
    if (FAILED(hr))
    {
        std::cerr << "Failed to read sample." << std::endl;
        return frame; 
    }
    
  2. داده های خام را از نمونه بگیرید و آن را به 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, &currentLength);
        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);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ساخت یک برنامه اسکنر بارکد ویندوز

برای ساخت اسکنر بارکد، هیچ تغییری در منطق اسکن بارکد لازم نیست. این مراحل را دنبال کنید:

  1. کتابخانه دوربین و Dynamsoft C++ Barcode SDK را برای ویندوز آماده کنید.
  2. را به روز کنید 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()
    
  3. برنامه را با استفاده از CMake بسازید.

    mkdir build
    cd build
    cmake ..
    cmake --build .
    

    اسکنر بارکد ویندوز

کد منبع

https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/litecam

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا