You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows下用Rust捕获指定窗口截图(忽略覆盖层、优化帧率)

问题:捕获被遮挡的指定窗口区域并优化截图效率

我需要捕获指定窗口的部分区域截图,该窗口可能被游戏覆盖层等上层内容遮挡,要求仅捕获目标窗口、忽略上层覆盖物。同时希望将截图效率优化至约5张/秒。

目前尝试了screenshots cargo包,代码如下:

use opencv::{core, highgui, imgcodecs};
use screenshots::Screen;
use std::{time::Instant};
use opencv::core::{log, Mat};

const WIDTH: i32 = 275;
const HEIGHT: i32 = 275;

fn get_img(screen: Screen) -> Mat {
    let image = screen.capture().unwrap();
    let buffer: Vec<u8> = image.into();

    // Change image type to OpenCV Mat
    let original_image: Mat = imgcodecs::imdecode(&core::Mat::from_slice(buffer.as_slice()).unwrap(), imgcodecs::IMREAD_COLOR).unwrap();
    return original_image;
}

fn main() {
    let window_name = "test".to_owned();
    highgui::named_window(&window_name, highgui::WINDOW_NORMAL).unwrap();
    highgui::resize_window(&window_name, WIDTH, HEIGHT).unwrap();


    let screens = Screen::all().unwrap();
    let screen = screens[1].clone();


    let mut img = get_img(screen);


    loop {
        let now = Instant::now();
        img = get_img(screen);

        // print in console the time it took to process the image
        println!("{} ms", now.elapsed().as_millis());
    }
}

但该方法无法捕获被覆盖层遮挡的指定窗口。

备注:使用cargo run --release运行代码,开发及目标系统均为Windows,截图需转换为OpenCV Mat用于后续处理。


解决方案

一、解决被遮挡窗口的捕获问题

在Windows平台,要绕过上层覆盖物直接捕获目标窗口,需要通过Windows API直接读取窗口的设备上下文(DC),而非捕获整个屏幕。核心思路是:

  1. 通过窗口标题或进程ID定位目标窗口的HWND句柄
  2. 从窗口DC中直接复制画面到内存位图,不受上层窗口遮挡影响
  3. 将位图数据直接转换为OpenCV Mat,跳过冗余的编码/解码步骤

二、优化截图效率至5张/秒

要达到5张/秒(单张耗时≈200ms)的目标,需重点优化:

  • 复用DC、位图等资源,避免重复创建销毁的开销
  • 直接从内存缓冲区转换为OpenCV Mat,消除imdecode的额外耗时
  • 仅捕获指定区域,减少数据处理量
  • 避免不必要的内存拷贝操作

完整示例代码

首先在Cargo.toml中添加依赖:

[dependencies]
opencv = "0.80.0"
windows = { version = "0.52.0", features = ["Win32_Graphics_Gdi", "Win32_Foundation", "Win32_UI_WindowsAndMessaging"] }

实现代码:

use opencv::{core, highgui, Mat};
use std::time::Instant;
use windows::{
    core::PCWSTR,
    Win32::Foundation::{HWND, RECT},
    Win32::Graphics::Gdi::{
        CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetClientRect,
        GetDC, SelectObject, BitBlt, SRCCOPY, HBITMAP, HDC,
    },
    Win32::UI::WindowsAndMessaging::FindWindowW,
};

const CAPTURE_WIDTH: i32 = 275;
const CAPTURE_HEIGHT: i32 = 275;

// 通过窗口标题查找目标窗口句柄
fn find_window_by_title(title: &str) -> Option<HWND> {
    let wide_title: Vec<u16> = title.encode_utf16().chain(std::iter::once(0)).collect();
    let hwnd = unsafe { FindWindowW(None, PCWSTR(wide_title.as_ptr())) };
    hwnd.0 != 0.then_some(hwnd)
}

// 捕获指定窗口的指定区域并转换为OpenCV Mat
fn capture_window_region(hwnd: HWND, x: i32, y: i32, width: i32, height: i32) -> Option<Mat> {
    unsafe {
        // 获取窗口设备上下文
        let window_dc = GetDC(hwnd);
        if window_dc.is_invalid() {
            return None;
        }

        // 创建兼容DC和位图
        let mem_dc = CreateCompatibleDC(window_dc);
        let hbitmap = CreateCompatibleBitmap(window_dc, width, height);
        if mem_dc.is_invalid() || hbitmap.is_invalid() {
            DeleteDC(window_dc);
            return None;
        }

        // 将位图绑定到兼容DC
        let old_bitmap = SelectObject(mem_dc, hbitmap);

        // 复制窗口指定区域到兼容DC
        let blt_success = BitBlt(
            mem_dc, 0, 0, width, height,
            window_dc, x, y, SRCCOPY
        ).as_bool();

        // 恢复DC状态并清理临时资源
        SelectObject(mem_dc, old_bitmap);
        DeleteDC(window_dc);

        if !blt_success {
            DeleteObject(hbitmap);
            DeleteDC(mem_dc);
            return None;
        }

        // 提取位图像素数据
        let mut bmi = windows::Win32::Graphics::Gdi::BITMAPINFO {
            bmiHeader: windows::Win32::Graphics::Gdi::BITMAPINFOHEADER {
                biSize: std::mem::size_of::<windows::Win32::Graphics::Gdi::BITMAPINFOHEADER>() as u32,
                biWidth: width,
                biHeight: -height, // 负数值表示像素顺序从上到下
                biPlanes: 1,
                biBitCount: 24,
                biCompression: windows::Win32::Graphics::Gdi::BI_RGB,
                biSizeImage: 0,
                biXPelsPerMeter: 0,
                biYPelsPerMeter: 0,
                biClrUsed: 0,
                biClrImportant: 0,
            },
            bmiColors: [Default::default(); 1],
        };

        let mut buffer = vec![0u8; (width * height * 3) as usize];
        let get_bits_success = windows::Win32::Graphics::Gdi::GetDIBits(
            mem_dc, hbitmap, 0, height as u32,
            Some(buffer.as_mut_ptr() as *mut _), &mut bmi,
            windows::Win32::Graphics::Gdi::DIB_RGB_COLORS
        ) != 0;

        DeleteObject(hbitmap);
        DeleteDC(mem_dc);

        if !get_bits_success {
            return None;
        }

        // 将RGB缓冲区转换为OpenCV的BGR格式Mat
        let rgb_mat = Mat::new_rows_cols_with_data(
            height, width, core::CV_8UC3,
            buffer.as_mut_ptr() as *mut _, core::Mat_AUTO_STEP
        ).ok()?;
        
        let mut bgr_mat = Mat::default();
        core::cvt_color(&rgb_mat, &mut bgr_mat, core::COLOR_RGB2BGR, 0).ok()?;

        Some(bgr_mat)
    }
}

fn main() -> opencv::Result<()> {
    let target_window_title = "test"; // 替换为你的目标窗口标题
    let hwnd = match find_window_by_title(target_window_title) {
        Some(hwnd) => hwnd,
        None => {
            eprintln!("无法找到目标窗口");
            return Ok(());
        }
    };

    // 获取窗口客户区大小(用于确认捕获区域边界)
    let mut rect = RECT::default();
    unsafe { GetClientRect(hwnd, &mut rect) };
    println!("窗口客户区大小: {}x{}", rect.right - rect.left, rect.bottom - rect.top);

    let window_name = "Capture Result";
    highgui::named_window(window_name, highgui::WINDOW_NORMAL)?;
    highgui::resize_window(window_name, CAPTURE_WIDTH, CAPTURE_HEIGHT)?;

    loop {
        let start = Instant::now();

        // 捕获窗口(0,0)位置开始的指定区域,可根据需求调整x、y参数
        if let Some(img) = capture_window_region(hwnd, 0, 0, CAPTURE_WIDTH, CAPTURE_HEIGHT) {
            highgui::imshow(window_name, &img)?;
        } else {
            eprintln!("捕获失败");
        }

        // 处理OpenCV事件,避免窗口无响应
        if highgui::wait_key(1)? == 27 { // ESC键退出循环
            break;
        }

        println!("捕获耗时: {} ms", start.elapsed().as_millis());
    }

    highgui::destroy_all_windows()?;
    Ok(())
}

关键优化说明

  1. 直接窗口DC捕获:绕过屏幕捕获逻辑,直接读取窗口内部画面,完全不受上层覆盖物影响
  2. 无编码解码开销:直接从内存位图转换为OpenCV Mat,消除了imdecode的冗余耗时
  3. 区域精准捕获:只处理指定大小的区域,减少数据传输和处理量
  4. 资源高效管理:每次捕获后及时释放临时DC和位图,避免内存泄漏

内容的提问来源于stack exchange,提问作者Unknow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 06:12:53