深度学习训练图片收集器——C++截图程序的实现3(主对话框响应键鼠消息进行截图)

版权声明:本文为博主原创文章,如需转载请注明出处。因博主水平有限,如有疏忽遗漏,敬请指出。 https://blog.csdn.net/ShadowN1ght/article/details/78394790

在上一节《深度学习训练图片收集器——C++截图程序的实现2(键鼠钩子篇)》,我们实现了键鼠截图消息的传递。在本节中,我们将实现主程序的截图操作。

现在的设想是,当主程序收到截图请求后,显示一个铺满整个屏幕的界面,把截图时的桌面图像粘贴到这个全屏界面上。当用户在全屏界面上拖拽鼠标时,通过调用OpenCV函数,显示一个红色边框的截图矩形。当用户在这个矩形上双击鼠标左键时,就进行保存截图的操作,并退出全屏,回到桌面。

为了提高程序的操作友好性,我们允许用户在全屏界面上取消已勾勒出的矩形,不需要退出全屏界面,就可以重画新的矩形。如果使用微信和QQ的截图功能,会发现单次截图操作中只能勾勒一个矩形,不能取消重选,只能在退出后再次截图重选。

请注意,本程序使用了OpenCV函数库。如要仿照本程序进行实验,请先安装OpenCV。

现在我们开始着手实现截图功能。

首先打开上一节《C++截图程序的实现2(键鼠钩子篇)》中建立的工程ScreenshotForML,为ScreenshotForML项目添加一个对话框,修改其ID为“IDD_DIALOG_SCREENSHOT”,去掉对话框上的按钮,如下图所示:


右键该对话框,选择“添加类...”,在弹出的MFC类向导中,类名输入“CScreenShotView”,基类选“CDialog”,如下图:


点击完成。

扫描二维码关注公众号,回复: 3193816 查看本文章

然后打开ScreenShotView.h头文件,在DECLARE_MESSAGE_MAP()上面添加以下函数声明:

    afx_msg LRESULT OnMouseDown(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnMouseUp(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnMouseMove(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnMouseDBClick(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnEscPressed(WPARAM wParam, LPARAM lParam);
在DECLARE_MESSAGE_MAP()下面添加以下声明:

public:
    void DrawScreenshot();
    
private:
    bool                 m_bHideWindow;                           // 是否准备隐藏窗口,如果是,则把DC的颜色全部置为灰色,否则重新显示窗口时会有明显的闪烁
    bool                 m_bStartDrawing;                          // 是否开始绘制矩形
    bool                 m_bIsDrawing;                               // 是否正在用鼠标拉矩形
    POINT             m_ptRectStart;                              // 鼠标所拉矩形的左上顶点坐标
    POINT             m_ptRectEnd;                                // 鼠标所拉矩形的右下顶点坐标
    BITMAPINFOHEADER m_bitmap_info;

public:
    Mat                  m_cvScreen;                                  // 该变量保存了桌面截图的原始图像
    Mat                  m_cvROI;

public:
    afx_msg void OnDestroy();
最后,在“ #pragma once”的下面添加以下两行:
#include "cv.h"

using namespace cv;

这时候按F7生成,会提示找不到cv.h头文件。只需要给工程指定OpenCV包含目录就可以解决这个错误提示。

右键ScreenshotForML,选择“属性”→“VC++目录”→“包含目录”→“编辑”,输入你的机器上OpenCV对应的路径,如下图,一共三行,然后点确定:

设置好包含目录后,还需要设置库目录,如下所示:


然后在属性页上选择“链接器”→“输入”→“附加依赖项”,在编辑页中加入以下依赖库(换成你机器上的opencv版本;事实上并不需要加入这么多依赖文件,这里只是为了后续方便加入其它功能):

opencv_core2411.lib

opencv_highgui2411.lib

opencv_imgproc2411.lib

opencv_legacy2411.lib

opencv_ml2411.lib

opencv_objdetect2411.lib

opencv_ts2411.lib

opencv_video2411.lib

opencv_features2d2411.lib

opencv_flann2411.lib

opencv_nonfree2411.lib


完成上述几步操作后,按F7生成,不再出现编译错误。

CScreenShotView类的实现文件ScreenShotView.cpp的全部代码如下:

// ScreenShotView.cpp : 实现文件
//

#include "stdafx.h"
#include "ScreenshotForML.h"
#include "ScreenShotView.h"
#include "afxdialogex.h"

#include "cv.h"
#include "highgui.h"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/nonfree/features2d.hpp"

using namespace cv;
using namespace std;

#define WM_MOUSE_LEFT_DOWN                   WM_USER + 113
#define WM_MOUSE_LEFT_UP                          WM_USER + 114
#define WM_MOUSE_MOVE                                WM_USER + 115
#define WM_MOUSE_DBCLICK                          WM_USER + 116
#define WM_ESC_PRESSED                               WM_USER + 117

#define WM_SCREENSHOT_FINISHED             WM_USER + 118


// CScreenShotView 对话框

IMPLEMENT_DYNAMIC(CScreenShotView, CDialog)

CScreenShotView::CScreenShotView(CWnd* pParent /*=NULL*/)
: CDialog(CScreenShotView::IDD, pParent)
{
    m_bStartDrawing = false;
    m_bIsDrawing = false;
    m_bHideWindow = false;
}

CScreenShotView::~CScreenShotView()
{
}

void CScreenShotView::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}


BEGIN_MESSAGE_MAP(CScreenShotView, CDialog)
    ON_MESSAGE(WM_MOUSE_LEFT_DOWN, OnMouseDown)
    ON_MESSAGE(WM_MOUSE_LEFT_UP, OnMouseUp)
    ON_MESSAGE(WM_MOUSE_MOVE, OnMouseMove)
    ON_MESSAGE(WM_MOUSE_DBCLICK, OnMouseDBClick)
    ON_MESSAGE(WM_ESC_PRESSED, OnEscPressed)
    ON_WM_DESTROY()
END_MESSAGE_MAP()

void CScreenShotView::OnDestroy()
{
    CDialog::OnDestroy();

    // TODO:  在此处添加消息处理程序代码
}


// CScreenShotView 消息处理程序


afx_msg LRESULT CScreenShotView::OnMouseDown(WPARAM wParam, LPARAM lParam)
{
    m_bStartDrawing = true;

    GetCursorPos(&m_ptRectStart);

    return 0;
}

afx_msg LRESULT CScreenShotView::OnMouseUp(WPARAM wParam, LPARAM lParam)
{
    m_bStartDrawing = false;
    m_bIsDrawing = false;

    return 0;
}

afx_msg LRESULT CScreenShotView::OnMouseMove(WPARAM wParam, LPARAM lParam)
{
    if (m_bStartDrawing) {
        m_bIsDrawing = true;
        DrawScreenshot();
    }

    return 0;
}

afx_msg LRESULT CScreenShotView::OnMouseDBClick(WPARAM wParam, LPARAM lParam)
{
    m_bStartDrawing = false;
    m_bIsDrawing = false;

    // 如果发生鼠标双击事件,并且m_cvROI对象存有图像数据,则发送截图完成的消息给主对话框,由主对话框进一步处理
    if (m_cvROI.rows > 0) {
        m_bHideWindow = true;
        DrawScreenshot();
        m_bHideWindow = false;

        this->ShowWindow(SW_HIDE);

        HWND hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning");
        ::PostMessage(hwnd, WM_SCREENSHOT_FINISHED, NULL, NULL);
    }

    return 0;
}

afx_msg LRESULT CScreenShotView::OnEscPressed(WPARAM wParam, LPARAM lParam)
{
    m_bStartDrawing = false;
    m_bIsDrawing = false;

    this->ShowWindow(SW_HIDE);

    HWND hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning");
    ::PostMessage(hwnd, WM_ESC_PRESSED, NULL, NULL);

    return 0;
}

void CScreenShotView::DrawScreenshot()
{
    char msg[256] = "11111 ";

    Mat cv_screen;

    POINT pt;
    GetCursorPos(&pt);

    // 矩形左上顶点和右下顶点坐标
    POINT rectPt1;
    POINT rectPt2;

    if (!m_bHideWindow) {
        if ((abs(pt.x - m_ptRectStart.x) <= 2) && (abs(pt.y - m_ptRectStart.y) <= 2))
            return;

        rectPt1.x = (m_ptRectStart.x < pt.x) ? m_ptRectStart.x : pt.x;
        rectPt1.y = (m_ptRectStart.y < pt.y) ? m_ptRectStart.y : pt.y;
        rectPt2.x = (m_ptRectStart.x < pt.x) ? pt.x : m_ptRectStart.x;
        rectPt2.y = (m_ptRectStart.y < pt.y) ? pt.y : m_ptRectStart.y;

        cv_screen = m_cvScreen.clone();

        // 让截图界面图像整体变暗
        for (int a = 0; a < cv_screen.rows; a++) {
            uchar *p = cv_screen.ptr<uchar>(a);
            for (int b = 0; b < cv_screen.cols; b++) {
                p[b * 4 + 0] = ((float)p[b * 4 + 0]) * 0.63;
                p[b * 4 + 1] = ((float)p[b * 4 + 1]) * 0.63;
                p[b * 4 + 2] = ((float)p[b * 4 + 2]) * 0.63;
            }
        }

        // 还原选中区域的亮度
        Mat roi = cv_screen(Range(rectPt1.y, rectPt2.y), Range(rectPt1.x, rectPt2.x));
        m_cvROI = roi.clone();
        for (int a = 0; a < roi.rows; a++) {
            uchar *p = roi.ptr<uchar>(a);
            for (int b = 0; b < roi.cols; b++) {
                p[b * 4 + 0] = ((float)p[b * 4 + 0]) * 1.5;
                p[b * 4 + 1] = ((float)p[b * 4 + 1]) * 1.5;
                p[b * 4 + 2] = ((float)p[b * 4 + 2]) * 1.5;
            }
        }
        m_cvROI = roi.clone();
    }
    else {
        cv_screen = m_cvScreen;
    }

    // 对选中区域绘制边界矩形
    int       lineType = 8;
    rectangle(cv_screen, Point(rectPt1.x, rectPt1.y), Point(rectPt2.x, rectPt2.y), Scalar(0, 0, 255), 3, lineType);

    // 设置Bitmap信息
    m_bitmap_info.biBitCount = 32;
    m_bitmap_info.biClrImportant = 0;
    m_bitmap_info.biCompression = BI_RGB;
    m_bitmap_info.biHeight = -m_cvScreen.rows;
    m_bitmap_info.biPlanes = 1;
    m_bitmap_info.biSize = sizeof(BITMAPINFOHEADER);
    m_bitmap_info.biSizeImage = m_cvScreen.cols*m_cvScreen.rows * 4;
    m_bitmap_info.biWidth = m_cvScreen.cols;
    m_bitmap_info.biXPelsPerMeter = 0;
    m_bitmap_info.biYPelsPerMeter = 0;

    byte *p = cv_screen.ptr<byte>(0);
    //sprintf(msg + 6, "rows, cols: %d, %d", p[0], p[1]);
    //OutputDebugStringA(msg);

    // 将经过OpenCV处理过的图像粘贴到对话框DC上显示出来
    int ret = SetDIBitsToDevice(::GetDC(this->m_hWnd), 0, 0, m_cvScreen.cols, m_cvScreen.rows, 0, 0, 0, m_cvScreen.rows, (void*)p, (BITMAPINFO*)&m_bitmap_info, DIB_RGB_COLORS);
    this->UpdateWindow();
}

上述代码主要是进行消息处理和图形绘制,逻辑并不复杂,已给出部分注释,故不再进一步说明。

完成 CScreenShotView类的声明和实现后,把以下代码拷贝粘贴覆盖到主对话框头文件 ScreenshotForMLDlg.h:

// ScreenshotForMLDlg.h : 头文件
//

#pragma once


// CScreenshotForMLDlg 对话框
class CScreenshotForMLDlg : public CDialogEx
{
// 构造
public:
	CScreenshotForMLDlg(CWnd* pParent = NULL);	// 标准构造函数

// 对话框数据
	enum { IDD = IDD_SCREENSHOTFORML_DIALOG };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持


    // 实现
protected:
    HICON m_hIcon;

    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg LRESULT OnStartScreenshot(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnEscPressed(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnScreenshotFinished(WPARAM wParam, LPARAM lParam);
    DECLARE_MESSAGE_MAP()

private:
    bool                 m_bStartScreenShot;                    // 是否处于截屏状态
    bool                 m_bIsDrawingRectangle;              // 是否正在用鼠标拉矩形
    POINT             m_ptRectStart;                              // 鼠标所拉矩形的左上顶点坐标
    POINT             m_ptRectEnd;                                // 鼠标所拉矩形的右下顶点坐标

private:
    CDialog         *m_screenshot_dlg;

public:
    afx_msg void OnDestroy();
};

然后,打开ScreenshotForMLDlg.cpp文件,在头文件包含声明处添加以下代码:

#include "ScreenShotView.h"
#include "cv.h"
#include "highgui.h"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/nonfree/features2d.hpp"

using namespace cv;
using namespace std;

#define WM_START_SCREENSHOT                  WM_USER + 111
#define WM_ESC_PRESSED                               WM_USER + 117
#define WM_SCREENSHOT_FINISHED             WM_USER + 118

在cpp文件的消息映射块中加入如下映射声明:

    ON_MESSAGE(WM_START_SCREENSHOT, OnStartScreenshot)
    ON_MESSAGE(WM_ESC_PRESSED, OnEscPressed)
    ON_MESSAGE(WM_SCREENSHOT_FINISHED, OnScreenshotFinished)
    ON_WM_DESTROY()

OnInitDialog()函数中加入以下代码:

    // 设置窗口标题,以便传递消息
    this->SetWindowTextW(L"ScreenShot for Machine Learning");

    // 初始化成员变量
    m_screenshot_dlg = NULL;

然后,在该cpp文件中添加以下四个函数定义:

void CScreenshotForMLDlg::OnDestroy()
{
    CDialogEx::OnDestroy();

    // TODO:  在此处添加消息处理程序代码
    if (m_screenshot_dlg) {
        delete m_screenshot_dlg;
        m_screenshot_dlg = NULL;
    }
}

afx_msg LRESULT CScreenshotForMLDlg::OnStartScreenshot(WPARAM wParam, LPARAM lParam)
{
    m_bStartScreenShot = true;

    // 窗口定位
    RECT rect;
    int nScreenX = GetSystemMetrics(SM_CXSCREEN);
    int nScreenY = GetSystemMetrics(SM_CYSCREEN);

    DWORD dwStyle;
    DWORD dwNewStyle;

    // 获取桌面DC
    CWnd *pDesktopWnd = GetDesktopWindow();
    CDC *pDeskDC = pDesktopWnd->GetDC();
    CRect re;

    // 获取窗口的大小
    pDesktopWnd->GetClientRect(&re);
    CBitmap cbmp;
    cbmp.CreateCompatibleBitmap(pDeskDC, re.Width(), re.Height());

    BITMAP bitmap;
    cbmp.GetBitmap(&bitmap);

    // 创建一个兼容的内存画板
    CDC memDC;
    memDC.CreateCompatibleDC(pDeskDC);
    memDC.SelectObject(&cbmp);

    // BitBlt 绘制
    memDC.BitBlt(0, 0, re.Width(), re.Height(), pDeskDC, 0, 0, SRCCOPY);

    // opencv 操作开始
    IplImage *cvImage = cvCreateImage(cvSize(nScreenX, nScreenY), 8, 3);
    DWORD size = bitmap.bmWidthBytes * bitmap.bmHeight;
    byte* pData = new BYTE[size];

    BITMAPINFOHEADER bit_info;
    bit_info.biBitCount = 32;
    bit_info.biClrImportant = 0;
    bit_info.biCompression = BI_RGB;
    bit_info.biHeight = -bitmap.bmHeight;
    bit_info.biPlanes = 1;
    bit_info.biSize = sizeof(BITMAPINFOHEADER);
    bit_info.biSizeImage = size;
    bit_info.biWidth = bitmap.bmWidth;
    bit_info.biXPelsPerMeter = 0;
    bit_info.biYPelsPerMeter = 0;

    // 获取桌面前景图像数据,并将数据保存在一个Mat对象中
    int ret = GetDIBits(pDeskDC->m_hDC, cbmp, 0, -bit_info.biHeight, pData, (BITMAPINFO*)&bit_info, DIB_RGB_COLORS);
    Mat    cv_screen = Mat(Size(nScreenX, nScreenY), CV_8UC4, pData);

    // 对桌面图像绘制边缘,提示用户进入截图操作
    int       lineType = 8;
    rectangle(cv_screen, Point(0, 0), Point(nScreenX - 1, nScreenY - 1), Scalar(255, 0, 255), 8, lineType);

    // 创建截图窗口时需要将父窗口指定为当前的前置窗口,否则默认父窗口为本程序的主界面,会导致本程序主界面自动前置
    m_screenshot_dlg = new CScreenShotView;
    m_screenshot_dlg->Create(IDD_DIALOG_SCREENSHOT, CWnd::FromHandle(::GetForegroundWindow()));
    m_screenshot_dlg->SetWindowTextW(L"ScreenShot for Machine Learning (full screen)");

    // 设置窗口样式,最大化显示截图窗口,并使其前置
    dwStyle = m_screenshot_dlg->GetStyle();
    dwNewStyle = WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP;
    dwNewStyle &= dwStyle;
    ::SetWindowLong(m_screenshot_dlg->m_hWnd, GWL_STYLE, dwNewStyle);       // 设置成新的样式
    ::SetWindowPos(m_screenshot_dlg->m_hWnd, HWND_TOPMOST, 0, 0, nScreenX, nScreenY, SWP_NOMOVE | SWP_SHOWWINDOW);
    m_screenshot_dlg->ShowWindow(SW_SHOWMAXIMIZED);

    // 将处理过的CV图像显示在截图窗口
    byte *p = cv_screen.ptr<byte>(0);
    ret = SetDIBitsToDevice(::GetDC(m_screenshot_dlg->m_hWnd), 0, 0, re.Width(), re.Height(), 0, 0, 0, re.Height(), (void*)p, (BITMAPINFO*)&bit_info, DIB_RGB_COLORS);

    // 更新截图窗口
    m_screenshot_dlg->UpdateWindow();

    // 将截图窗口图像保存在m_cvScreen
    ((CScreenShotView*)m_screenshot_dlg)->m_cvScreen = cv_screen.clone();

    // 销毁临时创建的内存区域
    delete[] pData;
    ReleaseDC(pDeskDC);
    ReleaseDC(&memDC);
    cvReleaseImage(&cvImage);
    DeleteObject(cbmp);

    return 0;
}


afx_msg LRESULT CScreenshotForMLDlg::OnEscPressed(WPARAM wParam, LPARAM lParam)
{
    if (m_screenshot_dlg) {
        delete m_screenshot_dlg;
        m_screenshot_dlg = NULL;
    }
    return 0;
}


afx_msg LRESULT CScreenshotForMLDlg::OnScreenshotFinished(WPARAM wParam, LPARAM lParam)
{
    if (m_screenshot_dlg) {
        if (((CScreenShotView*)m_screenshot_dlg)->m_cvROI.rows > 0) {
            // 截图结束,截取的图像保存在m_cvROI,通过对m_cvROI进行操作,就可以实现二次处理
            //::CreateThread(NULL, 0, Thread_ScreenshotHandler, (VOID*)(&((CScreenShotView*)m_screenshot_dlg)->m_cvROI), 0, NULL);

            // 这里睡眠100毫秒,是为了让Mat数据在m_screenshot_dlg被delete之前可以被处理线程完整拷贝;
            // 更好的做法是创建互斥体,或等待某个布尔变量发生变化;也可以使用WaitForSingleObject()函数
            Sleep(100);
        }
        delete m_screenshot_dlg;
        m_screenshot_dlg = NULL;
    }
    return 0;
}

至此,代码已经全部完成。截屏图像保存在m_screenshot_dlgm_cvROI成员,我们可以对m_cvROI进行二次处理,譬如轮廓提取、特征识别,也可以保存为文件。

现在按F7生成exe,启动exe就可以愉快地进行截图操作了,截屏快捷键是alt+a。在运行exe之前,请务必确保将你的opencv的dll路径加入到系统的环境变量中,也就是将类似于“C:\opencv241\opencv\build\x86\vc12\bin”和“C:\opencv241\opencv\build\x64\vc12\bin”这样的路径添加到环境变量Path。

运行效果如下图所示:












猜你喜欢

转载自blog.csdn.net/ShadowN1ght/article/details/78394790