C++调用opencv和windows api完成桌面窗口截图——以梦幻西游为例

发布时间 2023-12-12 15:01:52作者: 爆米LiuChen

程序简介

项目编写的C++程序,根据输入的字符串,遍历所有桌面窗口标题,查找包含该标题的窗口,对该桌面窗口进行截图,以梦幻西游为例

输入:桌面窗口包含的字符串 比如输入“梦幻”,程序就会截取桌面“梦幻西游”的窗口

输出:该桌面窗口的截图,数据类型为opencv的Mat矩阵

show

程序/数据集下载

点击进入下载地址

本文章只发布于博客园爆米算法CSDN,被抄袭后可能排版错乱或下载失效,作者:爆米LiuChen

代码环境、文件结构

VS2019 注意链接器需要加入dwmapi.lib,用来调用windows的API的

opencv4.5.5

show1

show2

show3

代码分析

FindWindow.h声明了查找和定位窗口的函数,定义解析请看下文

#pragma once
#include <dwmapi.h>
#include <windows.h> 
#include <vector> 
#include <string>  
#include <iostream>

struct WindowData;

BOOL CALLBACK WindowEnumerationCallback(HWND hwnd, LPARAM lParam);

HWND getWindowHWND(std::string titleSection);

RECT getWindowLoc(HWND hwnd);

FindWindow.cpp中核心函数getWindowHWND可以根据输入的窗口标题字符串,定位到含有该字符串的窗口,返回窗口句柄HWND,然后将句柄输入getWindowLoc函数,得到窗口的位置,这里调用了windows api DwmGetWindowAttribute,如果用传统的方法GetWindowRect会得到错误的结果,因为传统方法没考虑到桌面缩放且自windows vista后的系统桌面窗口加入了“毛玻璃边缘”效果,得到的窗口位置会有偏移

#include "FindWindow.h"

struct WindowData {
    HWND handle;//窗口句柄
    char title[256];//窗口标题
};

std::vector<WindowData> windowDatas;

// 声明回调函数  
BOOL CALLBACK WindowEnumerationCallback(HWND hwnd, LPARAM lParam) {
    // 通过IsWindow函数检查窗口是否有效  
    if (IsWindow(hwnd)) {
        // 通过IsWindowEnabled函数检查窗口是否启用  
        if (IsWindowEnabled(hwnd)) {
            // 通过IsWindowVisible函数检查窗口是否可见  
            if (IsWindowVisible(hwnd)) {
                // 获取窗口的文本,并打印  
                char windowText[256];
                GetWindowTextA(hwnd, windowText, sizeof(windowText));
                WindowData windowData;
                windowData.handle = hwnd;
                memcpy(windowData.title, windowText, 256);
                windowDatas.push_back(windowData);
            }
        }
    }
    // 继续枚举其他窗口  
    return TRUE;
}

//返回包含titleSection的桌面窗口句柄
HWND getWindowHWND(std::string titleSection)
{
    HWND handle = NULL;
    //每次都要清空容器
    windowDatas.clear();
    // 使用EnumWindows函数枚举所有窗口,并传递给回调函数处理  
    EnumWindows(WindowEnumerationCallback, NULL);
    //一个个找包含指定字符串的
    for (auto it = windowDatas.begin(); it != windowDatas.end(); it++)
    {
        char title[256];
        memcpy(title, it->title, 256);
        std::string windowTitle(title);
        if (windowTitle.find(titleSection) != std::string::npos)
        {
            handle = it->handle;
        }
    }
    return handle;
}


//根据窗口句柄和桌面缩放获得窗口尺寸和位置
RECT getWindowLoc(HWND hwnd)
{
    RECT frame;
    DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));
    //std::cout << "窗口位置:(" << frame.left << ", " << frame.top << ")" << std::endl;
    //std::cout << "窗口大小:(" << frame.right-frame.left << ", " << frame.bottom-frame.top << ")" << std::endl;
    return frame;
}

WindowShot.h声明了根据坐标和尺寸的截图函数,定义解析请看下文

#pragma once
#include "FindWindow.h"
#include <opencv2/opencv.hpp>

struct WindowRect
{
    int x;
    int y;
    int width;
    int height;
};

class WindowShot
{
public:
    WindowShot();
    double static getZoom();
    cv::Mat getWindowMat(std::string titleSection);
    uchar* getWindowUchar(std::string titleSection);
    WindowRect windowRect;
    cv::Mat getDesktopMat();
    ~WindowShot();

private:
    int width;
    int height;
    double zoom;
    uchar* windowUchar;
    RECT rect;
    HDC screenDC;
    HDC compatibleDC;
    HBITMAP hBitmap;
    LPVOID shotData;
    HWND hwnd;
};

WindowShot.cpp定义了核心函数getWindowMat,该函数会调用FindWindow模块来查找窗口的句柄和位置,然后对整个屏幕具体的位置进行截图,当然同时也定义了getDesktopMat函数用来截图整个桌面,不同的是这个函数用到了个人桌面缩放率

#include "WindowShot.h"

//初始化变量
WindowShot::WindowShot()
{
    zoom = getZoom();//缩放率 比如1.25
}


//根据窗口标题是否包含该字符串,获得窗口截图
cv::Mat WindowShot::getWindowMat(std::string titleSection)
{
    hwnd = getWindowHWND(titleSection);
    //如果窗口小化 就将其展示 
    if (IsIconic(hwnd)) {
        ShowWindow(hwnd, SW_RESTORE);
    }
    SetForegroundWindow(hwnd); // 将窗口置顶  
    rect = getWindowLoc(hwnd); // 窗口位置
    width = rect.right - rect.left;
    height = rect.bottom - rect.top;
    windowRect.x = rect.left;
    windowRect.y = rect.top;
    windowRect.width = width;
    windowRect.height = height;
    shotData = new char[width * height * 4];
    screenDC = GetDC(NULL);// 获取屏幕 DC
    compatibleDC = CreateCompatibleDC(screenDC);//兼容新DC
    // 创建位图
    hBitmap = CreateCompatibleBitmap(screenDC, width, height);
    SelectObject(compatibleDC, hBitmap);
    // 得到位图的数据
    BitBlt(compatibleDC, 0, 0, width, height, screenDC, rect.left, rect.top, SRCCOPY);
    GetBitmapBits(hBitmap, width * height * 4, shotData);
    // 创建图像
    cv::Mat windowMat(height, width, CV_8UC4, shotData);
    return windowMat;
}

//根据窗口标题是否包含该字符串,获得窗口截图 将截图转为uchar* 供python使用
uchar* WindowShot::getWindowUchar(std::string titleSection)
{
    cv::Mat windowMat = this->getWindowMat(titleSection);
    int size = width * height * 4;
    free(windowUchar);
    windowUchar = (uchar*)malloc(sizeof(uchar) * size);
    memcpy(windowUchar, windowMat.data, size);
    return windowUchar;
}

cv::Mat WindowShot::getDesktopMat()
{
    width = GetSystemMetrics(SM_CXSCREEN) * zoom;
    height = GetSystemMetrics(SM_CYSCREEN) * zoom;
    rect.left = 0;
    rect.top = 0;
    rect.right = width;
    rect.bottom = height;
    width = rect.right - rect.left;
    height = rect.bottom - rect.top;
    shotData = new char[width * height * 4];
    screenDC = GetDC(NULL);// 获取屏幕 DC
    compatibleDC = CreateCompatibleDC(screenDC);//兼容新DC
    // 创建位图
    hBitmap = CreateCompatibleBitmap(screenDC, width, height);
    SelectObject(compatibleDC, hBitmap);
    // 得到位图的数据
    BitBlt(compatibleDC, 0, 0, width, height, screenDC, rect.left, rect.top, SRCCOPY);
    GetBitmapBits(hBitmap, width * height * 4, shotData);
    // 创建图像
    cv::Mat desktopMat(height, width, CV_8UC4, shotData);
    return desktopMat;
}

/* 获取屏幕缩放值 */
double WindowShot::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;
}

WindowShot::~WindowShot()
{
    DeleteObject(hBitmap);
    DeleteDC(compatibleDC);
}

结果展示

运行一下main.cpp,得到梦幻西游的窗口截图(文章开头已给出),对比下整个桌面截图

desktop