如何使用 C++ 和 OpenCV 实现截屏

如何使用 C++ 和 OpenCV 实现截屏

前言

实现屏幕截屏需要用到 Windows API,所以需要包括 Windows.h 头文件。同时我们想要对截图做进一步的处理,就需要用到 OpenCV。关于 OpenCV 的安装与编译可以参见 《再整理:Visual Studio Code(vscode)下的基于C++的OpenCV的最新搭建攻略解析》,亲测有效,但是 OpenCV 还有 MinGW 的版本最好和博客中保持一致,不然编译可能会失败。下面进入正题。

代码

头文件

Screenshot.h

复制#pragma once
#include <Windows.h>
#include <opencv2/opencv.hpp>

class Screenshot
{
public:
    Screenshot();
    double static getZoom();
    cv::Mat getScreenshot();
    cv::Mat getScreenshot(int x, int y, int width, int height);

private:
    int m_width;
    int m_height;
    HDC m_screenDC;
    HDC m_compatibleDC;
    HBITMAP m_hBitmap;
    LPVOID m_screenshotData = nullptr;
};

源文件

在截图之前需要获取屏幕的分辨率,一种很直观的想法就是调用 GetSystemMetrics(SM_C*SCREEN) 函数来获取宽度或者高度。如下图所示,设置屏幕缩放 125% 之后,得到的值会偏小。如果是 1920 × 1080 的分辨率,GetSystemMetrics(SM_CXSCREEN)GetSystemMetrics(SM_CYSCREEN) 返回分辨率会是 (1920, 1080) / 1.25 = (1536, 864)。所以我们需要先计算屏幕的缩放率。这个任务由 Screenshot::getZoom() 完成。剩下的步骤注释中解释的很充分了,不再赘述。

Screenshot.cpp

复制#include "Screenshot.h"
using cv::Mat;

Screenshot::Screenshot()
{
    double zoom = getZoom();
    m_width = GetSystemMetrics(SM_CXSCREEN) * zoom;
    m_height = GetSystemMetrics(SM_CYSCREEN) * zoom;
    m_screenshotData = new char[m_width * m_height * 4];
    memset(m_screenshotData, 0, m_width);

    // 获取屏幕 DC
    m_screenDC = GetDC(NULL);
    m_compatibleDC = CreateCompatibleDC(m_screenDC);

    // 创建位图
    m_hBitmap = CreateCompatibleBitmap(m_screenDC, m_width, m_height);
    SelectObject(m_compatibleDC, m_hBitmap);
}

/* 获取整个屏幕的截图 */
Mat Screenshot::getScreenshot()
{
    // 得到位图的数据
    BitBlt(m_compatibleDC, 0, 0, m_width, m_height, m_screenDC, 0, 0, SRCCOPY);
    GetBitmapBits(m_hBitmap, m_width * m_height * 4, m_screenshotData);

    // 创建图像
    Mat screenshot(m_height, m_width, CV_8UC4, m_screenshotData);

    return screenshot;
}

/** @brief 获取指定范围的屏幕截图
 * @param x 图像左上角的 X 坐标
 * @param y 图像左上角的 Y 坐标
 * @param width 图像宽度
 * @param height 图像高度
 */
Mat Screenshot::getScreenshot(int x, int y, int width, int height)
{
    Mat screenshot = getScreenshot();
    return screenshot(cv::Rect(x, y, width, height));
}

/* 获取屏幕缩放值 */
double Screenshot::getZoom()
{
    // 获取窗口当前显示的监视器
    HWND hWnd = GetDesktopWindow();
    HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);

    // 获取监视器逻辑宽度
    MONITORINFOEX monitorInfo;
    monitorInfo.cbSize = sizeof(monitorInfo);
    GetMonitorInfo(hMonitor, &monitorInfo);
    int cxLogical = (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);

    // 获取监视器物理宽度
    DEVMODE dm;
    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;
    EnumDisplaySettings(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &dm);
    int cxPhysical = dm.dmPelsWidth;

    return cxPhysical * 1.0 / cxLogical;
}

测试

对于 1920 × 1080 的分辨率,截一次屏在 30ms 左右,下面是测试代码:

复制#include "Screenshot.h"
using namespace cv;

int main()
{
    Screenshot screenshot;
    Mat img = screenshot.getScreenshot();
    Mat img_ = screenshot.getScreenshot(1040, 132, 800, 880);
    imwrite("screenshot.jpg", img);
    imwrite("screenshot_part.jpg", img_);
    return 0;
}
头像
posted @   之一Yo  阅读(8881)  评论(2编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示