Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have written a ScreenVideoCapture class that implements an interface for capturing frames from a specified monitor connected to the PC. It uses OpenCV structures (including a cv::Mat for image output) and implements the core of the methods that OpenCV's VideoCapture has so that they can be used mostly interchangeably.

The class's constructor/open method take an index that corresponds to the target monitor. It uses the Win32 API to find the proper target rectangle, and then captures from that rectangle when the read method or >> operator are used. Credit goes to this SO question for the basis of the capture code, although it has been modified.

Specific points of interest:

  • Could I structure my class in a way that made it more interchangeable with OpenCV's VideoCapture? Currently, it can be used in the same way as the VideoCapture, but it must be ifdef-ed in in place of the alternative or called separately. There is no way of swapping the implementation at runtime.
  • Is there a better way of specifying the target monitor that isn't reliant on the order that EnumDisplayMonitors uses? I remember reading docs that said that the order was constant and determined based on resolution, but I can no longer find that page.
  • Can I make the capture more efficient? I am reallocating buffers on each call and do a fair amount of copying from one location to another, but I am not completely sure of what can be done once and what must be done every time.

ScreenVideoCapture.h:

#pragma once

#include <opencv2\opencv.hpp>
#include <Windows.h>

class ScreenVideoCapture
{
public:
    ScreenVideoCapture(int displayIndex = -1);
    ~ScreenVideoCapture();

    void open(int displayIndex);
    void read(cv::Mat& destination);
    ScreenVideoCapture& operator>>(cv::Mat& destination);

private:
    cv::Rect2d captureArea;
    HWND targetWindow = NULL;


    void captureHwnd(HWND window, cv::Rect2d targetArea, cv::Mat& dest);
    static BOOL CALLBACK monitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData);
};

struct MonitorIndexLookupInfo
{
    int targetIndex;

    RECT outRect;
    int currentIndex;
};

ScreenVideoCapture.cpp:

#include "ScreenVideoCapture.h"

ScreenVideoCapture::ScreenVideoCapture(int displayIndex)
{
    if (displayIndex >= 0)
        open(displayIndex);
}

void ScreenVideoCapture::open(int displayIndex)
{
    MonitorIndexLookupInfo enumState = { displayIndex, NULL, 0 };
    EnumDisplayMonitors(NULL, NULL, monitorEnumProc, (LPARAM) &enumState);

    this->captureArea = cv::Rect2d(enumState.outRect.left, enumState.outRect.top, enumState.outRect.right - enumState.outRect.left, enumState.outRect.bottom - enumState.outRect.top);
    this->targetWindow = GetDesktopWindow();
}

BOOL CALLBACK ScreenVideoCapture::monitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MonitorIndexLookupInfo* enumState = (MonitorIndexLookupInfo*)dwData;
    if (enumState->targetIndex == enumState->currentIndex)
    {
        enumState->outRect = *lprcMonitor;
        return false;
    }

    enumState->currentIndex++;

}

ScreenVideoCapture::~ScreenVideoCapture()
{
}

void ScreenVideoCapture::read(cv::Mat& destination)
{
    if (targetWindow == NULL)
        throw new std::exception("No target monitor specified! The 'open()' method must be called to select a target monitor before frames can be read.");

    captureHwnd(targetWindow, captureArea, destination);
}

ScreenVideoCapture& ScreenVideoCapture::operator>>(cv::Mat& destination)
{
    read(destination);
    return *this;
}

void ScreenVideoCapture::captureHwnd(HWND window, cv::Rect2d targetArea, cv::Mat& dest)
{
    HDC hwindowDC, hwindowCompatibleDC;

    HBITMAP hbwindow;
    BITMAPINFOHEADER  bi;

    hwindowDC = GetDC(window);
    hwindowCompatibleDC = CreateCompatibleDC(hwindowDC);
    SetStretchBltMode(hwindowCompatibleDC, COLORONCOLOR);

    dest.create(targetArea.height, targetArea.width, CV_8UC4);

    // Initialize a bitmap
    hbwindow = CreateCompatibleBitmap(hwindowDC, targetArea.width, targetArea.height);
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = targetArea.width;
    // The negative height is required -- removing the inversion will make the image appear upside-down.
    bi.biHeight = -targetArea.height;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    SelectObject(hwindowCompatibleDC, hbwindow);
    // Copy from the window device context to the bitmap device context
    // Use BitBlt to do a copy without any stretching -- the output is of the same dimensions as the target area.
    BitBlt(hwindowCompatibleDC, 0, 0, targetArea.width, targetArea.height, hwindowDC, targetArea.x, targetArea.y, SRCCOPY);
    // Copy into our own buffer as device-independent bitmap
    GetDIBits(hwindowCompatibleDC, hbwindow, 0, targetArea.height, dest.data, (BITMAPINFO *) &bi, DIB_RGB_COLORS);

    // Clean up memory to avoid leaks
    DeleteObject(hbwindow);
    DeleteDC(hwindowCompatibleDC);
    ReleaseDC(window, hwindowDC);
}
share|improve this question

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.